Your First Collection
In this tutorial you'll accept your first QRPH payment — from creating a Payment Intent, to displaying the QR code, to confirming the payment was received.
Time: ~15 minutes Prerequisites: Sandbox credentials from /sandbox. A merchant and account already set up (follow Your First API Call first).
What you'll build
By the end of this tutorial you'll have:
- Created a Payment Intent for a specific amount
- Displayed a QRPH code
- Simulated a payment in sandbox
- Confirmed the payment via a webhook event
Step 1: Create a Payment Intent
A Payment Intent represents a single payment request. Specify the account to receive funds and the amount to collect.
- cURL
- Node.js
- Python
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-X POST "https://api.partners.nextpay.world/v2/payment-intents" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: tutorial-collection-001" \
-d '{
"account_id": "YOUR_ACCOUNT_UUID",
"amount": 25000,
"currency": "PHP/2",
"payment_instrument_options": {
"method_type": "qrph_p2m_reference",
"method_provider": "automatic"
},
"external_id": "tutorial-collection-001"
}'
const response = await fetch('https://api.partners.nextpay.world/v2/payment-intents', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + Buffer.from('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').toString('base64'),
'Content-Type': 'application/json',
'X-Idempotency-Key': 'tutorial-collection-001',
},
body: JSON.stringify({
account_id: 'YOUR_ACCOUNT_UUID',
amount: 25000, // PHP 250.00
currency: 'PHP/2',
payment_instrument_options: {
method_type: 'qrph_p2m_reference',
method_provider: 'automatic',
},
external_id: 'tutorial-collection-001',
}),
});
const intent = await response.json();
console.log('Payment Intent ID:', intent.id);
import requests
import base64
credentials = base64.b64encode(b'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').decode()
response = requests.post(
'https://api.partners.nextpay.world/v2/payment-intents',
headers={
'Authorization': f'Basic {credentials}',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'tutorial-collection-001',
},
json={
'account_id': 'YOUR_ACCOUNT_UUID',
'amount': 25000,
'currency': 'PHP/2',
'payment_instrument_options': {
'method_type': 'qrph_p2m_reference',
'method_provider': 'automatic',
},
'external_id': 'tutorial-collection-001',
}
)
intent = response.json()
print(f"Payment Intent ID: {intent['id']}")
Response
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"account_id": "123e4567-e89b-12d3-a456-426614174000",
"external_id": "tutorial-collection-001",
"amount": 25000,
"currency": "PHP/2",
"status": "pending",
"expires_at": "2025-11-15T10:05:00Z",
"created_at": "2025-11-15T10:00:00Z",
"payment_instrument": {
"id": "123e4567-e89b-12d3-a456-426614174001",
"method_type": "qrph_p2m_reference",
"method_provider": "ph_netbank",
"method_details": {
"merchant_name": "Your Merchant Name",
"reference_label": "...",
"routing_account": "...",
"amount": { "value": 25000, "currency": "PHP/2" },
"resolution": 480
},
"status": "active",
"persistence_mode": "temporary",
"usage_mode": "single"
}
}
Step 2: Display the QR code
The QR data lives in payment_intent.payment_instrument.method_details. Use a QR code library (e.g., qrcode) to render it client-side — there is no pre-rendered image URL.
<!-- In your app's checkout page -->
<div class="checkout-qr">
<p>Scan to pay PHP 250.00</p>
<!-- Render the QR code from method_details using a QR library -->
<canvas id="qr-canvas"></canvas>
<script>
// Use payment_intent.payment_instrument.method_details with a QRPH-compatible QR library
// Example: QRCode.toCanvas(document.getElementById('qr-canvas'), methodDetailsData)
</script>
<p>Expires in 5 minutes (default)</p>
</div>
The QR code works with all QRPH-compatible apps: GCash, Maya, BPI, BDO, UnionBank, and any bank app that supports QR Ph.
Step 3: Simulate a payment (sandbox)
In the sandbox, you can simulate a payment by calling the sandbox payment simulation endpoint:
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-X POST "https://api.partners.nextpay.world/v2/payment-simulations/payment-instrument" \
-H "Content-Type: application/json" \
-d '{
"payment_instrument_id": "YOUR_PAYMENT_INSTRUMENT_ID"
}'
Use the payment_instrument.id value from the Payment Intent creation response as payment_instrument_id.
In production, the customer scans and pays via their bank app — you don't control this step.
Step 4: Check the Payment Intent status
After the simulated payment, check the status:
- cURL
- Node.js
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
"https://api.partners.nextpay.world/v2/payment-intents/YOUR_PAYMENT_INTENT_ID"
const response = await fetch(
'https://api.partners.nextpay.world/v2/payment-intents/YOUR_PAYMENT_INTENT_ID',
{
headers: {
'Authorization': 'Basic ' + Buffer.from('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').toString('base64'),
},
}
);
const intent = await response.json();
console.log('Status:', intent.status); // "succeeded"
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "succeeded",
"amount": 25000,
"paid_at": "2025-11-15T10:05:33Z"
}
Step 5: Set up webhook handling (production)
In production, don't poll — handle the v2.payment_intent.succeeded webhook instead:
app.post('/webhooks/nextapi', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-nextpay-signature'];
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body);
if (event.event === 'v2.payment_intent.succeeded') {
const { external_id, amount } = event.payload;
// external_id is what you set: "tutorial-collection-001"
console.log(`Payment received for ${external_id}: PHP ${amount / 100}`);
// Mark the order as paid in your system
}
res.status(200).send('OK');
});
external_id is the key: set it to your internal order or invoice reference when creating the Payment Intent, and it comes back in the webhook — no need to store a mapping from Payment Intent IDs to your records.
Payment Intent lifecycle
pending → succeeded (customer pays successfully)
pending → expired (default 5 minutes / 300s passes without payment)
pending → canceled (you call PATCH /cancel)
Once paid, the funds are credited to the account immediately. You can check the account balance to confirm:
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
"https://api.partners.nextpay.world/v2/accounts/YOUR_ACCOUNT_UUID/balances"
What's next?
- Persistent QR codes: Setup Virtual Collection — static QR for counters and kiosks
- Verify webhook signatures: Verify Webhook Signatures — production security
- Understand the flow: Collections Lifecycle — full state machine