Skip to main content

Unified Checkout Integration

Orsunpay’s Unified Checkout provides a seamless payment experience that automatically displays the best payment methods for each customer based on their location, currency, and preferences.

Overview

The Unified Checkout process involves three main steps:
  1. Create Session: Your backend creates a checkout session
  2. Load SDK: Your frontend loads the Orsunpay Checkout SDK
  3. Handle Events: Process payment success/failure events

Step 1: Create Checkout Session

First, create a checkout session from your backend:
curl "https://api.orsunpay.com/v1/checkout/sessions" \
  -X POST \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "USD",
    "country": "US",
    "customer": {
      "email": "[email protected]",
      "firstName": "John",
      "lastName": "Doe"
    },
    "returnUrl": "https://yoursite.com/payment/return",
    "cancelUrl": "https://yoursite.com/payment/cancel",
    "metadata": {
      "orderId": "order_123",
      "productId": "prod_456"
    }
  }'

Session Response

{
  "id": "cs_clkj3h2n0000qjr8x4c5h6e7b",
  "clientSecret": "cs_clkj3h2n0000qjr8x4c5h6e7b_secret_abc123",
  "amount": 5000,
  "currency": "USD",
  "country": "US",
  "status": "open",
  "expiresAt": "2023-11-15T11:30:00.000Z",
  "createdAt": "2023-11-15T10:30:00.000Z"
}
Never expose your secret API key in frontend code. The clientSecret from the session response is safe to use in client-side code.

Session Parameters

ParameterTypeRequiredDescription
amountintegerYesAmount in smallest currency unit
currencystringYesThree-letter currency code
countrystringYesCustomer’s country (ISO 3166-1 alpha-2)
customerobjectYesCustomer information
returnUrlstringYesURL to redirect after payment
cancelUrlstringNoURL to redirect if canceled
methodsFilterarrayNoRestrict to specific payment methods
localestringNoLanguage preference (default: en-US)
metadataobjectNoCustom key-value data

Step 2: Load Checkout SDK

Add the Orsunpay Checkout SDK to your page:
<script src="https://checkout.orsunpay.com/sdk.js" async></script>
For sandbox testing:
<script src="https://sandbox-checkout.orsunpay.com/sdk.js" async></script>

Step 3: Initialize Checkout

Basic Integration

<div id="orsunpay-checkout"></div>

<script>
document.addEventListener('DOMContentLoaded', function() {
  OrsunpayCheckout.mount('#orsunpay-checkout', {
    clientSecret: 'cs_clkj3h2n0000qjr8x4c5h6e7b_secret_abc123',
    
    onReady: function() {
      console.log('Checkout is ready');
    },
    
    onSuccess: function(result) {
      console.log('Payment succeeded:', result);
      // Redirect to success page or update UI
      window.location.href = '/payment/success?transaction_id=' + result.transactionId;
    },
    
    onFail: function(error) {
      console.error('Payment failed:', error);
      // Show error message to user
      alert('Payment failed: ' + error.message);
    },
    
    onCancel: function() {
      console.log('Payment canceled');
      // Handle cancellation
      window.location.href = '/payment/cancel';
    }
  });
});
</script>

Advanced Configuration

OrsunpayCheckout.mount('#orsunpay-checkout', {
  clientSecret: 'cs_clkj3h2n0000qjr8x4c5h6e7b_secret_abc123',
  
  // Appearance customization
  theme: 'light', // 'light', 'dark', or 'auto'
  locale: 'en-US',
  
  // Display options
  amount: 5000,
  currency: 'USD',
  
  // Payment method filtering
  methodsFilter: ['card', 'paypal', 'apple_pay'],
  
  // Event handlers
  onReady: function() {
    document.getElementById('loading').style.display = 'none';
  },
  
  onSuccess: function(result) {
    // result.transactionId - Orsunpay transaction ID
    // result.status - Transaction status
    // result.metadata - Your custom metadata
    
    // Update your order status
    fetch('/api/orders/update', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        orderId: result.metadata.orderId,
        transactionId: result.transactionId,
        status: 'paid'
      })
    }).then(() => {
      window.location.href = '/order/confirmation';
    });
  },
  
  onFail: function(error) {
    // error.code - Error code
    // error.message - Human-readable message
    // error.details - Additional error details
    
    showErrorMessage(error.message);
  },
  
  onCancel: function() {
    // User canceled the payment
    analytics.track('checkout_canceled');
    window.location.href = '/cart';
  }
});

