Code Examples
Ready-to-use code examples for integrating UltraPay into your application. Choose your preferred language below.
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 bodyheaders: {'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, 429throw 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}`);}}// Usageconst 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:
// ✅ Correct - Full page redirectwindow.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:
// When customer returns to your success URLconst payment = await ultrapay.getPayment(transactionId);if (payment.data.status === 'succeeded') {// ✅ Safe to fulfill orderawait 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.
// Handle webhookapp.post('/api/webhooks/ultrapay', async (req, res) => {const { transactionId } = req.body;// Treat the webhook as a notification: confirm with your API keyslet 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 existsconst key = `${transactionId}:${status}`;if (!(await db.webhookEvents.claim(key))) {return res.json({ received: true });}try {switch (status) {case 'succeeded':// ✅ Confirmed paid - fulfill the orderawait fulfillOrder(orderId);break;case 'refunded':case 'partially_refunded':// Handle refundawait processRefund(orderId);break;case 'disputed':case 'dispute_lost':// Handle disputeawait handleDispute(orderId);break;}} catch (err) {// Release the claim and return a 5xx so UltraPay retries the webhookawait 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:
// Save transaction ID with orderawait 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:
// Cancel old paymenttry {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 paymentreturn handlePaidOrder(orderId);}}// Create new payment with updated amountconst newPayment = await ultrapay.createPayment(newAmount,'usd',successUrl,cancelUrl,{ customerEmail });// Redirect to new checkoutres.redirect(newPayment.paymentUrl);