Hosted Checkout
The hosted checkout is NjiaPay's secure, PCI-compliant payment page that handles all the complexity of collecting payments. It supports multiple payment methods, 3D Secure authentication, and provides a seamless customer experience.
Why Use Hosted Checkout?
✅ PCI Compliance Made Easy
- No card data touches your servers
- NjiaPay handles all PCI requirements
- Automatic security updates
✅ Multiple Payment Methods
- Cards (Visa, Mastercard, Amex, etc.)
- Bank transfers
- Mobile money
- Digital wallets (PayPal, Apple Pay, etc.)
✅ Built-in Features
- 3D Secure authentication
- Real-time validation
- Multi-step flows
- Mobile-responsive design
- Multi-language support
✅ Reduced Development Time
- No payment form to build
- No validation logic needed
- Works across all devices
Integration Flow
Step-by-Step Implementation
Step 1: Create a Payment Intent
When your customer is ready to pay, create a payment intent from your server:
curl -X POST https://app.infinic.com/api/intents \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "ZAR",
"reference_id": "order_12345",
"purchaser_id": "customer_abc",
"return_url": "https://yoursite.com/payment/callback",
"allow_remember_credentials": false,
"purchaser_info": {
"email": "customer@example.com",
"phone": "+27123456789",
"country": "ZA"
}
}'
const response = await fetch("https://app.infinic.com/api/intents", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 50000,
currency: "ZAR",
reference_id: "order_12345",
purchaser_id: "customer_abc",
return_url: "https://yoursite.com/payment/callback",
allow_remember_credentials: false,
purchaser_info: {
email: "customer@example.com",
phone: "+27123456789",
country: "ZA",
},
}),
});
const { redirect_url } = await response.json();
response = requests.post(
'https://app.infinic.com/api/intents',
headers={'Authorization': f'Bearer {API_KEY}'},
json={
'amount': 50000,
'currency': 'ZAR',
'reference_id': 'order_12345',
'purchaser_id': 'customer_abc',
'return_url': 'https://yoursite.com/payment/callback',
'allow_remember_credentials': False,
'purchaser_info': {
'email': 'customer@example.com',
'phone': '+27123456789',
'country': 'ZA'
}
}
)
redirect_url = response.json()['redirect_url']
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"redirect_url": "https://app.infinic.com/checkout?intent=550e8400-e29b-41d4-a716-446655440000&token=eyJ0eXAi..."
}
Step 2: Redirect Customer to Checkout
Take the redirect_url and redirect your customer to it. You can use:
HTTP 302/307 Redirect (Server-side):
app.post("/checkout", async (req, res) => {
const { redirect_url } = await createPaymentIntent(req.body);
res.redirect(redirect_url);
});
@app.route('/checkout', methods=['POST'])
def checkout():
redirect_url = create_payment_intent(request.json)
return redirect(redirect_url)
<?php
$redirect_url = createPaymentIntent($_POST);
header('Location: ' . $redirect_url);
exit;
JavaScript Redirect (Client-side):
// After creating intent on your server
window.location.href = redirect_url;
HTML Link (Client-side):
<a href="{{ redirect_url }}" class="btn btn-primary"> Pay Now </a>
Step 3: Customer Completes Payment
On the hosted checkout page, the customer will:
- See available payment methods
- Select their preferred method
- Enter payment details (securely handled by NjiaPay)
- Complete any additional authentication (3D Secure, OTP, etc.)
- See payment result
The checkout page handles all validation, error messages, and user experience.
Step 4: Customer Returns to Your Site
After payment (success or failure), the customer is redirected to your return_url with query parameters:
https://yoursite.com/payment/callback?intent_id=550e8400-e29b-41d4-a716-446655440000&status=success
Step 5: Handle Webhook (Recommended)
Use webhooks to receive real-time payment updates:
app.post("/webhook", async (req, res) => {
const event = req.body;
if (event.type === "StatusChange") {
const { intent_id, status, reference_id } = event.content;
if (status === "success") {
await fulfillOrder(reference_id);
await sendConfirmationEmail(reference_id);
} else if (status === "failed") {
await logFailedPayment(reference_id);
}
}
res.status(200).send("OK");
});
See Webhooks Guide for complete implementation.
Customization Options
Pre-filling Customer Information
Use purchaser_info to pre-fill customer details:
{
"purchaser_info": {
"email": "customer@example.com",
"phone": "+27123456789",
"country": "ZA",
"locale": "en-ZA"
}
}
This improves user experience by reducing data entry and helps improve the payment success rate.
Filtering Payment Methods
Limit which payment methods are shown:
{
"payment_method_filter": {
"filter_type": "allow",
"payment_methods": ["card", "paypal"]
}
}
Or block specific methods:
{
"payment_method_filter": {
"filter_type": "block",
"payment_methods": ["bancontact"]
}
}
Merchant Branding
Configure your merchant styling in the portal:
- Logo
- Brand colors
- Company name
This branding appears on the checkout page.
Important Parameters
return_url Requirements
Your return_url must be:
- HTTPS (not HTTP) for security
- Publicly accessible (not localhost for production)
- Under your control (your domain)
- Able to handle query parameters
Example valid URLs:
✅ https://yoursite.com/payment/callback
✅ https://shop.example.com/checkout/return
✅ https://app.yourdomain.io/payments/complete
Example invalid URLs:
❌ http://yoursite.com/callback (not HTTPS)
❌ http://localhost:3000/callback (localhost not allowed)
❌ https://google.com (not your domain)
allow_remember_credentials
Set to true if you want to enable:
- One-click payments (customer can save card)
- Future recurring payments (MIT)
Set to false for one-time payments only.
allow_remember_credentials: true AND request_unscheduled_mit: true.Testing the Flow
Test in sandbox with MockPSP:
- Create a payment intent
- Open the
redirect_urlin a browser - Select "Card" payment method
- Enter any card details
- For successful payment: Use any card holder name
- For declined: Set holder name to "DECLINED"
- For error: Set holder name to "ERROR"
See Testing Guide for more scenarios.
Common Issues
Checkout Page Not Loading
Symptom: Redirect URL results in error or blank page
Solutions:
- Verify the
redirect_urlis complete and unmodified - Ensure
intent_idandtokenare in the URL
Customer Can't Complete Payment
Symptom: Payment gets stuck or fails unexpectedly
Solutions:
- Verify payment method is enabled
- Ensure currency is supported by PSP
- Check customer's card/account has sufficient funds
Return URL Not Working
Symptom: Customer doesn't get redirected back
Solutions:
- Verify
return_urlis HTTPS and publicly accessible - Check for typos in the URL
- Ensure your server is handling the redirect properly
- Test the URL directly in a browser
Security Best Practices
✅ DO
- Always verify payment status via API or webhook
- Use HTTPS for your
return_url - Store API keys securely (environment variables)
- Validate
intent_idbelongs to your merchant - Log all payment attempts for audit
❌ DON'T
- Trust
return_urlquery parameters alone - Expose API keys in client-side code
- Hard-code payment amounts in client code
- Skip webhook verification
- Use HTTP for
return_url