returnUrl is only for display, status re-checks, and guiding the customer to the next step.After a customer completes payment through Onerway Checkout, you still need to deliver the product, service, or access they purchased. This delivery step is called fulfillment.
Common fulfillment scenarios include:
There are two main ways to handle fulfillment:
Manual fulfillment works well for low-volume or pilot operations. For workflows that need stable scaling, automatic fulfillment is recommended.
Automatic fulfillment is typically built on a dual-trigger mechanism: one trigger comes from the webhook (server-to-server asynchronous notification), and the other comes from the landing-page status check (a re-check after the customer returns to your page):
Whether fulfillment is triggered by a webhook or a landing-page re-check, your server should call the same fulfillment function. In this document, we use fulfillCheckout as an example name.
The key requirement is simple: the same order must only be fulfilled once. Because of retries, duplicate webhook deliveries, or concurrent requests, the fulfillment function must be idempotent.
fulfillCheckout should at least:
merchantTxnIdasync function fulfillCheckout(merchantTxnId, verifiedTxn = null) {
return db.transaction(async (tx) => {
const existing = await tx.fulfillments.findByMerchantTxnId(merchantTxnId);
if (existing) {
return existing;
}
const txn = verifiedTxn ?? await queryOrderByMerchantTxnId(merchantTxnId);
if (!txn || txn.txnType !== 'SALE' || txn.status !== 'S') {
return { success: false, reason: 'Payment not completed' };
}
await provisionService(txn);
const record = await tx.fulfillments.create({
merchantTxnId,
status: 'fulfilled',
fulfilledAt: new Date()
});
return { success: true, record };
});
}
If your webhook handler has already confirmed a successful payment, pass that result directly as verifiedTxn. Otherwise, query the order first and then decide whether to fulfill.
The exact implementation of fulfillCheckout depends on your business, but common fulfillment actions include:
The webhook handler receives payment events and calls fulfillCheckout when the payment is successful.
When a new payment transaction is created, Onerway sends a webhook to the URL you configured. Set up a dedicated endpoint on your server to receive, process, and acknowledge these events.
For fulfillment, the relevant events are:
notifyType = TXN + txnType = SALE + status = S: Payment succeeded and should be fulfillednotifyType = TXN + txnType = SALE + status = F: Payment failed or was declined and should not be fulfilledStatus values:
S = Success, fulfill the orderF = Failed/Declined/Rejected, do not fulfillSee Transaction NotificationPayments API for the full event structure.
app.post('/webhook', async (req, res) => {
const payload = req.body;
if (!verifySignature(payload, process.env.MERCHANT_SECRET_KEY)) {
return res.status(401).send('Invalid signature');
}
res.status(200).send(payload.transactionId);
if (payload.notifyType === 'TXN' &&
payload.txnType === 'SALE' &&
payload.status === 'S') {
setImmediate(async () => {
try {
await fulfillCheckout(payload.merchantTxnId, payload);
} catch (error) {
console.error(`Fulfillment error: ${error.message}`);
}
});
}
});
merchantTxnId as the deduplication key for fulfillmentSome payment methods, such as bank transfers and convenience store payments, use asynchronous settlement. Finishing the checkout page does not mean the funds have fully settled yet.
Common delayed payment methods:
| Payment Method | Type | Typical Delay |
|---|---|---|
| Trustly | Bank transfer | 2-5 days |
| Konbini | Convenience store payment | Up to 14 days |
| OXXO | Convenience store payment | Up to 14 days |
For these methods, transactions may remain in Pending for some time. Only fulfill after webhook or order query clearly returns status = S.
Fulfillment rule:
status = S before fulfillingstatus = P or status = F