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 —

Lesson 4.