diff --git a/app/App.tsx b/app/App.tsx
index d774074..0bf5022 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -11,14 +11,18 @@ import { persistor, store } from './store';
import { RootNavigator } from './navigation/rootNavigator';
import { PersistGate } from 'redux-persist/integration/react';
import { colors } from '@theme';
+import { StripeProvider } from '@stripe/stripe-react-native';
+import { ENV } from './config';
function App() {
return (
-
-
-
+
+
+
+
+
);
diff --git a/app/api/paymentMethodsApi.ts b/app/api/paymentMethodsApi.ts
index e27ef59..148be13 100644
--- a/app/api/paymentMethodsApi.ts
+++ b/app/api/paymentMethodsApi.ts
@@ -4,3 +4,21 @@ import { apiClient } from '@services';
export const getAllPaymentMethodsApi = async () => {
return await apiClient.get('/payments/options');
};
+
+export interface VerifyPaymentPayload {
+ gatewayOrderId: string;
+ gatewayPaymentId: string;
+ gatewaySignature: string;
+}
+
+export interface VerifyPaymentResponse {
+ success: boolean;
+ message: string;
+}
+
+export const verifyPaymentApi = async (payload: VerifyPaymentPayload) => {
+ return await apiClient.post(
+ '/payments/process',
+ payload,
+ );
+};
diff --git a/app/config/index.ts b/app/config/index.ts
new file mode 100644
index 0000000..478c2dd
--- /dev/null
+++ b/app/config/index.ts
@@ -0,0 +1,10 @@
+// ─── Environment Configuration ───────────────────────────────────────────────
+// Centralizes all environment keys for the application.
+// Since React Native doesn't natively expose process.env at runtime,
+// we export them directly from here.
+
+export const ENV = {
+ STRIPE_PUBLISHABLE_KEY:
+ 'pk_test_51Ox04DSHlFQYe8R5Cvm8i6n99QynUNj7WQzACB89PImnt0X8Z54SBFM22ghSNHYFo7OgVcyba9QhhyrrdRqRPJpF00Us8XJv7f',
+ RAZORPAY_KEY_ID: 'rzp_test_TCxbW8AxcXgMCj',
+};
diff --git a/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx
index 1ca579d..9468b03 100644
--- a/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx
+++ b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx
@@ -16,6 +16,7 @@ import { getAllPaymentMethodsThunk, placeOrderThunk } from './thunk';
import { useSelector } from 'react-redux';
import uuid from 'react-native-uuid';
import { v4 } from 'react-native-uuid/dist/v4';
+import { useStripePayment } from './hooks/useStripePayment';
type CheckoutPaymentNavProp = StackNavigationProp<
AppStackParamList,
@@ -52,25 +53,15 @@ export const CheckoutPaymentScreen: React.FC = () => {
// console.log(uuid);
const {
paymentMethods,
- placeOrderSuccess,
placeOrderLoading,
- placeOrderError,
} = useAppSelector((state: RootState) => state.paymentMethods);
+ const { processCardPayment, isPaymentProcessing } = useStripePayment();
+
useEffect(() => {
dispatch(getAllPaymentMethodsThunk());
}, [dispatch]);
- useEffect(() => {
- if (placeOrderSuccess) {
- navigation.navigate('OrderConfirmedScreen', {
- orderId: 'ORD-' + Math.floor(Math.random() * 900000 + 100000),
- });
- } else if (placeOrderError) {
- Alert.alert('Error', placeOrderError);
- }
- }, [placeOrderSuccess, placeOrderError]);
-
// console.log('paymentMethods', paymentMethods);
const selectedPaymentMethod = paymentMethods.find(
@@ -84,18 +75,43 @@ export const CheckoutPaymentScreen: React.FC = () => {
// },);
- const handlePlaceOrder = () => {
- dispatch(
- placeOrderThunk({
- addressId: selectedAddressId || '',
- paymentMethodId: selectedMethod,
- paymentMethod: methodName || '',
- orderType: 'DELIVERY',
- idempotencyKey: `idemp-key-${uuid}`,
- }),
- );
+ const handlePlaceOrder = async () => {
+ try {
+ const response = await dispatch(
+ placeOrderThunk({
+ addressId: selectedAddressId || '',
+ paymentMethodId: selectedMethod,
+ paymentMethod: methodName || '',
+ orderType: 'DELIVERY',
+ idempotencyKey: `idemp-key-${uuid}`,
+ }),
+ ).unwrap();
+
+ if (methodName === 'CARD') {
+ // Delegate entire Stripe flow to the hook
+ const success = await processCardPayment(response);
+ if (success) {
+ navigation.navigate('OrderConfirmedScreen', {
+ orderId:
+ response.orders[0]?.orderNumber ||
+ 'ORD-' + Math.floor(Math.random() * 900000 + 100000),
+ });
+ }
+ } else {
+ // Non-card methods (COD, UPI, Wallet) – navigate directly
+ navigation.navigate('OrderConfirmedScreen', {
+ orderId:
+ response.orders[0]?.orderNumber ||
+ 'ORD-' + Math.floor(Math.random() * 900000 + 100000),
+ });
+ }
+ } catch (err: any) {
+ Alert.alert('Error', err || 'Failed to place order');
+ }
};
+ const isLoading = placeOrderLoading || isPaymentProcessing;
+
return (
navigation.goBack()} />
@@ -114,9 +130,9 @@ export const CheckoutPaymentScreen: React.FC = () => {
diff --git a/app/features/screens/checkoutPaymentScreen/hooks/useStripePayment.ts b/app/features/screens/checkoutPaymentScreen/hooks/useStripePayment.ts
new file mode 100644
index 0000000..7c7d598
--- /dev/null
+++ b/app/features/screens/checkoutPaymentScreen/hooks/useStripePayment.ts
@@ -0,0 +1,102 @@
+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 => {
+ 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 };
+};
diff --git a/app/interfaces/order.ts b/app/interfaces/order.ts
index f45c7b6..6abd5ad 100644
--- a/app/interfaces/order.ts
+++ b/app/interfaces/order.ts
@@ -12,6 +12,16 @@ export interface PlaceOrderResponse {
success: boolean;
message: string;
orders: Order[];
+ checkoutSessions: CheckoutSession[];
+}
+
+export interface CheckoutSession {
+ orderId: string;
+ orderNumber: string;
+ paymentId: string;
+ gatewayOrderId: string;
+ gatewayToken: string;
+ amount: number;
}
export interface Order {