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://api.njiapay.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://api.njiapay.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://api.njiapay.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://pay.njiapay.com/pay/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 === "status_change") {
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.
Checkout Language
The checkout page is displayed in one language, resolved in this order:
purchaser_info.localefrom your Create Intent request (recommended). Send the shopper's language and the page renders in it. We use the language part of the locale, sopt-PTandpt-BRboth render Portuguese.- Shopper's browser language — if you do not send a locale, the page uses the browser's preferred language.
- English (
en) — used as the default and fallback when neither is available or supported.
Language is determined by the API request, not by the shopper's IP address or location. To control the language yourself, always send
purchaser_info.locale.
Supported languages: English (en), Dutch (nl), French (fr), German (de), Spanish (es), Italian (it), Portuguese (pt).
Need another language? Contact us — additional languages can be added on request.
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