Code Examples

Ready-to-use code examples for integrating UltraPay into your application. Choose your preferred language below.

javascript
const axios = require('axios');
class UltraPay {
constructor(publicKey, secretKey) {
this.client = axios.create({
baseURL: 'https://pay.ultrapay.cc',
// No default Content-Type: axios adds it for JSON bodies, and the API
// rejects a DELETE that sends Content-Type: application/json with no body
headers: {
'X-Public-Key': publicKey,
'X-Secret-Key': secretKey,
},
});
}
// API errors come back with a non-2xx status, which axios throws.
// Rethrow them with UltraPay's error code and message.
async request(method, url, data) {
try {
const response = await this.client.request({ method, url, data });
return response.data;
} catch (err) {
const apiError = err.response?.data?.error;
if (!apiError) throw err;
const error = new Error(apiError.message);
error.code = apiError.code; // e.g. 'INVALID_REQUEST', 'RATE_LIMIT_EXCEEDED'
error.status = err.response.status; // e.g. 400, 429
throw error;
}
}
async createPayment(amount, currency, successUrl, cancelUrl, options = {}) {
const { data } = await this.request('post', '/api/v1/payments/create', {
amount,
currency,
successUrl,
cancelUrl,
...options,
});
return data;
}
async getPayment(transactionId) {
return this.request('get', `/api/v1/payments/${transactionId}`);
}
async cancelPayment(transactionId) {
return this.request('delete', `/api/v1/payments/${transactionId}`);
}
}
// Usage
const ultrapay = new UltraPay('upp_your_public_key', 'ups_your_secret_key');
const payment = await ultrapay.createPayment(
5000,
'usd',
'https://yoursite.com/thank-you',
'https://yoursite.com/checkout',
{
customerEmail: 'customer@example.com',
description: 'Order #12345',
metadata: { orderId: '12345' },
}
);
console.log('Redirect to:', payment.paymentUrl);

Best Practices

1. Never Use Iframes

Always redirect customers to the payment URL using a full page redirect. Never embed in an iframe:

javascript
// ✅ Correct - Full page redirect
window.location.href = payment.paymentUrl;
// ❌ Wrong - Never do this!
// <iframe src={payment.paymentUrl} /> // Will fail!

2. Always Verify Payments Server-Side

Never trust client-side confirmation. Always call GET /api/v1/payments/:id to verify:

javascript
// When customer returns to your success URL
const payment = await ultrapay.getPayment(transactionId);
if (payment.data.status === 'succeeded') {
// ✅ Safe to fulfill order
await fulfillOrder(orderId);
}

3. Use Webhooks for Async Updates

Webhooks notify you when a payment completes, fails, expires, is refunded or is disputed. They are sent to the webhookUrl you pass when creating the payment. Treat a webhook as a notification only: before fulfilling an order, confirm the payment by calling GET /api/v1/payments/:id with your API keys and checking that status is succeeded.

Delivery is at least once: the same event can arrive more than once, and events can arrive out of order. Make your handler idempotent, for example by recording each transactionId and status you have handled and ignoring repeats. The webhook's metadata contains your own keys and may also include description and internal keys that start with an underscore. Ignore the underscore keys.

javascript
// Handle webhook
app.post('/api/webhooks/ultrapay', async (req, res) => {
const { transactionId } = req.body;
// Treat the webhook as a notification: confirm with your API keys
let payment;
try {
payment = await ultrapay.getPayment(transactionId);
} catch (err) {
// 404: not one of your payments, so reject it (a 4xx stops retries).
// Anything else, such as a temporary API error: return a 5xx so UltraPay retries.
return res.status(err.status === 404 ? 400 : 503).json({ received: false });
}
const { status, metadata } = payment.data;
const orderId = metadata?.orderId;
// Events can repeat (even at the same moment) or arrive out of order, so act once per status.
// claim() must be atomic, e.g. an INSERT on a unique key that returns false if it already exists
const key = `${transactionId}:${status}`;
if (!(await db.webhookEvents.claim(key))) {
return res.json({ received: true });
}
try {
switch (status) {
case 'succeeded':
// ✅ Confirmed paid - fulfill the order
await fulfillOrder(orderId);
break;
case 'refunded':
case 'partially_refunded':
// Handle refund
await processRefund(orderId);
break;
case 'disputed':
case 'dispute_lost':
// Handle dispute
await handleDispute(orderId);
break;
}
} catch (err) {
// Release the claim and return a 5xx so UltraPay retries the webhook
await db.webhookEvents.release(key);
return res.status(500).json({ received: false });
}
res.json({ received: true });
});

4. Store Transaction IDs

Always save the transactionId with your order for reference:

javascript
// Save transaction ID with order
await db.orders.update({
where: { id: orderId },
data: { ultrapayTransactionId: payment.transactionId },
});

5. Handle Cart Changes

If a customer's cart changes, cancel the old payment and create a new one. Cancelling marks the payment as cancelled in UltraPay, but the hosted checkout link is not closed: it remains open until it expires, about 30 minutes after the payment was created. Always send the customer to the new payment URL.

Only a pending or processing payment can be cancelled. Otherwise the API returns 400 ALREADY_COMPLETED (for example, the old payment already expired or was paid), so check its status before creating a new one:

javascript
// Cancel old payment
try {
await ultrapay.cancelPayment(oldTransactionId);
} catch (err) {
if (err.code !== 'ALREADY_COMPLETED') throw err;
// The old payment is no longer pending (e.g. expired or already paid)
const oldPayment = await ultrapay.getPayment(oldTransactionId);
if (oldPayment.data.status === 'succeeded') {
// Customer already paid - don't create a second payment
return handlePaidOrder(orderId);
}
}
// Create new payment with updated amount
const newPayment = await ultrapay.createPayment(
newAmount,
'usd',
successUrl,
cancelUrl,
{ customerEmail }
);
// Redirect to new checkout
res.redirect(newPayment.paymentUrl);
↑ ↓ navigate↵ select