diff --git a/public/locales/bg/donation-flow.json b/public/locales/bg/donation-flow.json
index e039106a0..79bff765d 100644
--- a/public/locales/bg/donation-flow.json
+++ b/public/locales/bg/donation-flow.json
@@ -85,9 +85,9 @@
}
},
"alert": {
- "card-fee": "Таксата на Stripe се изчислява според района на картодържателя: 1.2% + 0.5лв. за Европейската икономическа зона",
- "bank-fee": "Таксата за транзакция при банков превод зависи от индивидуалните условия на Вашата банка (от 0 до 4 лв.)",
- "calculated-fees": "За вашия превод от {{totalAmount}}, таксата на Stripe ще е {{fees}}, а кампанията ще получи {{amount}}"
+ "card-fee": "Таксата на Stripe се изчислява според района на картодържателя: 1.2% + 0.5лв. за Европейската икономическа зона.",
+ "bank-fee": "Таксата за транзакция при банков превод зависи от индивидуалните условия на Вашата банка (от 0 до 4 лв.).",
+ "calculated-fees": "За вашия превод от {{totalAmount}}, таксата на Stripe ще е {{fees}}, а кампанията ще получи {{amount}}."
}
},
"authentication": {
@@ -107,16 +107,16 @@
"field": {
"password": "Парола",
"email": {
- "error": "Трябва да въведете валиден имейл"
+ "error": "Трябва да въведете валиден имейл."
}
},
"alert": {
"authenticate": {
"title": "Избирайки да се впишете, ще можете да",
- "create-account": "създадете акаунт като физическо или юридическо лице",
- "certificate": "получите сертификат за дарение",
- "monthly-donation": "правите месечни дарения по избрана кампания",
- "notification": "можете да получавате и известия за статуса на подкрепени вече кампании"
+ "create-account": "създадете акаунт като физическо или юридическо лице.",
+ "certificate": "получите сертификат за дарение.",
+ "monthly-donation": "правите месечни дарения по избрана кампания.",
+ "notification": "можете да получавате и известия за статуса на подкрепени вече кампании."
}
}
},
@@ -129,13 +129,14 @@
"field": {
"anonymous": {
"label": "Искам да съм анонимен",
- "description": "Ако останете анонимен името ви няма да бъде показано на кампанията"
+ "description": "Ако останете анонимен името ви няма да бъде показано на кампанията."
},
"privacy": {
"error": "Трябва да приемете политиката за поверителност"
}
},
"alerts": {
+ "subscription-unauthorized": "Ако желаете да правите ежемесечни дарения, моля влезте с вашия профил в платформата или се регистрирайте, ако все още не сте го направили",
"error": "Нещо се обърка, моля опитайте пак или презаредете страницата"
},
"total": "Общо"
diff --git a/public/locales/en/donation-flow.json b/public/locales/en/donation-flow.json
index 2f6c0ca80..7cda46ca4 100644
--- a/public/locales/en/donation-flow.json
+++ b/public/locales/en/donation-flow.json
@@ -117,10 +117,10 @@
"alert": {
"authenticate": {
"title": "Choosing to login you will be able to",
- "create-account": "Create an account",
- "certificate": "Get a donation certificate",
- "monthly-donation": "Make a monthly donation",
- "notification": "Get notifications about campaigns you donated to"
+ "create-account": "Create an account.",
+ "certificate": "Get a donation certificate.",
+ "monthly-donation": "Make a monthly donation.",
+ "notification": "Get notifications about campaigns you donated to."
}
}
},
@@ -130,11 +130,15 @@
"title": "Transaction",
"description": "The transaction is only to compensate the transfer and is calculated based on your method of payment. \"Podkrepi.bg\" works with 0% commission."
},
+ "alerts": {
+ "subscription-unauthorized": "If you wish to make monthly donations, please sign in with your account or register on the platform if you still have not.",
+ "error": "Something went wrong, please try again or refresh the page."
+ },
"total": "Total",
"field": {
"anonymous": {
"label": "I want to be anonymous",
- "description": "If you choose to be anonymous, your name will not be visible in the campaign"
+ "description": "If you choose to be anonymous, your name will not be visible in the campaign."
},
"privacy": {
"error": "You have to accept the privacy policy"
diff --git a/src/components/client/donation-flow/DonationFlowForm.tsx b/src/components/client/donation-flow/DonationFlowForm.tsx
index 7ee93d06a..1b0a5b3f3 100644
--- a/src/components/client/donation-flow/DonationFlowForm.tsx
+++ b/src/components/client/donation-flow/DonationFlowForm.tsx
@@ -101,7 +101,7 @@ export const validationSchema: yup.SchemaOf = yup
export function DonationFlowForm() {
const formikRef = useRef | null>(null)
const { t } = useTranslation('donation-flow')
- const { campaign, setupIntent, paymentError, setPaymentError, idempotencyKey } = useDonationFlow()
+ const { campaign, setupIntent, paymentError, setPaymentError } = useDonationFlow()
const stripe = useStripe()
const elements = useElements()
const router = useRouter()
@@ -109,7 +109,6 @@ export function DonationFlowForm() {
const cancelSetupIntentMutation = useCancelSetupIntent()
const paymentMethodSectionRef = React.useRef(null)
const authenticationSectionRef = React.useRef(null)
- const stripeChargeRef = React.useRef(idempotencyKey)
const [showCancelDialog, setShowCancelDialog] = React.useState(false)
const [submitPaymentLoading, setSubmitPaymentLoading] = React.useState(false)
const { data: { user: person } = { user: null } } = useCurrentPerson()
@@ -172,11 +171,19 @@ export function DonationFlowForm() {
return
}
+ if (values.mode === 'subscription' && !session?.user?.sub) {
+ setSubmitPaymentLoading(false)
+ formikRef.current?.setFieldError(
+ 'authentication',
+ t('step.summary.alerts.subscription-unauthorized'),
+ )
+ return
+ }
+
// Update the setup intent with the latest calculated amount
try {
const updatedIntent = await updateSetupIntentMutation.mutateAsync({
id: setupIntent.id,
- idempotencyKey,
payload: {
metadata: {
type: person?.company ? DonationType.corporate : DonationType.donation,
@@ -199,7 +206,6 @@ export function DonationFlowForm() {
campaign,
values,
session,
- stripeChargeRef.current,
)
router.push(
`${window.location.origin}${routes.campaigns.donationStatus(campaign.slug)}?p_status=${
@@ -212,7 +218,6 @@ export function DonationFlowForm() {
type: 'invalid_request_error',
message: (error as StripeError).message ?? t('step.summary.alerts.error'),
})
- stripeChargeRef.current = crypto.randomUUID()
return
}
diff --git a/src/components/client/donation-flow/DonationFlowPage.tsx b/src/components/client/donation-flow/DonationFlowPage.tsx
index 4145bba02..611638475 100644
--- a/src/components/client/donation-flow/DonationFlowPage.tsx
+++ b/src/components/client/donation-flow/DonationFlowPage.tsx
@@ -10,21 +10,16 @@ import { CampaignResponse } from 'gql/campaigns'
export default function DonationFlowPage({
slug,
setupIntent,
- idempotencyKey,
}: {
slug: string
setupIntent: Stripe.SetupIntent
- idempotencyKey: string
}) {
const { data } = useViewCampaign(slug)
//This query needs to be prefetched in the pages folder
//otherwise on the first render the data will be undefined
const campaign = data?.campaign as CampaignResponse
return (
-
+
diff --git a/src/components/client/donation-flow/contexts/DonationFlowProvider.tsx b/src/components/client/donation-flow/contexts/DonationFlowProvider.tsx
index c1a8167be..1709031cc 100644
--- a/src/components/client/donation-flow/contexts/DonationFlowProvider.tsx
+++ b/src/components/client/donation-flow/contexts/DonationFlowProvider.tsx
@@ -11,7 +11,6 @@ type DonationContext = {
setPaymentError: React.Dispatch>
campaign: CampaignResponse
stripe: Promise
- idempotencyKey: string
}
const DonationFlowContext = React.createContext({} as DonationContext)
@@ -19,17 +18,14 @@ const DonationFlowContext = React.createContext({} as DonationContext)
export const DonationFlowProvider = ({
campaign,
setupIntent,
- idempotencyKey,
children,
}: PropsWithChildren<{
campaign: CampaignResponse
setupIntent: Stripe.SetupIntent
- idempotencyKey: string
}>) => {
const [paymentError, setPaymentError] = React.useState(null)
const value = {
- idempotencyKey,
setupIntent,
paymentError,
setPaymentError,
diff --git a/src/components/client/donation-flow/helpers/confirmStripeDonation.ts b/src/components/client/donation-flow/helpers/confirmStripeDonation.ts
index f47accd95..ab1ba8547 100644
--- a/src/components/client/donation-flow/helpers/confirmStripeDonation.ts
+++ b/src/components/client/donation-flow/helpers/confirmStripeDonation.ts
@@ -13,7 +13,6 @@ export async function confirmStripePayment(
campaign: CampaignResponse,
values: DonationFormData,
session: Session | null,
- idempotencyKey: string,
): Promise {
if (setupIntent.status !== DonationFormPaymentStatus.SUCCEEDED) {
const { error: intentError } = await stripe.confirmSetup({
@@ -30,12 +29,7 @@ export async function confirmStripePayment(
throw intentError
}
}
- const payment = await createIntentFromSetup(
- setupIntent.id,
- idempotencyKey,
- values.mode as PaymentMode,
- session,
- )
+ const payment = await createIntentFromSetup(setupIntent.id, values.mode as PaymentMode, session)
if (payment.data.status === DonationFormPaymentStatus.REQUIRES_ACTION) {
const { error: confirmPaymentError } = await stripe.confirmCardPayment(
diff --git a/src/components/client/donation-flow/steps/payment-method/PaymentMethod.tsx b/src/components/client/donation-flow/steps/payment-method/PaymentMethod.tsx
index a021bf558..383941a72 100644
--- a/src/components/client/donation-flow/steps/payment-method/PaymentMethod.tsx
+++ b/src/components/client/donation-flow/steps/payment-method/PaymentMethod.tsx
@@ -35,13 +35,13 @@ export default function PaymentMethod({
{
value: 'card',
label: t('step.payment-method.field.method.card'),
- icon: ,
+ icon: ,
disabled: false,
},
{
value: 'bank',
label: t('step.payment-method.field.method.bank'),
- icon: ,
+ icon: ,
disabled: mode.value === 'subscription',
},
]
diff --git a/src/components/client/donation-flow/steps/payment-method/TaxesCheckbox.tsx b/src/components/client/donation-flow/steps/payment-method/TaxesCheckbox.tsx
index 2d0af6358..26f7964bd 100644
--- a/src/components/client/donation-flow/steps/payment-method/TaxesCheckbox.tsx
+++ b/src/components/client/donation-flow/steps/payment-method/TaxesCheckbox.tsx
@@ -12,6 +12,7 @@ export const TaxesCheckbox = () => {
const { t } = useTranslation('donation-flow')
const [amountWithFees] = useField('finalAmount')
const [amountWithoutFees] = useField('amountWithoutFees')
+ const showCalculatedFees = Boolean(amountWithFees.value)
return (
<>
@@ -50,15 +51,17 @@ export const TaxesCheckbox = () => {
/>
-
+ {showCalculatedFees && (
+
+ )}
>
)
}
diff --git a/src/components/common/ExternalLink.tsx b/src/components/common/ExternalLink.tsx
index e109b74c9..6adace6ea 100644
--- a/src/components/common/ExternalLink.tsx
+++ b/src/components/common/ExternalLink.tsx
@@ -1,11 +1,12 @@
-import { Link, LinkProps } from '@mui/material'
+import { LinkProps } from '@mui/material'
import { PropsWithChildren } from 'react'
+import Link from './Link'
type ExternalLinkParams = PropsWithChildren
export default function ExternalLink({ children, ...props }: ExternalLinkParams) {
return (
-
+
{children}
)
diff --git a/src/components/common/Link.tsx b/src/components/common/Link.tsx
index bba88fbfd..2ebc13be6 100644
--- a/src/components/common/Link.tsx
+++ b/src/components/common/Link.tsx
@@ -2,5 +2,5 @@ import { LinkProps, Link as MuiLink } from '@mui/material'
import NextLink from 'next/link'
export default function Link(props: LinkProps<'a'>) {
- return
+ return
}
diff --git a/src/components/common/form/RadioButton.tsx b/src/components/common/form/RadioButton.tsx
index f630754dc..c843c4a99 100644
--- a/src/components/common/form/RadioButton.tsx
+++ b/src/components/common/form/RadioButton.tsx
@@ -70,13 +70,21 @@ type RadioButtonProps = {
error?: boolean
}
-function RadioButton({ checked, label, muiRadioButtonProps, value, error }: RadioButtonProps) {
+function RadioButton({
+ checked,
+ label,
+ muiRadioButtonProps,
+ value,
+ disabled,
+ error,
+}: RadioButtonProps) {
return (
{label}
@@ -84,6 +92,7 @@ function RadioButton({ checked, label, muiRadioButtonProps, value, error }: Radi
}
control={
}
checkedIcon={
diff --git a/src/gql/donations.d.ts b/src/gql/donations.d.ts
index 4f5b9236d..de7b43730 100644
--- a/src/gql/donations.d.ts
+++ b/src/gql/donations.d.ts
@@ -42,7 +42,6 @@ export type CancelSetupIntentInput = {
export type UpdateSetupIntentInput = {
id: string
- idempotencyKey: string
payload: Stripe.SetupIntentUpdateParams
}
diff --git a/src/pages/campaigns/donation/[slug]/index.tsx b/src/pages/campaigns/donation/[slug]/index.tsx
index e76de7f95..a5eefa024 100644
--- a/src/pages/campaigns/donation/[slug]/index.tsx
+++ b/src/pages/campaigns/donation/[slug]/index.tsx
@@ -23,19 +23,17 @@ export const getServerSideProps: GetServerSideProps = async (ctx: GetServerSideP
)
//Generate idempotencyKey to prevent duplicate creation of resources in stripe
- const idempotencyKey = crypto.randomUUID()
//create and prefetch the payment intent
const { data: setupIntent } = await apiClient.post<
Stripe.SetupIntentCreateParams,
AxiosResponse
- >(endpoints.donation.createSetupIntent.url, idempotencyKey)
+ >(endpoints.donation.createSetupIntent.url)
return {
props: {
slug,
setupIntent,
- idempotencyKey,
dehydratedState: dehydrate(client),
...(await serverSideTranslations(ctx.locale ?? 'bg', [
'common',
diff --git a/src/service/apiEndpoints.ts b/src/service/apiEndpoints.ts
index 163479a02..e054c029f 100644
--- a/src/service/apiEndpoints.ts
+++ b/src/service/apiEndpoints.ts
@@ -154,19 +154,19 @@ export const endpoints = {
createSubscriptionPayment: { url: '/stripe/create-subscription', method: 'POST' },
createPaymentIntent: { url: '/stripe/payment-intent', method: 'POST' },
createSetupIntent: { url: '/stripe/setup-intent', method: 'POST' },
- createPaymentIntentFromSetup: (id: string, idempotencyKey: string) =>
+ createPaymentIntentFromSetup: (id: string) =>
{
- url: `/stripe/setup-intent/${id}/payment-intent?idempotency-key=${idempotencyKey}`,
+ url: `/stripe/setup-intent/${id}/payment-intent`,
method: 'POST',
},
- createSubscriptionFromSetup: (id: string, idempotencyKey: string) =>
+ createSubscriptionFromSetup: (id: string) =>
{
- url: `/stripe/setup-intent/${id}/subscription?idempotency-key=${idempotencyKey}`,
+ url: `/stripe/setup-intent/${id}/subscription`,
method: 'POST',
},
- updateSetupIntent: (id: string, idempotencyKey: string) =>
+ updateSetupIntent: (id: string) =>
{
- url: `/stripe/setup-intent/${id}?idempotency-key=${idempotencyKey}`,
+ url: `/stripe/setup-intent/${id}`,
method: 'POST',
},
cancelSetupIntent: (id: string) =>
diff --git a/src/service/donation.ts b/src/service/donation.ts
index dcb2c2431..41c6f9ac0 100644
--- a/src/service/donation.ts
+++ b/src/service/donation.ts
@@ -48,15 +48,11 @@ export function useUpdateSetupIntent() {
//Create payment intent useing the react-query mutation
const { data: session } = useSession()
return useMutation({
- mutationFn: async ({ id, idempotencyKey, payload }: UpdateSetupIntentInput) => {
+ mutationFn: async ({ id, payload }: UpdateSetupIntentInput) => {
return await apiClient.post<
Stripe.SetupIntentUpdateParams,
AxiosResponse
- >(
- endpoints.donation.updateSetupIntent(id, idempotencyKey).url,
- payload,
- authConfig(session?.accessToken),
- )
+ >(endpoints.donation.updateSetupIntent(id).url, payload, authConfig(session?.accessToken))
},
})
}
@@ -84,14 +80,13 @@ export function useCreateSubscriptionPayment() {
export async function createIntentFromSetup(
setupIntentId: string,
- idempotencyKey: string,
mode: PaymentMode,
session: Session | null,
): Promise> {
return await apiClient.post, AxiosError>(
mode === 'one-time'
- ? endpoints.donation.createPaymentIntentFromSetup(setupIntentId, idempotencyKey).url
- : endpoints.donation.createSubscriptionFromSetup(setupIntentId, idempotencyKey).url,
+ ? endpoints.donation.createPaymentIntentFromSetup(setupIntentId).url
+ : endpoints.donation.createSubscriptionFromSetup(setupIntentId).url,
undefined,
authConfig(session?.accessToken),
)