Lesson 3 — Complete the payment
next_action.type tells you the single next step, independent of rail. This
lesson walks both shapes: the redirect rails (M-PAiSA, cards) and OTP
collection (MyCash).
Redirect rails — M-PAiSA and cards
Send the customer to next_action.url. In live mode this is Vodafone's or
the gateway's hosted page; the aggregator verifies the signed callback before
the intent moves to processing.
In test mode the redirect target is the simulator:
transxact simulate:redirect pi_...
MyCash — collect a mobile number and OTP
MyCash is server-to-server. Two confirm calls drive it:
1. Relay the customer's mobile number:
curl https://api.transxact.io/payment-intents/pi_.../confirm \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "mobile": "+6797000000" }'
The rail texts an OTP to that number.
2. Confirm the OTP the customer reads back to you:
curl https://api.transxact.io/payment-intents/pi_.../confirm \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "otp": "123456" }'
After a successful confirm the intent reaches succeeded (or processing
first — poll or wait for the webhook, which is Lesson 5).
Why one flow for all rails
Your integration code looks like this, no matter which rails you enable:
if next_action.type == "redirect": send customer to next_action.url
if next_action.type == "collect_otp": POST mobile, then POST otp
Adding cards later changes nothing in your code.
Exercise
1. Create an intent with "payment_method": "mycash" and walk both confirm
steps with any mobile number and any OTP except 000000.
2. Repeat with OTP 000000 and inspect the error: payment_error /
otp_incorrect.
3. Create a default (redirect) intent and follow its next_action.url.
What's next
You can already take payments. Lesson 4 shows how to force *specific*
outcomes deterministically —
Nearby lessons
3. Complete the payment