103 lines
3.8 KiB
TypeScript
103 lines
3.8 KiB
TypeScript
import { useState, useCallback } from 'react';
|
||
import { Alert } from 'react-native';
|
||
import { useStripe } from '@stripe/stripe-react-native';
|
||
import { PlaceOrderResponse } from '@interfaces';
|
||
import { verifyPaymentApi } from '@api';
|
||
|
||
/**
|
||
* Custom hook that encapsulates the entire Stripe card payment flow:
|
||
* 1. Extract session info from the place-order response
|
||
* 2. Initialize the Stripe Payment Sheet
|
||
* 3. Present the Payment Sheet to the user
|
||
* 4. Verify the payment on the backend via /payments/process
|
||
*
|
||
* Returns:
|
||
* - processCardPayment: async function to run the full flow
|
||
* - isPaymentProcessing: loading flag for UI
|
||
*/
|
||
export const useStripePayment = () => {
|
||
const { initPaymentSheet, presentPaymentSheet } = useStripe();
|
||
const [isPaymentProcessing, setIsPaymentProcessing] = useState(false);
|
||
|
||
/**
|
||
* Runs the complete Stripe card payment flow.
|
||
* @param response – the PlaceOrderResponse from the backend (contains checkoutSessions)
|
||
* @returns `true` if payment was verified successfully, `false` otherwise
|
||
*/
|
||
const processCardPayment = useCallback(
|
||
async (response: PlaceOrderResponse): Promise<boolean> => {
|
||
try {
|
||
setIsPaymentProcessing(true);
|
||
|
||
// ── 1. Extract session details ─────────────────────────────────────
|
||
const session = response.checkoutSessions?.[0];
|
||
// console.log('session', session);
|
||
const clientSecret = session?.gatewayToken;
|
||
// console.log('clientSecret', clientSecret);
|
||
const gatewayOrderId = session?.gatewayOrderId;
|
||
// console.log('gatewayOrderId', gatewayOrderId);
|
||
|
||
if (!clientSecret) {
|
||
Alert.alert('Error', 'Payment session token not found.');
|
||
return false;
|
||
}
|
||
|
||
// ── 2. Initialize Payment Sheet ────────────────────────────────────
|
||
const { error: initError } = await initPaymentSheet({
|
||
paymentIntentClientSecret: clientSecret,
|
||
merchantDisplayName: 'SG Delivery',
|
||
});
|
||
|
||
if (initError) {
|
||
Alert.alert('Error', `Stripe init failed: ${initError.message}`);
|
||
return false;
|
||
}
|
||
|
||
// ── 3. Present Payment Sheet ───────────────────────────────────────
|
||
const { error: presentError } = await presentPaymentSheet();
|
||
|
||
if (presentError) {
|
||
if (presentError.code === 'Canceled') {
|
||
Alert.alert(
|
||
'Payment Cancelled',
|
||
'You cancelled the payment process.',
|
||
);
|
||
} else {
|
||
console.log(presentError.message);
|
||
Alert.alert('Payment Error', presentError.message);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ── 4. Verify payment on backend ───────────────────────────────────
|
||
const verifyRes = await verifyPaymentApi({
|
||
gatewayOrderId: gatewayOrderId || '',
|
||
gatewayPaymentId: gatewayOrderId || '',
|
||
gatewaySignature: 'stripe_signature_verified',
|
||
});
|
||
|
||
if (verifyRes.success) {
|
||
return true;
|
||
} else {
|
||
Alert.alert(
|
||
'Verification Failed',
|
||
verifyRes.message || 'Unable to confirm payment.',
|
||
);
|
||
return false;
|
||
}
|
||
} catch (error: any) {
|
||
Alert.alert(
|
||
'Payment Error',
|
||
error?.message || 'Something went wrong during payment.',
|
||
);
|
||
return false;
|
||
} finally {
|
||
setIsPaymentProcessing(false);
|
||
}
|
||
},
|
||
[initPaymentSheet, presentPaymentSheet],
|
||
);
|
||
|
||
return { processCardPayment, isPaymentProcessing };
|
||
};
|