All files / features/donation/components/payment PayPalPaymentForm.tsx

92.3% Statements 24/26
68.75% Branches 11/16
100% Functions 4/4
91.3% Lines 21/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                    1x 7x 7x       7x 7x   7x 1x     6x   1x       1x                         6x 2x 2x         2x   1x   1x   1x 1x       6x                             1x 1x                              
import {
    PayPalScriptProvider,
    PayPalButtons,
    type PayPalButtonsComponentProps,
} from '@paypal/react-paypal-js'
import { useTranslation } from 'react-i18next'
import { CardContent, CardFooter } from '@core/components/ui/card'
import { Button } from '@core/components/ui/button'
import type { PaymentProviderProps } from '../../types/payment.types'
 
export const PayPalPaymentForm = (props: PaymentProviderProps) => {
    const { sessionData, config, onSuccess, onError, onBack, amount, currency } = props
    const { t } = useTranslation('common')
 
    // For PayPal, sessionData might contain an orderID pre-created on the backend, or we might create it here.
    // Assuming backend creation for security (consistent with our Stripe intent flow).
    const initialOrderId = sessionData.orderID || sessionData.id
    const clientId = config?.clientId as string | undefined
 
    if (!clientId) {
        return <div className="text-red-500">{t('payment.error_missing_config')}</div>
    }
 
    const createOrder: PayPalButtonsComponentProps['createOrder'] = async (_data, actions) => {
        // If we already have an orderID from the backend intention
        Iif (initialOrderId) {
            return initialOrderId
        }
        // Fallback: Create order on client (less secure, but valid for simple flows)
        return actions.order.create({
            intent: 'CAPTURE',
            purchase_units: [
                {
                    amount: {
                        currency_code: currency.toUpperCase(),
                        value: amount.toString(),
                    },
                },
            ],
        })
    }
 
    const onApprove: PayPalButtonsComponentProps['onApprove'] = async (_data, actions) => {
        try {
            Iif (!actions.order) {
                throw new Error('PayPal actions.order is undefined')
            }
 
            // Capture the funds from the transaction
            const details = await actions.order.capture()
 
            console.log('PayPal Transaction completed:', details)
 
            Eif (onSuccess) onSuccess()
        } catch (err) {
            console.error('PayPal Capture Error', err)
            Eif (onError) onError(t('payment.error_processing'))
        }
    }
 
    return (
        <PayPalScriptProvider
            options={{
                clientId: clientId,
                currency: currency.toUpperCase(),
                intent: 'capture',
            }}
        >
            <CardContent className="space-y-4 pt-6">
                <div className="min-h-[150px] flex flex-col justify-center">
                    <PayPalButtons
                        style={{ layout: 'vertical', shape: 'rect', label: 'donate' }}
                        createOrder={createOrder}
                        onApprove={onApprove}
                        onError={(err) => {
                            console.error('PayPal Error', err)
                            Eif (onError) onError(t('payment.error_generic'))
                        }}
                    />
                </div>
            </CardContent>
            <CardFooter>
                {onBack && (
                    <Button variant="ghost" onClick={onBack} className="w-full">
                        {t('common.back')}
                    </Button>
                )}
            </CardFooter>
        </PayPalScriptProvider>
    )
}