Payments SDK Integration Guide for Android
AppInChina Android Payments SDK payments_sdk_v20220912 (internal build acp-v20220222-prod-production). If your project uses a different artifact, review the compatibility matrix and changelog before copying this guide — SDK behaviour, dependencies, and callbacks can change between versions.
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.
Before you start: what "payment complete" means
A payment attempt has three separate stages, and only the last one grants access:
- Order creation and wallet handoff — your app calls
startPayment(), which creates the order and opens the wallet. - Payment processing — the user completes, cancels, or abandons the flow inside WeChat Pay or Alipay.
- Your app's verified entitlement update — your backend verifies the order's authoritative status and applies your entitlement rules; your app then refreshes access from your backend.
Do not grant access because the app returned from a wallet, or because an Android result/callback Activity was opened. startPayment() creates the order and hands off to the wallet — it does not deliver a final paid/failed/cancelled result to your app. Your backend must verify the order status through the documented AppInChina interface and apply your entitlement rules idempotently; the app should then refresh access from your backend.
See Server Verification and Entitlements for the authoritative flow.
App creates payment request
→ AppInChina creates order / wallet is launched
→ User completes, cancels, or leaves the wallet flow
→ App-owned callback or return handler runs
→ App asks its backend to verify/reconcile the order
→ Backend records final state and grants/revokes entitlement idempotently
→ App refreshes access from its backend
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/.
Complete all dependencies in Payments SDK Prerequisites and Environment Setup before initializing payments, including the required legacy FastJSON 1.x (com.alibaba:fastjson) dependency. This dependency is used by the AppInChina Payments SDK itself (not only by Alipay) and is not bundled in the SDK — your app must supply the exact pinned version. Missing or mismatched dependencies can cause runtime crashes during pay tool initialization. See Prerequisites §3.2.
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,
// AppInChina App ID + App Secret — NOT your WeChat App ID.
new AcpClientConfig("YOUR_APP_ID", "YOUR_APP_SECRET")
);
}
To get your AppInChina App ID (APP_ID) and APP_SECRET, start with the credential request step in the Prerequisites and environment setup guide. These are distinct from your WeChat App ID (the WeChat Open Platform AppID used to initialize the WeChat SDK).
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
);
});
}
PayResultActivity in the examples above is a client-owned class. The SDK does not define or provide it. You may name it anything; PayResultActivity is not an SDK class and is not a required name. You must implement it, declare it in AndroidManifest.xml, pass that exact class to startPayment(), and ship it in the release APK/AAB. See Section 7: the app-side payment result Activity.
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, and 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. The app-side payment result Activity (client-owned)
When you call startPayment(), you pass an Activity class as the third parameter to present a result screen after the payment process completes. The SDK redirects the user to that screen once the payment app returns control to your application.
The SDK intentionally does not define or provide a default payment-result Activity. The class you pass as the result Activity to startPayment() is app-side code that your team must implement and maintain. You may choose its class name; the example name PayResultActivity is not an SDK class and is not a required name.
A paid order can exist in the AppInChina backend while the installed build fails to launch a result Activity that was never implemented, declared, or packaged. That is an app-side defect, not a payment failure.
Your team must:
- Create the Activity (any package/class name you choose).
- Declare the same class in
AndroidManifest.xml. - Pass that exact class to
startPayment(). - Package it in the release APK/AAB (verify against the merged release manifest, not just source).
- Hand off to your entitlement-refresh flow after verifying the order server-side.
If you pass null as the result Activity, the SDK will not show a result screen, and you must implement the equivalent post-payment handling yourself (query the order, refresh entitlement, update the UI) — see Section 6: Understanding startPayment().
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.
7.3 Declare the result Activity in the manifest
The class you pass to startPayment() must be declared in AndroidManifest.xml. Use your own package and class name — the example below uses a client-owned name to make the ownership explicit:
// Client-owned class. Use your own package and class name.
startPayment(/* context, payOrder, */ AppInChinaPayResultActivity.class, /* callback */);
<!-- AndroidManifest.xml -->
<application ...>
<activity
android:name=".appinchina.AppInChinaPayResultActivity"
android:exported="false" />
</application>
Before delivering a test or production build, inspect the merged release manifest and the installed artifact. The Activity package/class you pass at runtime must match the class in the final merged manifest exactly. Do not validate only the debug manifest or the source manifest — minification, manifest merging, and split APK/AAB delivery can all cause a class that exists in source to be missing or renamed in the shipped artifact.
7.4 The four post-payment components (and who owns each)
The WeChat callback receiver and the result Activity are separate Android components with separate responsibilities. Do not conflate them.
| Component | Purpose | Who defines it | Required documentation |
|---|---|---|---|
WXPayEntryActivity (SDK-required callback receiver) | Receives the WeChat payment callback under the required package/path conventions. | Client app, following the WeChat/SDK integration requirement. See Prerequisites §7. | Exact class/package rule, manifest declaration, callback code, troubleshooting checks. |
| Client result Activity | Receives/presents the app's post-payment result and triggers the next app-side step. | Client app. Not supplied by the AppInChina SDK. | Creation, manifest declaration, startPayment() parameter, release-build check, entitlement-refresh handoff. |
| Client backend | Verifies authoritative order status and grants/revokes access. | Client backend. | Idempotent verification endpoint/service and entitlement update contract. See Server Verification and Entitlements. |
| App entitlement refresh | Reads current access from the client backend and updates the UI. | Client app/backend. | Refresh after payment, app resume/relaunch, login, and reinstall/restore where relevant. |
The WXPayEntryActivity example in Prerequisites §7 launches a PayResultActivity — that target is the client result Activity described here, which you must implement and which must reach your backend for verification.
7.5 Diagnostics and observability
When capturing evidence around a payment attempt (see Troubleshooting and Support Evidence):
-
Log tag: the SDK logs under the
ACPtag family. Capture with:adb logcat -v time ACP:V AndroidRuntime:E *:S -
Surround the attempt with your own client-side logs at
startPayment(), return-to-app,querySingleOrder(), and entitlement grant, each with a timestamp and time zone. -
For each SDK callback/error, decide the action explicitly: retry locally, check wallet readiness, inspect app integration, query backend order state, or send a sanitized evidence excerpt to AppInChina. See the Error Reference.
-
Never log
APP_SECRET, tokens, wallet credentials, or card data.
7.6 Alipay return behaviour
After the Alipay SDK returns control, the AppInChina SDK opens the nextActivity (the result Activity) you supplied to startPayment(). This navigation is not a paid-order confirmation and may occur after an unsuccessful or cancelled attempt — the SDK does not use the Alipay result to decide success, failure, or cancellation before opening the Activity.
In that Activity:
- Show a pending/refreshing state.
- Obtain the final access state from your backend after it verifies the AppInChina order.
- Do not unlock membership directly from the Activity launch or the local wallet-return result.
You choose and own nextActivity. It must be present in the installed release artifact and declared in the final merged manifest (see §7.3). A safe post-return sequence:
receive return → call client backend → backend queries/reconciles the order
→ refresh entitlement → render final state (with a pending/retry UX until the backend observes a final state)
7.7 WeChat payment lifecycle
Unlike Alipay, WeChat does not return through nextActivity. The WeChat payment result arrives at your app's required WXPayEntryActivity. Document and implement the full lifecycle as one connected path:
- Call
AcpClient.init(...)during app initialization (§2). - Call
initPayTools(...)and confirm it succeeds before offering or testing WeChat Pay (§3). This retrieves the available payment tools and initializes the cached WeChatIWXAPIhandle that the payment call relies on. - Implement
WXPayEntryActivityin the package/path required by WeChat and declare it in the manifest (Prerequisites §7). - When WeChat returns to
WXPayEntryActivity, route the event into your payment-verification and entitlement-refresh flow. It may navigate to your result UI if you want, but this does not makenextActivitythe WeChat callback receiver. - Refresh the app's entitlement state only after your backend has resolved the order state.
initPayTools() is a required WeChat prerequisiteDo not defer initPayTools() until after the user presses Pay. If it has not completed successfully, do not start a WeChat payment attempt — surface a recoverable setup error and capture the SDK/app logs. Without it, the SDK has not initialised the WeChat API handle used to dispatch the payment request.
WXPayEntryActivity (the WeChat callback receiver) and your client result UI are different components — see the component map in §7.4.
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 AppInChina 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
The return to your app is not the success signal — your backend's verified order state is. The two wallets return control differently:
App calls startPayment() → SDK POSTs /order.json and opens the wallet
↓
User completes, cancels, or leaves the wallet flow
↓
Control returns to your app:
• Alipay → SDK opens your nextActivity (regardless of outcome)
• WeChat → result arrives at your WXPayEntryActivity
↓
Your return/callback handler runs and triggers a backend verification request
↓
Backend queries the authoritative order state and grants/revokes entitlement idempotently
↓
App refreshes access from your backend ← the only authoritative "complete" signal
Neither the Activity launch (Alipay) nor the callback arrival (WeChat) proves the payment succeeded. Only your backend's verified state does.
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.