Hosted Checkout Integration
The Payment Gateway provides a fully-hosted, PCI-compliant checkout page that handles card collection and payment processing. This eliminates your PCI compliance burden and provides a secure, optimized payment experience.
Checkout URL: https://checkout-page-amber.vercel.appBuilt with Next.js 15, React 19, and TypeScript
Why Use Hosted Checkout?
Zero PCI Compliance Burden Card data never touches your server No PCI-DSS compliance requirements for your infrastructure
Enterprise-Grade Security Client-side validation with Luhn algorithm HTTPS enforcement, input sanitization, automatic expiration
Optimized Conversion Mobile-first responsive design Real-time validation, clear error messages, smooth animations
Zero Maintenance Fully managed and updated Works across all modern browsers, automatic security patches
Security Features
PCI-DSS Compliance
Card Data Isolation:
Card numbers, CVV, and expiry dates never touch your server. All sensitive data is collected directly by the checkout page and sent to the Payment API over encrypted connections.
Card Validation:
Luhn algorithm validation (detects typos)
Expiry date validation (rejects expired cards)
CVV format validation (3-4 digits)
Real-time error feedback
Input Sanitization:
Special characters blocked
Paste prevention on CVV field
Numeric-only inputs for card numbers
HTTPS Enforcement:
TLS 1.3 encryption for all requests
Strict-Transport-Security headers
Content-Security-Policy enforcement
Authentication:
Client secret validation (not API keys)
One-time use secrets per payment intent
Automatic expiration after 1 hour
Expiration Handling:
Payment intents expire after 1 hour
Automatic redirect on expiration
Clear expiration messaging
Attempt Limiting:
Maximum 5 payment attempts per intent
Progressive delay between attempts
Automatic intent invalidation after max attempts
No Data Persistence:
Card data never logged or stored client-side
Tokenization happens server-side immediately
Only last 4 digits stored for receipts
Error Handling:
Generic error messages (no sensitive data exposed)
No card details in error logs
Rate limiting on validation failures
How It Works
Payment Flow
Integration Steps
Create Payment Intent (Server-Side)
Create a payment intent with your API key: const response = await fetch ( 'https://paymentgateway.redahaloubi.com/api/v1/payment-intents' , {
method: 'POST' ,
headers: {
'X-API-Key' : process . env . PAYMENT_GATEWAY_API_KEY ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
amount: 9999 ,
currency: 'USD' ,
success_url: 'https://yourstore.com/order/success?session_id={CHECKOUT_SESSION_ID}' ,
cancel_url: 'https://yourstore.com/order/cancel' ,
description: 'Order #12345'
})
});
const { checkout_url , client_secret , id } = await response . json ();
Redirect Customer to Checkout
Use the checkout_url from the response: // Option 1: Immediate redirect
window . location . href = checkout_url ;
// Option 2: Button click
< a href = { checkout_url } className = "btn btn-primary" >
Complete Payment
</ a >
Checkout URL format: https://checkout-page-amber.vercel.app/checkout/{intent_id}?client_secret={secret}
Customer Completes Payment
The checkout page:
Validates payment intent hasn’t expired
Displays order summary (amount, currency)
Collects card details with real-time validation
Submits payment to API with client_secret
Shows success animation or error message
Redirects to your success/cancel URL
Handle Customer Return
Verify payment on your server: app . get ( '/order/success' , async ( req , res ) => {
const paymentIntentId = req . query . session_id ;
// Verify payment status server-side
const response = await fetch (
`https://paymentgateway.redahaloubi.com/api/v1/payment-intents/ ${ paymentIntentId } ` ,
{
headers: { 'X-API-Key' : process . env . PAYMENT_GATEWAY_API_KEY }
}
);
const intent = await response . json ();
if ( intent . data . status === 'authorized' || intent . data . status === 'captured' ) {
// Payment successful - fulfill order
await fulfillOrder ( intent . data . payment_id );
res . render ( 'success' , { order: intent . data });
} else {
// Payment incomplete
res . redirect ( '/order/cancel' );
}
});
Checkout Page Features
User Experience
Responsive Design Mobile-first layout optimized for all screen sizes Touch-friendly inputs, large tap targets
Real-Time Validation Instant feedback on card input errors Clear, actionable error messages
Smart Card Detection Automatic brand detection (Visa, Mastercard, etc.) Dynamic card logo display
Accessibility WCAG 2.1 AA compliant Screen reader friendly, keyboard navigation
Payment Summary Display
The checkout page automatically displays:
┌─────────────────────────────────────┐
│ Order #12345 - Premium Plan │
│ │
│ Amount: $99.99 │
│ Currency: USD │
│ │
│ [Card Number Input] │
│ [Cardholder Name] │
│ [MM/YY] [CVV] │
│ │
│ [ Pay $99.99 ] │
│ │
│ 🔒 Secured by Payment Gateway │
└─────────────────────────────────────┘
Supported Payment Methods
Credit Cards
Validation Rules
Test Cards
Visa
All Visa cards supported
Test card: 4242 4242 4242 4242
Mastercard
All Mastercard types supported
Test card: 5555 5555 5555 4444
Card Requirements:
13-19 digit card number
Valid expiry date (MM/YY format)
3-4 digit CVV/CVC code
Cardholder name
Card Number:
Luhn algorithm validation
Automatic formatting (spaces every 4 digits)
Brand detection (Visa, Mastercard)
Expiry Date:
Must be future date
MM/YY format enforced
Automatic slash insertion
CVV:
3 digits (Visa, Mastercard)
4 digits (Amex - future support)
Paste disabled for security
Cardholder Name:
Minimum 3 characters
Letters and spaces only
Title case formatting
Successful Payments: Visa: 4242 4242 4242 4242
Mastercard: 5555 5555 5555 4444
Expiry: Any future date (e.g., 12/2027)
CVV: Any 3 digits (e.g., 123)
Declined Scenarios: Insufficient Funds: 4000 0000 0000 9995
Expired Card: 4000 0000 0000 0069
CVV Mismatch: 4000 0000 0000 0127
Generic Decline: 4000 0000 0000 0002
See Payment API Test Cards for complete list.
Error Handling
Payment Intent Errors
Intent Expired (410 Gone)
Displayed Message: This payment session has expired
Please return to merchant and try again
Action:
Automatic redirect to cancel_url after 5 seconds
“Return to Merchant” button shown immediately
Cause:
Payment intent created more than 1 hour ago
Invalid Client Secret (401 Unauthorized)
Displayed Message: Invalid or expired payment session
Please contact merchant support
Action:
Show support contact information
Log error for merchant review
Cause:
Tampered client_secret parameter
Client secret from different payment intent
Intent Already Completed (422 Unprocessable)
Displayed Message: This payment has already been completed
Redirecting to confirmation page...
Action:
Redirect to success_url immediately
Cause:
Customer refreshed page after successful payment
Attempting to pay twice with same intent
Maximum Attempts Reached (410 Gone)
Displayed Message: Too many payment attempts
Please create a new payment session
Action:
Redirect to cancel_url
Show “Contact Support” button
Cause:
5 failed payment attempts on this intent
Card Validation Errors
Invalid Card Number Error: “Invalid card number”Triggers:
Luhn check fails
Less than 13 or more than 19 digits
Non-numeric characters
Expired Card Error: “Card has expired”Triggers:
Expiry date in the past
Invalid month (>12)
Invalid CVV Error: “Invalid security code”Triggers:
Less than 3 digits
Non-numeric characters
Missing Name Error: “Cardholder name required”Triggers:
Empty field
Less than 3 characters
Payment Declined Errors
// Example error response structure
{
"success" : false ,
"error" : {
"code" : "PAYMENT_DECLINED" ,
"message" : "Card declined: insufficient funds" ,
"remaining_attempts" : 4
}
}
User-Friendly Messages:
Error Code Displayed Message Action PAYMENT_DECLINED ”Your card was declined. Please try a different payment method.” Show retry button INSUFFICIENT_FUNDS ”Insufficient funds. Please use a different card.” Show retry button CVV_MISMATCH ”Security code is incorrect. Please check and try again.” Highlight CVV field EXPIRED_CARD ”This card has expired. Please use a different card.” Highlight expiry field
Customization Options
URL Configuration
Where to redirect customer after successful payment Placeholder: Use {CHECKOUT_SESSION_ID} to include payment intent IDExample: https://yourstore.com/order/success?session_id={CHECKOUT_SESSION_ID}&order=12345
Result: https://yourstore.com/order/success?session_id=pi_abc123&order=12345
Where to redirect customer on cancellation or failure Example: https://yourstore.com/order/cancel
Triggers:
Customer clicks “Cancel” button
Payment intent expires
Maximum attempts reached
Pass custom data that appears on receipts and webhooks:
{
"amount" : 9999 ,
"currency" : "USD" ,
"description" : "Order #12345 - Premium Plan" ,
"metadata" : {
"order_id" : "12345" ,
"customer_id" : "cus_abc123" ,
"sku" : "PREMIUM_ANNUAL" ,
"source" : "web"
}
}
Metadata is:
Returned in webhook events
Visible in payment dashboard
Searchable for reporting
Limited to 50 keys, 500 chars per value
Best Practices
Always Verify Server-Side
Never trust client-side redirects alone. // ❌ BAD: Trust the redirect
app . get ( '/success' , ( req , res ) => {
res . render ( 'order-success' );
});
// ✅ GOOD: Verify payment status
app . get ( '/success' , async ( req , res ) => {
const intent = await verifyPaymentIntent ( req . query . session_id );
if ( intent . status === 'authorized' ) {
await fulfillOrder ( intent . payment_id );
res . render ( 'order-success' );
} else {
res . redirect ( '/order/cancel' );
}
});
Handle All Redirect Scenarios
Implement both success and cancel handlers. // Success handler
app . get ( '/order/success' , handleSuccess );
// Cancel handler (payment failed, expired, or user canceled)
app . get ( '/order/cancel' , handleCancellation );
Cancel reasons:
Customer clicked “Cancel” button
Payment declined after max attempts
Payment intent expired
Customer abandoned checkout
Use Webhooks for Critical Logic
Don’t rely solely on redirect for order fulfillment. Redirects can be missed (browser crash, network issue). Use webhooks as the primary fulfillment trigger: app . post ( '/webhooks/payment' , ( req , res ) => {
const event = req . body ;
if ( event . event === 'payment.authorized' ) {
// Fulfill order (primary path)
await fulfillOrder ( event . data . payment_id );
}
res . status ( 200 ). send ( 'OK' );
});
Use redirects for user feedback only.
One payment intent = one order. // ❌ BAD: Reuse intent for multiple orders
const intent = await createPaymentIntent ({ amount: 9999 });
// Store intent.id for later...
// ✅ GOOD: New intent per order
app . post ( '/checkout' , async ( req , res ) => {
const intent = await createPaymentIntent ({
amount: order . total ,
metadata: { order_id: order . id }
});
res . redirect ( intent . checkout_url );
});
Set Descriptive Descriptions
Help customers recognize charges. // ❌ BAD: Generic description
description : "Payment"
// ✅ GOOD: Descriptive
description : "Order #12345 - Premium Plan (Annual)"
Appears on:
Checkout page header
Customer bank statement
Payment receipts
Browser Support
Desktop Browsers Chrome/Edge: 88+Firefox: 85+Safari: 14+
Mobile Browsers iOS Safari: 14+Chrome Mobile: 88+Samsung Internet: 13+
Features:
ES6+ JavaScript
CSS Grid & Flexbox
Fetch API
Web Crypto API (for validation)
FAQ
Can I customize the checkout page design?
Currently: No, the checkout page uses a standard design for all merchants.Future: We’re working on customization options:
Custom logo upload
Brand color selection
Custom success messages
Contact sales for enterprise customization.
What happens if a customer closes the browser?
Short answer: Nothing is charged.Details:
Payment intent remains in created status
Customer can return to same checkout URL (if not expired)
Intent auto-expires after 1 hour
No money is held or charged
Can I collect billing address on checkout?
Currently: No, checkout collects card details only.Workaround: Collect billing address on your order form before redirecting to checkout. Pass it in metadata:{
"metadata" : {
"billing_address" : "123 Main St, City, State 12345" ,
"billing_country" : "US"
}
}
How do I test without real cards?
Use test cards (see Test Cards section).All test cards work in production environment without charging real money. Results are simulated based on card number.
Can customers save cards for future purchases?
Currently: No, each payment requires new card entry.Future: Card-on-file and subscription features are planned for Q2 2026.
Troubleshooting
Checkout page shows 'Invalid payment session'
Causes:
Payment intent ID in URL doesn’t exist
Client secret doesn’t match intent
Intent was created for different merchant
Solution:
Verify checkout_url from payment intent response is used exactly
Don’t modify URL parameters
Check API key belongs to correct merchant
'This payment session has expired' error
Cause: Payment intent created more than 1 hour ago.Solution:
Create new payment intent
Reduce time between intent creation and checkout redirect
Consider creating intent when customer clicks “Checkout” button, not when cart is created
Customer redirected but payment status is 'created'
Causes:
Customer clicked browser back button before completing payment
Payment was declined but customer was redirected to success_url (shouldn’t happen)
Solution:
Always verify payment status server-side (see Best Practices )
Check for status: 'authorized' or status: 'captured' before fulfilling
Causes:
Using real card number in test mode
Incorrect expiry format
Test card not in supported list
Solution:
Use exact test card numbers (4242 4242 4242 4242 for success)
Use any future expiry date (e.g., 12/2027)
Use any 3-digit CVV
Next Steps
Payment API Reference Complete Payment Intent API documentation
Quick Start Guide Create your first payment in 5 minutes
Webhooks Guide Implement webhook handlers
CLI Tool Test payments from command line
Questions? Contact support at [email protected]