Skip to main content

Payments SDK Integration Guide for Android

Applies to

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:

  1. Order creation and wallet handoff — your app calls startPayment(), which creates the order and opens the wallet.
  2. Payment processing — the user completes, cancels, or abandons the flow inside WeChat Pay or Alipay.
  3. 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.
The SDK does not tell your app that a payment succeeded

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
Download

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/.

caution

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:

  1. Import the SDK classes:
import com.mandou.acp.sdk.AcpClient;
import com.mandou.acp.sdk.AcpClientConfig;
  1. 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();
}
});
tip

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 result Activity is your code, not the SDK's

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.

note

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

ParameterRequiredDescriptionExample
amountPayment amount (in cents)1000 (¥10.00)
bizNoUnique business order IDORDER20250427
goodsTitleProduct or service title"VIP Subscription"
payChannelPayment method (WECHAT or ALIPAY)"WECHAT"
customerIdentity🔶User identifier for order tracking"user_001"
attachDataExtra metadata if needed{EXPIRE_DATE: 2025-05-01}

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 attachData as your only source of truth: your backend should still persist the internal order for bizNo and decide fulfillment only after verifying paymentStatus == 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

FieldRecommendation
bizNoGenerate a unique string for each order. For example: \"{userId}_{timestamp}\" or \"ORDER_{UUID}\". This ensures idempotency and helps trace payment attempts.
goodsTitleProvide a short, meaningful description of the item or service being purchased. For example: \"Premium Subscription\" or \"Game Coins - 1000 Pack\".
customerIdentityUse 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

ParameterDescriptionRequired?
contextThe current Activity context (e.g., this).
payOrderThe PayOrder object with all payment details.
resultActivityClassOptional Activity for showing a post-payment result screen. If you pass null, you should handle post-payment UX yourself.Optional
callbackOptional PayResultCallback to receive payment result events in your code.Optional but recommended

What happens after calling startPayment()

StepActionHandled By
1Validate PayOrder fieldsSDK
2Choose payment channelSDK
3Open WeChat/Alipay appSDK + Payment App
4User completes or cancels paymentPayment App
5Control returns to appSDK

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 does not provide a result Activity

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:

  1. Create the Activity (any package/class name you choose).
  2. Declare the same class in AndroidManifest.xml.
  3. Pass that exact class to startPayment().
  4. Package it in the release APK/AAB (verify against the merged release manifest, not just source).
  5. 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().

caution

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.
  • pmtDt: only populated when paymentStatus is PAID.

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.");
});
}
}
);
}
}
tip

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>
Release-build validation

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.

ComponentPurposeWho defines itRequired 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 ActivityReceives/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 backendVerifies authoritative order status and grants/revokes access.Client backend.Idempotent verification endpoint/service and entitlement update contract. See Server Verification and Entitlements.
App entitlement refreshReads 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 ACP tag 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:

  1. Call AcpClient.init(...) during app initialization (§2).
  2. Call initPayTools(...) and confirm it succeeds before offering or testing WeChat Pay (§3). This retrieves the available payment tools and initializes the cached WeChat IWXAPI handle that the payment call relies on.
  3. Implement WXPayEntryActivity in the package/path required by WeChat and declare it in the manifest (Prerequisites §7).
  4. 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 make nextActivity the WeChat callback receiver.
  5. Refresh the app's entitlement state only after your backend has resolved the order state.
initPayTools() is a required WeChat prerequisite

Do 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
note

All requests must include APP_ID and APP_SECRET headers. See the Payments API reference for details.

8.2 Required headers

Header NameValueDescription
APP_IDYour AppInChina App IDProvided by AppInChina Dashboard or operations team.
APP_SECRETYour App SecretProvided by AppInChina Dashboard or operations team.
Content-Typeapplication/jsonOptional but recommended.

8.3 Request parameters

ParameterTypeRequiredDescription
bizNostringThe unique order identifier (same value used in PayOrder.setBizNo()).
customerIdentitystringThe 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"
}
}
tip

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.