Payments SDK Integration Guide for Android
Introduction
This guide explains how to integrate the AppInChina Payments SDK into your Android application. With this SDK, you can accept payments through WeChat Pay and Alipay.
This guide assumes basic familiarity with Android development and Java.
Related docs
- Setup checklist (required): Payments SDK Prerequisites and Environment Setup
- Login → Payments integration (identity mapping): Login → Payments integration (customer identity)
- Payment verification and flows: Understanding the IAP SDK/API Flow
- Troubleshooting errors: Error Reference
Download the latest Android SDK package: payments_sdk_latest.zip
1. Install the SDK
Add the Payments SDK to your app's build.gradle file:
dependencies {
// The AAR is shipped inside /downloads/payments_sdk_latest.zip
implementation(name: 'payments_sdk_v20220912', ext: 'aar')
}
You also need to ensure Gradle can resolve local AARs. Add this to your module (app) build.gradle:
repositories {
flatDir {
dirs 'libs'
}
}
Then place the file payments_sdk_v20220912.aar into app/libs/.
2. Initialize the SDK
In your Application class:
- Import the SDK classes:
import com.mandou.acp.sdk.AcpClient;
import com.mandou.acp.sdk.AcpClientConfig;
- Initialize the SDK in
onCreate():
@Override
public void onCreate() {
super.onCreate();
AcpClient.sharedInstance().init(
this,
new AcpClientConfig("YOUR_APP_ID", "YOUR_APP_SECRET")
);
}
To get your APP_ID and APP_SECRET, start with the credential request step in the Prerequisites and environment setup guide.
3. Initialize payment tools
When your payment screen is loaded, you should initialize payment tools:
AcpClient.sharedInstance().initPayTools(new PayToolCallback() {
@Override
public void onSuccess(String payChannel) {
if ("WECHAT".equalsIgnoreCase(payChannel)) {
initWechat();
} else if ("ALIPAY".equalsIgnoreCase(payChannel)) {
initAlipay();
}
}
@Override
public void onFail(String s, Throwable throwable) {
Toast.makeText(PayActivity.this, "Payment environment initialization failed", Toast.LENGTH_LONG).show();
}
});
Always call initPayTools() when the payment screen loads. It fetches the signed/available pay channels for your app (e.g., WeChat vs Alipay) and initializes the SDK state used to decide which payment buttons to show. Skipping it commonly results in missing buttons or startPayment() failing due to incomplete environment setup.
4. Create payment buttons and start payment flow
Once the payment environment is initialized, you can create buttons to trigger payments.
Each button will:
- Call
buildPayOrder()to prepare the payment information. - Call
startPayment()to begin the payment process through the SDK.
We recommend creating one button for each payment method your app supports.
Example — WeChat Pay button
private void initWechat() {
Button wechatBtn = findViewById(R.id.btn_pay_wechat);
wechatBtn.setVisibility(View.VISIBLE);
wechatBtn.setOnClickListener(v -> {
PayOrder payOrder = buildPayOrder("WECHAT");
AcpClient.sharedInstance().startPayment(
PayActivity.this,
payOrder,
PayResultActivity.class,
PayActivity.this
);
});
}
Example — Alipay button
private void initAlipay() {
Button alipayBtn = findViewById(R.id.btn_pay_alipay);
alipayBtn.setVisibility(View.VISIBLE);
alipayBtn.setOnClickListener(v -> {
PayOrder payOrder = buildPayOrder("ALIPAY");
AcpClient.sharedInstance().startPayment(
PayActivity.this,
payOrder,
PayResultActivity.class,
PayActivity.this
);
});
}
The third parameter in startPayment() (resultActivityClass) must not be null if you want to handle payment results visually.
If set to null, the SDK will not show a result page automatically. You must handle post-payment behavior manually.
If WeChat or Alipay is not installed on the user's device, the SDK will automatically detect this and display a helpful message (e.g., "WeChat not installed") before attempting to launch the payment app.
5. Building a pay order: the PayOrder class
The buildPayOrder() function creates the PayOrder object needed to start a payment.
A PayOrder includes:
- Payment amount
- Product title
- Unique business order number
- Payment method (WeChat or Alipay)
- Optional additional data
Example — building a PayOrder
private PayOrder buildPayOrder(String payChannel) {
PayOrder payOrder = PayOrder.payWith(payChannel);
payOrder.setAmount(new BigDecimal(amountStr).multiply(new BigDecimal(100)).longValue());
payOrder.setBizNo(bizNoStr);
payOrder.setGoodsTitle(titleStr);
payOrder.setCustomerIdentity(customerIdStr);
payOrder.setAttachData(extraDataMap);
return payOrder;
}
PayOrder parameter reference
| Parameter | Required | Description | Example |
|---|---|---|---|
| amount | ✅ | Payment amount (in cents) | 1000 (¥10.00) |
| bizNo | ✅ | Unique business order ID | ORDER20250427 |
| goodsTitle | ✅ | Product or service title | "VIP Subscription" |
| payChannel | ✅ | Payment method (WECHAT or ALIPAY) | "WECHAT" |
| customerIdentity | 🔶 | User identifier for order tracking | "user_001" |
| attachData | ❌ | Extra metadata if needed | {EXPIRE_DATE: 2025-05-01} |
Using attachData (recommended)
attachData is the easiest way to link a payment back to your internal commerce model (SKU, plan, promo/campaign, etc.). It is recorded with the transaction and returned in order query results.
Example (key/value map)
Map<String, String> attachData = new HashMap<>();
attachData.put("productId", "pro_3m");
attachData.put("promoId", "winter25");
attachData.put("priceVersion", "v3");
payOrder.setAttachData(attachData);
Best practices
- Keep keys stable and documented (so analytics and support can rely on them).
- Avoid PII and secrets.
- Don’t use
attachDataas your only source of truth: your backend should still persist the internal order forbizNoand decide fulfillment only after verifyingpaymentStatus == PAID.
While customerIdentity is optional and payments will process without it, we strongly recommend setting and storing this value since it's essential for tracking order status and results.
Choosing values for bizNo, goodsTitle, and customerIdentity
| Field | Recommendation |
|---|---|
| bizNo | Generate a unique string for each order. For example: \"{userId}_{timestamp}\" or \"ORDER_{UUID}\". This ensures idempotency and helps trace payment attempts. |
| goodsTitle | Provide a short, meaningful description of the item or service being purchased. For example: \"Premium Subscription\" or \"Game Coins - 1000 Pack\". |
| customerIdentity | Use a persistent user ID from your app or backend system. This helps with querying order history and handling disputes. For example: \"user_12345\" or \"openid_xyz\". |
If you need help mapping your login system to customerIdentity, see: Login → Payments integration (customer identity).
6. Understanding the startPayment() method
Method overview
AcpClient.sharedInstance().startPayment(
context,
payOrder,
resultActivityClass,
callback
);
Parameters
| Parameter | Description | Required? |
|---|---|---|
context | The current Activity context (e.g., this). | ✅ |
payOrder | The PayOrder object with all payment details. | ✅ |
resultActivityClass | Optional Activity for showing a post-payment result screen. If you pass null, you should handle post-payment UX yourself. | Optional |
callback | Optional PayResultCallback to receive payment result events in your code. | Optional but recommended |
What happens after calling startPayment()
| Step | Action | Handled By |
|---|---|---|
| 1 | Validate PayOrder fields | SDK |
| 2 | Choose payment channel | SDK |
| 3 | Open WeChat/Alipay app | SDK + Payment App |
| 4 | User completes or cancels payment | Payment App |
| 5 | Control returns to app | SDK |
7. Handling payment results in a custom activity
When you call startPayment(), you should pass an Activity class as the third parameter to present a custom result screen after the payment process completes. The SDK will redirect the user to that screen once the payment app returns control to your application.
Do not treat client-side callbacks or “return-to-app” events as proof of payment success.
You must use querySingleOrder() (or the REST order-query endpoint) to confirm the final payment status using bizNo and customerIdentity.
7.1 Query single order
AcpClient.sharedInstance().querySingleOrder(
"customerIdentity",
"bizNo",
new PayOrderCallback() {
@Override
public void onSuccess(List<PayOrderInfo> list) {
if (!list.isEmpty() && "PAID".equals(list.get(0).getPaymentStatus())) {
// Payment succeeded
}
}
@Override
public void onFail(String s, Throwable throwable) {
// Handle error
}
}
);
The query result will contain a maximum of one PayOrderInfo object.
The PayOrderInfo class is structured as follows:
public class PayOrderInfo {
private String appId;
private String id;
private long amount;
private String bizNo;
private String goodsTitle;
private String payChannel;
private String customerIdentity;
private Map<String, String> attachData;
private String sourceFrom;
private Date pmtDt;
private String paymentStatus;
}
Important fields
- paymentStatus:
- Possible values:
PENDING— The payment is in progress or awaiting completion.PAID— The customer has successfully paid for the order.CLOSE— The payment was closed or canceled.REFUND— The payment was refunded.
- Possible values:
- pmtDt: only populated when
paymentStatusisPAID.
7.2 Implement a result activity
public class PayResultActivity extends AppCompatActivity {
private TextView resultText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pay_result);
resultText = findViewById(R.id.pay_result_text);
fetchPaymentStatus();
}
private void fetchPaymentStatus() {
String bizNo = PayToolInfo.getCurrentBizNo(); // Provided by the SDK
String customerId = "user_001"; // Store this when initiating the order
AcpClient.sharedInstance().querySingleOrder(
customerId,
bizNo,
new PayOrderCallback() {
@Override
public void onSuccess(List<PayOrderInfo> list) {
runOnUiThread(() -> {
if (!list.isEmpty() && "PAID".equals(list.get(0).getPaymentStatus())) {
resultText.setText("Payment Successful via " + list.get(0).getPayChannel());
} else {
resultText.setText("Payment Failed or Canceled");
}
});
}
@Override
public void onFail(String s, Throwable throwable) {
runOnUiThread(() -> {
resultText.setText("Failed to fetch payment result. Please try again.");
});
}
}
);
}
}
We recommend using querySingleOrder() instead of relying on Intent extras, since payment results are not passed directly by the SDK.
8. Query single order via REST API
In addition to using querySingleOrder() on the client side, you can also verify or retrieve order details from your backend using a dedicated HTTP endpoint.
8.1 Endpoint
GET /detail.json
All requests must include APP_ID and APP_SECRET headers. See the Payments API reference for details.
8.2 Required headers
| Header Name | Value | Description |
|---|---|---|
APP_ID | Your App ID | Provided by AppInChina Dashboard or operations team. |
APP_SECRET | Your App Secret | Provided by AppInChina Dashboard or operations team. |
Content-Type | application/json | Optional but recommended. |
8.3 Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bizNo | string | ✅ | The unique order identifier (same value used in PayOrder.setBizNo()). |
customerIdentity | string | ✅ | The customer ID used during order creation (PayOrder.setCustomerIdentity()). |
Example request
GET https://api.appinchinaservices.com/detail.json?bizNo=ORDER_123456789&customerIdentity=user_001
8.4 Successful response
{
"msg": "success",
"code": 0,
"data": {
"amount": 1000,
"appId": "yourAppId",
"bizNo": "ORDER_123456789",
"extInfo": {},
"gmtCreate": 1688000000000,
"gmtModified": 1688000000000,
"pmtDt": 1688000300000,
"attachData": {"EXPIRE_DATE": "2025-05-01"},
"sourceFrom": "wechat",
"goodsTitle": "VIP Subscription",
"id": "orderId123",
"payChannel": "WECHAT",
"paymentStatus": "PAID"
}
}
Treat payment as successful only if paymentStatus is PAID.
9. Payment flow summary
App calls startPayment()
↓
SDK opens payment app
↓
User completes payment or cancels
↓
Control returns to app
↓
Custom result activity is launched
↓
Activity uses bizNo and customerIdentity to query payment status
↓
Backend confirms payment outcome
10. Query payment history
AcpClient.sharedInstance().queryHistoryOrder(
"customerIdentity",
"PAID",
1,
10,
new PayOrderCallback() {
@Override
public void onSuccess(List<PayOrderInfo> list) {
// Display payment history
}
@Override
public void onFail(String s, Throwable throwable) {
// Handle error
}
}
);
11. Refunds
The SDK does not provide any refund APIs.
To process refunds, log in to the AppInChina Dashboard and locate the specific order. From there, you can initiate and complete a refund. If you need assistance or have special cases, contact our operations team for support.