.. _checking_payment_status: Checking Payment Status ======================= After redirecting a customer to checkout, you need to monitor the payment status to update your order accordingly. Payment Status Values --------------------- .. list-table:: :widths: 20 15 65 :header-rows: 1 * - Status - Terminal - Description * - ``pending`` - No - Payment created, awaiting customer action on checkout page * - ``successful`` - Yes - Payment confirmed on blockchain - fulfill the order * - ``unsuccessful`` - Yes - Payment failed (e.g., transaction reverted) * - ``expired`` - Yes - 24-hour window passed without payment * - ``cancelled`` - Yes - Payment was cancelled by customer or system Polling for Status ------------------ Use the ``GET /external/payments/prompt/:id`` endpoint to check payment status. **Endpoint:** ``GET /external/payments/prompt/:id`` **Example Request:** .. code-block:: bash curl -X GET https://api.miraclecash.info/external/payments/prompt/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Basic $(echo -n 'your_client_id:your_api_key' | base64)" **Example Response:** .. code-block:: json { "id": "550e8400-e29b-41d4-a716-446655440000", "blockchainId": "eth", "status": "successful", "createdAt": "2025-01-28T14:30:00.000Z" } Polling Implementation ---------------------- Node.js / TypeScript ~~~~~~~~~~~~~~~~~~~~ .. code-block:: typescript interface PaymentStatus { id: string; blockchainId: string | null; status: 'pending' | 'successful' | 'unsuccessful' | 'expired' | 'cancelled'; createdAt: string; } async function waitForPayment( promptId: string, timeoutMs: number = 300000 // 5 minutes ): Promise { const startTime = Date.now(); const pollInterval = 5000; // 5 seconds while (Date.now() - startTime < timeoutMs) { const status = await getPaymentStatus(promptId); // Return immediately for terminal states if (status.status !== 'pending') { return status; } // Wait before next poll await new Promise(resolve => setTimeout(resolve, pollInterval)); } throw new Error('Payment polling timeout'); } // Usage in your order flow async function processOrder(orderId: string, promptId: string) { try { const result = await waitForPayment(promptId); if (result.status === 'successful') { await Order.update(orderId, { status: 'paid', paidAt: new Date(), blockchainId: result.blockchainId, }); return { success: true, message: 'Payment confirmed!' }; } else { await Order.update(orderId, { status: 'payment_failed', failureReason: result.status, }); return { success: false, message: `Payment ${result.status}` }; } } catch (error) { // Handle timeout - payment may still complete return { success: false, message: 'Payment pending - check back later' }; } } Python ~~~~~~ .. code-block:: python import time from typing import Literal def wait_for_payment( prompt_id: str, timeout_seconds: int = 300, poll_interval: int = 5 ) -> dict: """Poll until payment reaches a terminal state.""" start_time = time.time() while time.time() - start_time < timeout_seconds: status = get_payment_status(prompt_id) if status['status'] != 'pending': return status time.sleep(poll_interval) raise TimeoutError('Payment polling timeout') def process_order(order_id: str, prompt_id: str) -> dict: """Process order after customer completes checkout.""" try: result = wait_for_payment(prompt_id) if result['status'] == 'successful': Order.objects.filter(id=order_id).update( status='paid', paid_at=datetime.now(), blockchain_id=result['blockchainId'] ) return {'success': True, 'message': 'Payment confirmed!'} else: Order.objects.filter(id=order_id).update( status='payment_failed', failure_reason=result['status'] ) return {'success': False, 'message': f"Payment {result['status']}"} except TimeoutError: return {'success': False, 'message': 'Payment pending'} Background Job Pattern ---------------------- For production systems, use a background job instead of blocking requests: **1. Create payment and store prompt ID:** .. code-block:: javascript // When customer initiates checkout const payment = await createPayment(amount, 'eth'); await Order.update(orderId, { paymentPromptId: payment.prompt.id, status: 'awaiting_payment' }); // Schedule background job await jobQueue.add('check-payment', { orderId, promptId: payment.prompt.id }); **2. Background job polls for status:** .. code-block:: javascript // Background worker jobQueue.process('check-payment', async (job) => { const { orderId, promptId } = job.data; const status = await getPaymentStatus(promptId); if (status.status === 'pending') { // Re-queue job to check again in 30 seconds throw new Error('Still pending - retry'); } // Update order based on final status await Order.update(orderId, { status: status.status === 'successful' ? 'paid' : 'payment_failed', paymentStatus: status.status, }); // Send notification to customer if (status.status === 'successful') { await sendOrderConfirmation(orderId); } }); Status Transition Diagram ------------------------- .. code-block:: text ┌─────────────────┐ │ Payment Created │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ pending │ └────────┬────────┘ │ ┌───────────────┬───────────┼───────────┬───────────────┐ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌───────────────┐ ┌───────────┐ ┌─────────┐ ┌───────────┐ ┌───────────┐ │ successful │ │unsuccessful│ │ expired │ │ cancelled │ │ (stays │ │ (confirmed) │ │ (failed) │ │(timeout)│ │ (user) │ │ pending) │ └───────────────┘ └───────────┘ └─────────┘ └───────────┘ └───────────┘ Best Practices -------------- 1. **Don't block user requests**: Use background jobs or webhooks instead of synchronous polling in HTTP handlers. 2. **Set reasonable timeouts**: 5 minutes is usually enough for most blockchain confirmations. 3. **Handle all terminal states**: Your code should handle ``successful``, ``unsuccessful``, ``expired``, and ``cancelled``. 4. **Implement idempotency**: Ensure processing the same payment twice doesn't cause issues (e.g., double fulfillment). 5. **Log everything**: Store the full response for debugging and audit trails. 6. **Graceful degradation**: If polling times out, don't assume failure - the payment may still complete. .. code-block:: javascript // Example: Idempotent order update async function markOrderPaid(orderId: string, promptId: string) { const order = await Order.findById(orderId); // Already processed - skip if (order.status === 'paid') { return order; } // Verify payment status const status = await getPaymentStatus(promptId); if (status.status !== 'successful') { throw new Error(`Payment not successful: ${status.status}`); } // Update order atomically return Order.findOneAndUpdate( { _id: orderId, status: { $ne: 'paid' } }, { status: 'paid', paidAt: new Date() } ); }