.. _redirect_to_checkout: Redirect to Checkout ==================== After creating a payment prompt, redirect your customer to the MiraclePay hosted checkout page where they can complete the payment. Checkout URL Structure ---------------------- The ``checkoutUrl`` returned from the Create Payment Prompt API depends on your account type. For standard accounts it has this format: .. code-block:: text https://checkout.miraclecash.info/?sessionId= **Example:** .. code-block:: text https://checkout.miraclecash.info/?sessionId=550e8400-e29b-41d4-a716-446655440000 US merchant accounts receive a different format: .. code-block:: text https://checkout.miraclecash.info/checkout/pay/ .. note:: Always redirect to the ``checkoutUrl`` value returned by the API — never construct the URL yourself. The host or format may change; the returned value is authoritative. Redirect Methods ---------------- Server-Side Redirect (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Redirect the user from your backend after creating the payment: **Node.js / Express:** .. code-block:: javascript app.post('/checkout', async (req, res) => { // createPayment wraps POST /external/v1/payments/prompt (amount in cents) const { prompt, checkoutUrl } = await createPayment( req.body.amountCents, ['eth-usdc'] ); // Store payment ID with order await Order.update(req.body.orderId, { paymentPromptId: prompt.id }); // Redirect to checkout res.redirect(checkoutUrl); }); Client-Side Redirect ~~~~~~~~~~~~~~~~~~~~ If your frontend calls your API, return the checkout URL for client-side redirect: **API Response:** .. code-block:: json { "success": true, "checkoutUrl": "https://checkout.miraclecash.info/?sessionId=550e8400..." } **Frontend JavaScript:** .. code-block:: javascript async function handlePayment() { const response = await fetch('/api/create-payment', { method: 'POST', body: JSON.stringify({ amountCents: 5000, orderId: 'ORD-123' }), }); const { checkoutUrl } = await response.json(); // Redirect to MiraclePay checkout window.location.href = checkoutUrl; } .. warning:: Never call the External Payments API from the browser — the API secret must stay on your backend. The frontend should only ever receive the ``checkoutUrl``. Checkout Flow ------------- Once redirected, the customer experiences this flow: .. code-block:: text Your Site ──► MiraclePay Checkout ──► Select Wallet ──► Connect Wallet │ ▼ Confirm Payment │ ▼ Transaction Status / \ Success Failed │ │ ▼ ▼ Payment Complete Retry/Cancel **Checkout Page Features:** 1. **Wallet Selection**: Customer chooses their crypto wallet 2. **Amount Display**: Shows payment amount in USD and crypto equivalent 3. **QR Code**: For mobile wallet scanning 4. **Transaction Confirmation**: Real-time status updates 5. **Expiration Timer**: Shows remaining time to complete payment .. note:: On US merchant accounts the checkout steps differ: the customer verifies their email, picks a network, and sends the displayed amount to the shown deposit address. The outcome is reported through the same payment statuses and webhooks. Post-Payment Handling --------------------- Automatic Redirect (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you provide a ``redirectUrl`` when creating the payment prompt, MiraclePay will redirect the customer back to your site after payment: - **Successful payment**: Customer is auto-redirected after 5 seconds, with a "Return to store" button for immediate redirect - **Failed, expired, or cancelled**: A "Return to store" button is shown (no auto-redirect) .. note:: Automatic redirect applies to standard accounts. On US merchant accounts the customer completes the payment on the hosted page and is **not** redirected back — track the outcome with :ref:`webhooks` or by polling, as described under `Manual Polling (Without Redirect URL)`_. The redirect URL will have query parameters appended: .. code-block:: text https://shop.example.com/order/123/complete?promptId=550e8400-...&status=successful **Query parameters:** .. list-table:: :widths: 20 80 :header-rows: 1 * - Parameter - Description * - ``promptId`` - The payment prompt UUID * - ``status`` - Payment result: ``successful``, ``unsuccessful``, ``expired``, or ``cancelled`` .. warning:: **Do not trust the redirect query parameters as proof of payment.** A customer could manually navigate to your redirect URL with a forged ``status=successful`` parameter. Always verify the payment server-side via ``GET /external/v1/payments/:id`` before fulfilling the order. Manual Polling (Without Redirect URL) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you don't provide a ``redirectUrl``, you need to verify the payment status yourself. The checkout page will show "You can now safely close this window" after completion. **Recommended approach:** 1. **Before redirect**: Show a "Processing payment..." page 2. **Track the outcome**: Subscribe to :ref:`webhooks`, or poll the payment status every few seconds 3. **Update order**: Mark order as paid when status is ``successful`` 4. **Show confirmation**: Display order confirmation to customer **Example polling implementation** (``getPayment`` is the signed helper from :ref:`checking_payment_status`): .. code-block:: javascript async function pollPaymentStatus(promptId) { const maxAttempts = 60; // 5 minutes at 5-second intervals let attempts = 0; while (attempts < maxAttempts) { const payment = await getPayment(promptId); switch (payment.status) { case 'successful': return { success: true, payment }; case 'unsuccessful': case 'expired': case 'cancelled': return { success: false, payment }; case 'pending': // Continue polling break; } await new Promise(resolve => setTimeout(resolve, 5000)); attempts++; } throw new Error('Payment status polling timeout'); } .. tip:: For server-to-server notification without polling, use :ref:`webhooks`. Payment Expiration ------------------ Every payment prompt carries an ``expiresAt`` timestamp; the expiry window is configured per blockchain network (typically minutes, not hours). If the customer doesn't complete payment in time: - The payment status changes to ``expired`` - The checkout URL becomes invalid - You should create a new payment prompt if the customer wants to retry .. tip:: Read ``expiresAt`` from the payment object rather than assuming a fixed duration, and display the remaining time to customers.