SoftPOS SDK
Accept in-person card payments on Android by creating a PaymentIntent on your server and confirming with the Airwallex SoftPOS SDK.
Accept in-person card payments on Android using the Airwallex SoftPOS SDK. Payment is fulfilled in two steps: your server creates a PaymentIntent without a payment method, then your Android app calls the SDK to collect the card-present details and confirm the payment. The SDK request is authenticated by the PaymentIntent client_secret rather than a merchant-level access token.
You'll do the following:
- Obtain an access token on your server.
- Create a PaymentIntent for the sale amount.
- Call the SoftPOS SDK on your Android app to confirm the payment.
- Verify the result on your server (and optionally capture, cancel, refund, or query the PaymentIntent).
Server-side API calls only
Call every Payment API endpoint in this guide from your backend except Step 3: Confirm payment with the SoftPOS SDK. The access token carries high privileges and exposing it on a client device creates a significant security risk.
SDK package and regions
Obtain the SoftPOS SDK package, sandbox credentials, and region availability from your Airwallex Account Manager. This guide covers the payment flow and REST APIs; follow the SDK reference supplied with your integration kit for method names, initialization, and UI configuration.
Before you begin
- POS payments and SoftPOS are enabled on your Airwallex account.
- You have your Client ID and API key from the Airwallex web app.
- You can run HTTPS requests from a backend server.
- You have an Android app target that meets the SoftPOS SDK requirements in your integration kit.
For product context, see POS payments overview.
How the payment flow works
SoftPOS payments use a two-step model:
- Create a PaymentIntent (server): Your backend calls Create a PaymentIntent API without a payment method. The response includes
id,status, andclient_secret. - Confirm with the SDK (app): Your Android app passes the
client_secretto the SoftPOS SDK. The SDK sends a confirm payment request to the Airwallex host to deduct funds from the shopper's card.
Accept payments with local payment methods
You do not need the SoftPOS SDK to process a Local Payment Method (LPM). Call Confirm a PaymentIntent API from your server to obtain the QR code string, then display the QR code on the device. SoftPOS is for card-present acceptance on Android.
Step 1: Get an access token on your server
Authenticate with your Client ID and API key.
Request
1curl -X POST https://api-demo.airwallex.com/api/v1/authentication/login \2 -H 'Content-Type: application/json' \3 -H 'x-api-key: {{YOUR_API_KEY}}' \4 -H 'x-client-id: {{YOUR_CLIENT_ID}}'
Response
1{2 "expires_at": "2026-10-26T06:38:13+0000",3 "token": "eyJhbGci..."4}
Save token on your server and send Authorization: Bearer {{token}} on subsequent Payment API requests. Refresh the token before expires_at.
token: Bearer token for Payment API calls from your server.expires_at: Expiration time in ISO 8601 format.
Step 2: Create a PaymentIntent on your server
Create a PaymentIntent when the shopper is ready to pay. Do not attach a payment method in this request—the SoftPOS SDK collects card-present details during confirm.
Request
1curl -X POST https://api-demo.airwallex.com/api/v1/pa/payment_intents/create \2 -H 'Authorization: Bearer {{ACCESS_TOKEN}}' \3 -H 'Content-Type: application/json' \4 -d '{5 "request_id": "b01737e5-c5ab-4765-8834-cbd92dfeaf81",6 "amount": 10.99,7 "currency": "USD",8 "merchant_order_id": "D202503210001"9 }'
Response (201 Created)
1{2 "id": "int_hkpdskz7vg1xc7uscdj",3 "request_id": "b01737e5-c5ab-4765-8834-cbd92dfeaf81",4 "amount": 10.99,5 "currency": "USD",6 "merchant_order_id": "D202503210001",7 "status": "REQUIRES_PAYMENT_METHOD",8 "client_secret": "eyJhbGci..."9}
id: PaymentIntent identifier. Use it for capture, cancel, refund, and retrieve calls.status:REQUIRES_PAYMENT_METHODmeans the intent is created and waiting for SDK confirmation.client_secret: Pass this to the SoftPOS SDK on the device. Valid for 60 minutes.
Return client_secret (and optionally id) to your Android app over a secure channel.
Step 3: Confirm payment with the SoftPOS SDK
After Step 2, your mobile app invokes the SoftPOS SDK with the PaymentIntent client_secret to initiate payment processing—the payment confirming stage in Airwallex. The SDK submits the confirm payment request to the Airwallex host to deduct funds from the shopper's card.
- Authentication on the device uses
client_secret, not the merchant access token from Step 1. - Auto-capture is enabled by default. The PaymentIntent moves to
SUCCEEDEDafter a successful confirm, so you do not need to call Capture a PaymentIntent API. - If you set
auto_capture = falsein the SDK, the PaymentIntent resolves toREQUIRES_CAPTUREafter the SDK confirms. You must call the capture API to settle funds to your wallet (see Step 4: Capture a PaymentIntent). Otherwise, after a long time (for example, a month) the issuer cancels the authorized payment without notifying Airwallex. See Place a hold on a payment method. - For Visa and Mastercard PIN flows (test amounts
9.71and9.73), the first call returns acontinue-with-pinresponse. Use the SDK's confirm continue payment intent call to provide PIN data and complete the confirm.
Implement confirm using the method and parameters defined in your SoftPOS SDK reference (for example initialize the SDK, pass client_secret, handle success, failure, and continue-with-PIN flows).
Receipt data
To print receipts in your app, retrieve the PaymentIntent with Retrieve a PaymentIntent API after success and read card-present details from latest_payment_attempt.payment_method.card_present in the response.
Check the payment result
Server side
Use the Retrieve a PaymentIntent API result on your backend as the source of truth for the payment outcome. Subscribe to Payments webhooks (for example payment_intent.succeeded) as a supplementary signal.
If a timeout happens and you do not know the PaymentIntent id, look up the PaymentIntent using merchant_order_id from your server's records.
App side
If the SDK returns an explicit success or failure, your app can trust that outcome.
If the SDK does not return a definitive result—for example a request timeout where the POS terminal does not receive the payment result in time—query the PaymentIntent from your server before showing a final outcome to the shopper or staff.
Step 4: Capture a PaymentIntent (manual capture only)
If auto-capture is disabled in the SDK and status is REQUIRES_CAPTURE, call capture on your server to settle funds to your wallet. The capture amount can be less than or equal to the original PaymentIntent amount.
1curl -X POST https://api-demo.airwallex.com/api/v1/pa/payment_intents/{{PAYMENT_INTENT_ID}}/capture \2 -H 'Authorization: Bearer {{ACCESS_TOKEN}}' \3 -H 'Content-Type: application/json' \4 -d '{5 "request_id": "ee939540-3203-4a2c-9172-89a566485dd9",6 "amount": 10.997 }'
Cancel a PaymentIntent
Use the cancel API for both reversal and void purposes. Cancel is allowed when status is REQUIRES_PAYMENT_METHOD or REQUIRES_CAPTURE. Canceling an authorized payment (REQUIRES_CAPTURE) reverses the authorized amount.
1curl -X POST https://api-demo.airwallex.com/api/v1/pa/payment_intents/{{PAYMENT_INTENT_ID}}/cancel \2 -H 'Authorization: Bearer {{ACCESS_TOKEN}}' \3 -H 'Content-Type: application/json' \4 -d '{5 "request_id": "1ccb6e97-37c6-45fe-ac3f-1380b7c0a0cd",6 "cancellation_reason": "Cancelled Reason",7 "cancellation_reason_code": "cancelled_by_customer"8 }'
cancelled_by_customer: Default. The shopper or merchant canceled the payment.processing_error: Processing error—for example a card-present chip card whoseemv_tagsfail chip verification.timeout: The app or terminal did not receive a response in time.
See Cancel a PaymentIntent API in the API reference.
Declined by card (contact)
In demo testing, amount 90.99 simulates a transaction that is approved by the API but declined by the card on a contact transaction (Issuer Script Processing failure). Your terminal must call cancel in this scenario. See Special test scenarios below.
Refund a payment
Refund a SUCCEEDED PaymentIntent with Create a Refund API. The refund amount can be equal to or less than the original PaymentIntent amount; multiple refunds are allowed until the full amount is refunded.
Request
1curl -X POST https://api-demo.airwallex.com/api/v1/pa/refunds/create \2 -H 'Authorization: Bearer {{ACCESS_TOKEN}}' \3 -H 'Content-Type: application/json' \4 -d '{5 "request_id": "ee939540-3203-4a2c-9172-89a566485dd9",6 "payment_intent_id": "int_ps8e0ZgQzd2QnCxVpzJrHD6KOVu",7 "amount": 10.01,8 "reason": "Return good"9 }'
Response
1{2 "acquirer_reference_number": "02709821210000001031868",3 "amount": 10.01,4 "created_at": "2019-09-18T03:11:00+0000",5 "currency": "USD",6 "id": "rfd_ps8e0ZgQzd2QnCxVpzJrHD6KOVu",7 "payment_attempt_id": "att_ps8e0ZgQzd2QnCxVpzJrHD6KOVu",8 "payment_intent_id": "int_ps8e0ZgQzd2QnCxVpzJrHD6KOVu",9 "reason": "Return good",10 "request_id": "ee939540-3203-4a2c-9172-89a566485dd9",11 "status": "SUCCEEDED",12 "updated_at": "2019-09-18T03:11:00+0000"13}
Query a PaymentIntent
Use Retrieve a PaymentIntent API on your server to look up a PaymentIntent by id. If you don't have the id—for example after a confirm timeout—query by merchant_order_id from your records.
1curl -G https://api-demo.airwallex.com/api/v1/pa/payment_intents/{{PAYMENT_INTENT_ID}} \2 -H 'Authorization: Bearer {{ACCESS_TOKEN}}'
Test your integration in sandbox
In the sandbox environment, you can trigger specific outcomes using the payment amount (in any processing currency), regardless of card details.
| Scenario | Test condition |
|---|---|
| Airwallex internal error | amount = 95.00 |
| Succeed after 30 seconds | amount = 93.01 |
| Issuer declined after 30 seconds | amount = 93.02 |
Issuer declined for 55 — Incorrect PIN | amount = 90.55 or 9055.00 and cardholderVerificationMethod = online_pin |
Issuer declined for 51 — Insufficient funds | amount = 90.51 |
Issuer declined — Switch interface (Visa 1A, Mastercard 65, Amex 130) | amount = 9.72 |
Single tap with PIN — Succeed (first response continue-with-pin; provide PIN via confirm continue, then succeed) | amount = 9.71 |
Single tap with PIN — Declined (first response continue-with-pin; provide PIN, then declined: Visa/Mastercard 55, Amex 117) | amount = 9.73 |
| Declined by Card on contact (approved in API; card declines due to issuer script processing failure—terminal must make a cancel request) | amount = 90.99 |