Bug fixes where somehow payment_collection became an array and broke everything.

Somehow the order is not being marked as captured.
This commit is contained in:
Amritanshu Agrawal 2025-02-16 04:34:07 +00:00
parent 12d7d21d28
commit e7c40cb5f1
3 changed files with 73 additions and 40 deletions

View File

@ -0,0 +1,17 @@
// app/order-success/page.tsx
'use client';
import Link from 'next/link';
export default function OrderSuccessPage() {
return (
<div className='mx-auto max-w-lg py-10 text-center'>
<h1 className='text-2xl font-bold mb-4'>Thank you for your order!</h1>
<p className='mb-6'>Your order has been placed successfully.</p>
<Link href='/' className='text-blue-600 underline'>
Return to Homepage
</Link>
</div>
);
}

View File

@ -1,6 +1,7 @@
'use client'; // include with Next.js 13+ 'use client'; // include with Next.js 13+
import { HttpTypes } from '@medusajs/types'; import { HttpTypes } from '@medusajs/types';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import RazorpayPayment from './razorpay'; import RazorpayPayment from './razorpay';
@ -8,7 +9,8 @@ import RazorpayPayment from './razorpay';
import { useCart } from '@/providers/cart'; import { useCart } from '@/providers/cart';
export default function CheckoutPaymentStep() { export default function CheckoutPaymentStep() {
const { cart, setCart } = useCart(); const router = useRouter();
const { cart, setCart, refreshCart } = useCart();
const [paymentProvider, setPaymentProvider] = useState<HttpTypes.StorePaymentProvider | null>(null); const [paymentProvider, setPaymentProvider] = useState<HttpTypes.StorePaymentProvider | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -26,7 +28,6 @@ export default function CheckoutPaymentStep() {
if (!cart) { if (!cart) {
return; return;
} }
console.log('useEffect cart', cart);
fetch(`${process.env.NEXT_PUBLIC_STORE_URL}/store/payment-providers?region_id=${cart.region_id}`, { fetch(`${process.env.NEXT_PUBLIC_STORE_URL}/store/payment-providers?region_id=${cart.region_id}`, {
credentials: 'include', credentials: 'include',
@ -42,7 +43,8 @@ export default function CheckoutPaymentStep() {
}); });
}, [cart]); }, [cart]);
const fetchOrCreatePaymentCollection = async (cart: any) => { const fetchOrCreatePaymentCollection = async (cartId: string) => {
const cart = await fetchUpdatedCart(cartId);
const paymentCollectionId = cart.payment_collection?.id; const paymentCollectionId = cart.payment_collection?.id;
if (paymentCollectionId) { if (paymentCollectionId) {
return paymentCollectionId; return paymentCollectionId;
@ -59,7 +61,6 @@ export default function CheckoutPaymentStep() {
cart_id: cart.id, cart_id: cart.id,
}), }),
}).then((res) => res.json()); }).then((res) => res.json());
return payment_collection.id; return payment_collection.id;
}; };
const initializePaymentSession = async (paymentCollectionId: string, providerId: string) => { const initializePaymentSession = async (paymentCollectionId: string, providerId: string) => {
@ -93,20 +94,19 @@ export default function CheckoutPaymentStep() {
}; };
useEffect(() => { useEffect(() => {
console.log('useEffect paymentProvider', cart, paymentProvider); if (!paymentProvider || !cart) {
if (!cart || !paymentProvider) {
return; return;
} }
const initializePayment = async () => { const initializePayment = async () => {
try { try {
// Step 1: Fetch or create the Payment Collection // Step 1: Fetch or create the Payment Collection
const paymentCollectionId = await fetchOrCreatePaymentCollection(cart); const paymentCollectionId = await fetchOrCreatePaymentCollection(cart?.id);
// Step 2: Initialize Payment Session for the provider // Step 2: Initialize Payment Session for the provider
const session = await initializePaymentSession(paymentCollectionId, paymentProvider.id); const session = await initializePaymentSession(paymentCollectionId, paymentProvider.id);
// Step 3: Fetch and update the cart to reflect the changes // Step 3: Fetch and update the cart to reflect the changes
const updatedCart = await fetchUpdatedCart(cart.id); const updatedCart = await fetchUpdatedCart(cart?.id);
if (JSON.stringify(cart) !== JSON.stringify(updatedCart)) { if (JSON.stringify(cart) !== JSON.stringify(updatedCart)) {
setCart(updatedCart); setCart(updatedCart);
} }
@ -120,10 +120,47 @@ export default function CheckoutPaymentStep() {
initializePayment(); initializePayment();
}, [paymentProvider]); }, [paymentProvider]);
/**
* CAPTURE PAYMENT:
* - Called once Razorpay payment succeeds in the child component
* - Tries to "complete" the cart in Medusa which effectively captures the payment
*/
const capturePayment = useCallback(
async (cartId: string) => {
setLoading(true);
try {
// Complete the cart in Medusa
const result = await fetch(`${process.env.NEXT_PUBLIC_STORE_URL}/store/carts/${cartId}/complete`, {
credentials: 'include',
headers: {
'x-publishable-api-key': process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || 'temp',
},
method: 'POST',
})
.then((res) => res.json())
.then(({ type, cart, order, error }) => {
if (type === 'cart' && cart) {
// an error occured
console.error('Error completing cart:', error);
} else if (type === 'order' && order) {
console.log(order);
// Clears/reloads cart in your client state
refreshCart();
router.push('/checkout/order-success');
}
});
} catch (err) {
console.error('Error completing cart:', err);
} finally {
setLoading(false);
}
},
[cart],
);
const getPaymentUi = useCallback(() => { const getPaymentUi = useCallback(() => {
const activePaymentSession = cart?.payment_collection?.payment_sessions?.[0]; const activePaymentSession = cart?.payment_collection?.payment_sessions?.[0];
if (activePaymentSession) { if (activePaymentSession) {
return <RazorpayPayment />; return <RazorpayPayment cart={cart} capturePayment={capturePayment} />;
} }
}, [cart]); }, [cart]);

View File

@ -5,11 +5,16 @@ import React, { useState } from 'react';
import { useCart } from '@/providers/cart'; import { useCart } from '@/providers/cart';
interface RazorpayPaymentProps {
cart: any;
capturePayment: (cartId: string) => Promise<void>;
}
/** /**
* This component injects the Razorpay checkout script * This component injects the Razorpay checkout script
* and displays the `RazorpayForm` after the script has loaded. * and displays the `RazorpayForm` after the script has loaded.
*/ */
export default function RazorpayPayment() { export default function RazorpayPayment({ cart, capturePayment }: RazorpayPaymentProps) {
const [scriptLoaded, setScriptLoaded] = useState(false); const [scriptLoaded, setScriptLoaded] = useState(false);
return ( return (
@ -21,7 +26,7 @@ export default function RazorpayPayment() {
<Script src='https://checkout.razorpay.com/v1/checkout.js' onLoad={() => setScriptLoaded(true)} /> <Script src='https://checkout.razorpay.com/v1/checkout.js' onLoad={() => setScriptLoaded(true)} />
{/* Only render the payment form if the script has loaded */} {/* Only render the payment form if the script has loaded */}
{scriptLoaded && <RazorpayForm />} {scriptLoaded && <RazorpayForm cart={cart} capturePayment={capturePayment} />}
</> </>
); );
} }
@ -30,8 +35,8 @@ export default function RazorpayPayment() {
* A simple form that triggers the Razorpay checkout * A simple form that triggers the Razorpay checkout
* using data from the cart. * using data from the cart.
*/ */
function RazorpayForm() { function RazorpayForm({ cart, capturePayment }: RazorpayPaymentProps) {
const { cart, refreshCart } = useCart(); const { refreshCart } = useCart();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Typically the order_id is generated on your server (Medusa backend) // Typically the order_id is generated on your server (Medusa backend)
@ -67,33 +72,7 @@ function RazorpayForm() {
// `response.razorpay_order_id` will have the Order ID // `response.razorpay_order_id` will have the Order ID
// `response.razorpay_signature` will have the signature // `response.razorpay_signature` will have the signature
try { await capturePayment(cart.id);
// Complete the cart in Medusa
const result = await fetch(`${process.env.NEXT_PUBLIC_STORE_URL}/store/carts/${cart.id}/complete`, {
credentials: 'include',
headers: {
'x-publishable-api-key': process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || 'temp',
},
method: 'POST',
});
const data = await result.json();
// Example response shape:
// { type: "order" | "cart", order?: any, cart?: any, error?: any }
if (data.type === 'cart' && data.cart) {
// Some error or failure state
console.error('Error completing cart:', data.error);
} else if (data.type === 'order' && data.order) {
// Payment and order creation successful
alert('Order placed.');
// Clears/reloads cart in your client state
refreshCart();
}
} catch (err) {
console.error('Error completing cart:', err);
} finally {
setLoading(false);
}
}, },
prefill: { prefill: {
// Prefill customer data in the Razorpay form // Prefill customer data in the Razorpay form