Next.js Integration (App Router)

Server Component (Create Session)

// app/api/checkout/session/route.ts
import { NextRequest, NextResponse } from 'next/server';

const ORSUNPAY_API_KEY = process.env.ORSUNPAY_SECRET_KEY;
const ORSUNPAY_API_URL = process.env.NODE_ENV === 'production' 
  ? 'https://api.orsunpay.com/v1'
  : 'https://sandbox-api.orsunpay.com/v1';

export async function POST(request: NextRequest) {
  try {
    const { amount, currency, customer, orderId } = await request.json();
    
    const response = await fetch(`${ORSUNPAY_API_URL}/checkout/sessions`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${ORSUNPAY_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount,
        currency,
        country: 'US', // You might detect this from user's IP
        customer,
        returnUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/return`,
        cancelUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/cancel`,
        metadata: { orderId }
      })
    });
    
    const session = await response.json();
    
    if (!response.ok) {
      throw new Error(session.error?.message || 'Failed to create session');
    }
    
    return NextResponse.json({ clientSecret: session.clientSecret });
    
  } catch (error) {
    console.error('Checkout session error:', error);
    return NextResponse.json(
      { error: 'Failed to create checkout session' },
      { status: 500 }
    );
  }
}

Client Component (Checkout Form)

// app/checkout/checkout-form.tsx
'use client';

import { useEffect, useRef, useState } from 'react';

interface CheckoutFormProps {
  amount: number;
  currency: string;
  customer: {
    email: string;
    firstName: string;
    lastName: string;
  };
  orderId: string;
}

export default function CheckoutForm({ 
  amount, 
  currency, 
  customer, 
  orderId 
}: CheckoutFormProps) {
  const checkoutRef = useRef<HTMLDivElement>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let mounted = true;

    async function initializeCheckout() {
      try {
        // Create checkout session
        const response = await fetch('/api/checkout/session', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            amount,
            currency,
            customer,
            orderId
          })
        });

        const { clientSecret, error } = await response.json();

        if (error) {
          throw new Error(error);
        }

        // Wait for Orsunpay SDK to load
        if (typeof window !== 'undefined' && window.OrsunpayCheckout) {
          mountCheckout(clientSecret);
        } else {
          // Wait for SDK to load
          const script = document.querySelector('script[src*="checkout.orsunpay.com"]');
          if (script) {
            script.addEventListener('load', () => {
              if (mounted) {
                mountCheckout(clientSecret);
              }
            });
          }
        }
      } catch (err) {
        if (mounted) {
          setError(err instanceof Error ? err.message : 'Failed to initialize checkout');
          setIsLoading(false);
        }
      }
    }

    function mountCheckout(clientSecret: string) {
      if (!mounted || !checkoutRef.current) return;

      window.OrsunpayCheckout.mount(checkoutRef.current, {
        clientSecret,
        theme: 'light',
        locale: 'en-US',
        
        onReady() {
          if (mounted) {
            setIsLoading(false);
          }
        },
        
        onSuccess(result) {
          console.log('Payment succeeded:', result);
          // Redirect to success page
          window.location.href = `/order/success?transaction_id=${result.transactionId}`;
        },
        
        onFail(error) {
          console.error('Payment failed:', error);
          if (mounted) {
            setError(error.message);
            setIsLoading(false);
          }
        },
        
        onCancel() {
          console.log('Payment canceled');
          // Redirect back to cart
          window.location.href = '/cart';
        }
      });
    }

    initializeCheckout();

    return () => {
      mounted = false;
    };
  }, [amount, currency, customer, orderId]);

  if (error) {
    return (
      <div className="checkout-error">
        <p>Payment Error: {error}</p>
        <button onClick={() => window.location.reload()}>
          Try Again
        </button>
      </div>
    );
  }

  return (
    <div className="checkout-container">
      {isLoading && (
        <div className="checkout-loading">
          Loading payment methods...
        </div>
      )}
      <div ref={checkoutRef} id="orsunpay-checkout" />
    </div>
  );
}

