121 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback } from 'react';
import { Alert } from 'react-native';
// @ts-ignore
import RazorpayCheckout from 'react-native-razorpay';
import { PlaceOrderResponse } from '@interfaces';
import { verifyPaymentApi } from '@api';
import { useAppSelector } from '@store';
import { ENV } from '../../../../config';
/**
* Custom hook that encapsulates the entire Razorpay UPI payment flow:
* 1. Extract session details (gatewayOrderId, amount) from the place-order response
* 2. Configure Razorpay checkout options with prefilled customer profile details
* 3. Present the Razorpay Checkout overlay to the user
* 4. Verify the payment signature on the backend via /payments/process
*
* Returns:
* - processRazorpayPayment: async function to execute the Razorpay flow
* - isPaymentProcessing: loading flag for UPI payment step
*/
export const useRazorPayment = () => {
const [isPaymentProcessing, setIsPaymentProcessing] = useState(false);
const user = useAppSelector(
state => state.customerProfile.customerDetails?.user,
);
/**
* Runs the complete Razorpay payment flow.
* @param response the PlaceOrderResponse from the backend
* @returns `true` if payment was completed and verified successfully, `false` otherwise
*/
const processRazorpayPayment = useCallback(
async (
response: PlaceOrderResponse,
paymentMethod: 'upi' | 'card' | undefined = undefined,
): Promise<boolean> => {
try {
setIsPaymentProcessing(true);
// ── 1. Extract session details ─────────────────────────────────────
const session = response.checkoutSessions?.[0];
if (!session) {
Alert.alert('Error', 'Payment session not found.');
return false;
}
const { gatewayOrderId, amount } = session;
if (!gatewayOrderId) {
Alert.alert('Error', 'Razorpay Order ID not found.');
return false;
}
// Amount in checkoutSessions represents standard rupees.
// Fallback to first order's total amount if needed.
const amountVal =
amount ?? parseFloat(response.orders?.[0]?.totalAmount || '0');
// Razorpay SDK requires the amount in the smallest currency sub-units (paise for INR)
const amountInPaise = Math.round(amountVal * 100);
// ── 2. Configure Checkout Options ──────────────────────────────────
const options = {
key: ENV.RAZORPAY_KEY_ID,
amount: amountInPaise,
currency: 'INR',
name: 'SG Delivery',
description: `Payment for Order #${
session.orderNumber || response.orders?.[0]?.orderNumber || ''
}`,
order_id: gatewayOrderId,
method: 'upi',
prefill: {
name: user?.name || '',
email: user?.email || '',
contact: user?.phone || '',
},
theme: { color: '#05824C' }, // Matching brand green primary color
};
// ── 3. Present Razorpay Checkout SDK ───────────────────────────────
const rzpData = await RazorpayCheckout.open(options);
// ── 4. Verify payment cryptographically on backend ────────────────
const verifyRes = await verifyPaymentApi({
gatewayOrderId: rzpData.razorpay_order_id || gatewayOrderId,
gatewayPaymentId: rzpData.razorpay_payment_id,
gatewaySignature: rzpData.razorpay_signature,
});
if (verifyRes.success) {
return true;
} else {
Alert.alert(
'Verification Failed',
verifyRes.message || 'Unable to confirm payment.',
);
return false;
}
} catch (error: any) {
// Razorpay Checkout library throws an error if user cancels or transaction fails
if (error && error.code) {
Alert.alert(
'Payment Cancelled',
error.description || 'You cancelled the payment process.',
);
} else {
Alert.alert(
'Payment Error',
error?.message || 'Something went wrong during payment.',
);
}
return false;
} finally {
setIsPaymentProcessing(false);
}
},
[user],
);
return { processRazorpayPayment, isPaymentProcessing };
};