This endpoint receives orders when a merchant scans your user’s QR code. The sessionId identifies which user will pay.
Flow
User opens your app and requests to pay
You generate a sessionId and display a QR code
Merchant scans the QR code
Merchant POSTs their signed order to this endpoint
You validate the order and prompt user for confirmation
User confirms, you process payment
You send proof to merchant’s webhook
Implementation
Session Management
When your mobile app user requests to display a qrcode for the merchant to scan
generate secure, single-use sessions and generate a qrCode.
// This is your internal app endpoint and it is not part of api the spec.
// Lets assume you mobile app calls this endpoint to get teh QrCode.
app . post ( '/app/pay' , authenticateUser , async ( req , res ) => {
const user = req . user ;
// Generate session
const sessionId = crypto . randomBytes ( 16 ). toString ( 'hex' );
await db . createSession ({
id: sessionId ,
userId: user . id ,
status: 'pending' ,
expiresAt: Date . now () + 5 * 60 * 1000 // 5 minutes
});
// Build QR payload -> url points to endpoint /orders/create/:sessionId below
const qrPayload = {
ocid: YOUR_OCID ,
order: `https://api.yourwallet.com/opencharge/orders/create/ ${ sessionId } ` ,
expiresAt: Math . floor (( Date . now () + 5 * 60 * 1000 ) / 1000 )
};
res . json ({
sessionId ,
qrPayload ,
qrCode: await generateQRCode ( JSON . stringify ( qrPayload ))
});
});
The merchant app scans the qrCode, and posts the order and signature to the session order url you set in the qrcode.
app . post ( '/orders/create/:sessionId' , signatureAuth , async ( req , res ) => {
const { sessionId } = req . params ;
const { order , signature , urls } = req . body ;
const merchantOcid = parseInt ( req . headers [ 'x-oc-id' ]);
// 1. Find session
const session = await db . getSession ( sessionId );
if ( ! session ) {
return res . status ( 404 ). json ({
error: { code: 'SESSION_NOT_FOUND' , message: 'Session not found' }
});
}
// 2. Check expiration
if ( session . expiresAt < Date . now ()) {
return res . status ( 404 ). json ({
error: { code: 'SESSION_EXPIRED' , message: 'Session has expired' }
});
}
// 3. Check not already used
if ( session . status !== 'pending' ) {
return res . status ( 410 ). json ({
error: { code: 'SESSION_USED' , message: 'Session already used' }
});
}
// 4. Verify merchant signature on order
const merchantPublicKey = await getMerchantPublicKey ( merchantOcid );
if ( ! verifyOrderSignature ( order , signature , merchantPublicKey )) {
return res . status ( 400 ). json ({
error: { code: 'INVALID_SIGNATURE' , message: 'Invalid order signature' }
});
}
// 5. Verify order not expired
if ( order . expiresAt && order . expiresAt < Date . now () / 1000 ) {
return res . status ( 400 ). json ({
error: { code: 'ORDER_EXPIRED' , message: 'Order has expired' }
});
}
// 6. Check we can settle
if ( ! canSettle ( order . accepts )) {
return res . status ( 400 ). json ({
error: { code: 'SETTLEMENT_NOT_SUPPORTED' , message: 'Cannot settle with merchant' }
});
}
// 7. Invalidate session and store order
await db . updateSession ( sessionId , {
status: 'order_received' ,
order ,
signature ,
merchantOcid ,
urls
});
// 8. If your user needs to confirm orders, Notify your user's app to load the order. (websocket/push)
await notifyUser ( session . userId , 'payment_request' , {
merchantOcid ,
order ,
sessionId
});
// 9. Return status URL
res . json ({
urls: {
status: `https://api.yourwallet.com/orders/ ${ order . id } /status`
}
});
});
Processing Payment After User Confirms
If your user needs to confirm orders, implement an internal endpoint to process confirmations.
app . post ( '/app/confirm/:sessionId' , authenticateUser , async ( req , res ) => {
const { sessionId } = req . params ;
const user = req . user ;
const session = await db . getSession ( sessionId );
// Verify session belongs to user
if ( session . userId !== user . id ) {
return res . status ( 403 ). json ({ error: 'Not your session' });
}
// Check user has sufficient balance
if ( user . balance < parseFloat ( session . order . amount )) {
return res . status ( 402 ). json ({ error: 'Insufficient funds' });
}
// Process payment
await processPayment ( session , user );
res . json ({ success: true });
});
Settlement
After collecting payment from the user, you must settle with the merchant through a trusted gateway. See the Settlement Guide for the complete details.
async function processPayment ( session , user ) {
// Debit user
await db . debitUser ( user . id , session . order . amount );
// Settle with merchant - see Settlement Guide for strategies
const proof = await settlePayment ( user , session . order , session . merchantOcid );
// Update session
await db . updateSession ( session . id , { status: 'completed' , proof });
}
Settlement Guide Learn about direct settlement, partner reserves, and third-party settlement