Page Component

// app/checkout/page.tsx
import Script from 'next/script';
import CheckoutForm from './checkout-form';

// This would typically come from your cart/order context
const orderData = {
  amount: 5000, // $50.00
  currency: 'USD',
  customer: {
    email: '[email protected]',
    firstName: 'John',
    lastName: 'Doe'
  },
  orderId: 'order_123'
};

export default function CheckoutPage() {
  const checkoutSDKUrl = process.env.NODE_ENV === 'production'
    ? 'https://checkout.orsunpay.com/sdk.js'
    : 'https://sandbox-checkout.orsunpay.com/sdk.js';

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="max-w-2xl mx-auto">
        <h1 className="text-2xl font-bold mb-6">Complete Your Payment</h1>
        
        <div className="bg-white p-6 rounded-lg shadow">
          <CheckoutForm {...orderData} />
        </div>
      </div>

      <Script
        src={checkoutSDKUrl}
        strategy="beforeInteractive"
      />
    </div>
  );
}

Event Handling

Success Event

The onSuccess callback receives a result object:
{
  transactionId: 'tr_clkj3h2n0000qjr8x4c5h6e7b',
  status: 'SUCCESS',
  amount: 5000,
  currency: 'USD',
  metadata: {
    orderId: 'order_123',
    productId: 'prod_456'
  }
}

Error Event

The onFail callback receives an error object:
{
  code: 'card_declined',
  message: 'Your card was declined',
  details: {
    declineReason: 'insufficient_funds'
  }
}

Cancel Event

The onCancel callback is triggered when users explicitly cancel the payment flow.

Styling and Theming

Theme Options

OrsunpayCheckout.mount('#checkout', {
  clientSecret: 'cs_...',
  
  // Theme options
  theme: 'light',    // 'light', 'dark', 'auto'
  
  // Custom CSS variables
  style: {
    '--primary-color': '#007bff',
    '--border-radius': '8px',
    '--font-family': 'Arial, sans-serif'
  }
});

CSS Customization

/* Custom styles for checkout container */
#orsunpay-checkout {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: white;
}

/* Responsive design */
@media (max-width: 768px) {
  #orsunpay-checkout {
    padding: 15px;
    margin: 10px;
  }
}

Best Practices

Security

  1. Server-side session creation: Always create sessions from your secure backend
  2. Webhook verification: Use webhooks as the authoritative source of payment status
  3. Client secret protection: The client secret is safe for frontend use but should be unique per session

User Experience

  1. Loading states: Show loading indicators while the checkout initializes
  2. Error handling: Provide clear error messages and recovery options
  3. Mobile optimization: Ensure the checkout works well on mobile devices
  4. Accessibility: The checkout SDK is built with accessibility in mind

Integration

  1. Webhook backup: Always implement webhook handling as primary status source
  2. Idempotency: Handle duplicate success callbacks gracefully
  3. Testing: Thoroughly test all payment flows in sandbox environment

Troubleshooting

Common Issues

IssueSolution
SDK not loadingCheck script URL and network connectivity
Client secret invalidVerify session creation on backend
Payment methods not showingCheck country/currency configuration
Success callback not firingVerify webhook endpoint configuration

Debug Mode

Enable debug mode for additional logging:
OrsunpayCheckout.mount('#checkout', {
  clientSecret: 'cs_...',
  debug: true // Enable debug logging
});
Always test your integration thoroughly in sandbox mode before going live. Use webhook notifications as the primary source of truth for payment status updates.