# AppInChina Payments Documentation

> This file contains the AppInChina Payments documentation concatenated into
> a single document. You can paste it into an AI assistant (ChatGPT, Gemini,
> Claude, etc.) to ask questions about your Payments integration.
>
> Generated automatically at build time.

---


<!-- source: payments/api/reference.md -->


:::info Applies to
Endpoint contracts on this page correspond to the currently supported SDK artifact. See the [Compatibility Matrix & Changelog](../reference/compatibility-and-changelog.md) for the artifact/version and dependency ranges, and review it before copying behaviour if your project uses a different artifact.
:::

## 1. Authentication and base URL

To use the AppInChina Payments API, all requests must include authentication headers that identify your application.

### 1.1 Required headers

Each request must include the following two headers:

| Header | Description |
| --- | --- |
| `APP_ID` | Your **AppInChina App ID** — your application’s unique identifier in the AppInChina IAP system, provided by AppInChina. Not the same as your WeChat App ID. |
| `APP_SECRET` | The secret key associated with your APP_ID. Keep this value secure and do not expose it in client-side code. |

#### Example (Java)

```java
private static final String APP_ID = "your_app_id_here";
private static final String APP_SECRET = "your_app_secret_here";

private static final Headers headers = Headers.of(
    "APP_ID", APP_ID,
    "APP_SECRET", APP_SECRET
);
```

:::caution
Keep your `APP_SECRET` confidential. Do not include it in frontend code or expose it in logs or error messages.
:::

## Related docs

- Identity mapping (`customerIdentity`): [Login → Payments integration (customer identity)](../guides/login-identity.md)
- High-level flow: [Understanding the IAP SDK/API Flow](../concepts/iap-flow.md)
- Android SDK guide: [Payments SDK Integration Guide for Android](../sdk/android.md)
- PC & H5 guide: [PC and H5 Payments Integration Guide](../web/pc-h5.md)
- Troubleshooting errors: [Error Reference](../reference/errors.md)

### 1.2 Base URL

All API requests should be made to the following base URL:

```bash
https://api.appinchinaservices.com
```

You will append specific endpoints (such as `/order.json`, `/detail.json`, etc.) to this base URL.


## 3. Create order

### 3.1 Endpoint

```
POST /order.json
```

This endpoint is used to create a new payment order.

It generates the necessary information to initiate a payment with either **WeChat Pay** or **Alipay**, depending on the selected payment channel and environment.

### 3.2 Purpose

Use this endpoint to:

- Create a unique payment order for a user transaction.
- Initiate payment through the appropriate channel (WeChat or Alipay).
- Receive parameters needed to proceed with payment (e.g., `prepayid`, `sign`, etc. for WeChat).

### 3.3 Required headers

Authentication headers (`APP_ID` and `APP_SECRET`) must be included in the request.

See [Authentication and base URL](#1-authentication-and-base-url) for details.

### 3.4 Request parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | `long` | Required | Payment amount in **cents**. (e.g., 100 = ¥1 RMB) |
| `bizNo` | `string` | Required | Unique transaction number generated by the client for internal record keeping. |
| `goodsTitle` | `string` | Required | Title or description of the item being purchased (displayed during payment). |
| `payChannel` | `string` | Required | Payment channel: `WECHAT` or `ALIPAY`. |
| `paySource` | `string` | Optional | Payment source: `NATIVE`, `H5`, or `APP`. (Default is `APP` if not specified.) |
| `customerIdentity` | `string` | Required¹ | Stable, non-secret internal user identifier. **Required for account-based apps** unless you use a documented alternate identity design. Not a wallet account, email, card number, or per-attempt value. See [Login → Payments integration (customer identity)](../guides/login-identity.md). |
| `customerName` | `string` | Recommended | User's username, phone number, or other human-readable identifier. |
| `serviceNo` | `string` | Deprecated | Service order number tied to subscription or timed services. |
| `serviceTypeNo` | `string` | Deprecated | Service type identifier created in the AppInChina Dashboard. |
| `attachData` | `string` | Optional | Additional metadata to associate with the order (custom JSON string or plain text). If you use the Android SDK, you may provide a key/value map which the SDK serializes for you. |
| `sourceFrom` | `string` | Optional | Identifier of the app store where the payment was initiated (see below for accepted values). |

¹ **`customerIdentity` and `bizNo` are operational requirements**, not incidental fields. `bizNo` is your client-controlled business reference: it must be **unique** per attempt, **immutable** once created, and **retry-safe** (reusing it re-queries the same order without creating a duplicate). Store both in your backend so a payment attempt can be correlated across verification, entitlement, purchase history, and support. See [Server Verification and Entitlements](../backend/verification-entitlements.md) and [Purchase History & Refunds](../guides/purchase-history-refunds.md).

### 3.4.1 Using `attachData` effectively

`attachData` is your **order metadata** field. AppInChina Payments records it with the transaction and returns it back in order query responses (for example `/detail.json` and `/history.json`).

Use it when you need more than “a transaction happened” — for example, to connect payments to:

- Your **SKU / product ID**
- Subscription **plan ID** / tier
- A **promo / campaign** identifier
- A pricing **version** (useful for A/B tests or regional price tables)
- Any internal IDs your backend needs to **reconcile** and **fulfill** correctly

#### Recommended format

- Keep it small and stable.
- Prefer a compact JSON object encoded as a string (or plain text if you prefer).
- If you use the Android SDK, you can pass a key/value map and the SDK serializes it.

#### Security and privacy

- Do **not** store secrets (tokens, API keys) in `attachData`.
- Avoid raw PII when possible (phone, email, full name).
- Treat `attachData` as metadata for reporting/reconciliation — not as an authorization mechanism.

#### Example (CreateOrder request body)

```json
{
  "amount": 999,
  "bizNo": "ORDER_20260205_0001",
  "goodsTitle": "Pro plan (3 months)",
  "payChannel": "WECHAT",
  "customerIdentity": "user_12345",
  "attachData": "{\"productId\":\"pro_3m\",\"promoId\":\"winter25\",\"priceVersion\":\"v3\"}"
}
```

### Accepted values for `sourceFrom`

| App Store | Value |
| --- | --- |
| Tencent MyApp | `tencent` |
| Huawei App Market | `huawei` |
| Oppo Software Store | `oppo` |
| 360 Mobile Assistant | `360` |
| Baidu Mobile Assistant / 91 Assistant / Himarket | `baidu` |
| MIUI App Store | `miui` |
| VIVO App Store | `vivo` |
| PP Assistant / Wandoujia / Taobao | `pp` |
| China Mobile MM Store | `chinamm` |
| Anzhi Market | `anzhi` |
| Sogou Mobile Assistant | `sogou` |
| Meizu Flyme | `meizu` |
| Coolpad | `coolpad` |
| Lenovo Store | `lenovo` |
| Samsung App Store | `samsung` |
| AppChina | `appchina` |
| Others | `others` |

### 3.5 Successful response examples

#### WeChat Pay example

```json
{
  "msg": "success",
  "traceId": "b7c04e1317476299292752036",
  "code": 0,
  "data": {
    "appid": "wxappidexample",
    "noncestr": "randomString",
    "package": "Sign=WXPay",
    "partnerid": "wechatPartnerId",
    "prepayid": "wechatPrepayId",
    "sign": "generatedSignature",
    "timestamp": 1747629929
  }
}
```

:::caution `appid` here is WeChat's, not yours
In the WeChat response above, `appid` (all lowercase) is your **WeChat App ID** — the WeChat Open Platform Mobile Application AppID that WeChat needs to launch its payment UI. It is **not** your AppInChina App ID.

By contrast, order-query responses (`/detail.json`, `/history.json`, `/payTools.json`) return `appId` (camelCase) = your **AppInChina App ID**. The two differ only by casing, so map them carefully.
:::

#### Alipay example

```json
{
  "msg": "success",
  "traceId": "b7c04e1317476298157356165",
  "code": 0,
  "data": {
    "app_id": "2021001156621375",
    "biz_content": "{\"body\":\"1\",\"out_trade_no\":\"20250519000916970200108462\",\"product_code\":\"QUICK_MSECURITY_PAY\",\"subject\":\"1\",\"total_amount\":\"0.01\"}",
    "charset": "utf-8",
    "format": "json",
    "method": "alipay.trade.app.pay",
    "notify_url": "https://api.appinchinaservices.com/payBackAlipay",
    "return_url": "https://api.appinchinaservices.com/payBackAlipay",
    "sign": "...",
    "sign_type": "RSA2",
    "timestamp": "2025-05-19 12:43:35",
    "version": "1.0"
  },
  "message": "success"
}
```

### 3.6 Error response

```json
{
  "code": "error_code",
  "msg": "error_message"
}
```


## 5. Query order list

### 5.1 Endpoint

```
GET /history.json
```

This endpoint retrieves a paginated list of historical payment orders associated with a given customer.

### 5.2 Request parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `customerIdentity` | `string` | ✅ | Unique identifier of the customer whose order history is being queried |
| `paymentStatus` | `string` | Optional | Filter by status: `PAID`, `PENDING`, `CLOSE`, `REFUND`, etc. |
| `pageNum` | `number` | Optional | Page number to retrieve (default: `1`) |
| `pageSize` | `number` | Optional | Number of records per page (default: `20`) |

### 5.3 Successful response

```json
{
  "msg": "success",
  "code": 0,
  "data": {
    "payOrderList": [
      {
        "amount": 1,
        "appId": "",
        "bizNo": "",
        "extInfo": {},
        "gmtCreate": 1551676154000,
        "gmtModified": 1551676154000,
        "pmtDt": 1551676154000,
        "attachData": {},
        "sourceFrom": "",
        "goodsTitle": "",
        "id": "",
        "payChannel": "",
        "paymentStatus": "PAID"
      }
    ],
    "totalCount": 1
  }
}
```

:::note
Use `totalCount` to support pagination in your UI.
:::


## 7. Error codes and meanings

All API endpoints return a standardized error response format:

```json
{
  "code": "error_code",
  "msg": "error_message"
}
```

For the full list of error codes and their meanings, see the [Error reference](../reference/errors.md).

:::note
Always handle errors based on the `code` field rather than relying on HTTP status codes, as all responses may return HTTP 200 even when the request failed.
:::

---


<!-- source: payments/backend/verification-entitlements.md -->


This guide defines the **authoritative order state**, the retry strategy, and the entitlement contract that turn a completed payment into usable access.

AppInChina Payments processes the payment transaction. **Your backend makes the authoritative entitlement decision** after checking the transaction state through the documented AppInChina interface. The payment result returned to the app is useful for user experience, but it is not, by itself, proof that access should be granted.

:::danger The authoritative payment-completion rule
The app must grant or refresh access **only after** its backend has verified the order's authoritative status and applied the client's entitlement rules. **Neither a successful wallet handoff nor arrival at an Android result/callback Activity proves that payment succeeded.** 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.
:::

## Related docs

- Scope and ownership: [Start Here: integrate AppInChina IAP](../overview.md)
- Identity mapping: [Login → Payments integration (customer identity)](../guides/login-identity.md)
- Endpoint contract: [Payments API Reference](../api/reference.md)
- History and refunds: [Purchase History & Refund Workflows](../guides/purchase-history-refunds.md)
- Android integration: [Payments SDK Integration Guide for Android](../sdk/android.md)

## 1. The three states you must observe independently

A transaction can be *paid* while the app still shows an error or fails to grant access. Do not collapse these three states into one:

| State | Where it lives | How you observe it |
| --- | --- | --- |
| **Payment processing** | WeChat Pay / Alipay + AppInChina | The wallet returns control to your app; an order exists in the AppInChina backend. |
| **Backend verification** | AppInChina order-status interface | Your backend queries `/detail.json` (or the SDK query) and reads `paymentStatus == PAID`. |
| **Entitlement / UI state** | Your backend + your app | Your backend records the grant; your app refreshes and unlocks the feature. |

All three must pass. A `PAID` transaction with no entitlement grant is an incomplete integration, not a completed payment.

## 2. The single "first purchase" recipe

Follow this recipe end to end. Every payment integration must be able to complete all eight steps before it is considered done.

| Step | Owner | Required behavior | Pass condition |
| --- | --- | --- | --- |
| **1. Authenticate user** | Client app / backend | Only offer payment for a known app account; generate or load a stable internal user ID. | A stable `customerIdentity` is available before initiation. |
| **2. Create client order context** | Client backend | Create a unique client business/order reference (`bizNo`) and record product, user, intended entitlement, and pending state. | The reference is unique and retry-safe. |
| **3. Start payment** | Client app | Call the SDK/API with the required parameters, including the result/callback class where applicable. | Wallet flow opens or produces a diagnosable pre-payment failure. |
| **4. Receive return / callback** | Client app | Return safely to the app and trigger a backend refresh/verification request. Do **not** grant durable access solely from a UI success signal. | App does not crash; verification begins. |
| **5. Verify order state** | Client backend | Use the canonical documented query/verification endpoint; handle retryable states and failures. | Authoritative state is obtained and stored. |
| **6. Grant entitlement** | Client backend | Use an idempotent state transition keyed by the transaction / `bizNo`; store audit fields required for support. | Correct access is active exactly once. |
| **7. Refresh app access** | Client app | Fetch current entitlement state from the client backend and update the UI. | The product is usable without manual restart; relaunch also reflects access. |
| **8. Preserve history** | Client backend / UI | Expose relevant completed orders/purchases and their client references as needed by product support. | A support case can map an app user to `bizNo` / order records. |

:::caution Authoritative state
Treat the AppInChina backend's verified order state and your backend's entitlement record as authoritative. The app's immediate return from the wallet is **not** a substitute for verification. Conversely, a `PAID` transaction is **not** a substitute for granting access: your backend must complete the entitlement update and the app must refresh from that state.
:::

For a fully worked example (login → CreateOrder → verify → fulfill → renew → restore), see [Capabilities and Limitations → fixed-term subscription flow](../concepts/capabilities-limitations.md#example-fixed-term-subscription-flow-where-each-piece-lives).

## 3. Idempotency, retries, and lifecycle behavior

### 3.1 Idempotency

Document and implement the **unique key** that prevents duplicate grants — normally the `bizNo`, or the AppInChina order identifier where returned. Your backend should be able to repeat the same verification request and receive the same entitlement result **without extending or duplicating access**.

- Key the entitlement transition on `bizNo` (or order id), not on "the user tapped pay again".
- A repeated verification for an already-fulfilled order must be a no-op, not a second grant.

### 3.2 Pending or interrupted flows

Payments can be interrupted (the user backgrounds the wallet, loses network, or returns much later). Document:

- **What the app shows while verification is pending** (a neutral "confirming your purchase" state, not "success").
- **How the backend retries** a status query for a `PENDING` order.
- **How a user can refresh purchases** after returning later.

:::note
Do not promise a specific polling frequency in client-facing docs unless Engineering has defined and supports one. Describe the retry *behavior*, not a guaranteed interval.
:::

### 3.3 Restore points

Require an entitlement refresh after each of these events, where the product supports them:

- Payment return
- App relaunch
- Login
- Account switching
- Reinstall / restore

The app should retrieve access from **your backend**, not from a one-time device-local success state. See also [Purchase Restore and SMS Authentication](../guides/purchase-restore-sms.md).

## 4. Minimum support record

State the minimum data your backend should retain for every payment attempt, so a support case is diagnosable:

- Internal user ID
- Product / SKU
- `bizNo`
- AppInChina order identifier (if returned)
- Payment method
- Timestamps
- Latest verified state
- Entitlement action taken
- Error reason (if any)

:::caution Do not store
Never retain wallet credentials, full payment-card data, wallet authentication codes, or other secrets. Keep the support record to non-sensitive correlation fields.
:::

---


<!-- source: payments/concepts/capabilities-limitations.md -->


Clients sometimes expect a “payments system” to also manage products, users, discounts, promo codes, or subscriptions end-to-end.
AppInChina Payments is intentionally **not** that type of platform.

This page clarifies **what AppInChina Payments does**, **what it does not do**, and **where those responsibilities should live** in your architecture.

If your main question is “what sales models can we use (without auto-renew) and who should manage what?”, see: [Sales models in China (and recommended architecture)](./sales-models-and-architecture.md).

## What AppInChina Payments is

AppInChina Payments provides a **single SDK/API surface** to initiate payments with **WeChat Pay** and **Alipay**, and to **record and query transaction status**.

It is designed to be:

- A lightweight wrapper around the official payment providers (WeChat Pay / Alipay)
- A consistent way to create orders, track them, and reconcile outcomes
- A system that stores transaction records enriched with metadata you provide (for example `goodsTitle`, `customerIdentity`, `attachData`)

## Responsibility split (recommended architecture)

### Your system (you own this)

- **Product catalog**: SKUs, pricing, currency rules, availability, inventory, subscription plans, etc.
- **Discounts & promotions**: promo codes, coupons, pricing experiments, eligibility, stacked rules, etc.
- **User management**: registration/login, account recovery, identity linking, admin tools, RBAC, etc.
- **Order model & fulfillment**: internal order IDs, entitlements, content delivery, feature unlocks, subscription lifecycle logic.
- **Business reporting**: revenue analytics, tax/invoice logic, CRM and customer support workflows.
- **Customer Support UI (Required)**: You must implement a "Purchase History" or "Order History" screen in your app. Because AppInChina does not store user phone numbers for privacy reasons, users must be able to find their Order ID (`bizNo`) in your app to provide to customer support when requesting refunds. See [Purchase History & Refund Workflows](../guides/purchase-history-refunds.md) for details.

### AppInChina Payments (we own this)

- **Payment initiation** via SDK/API for WeChat Pay and Alipay
- **Transaction logging** (order creation + recorded payment outcomes)
- **Order querying** (single order and history)
- **Refund operations** via the AppInChina Dashboard (the SDK does not provide refund APIs)
- **Optional purchase restore support** via SMS authentication (for integrations that choose to use it)

## What AppInChina Payments supports

You can treat AppInChina Payments as the “payment rail” layer:

- **Create an order** with the final payable amount and your identifiers
- **Send the user through WeChat Pay or Alipay**
- **Verify the final status** server-side (treat payment as successful only when `paymentStatus == PAID`)
- **Query history** for a given user identity (`customerIdentity`)

Relevant docs:

- [Understanding the IAP SDK/API Flow](./iap-flow.md)
- [Payments API Reference](../api/reference.md)
- [Android SDK Integration](../sdk/android.md)
- [PC & H5 Integration](../web/pc-h5.md)

## What AppInChina Payments does not support (common misconceptions)

AppInChina Payments does **not** provide:

- **Product management**: creating products, managing SKUs, maintaining a price book, inventory, or catalog APIs
- **Discount engines**: promo codes, coupons, rule-based discounts, user segmentation for pricing, or “apply code at checkout”
- **User management**: account systems, user profiles, permissions, or admin management of users
- **Entitlement logic**: granting coins, unlocking features, provisioning services, or managing your subscription lifecycle rules
- **Client-side proof of payment**: return URLs, app callbacks, and “success screens” are not a secure confirmation (always verify server-side)
- **Refund webhooks / push notifications**: no refund webhook is sent unless a separately published interface explicitly states otherwise. Your backend must **query and reconcile** transaction status and apply the corresponding access change under your own policy. See [Refund limitations and reconciliation](../guides/purchase-history-refunds.md#refund-limitations-and-reconciliation).

If you need a managed login/identity system, see the Login docs:

- Login solutions overview: [/login/](/login/)
- Login → Payments integration (identity mapping): [Login → Payments integration (customer identity)](../guides/login-identity.md)

## How to handle users, products, and entitlements (recommended patterns)

Since AppInChina Payments does not manage any of these concepts internally, here is how to represent each one through the fields the API does accept.

### Users

AppInChina Payments does not maintain user accounts. To link transactions to your users:

- **`customerIdentity`** — pass your stable internal user ID (strongly recommended). This is the key used by `/detail.json` and `/history.json` to scope queries.
- **`customerName`** — pass a human-readable identifier (username, phone, etc.) for reference in the Dashboard.

If you need a managed login/identity system for China, see [Login solutions overview](/login/) and [Login → Payments integration (customer identity)](../guides/login-identity.md).

### Products

AppInChina Payments has no product catalog — every order is just an amount with metadata. To keep your products traceable:

- **`goodsTitle`** — set it to a human-readable label (e.g. `"Pro plan – 3 months"`). This is what appears during checkout.
- **`attachData`** — include your internal product/SKU ID so you can map the transaction back to your catalog at fulfillment time.
- **`amount`** — always the final price your backend resolved for that product (in cents).

Your backend owns the catalog; AppInChina Payments just records what you tell it.

### Entitlements

A successful payment (`paymentStatus == PAID`) does **not** automatically grant anything to the user. AppInChina Payments records the transaction — your backend is responsible for fulfillment:

- After verifying the order status via `/detail.json` or the SDK query, **your backend** decides what to unlock (features, content, subscription time, credits, etc.).
- Use `attachData` to carry the context your fulfillment logic needs (e.g. `productId`, `planId`, `tier`) so you can map a paid transaction to the right entitlement without ambiguity.
- If you support "restore purchases" flows, query `/history.json` by `customerIdentity` and re-derive entitlements from your own order records — AppInChina Payments provides the transaction data, not the entitlement state.

The key separation: **payment completion is not fulfillment**. Your system owns the link between "paid" and "granted".

### General principle

Your backend decides **who** is buying, **what** they're buying, and **what to grant** after payment succeeds. AppInChina Payments records the transaction with the identifiers you provide, but it does not interpret them as user accounts, products, or entitlements. See [Using `attachData` effectively](../api/reference.md#341-using-attachdata-effectively) for metadata best practices.

## Example: fixed-term subscription flow (where each piece lives)

This walks through a "3-month Pro plan" purchase — from login to renewal — to show exactly which steps belong to your system and which ones involve AppInChina Payments.

For more sales models (one-time purchases, credit top-ups, bundles, etc.) see [Sales models in China (and recommended architecture)](./sales-models-and-architecture.md).

### First purchase

1. **User logs in** through your login system (e.g. Authing, WeChat Login, or your own). Your backend returns a stable internal `userId`.
2. **App requests the product catalog** from your backend. Your backend returns the available plans (e.g. *Pro 1 month — ¥29*, *Pro 3 months — ¥69*, *Pro 1 year — ¥199*) with their internal IDs and prices.
3. **User selects "Pro 3 months"**. The app sends the selection to your backend.
4. **Your backend creates an internal order**:
   - Generates a unique order ID (this becomes `bizNo`).
   - Resolves the final price — applying any discount logic you own.
   - Records the order as `PENDING` in your database, linked to the `userId` and the plan.
5. **CreateOrder is called** (via your backend calling `POST /order.json`, or via the Android SDK from the client) with:
   - `amount`: `6900` (¥69 in cents)
   - `bizNo`: your order ID
   - `goodsTitle`: `"Pro plan – 3 months"`
   - `payChannel`: `WECHAT` or `ALIPAY`
   - `customerIdentity`: `userId`
   - `attachData`: `{"planId":"pro_3m","internalOrderId":"ORD_20260210_0042"}`
6. **User completes payment** inside WeChat Pay or Alipay.

### Verification and fulfillment

7. **Your backend verifies the payment** by calling `/detail.json` with `bizNo` + `customerIdentity`.
   - Only proceed if `paymentStatus == PAID`. Any other status means the payment is not confirmed.
8. **Your backend fulfills the entitlement**:
   - Reads `attachData` → knows this is `pro_3m`.
   - Sets `subscription.expiresAt = now + 90 days` for this user (or extends the existing expiry if already active).
   - Marks the internal order as `FULFILLED`.
9. **App refreshes entitlement state** from your backend and unlocks Pro features.

AppInChina Payments recorded the transaction. Your backend decided what that transaction *means* (3 months of Pro access) and granted it.

### Renewal (when the term expires)

10. **Your backend detects the subscription is expiring** (e.g. via a scheduled job, or when the app checks entitlement on launch).
11. **App prompts the user to renew**. If the user agrees, repeat from step 3 — your backend creates a new internal order, calls CreateOrder with a new `bizNo`, and on confirmed payment extends `expiresAt` by another term.

There is no auto-renew happening on the AppInChina side. Each renewal is a new, explicit purchase.

### Restore purchases (new device or reinstall)

12. User logs in on a new device. Your backend already has the entitlement record linked to their `userId`, so the app can restore access immediately.
13. If you need to cross-check against payment history, query `/history.json` with `customerIdentity` and filter by `paymentStatus == PAID`. Use the `attachData` on each record to map transactions back to plans — then re-derive the current entitlement state in your backend.

AppInChina Payments provides the transaction data; your backend owns the entitlement state.

---


<!-- source: payments/concepts/iap-flow.md -->


The IAP SDK/API provides a **lightweight, unified way** to integrate in-app payments without having to build and maintain two separate payment integrations (one for Alipay and one for WeChat Pay).

In practice, the AppInChina Payments SDK still **delegates to the official Alipay / WeChat Pay SDKs on the device**, so you will include those dependencies — but you interact with a single, consistent AppInChina SDK/API surface.

It’s important to understand that:

- **Alipay and WeChat Pay are the actual payment processors.**
- **The IAP system is a wrapper** that simplifies integration and ensures transactions are consistently logged with the right metadata (product and user identifiers).
- **Your system manages users and products.** The IAP system only records transactions.
- If you’re unsure whether a feature is in scope (products, discounts, promo codes, user management, etc.), read: [Capabilities and Limitations (Scope)](./capabilities-limitations.md).

## Key concepts

- **Your system (products & users)**: You define products and manage user accounts in your own backend.
- **IAP system (wrapper)**: Provides a single, consistent interface for payments. Internally, it launches the official Alipay or WeChat Pay SDK on the user’s device.
- **Payment providers (Alipay / WeChat Pay)**: They handle the actual transaction — payment authorization, funds transfer, and result confirmation.
- **IAP backend**: Stores the transaction record, enriched with fields you provide (`goodsTitle`, `customerIdentity`, `phoneNumber`).

If you’re unsure what to use for `customerIdentity`, read: [Login → Payments integration (customer identity)](../guides/login-identity.md).

## Typical flow

1. **User selects a product** in your app.
   - Product info comes from your backend (e.g., product ID, price).
2. **You call the IAP SDK** with:
   - `goodsTitle` (product label)
   - `customerIdentity` (your internal user ID)
3. **SDK delegates to payment provider:**
   - If user chooses WeChat → SDK invokes WeChat Pay SDK.
   - If user chooses Alipay → SDK invokes Alipay SDK.
4. **Alipay/WeChat Pay process the payment**
   - They show their native UI on the user’s device.
   - The user completes payment securely within the provider’s system.
5. **Result flows back**
   - Alipay/WeChat Pay return the outcome (success/failure).
   - IAP SDK wraps this result and delivers it to your app.
6. **Transaction is logged in the IAP backend**
   - Stored with product/user identifiers from the request.
   - Can later be grouped by `goodsTitle` or `customerIdentity`.
7. **Your system updates product/user state**
   - Example: unlock premium features, extend subscription, deliver digital goods.

## Why this design?

- **Lightweight**: You only integrate once with the IAP SDK, not twice with Alipay and WeChat Pay.
- **Consistent**: All transactions are stored with uniform metadata for reporting.
- **Flexible**: You remain in control of product catalogs and user management in your own system.
- **Secure**: Payments are always processed by the official Alipay and WeChat Pay SDKs.

---


<!-- source: payments/concepts/integration-options.md -->


We typically recommend one of two approaches, depending on how much control and customization you need.

## Option 1: Direct integration with Alipay and WeChat Pay

In this setup, your team integrates the **Alipay SDK** and **WeChat Pay SDK** directly into your app and backend.

This option is usually suitable if:

- You require **custom or non-standard payment workflows**
- You need **deep control** over payment logic or provider-specific features
- Your system has **special requirements** around order handling or payment states

Things to consider:

- Two separate SDK and API integrations
- Higher development and long-term maintenance effort
- Transaction logging and reconciliation must be handled in your own system

## Option 2: Integration with AppInChina IAP Payments System

Our IAP Payments System provides a **single SDK and API** that wraps both Alipay and WeChat Pay, allowing you to integrate payments once instead of managing each provider separately.

It is important to note that:

- **Alipay and WeChat Pay remain the actual payment processors**
- The IAP system acts as a **wrapper**, launching the official provider SDKs on the user’s device
- Your system continues to manage **users and products**; the IAP system only **records transactions**
- For a detailed scope breakdown (what the system does and does not include), see: [Capabilities and Limitations (Scope)](./capabilities-limitations.md)

### Typical payment flow

1. A user selects a product in your app.
2. Your app calls the IAP SDK with:
   - A product label (for example, `goodsTitle`)
   - Your internal user identifier (for example, `customerIdentity`). See: [Login → Payments integration (customer identity)](../guides/login-identity.md)
3. The SDK invokes either Alipay or WeChat Pay.
4. The user completes the payment in the provider’s native interface.
5. The payment result is returned to your app.
6. The transaction is recorded in the IAP backend with your provided identifiers.
7. Your backend updates the user’s access or entitlement.

This option is well suited for:

- Standard one-time purchases or subscriptions
- Faster integration timelines
- Unified transaction records across Alipay and WeChat Pay
- Reduced integration and maintenance complexity

---


<!-- source: payments/concepts/sales-models-and-architecture.md -->


This page answers a common question:

> “Can AppInChina provide payments **and** user/product management?”

**AppInChina Payments is a payments layer** (WeChat Pay + Alipay wrapper + transaction records). It does **not** replace your user system, product catalog, or entitlement/subscription management.

If you are unsure what is in scope, start with: [Capabilities and Limitations (Scope)](./capabilities-limitations.md).

## Common sales models (alternatives to auto-renew subscriptions)

In Mainland China, many teams avoid “silent” auto-renew and instead ship models that are easier to explain and support.

Typical approaches include:

- **One-time purchase (permanent unlock)**: user buys once, entitlement is permanent.
- **Fixed-term access (manual renewal)**: sell “1 month / 3 months / 1 year access” as *non-auto-renewing* products.
  - The user buys again when the term expires.
  - Your backend extends the entitlement window on confirmed payment.
- **Prepaid top-up / wallet**: user buys credits, then consumes credits for features/content.
  - Your backend tracks credit balance and consumption.
- **Bundles / feature packs**: user purchases a pack that unlocks a group of features.
- **Activation code / voucher**: user redeems a code (sold via a partner channel) that grants entitlement.
  - Your backend validates and redeems the code, then grants entitlement.

Auto-renewing subscriptions can work, but they require extra care: explicit consent, clear renewal rules, cancellation/refund policy, and support handling. AppInChina Payments can record transactions, but **subscription lifecycle policy and user support workflows remain on your side**.

## Responsibility split (who owns what)

| Area | Your system (client app + backend) | AppInChina Payments |
|---|---|---|
| User accounts | Login, account recovery, identity linking, admin tools | Not provided |
| Product catalog | SKUs, pricing, eligibility, promotions | Not provided |
| Orders & entitlements | Internal order model; grant/revoke/extend access; term logic | Records transaction status and history |
| Payment initiation | Trigger payment flow from your app/backend | SDK/API to start WeChat Pay / Alipay |
| Payment confirmation | Verify final status server-side before fulfillment | Query authoritative transaction status |
| Refunds | Your business policy + customer support. **Must provide a Purchase History UI so users can find their `bizNo` for refund requests.** | Refund operations via Dashboard (filtered by `bizNo`). |

For identity mapping, see: [Login → Payments integration (customer identity)](../guides/login-identity.md).

## Recommended implementation pattern

This pattern works for one-time purchases and fixed-term access.

1. **User logs in** (your login system, e.g. Authing / WeChat Login / custom) → your backend returns a stable internal `userId`.
2. **App loads product catalog** from your backend (often hosted in China for latency/compliance, depending on your architecture).
3. **Create internal order** in your backend:
   - Compute the final price (and any discount logic you own).
   - Create an internal order ID (`bizNo`) for idempotency and reconciliation.
4. **Create payment via AppInChina Payments** using the final amount and metadata:
   - Include `customerIdentity = userId`.
   - Optionally include `attachData` (your product ID, plan ID, etc.) for your own reconciliation.
5. **User completes payment** in WeChat Pay / Alipay.
6. **Verify payment server-side** by querying order status.
7. **Fulfill in your backend**:
   - One-time purchase: grant entitlement permanently.
   - Fixed-term access: extend `expiresAt` based on the product term (e.g. +30 days).
8. **App refreshes entitlements** from your backend and unlocks features.

## Checking access when the app opens

When a user opens the app again, the app should ask **your backend** whether the logged-in user currently has access. The app should not query AppInChina Payments directly to decide whether a subscription or fixed-term entitlement is still valid.

Your backend should check its own entitlement state first, such as the user's active plan, `expiresAt`, fulfillment status, and any refund reconciliation you have already recorded. If the backend needs to verify a specific transaction, it can query AppInChina Payments by `bizNo` and `customerIdentity`.

If an order has been refunded, AppInChina will report the order with `paymentStatus == REFUND`. Your backend should then update or revoke the related entitlement according to your refund policy, and return the current access state to the app.

## “Server in China” note (where to store what)

Many clients keep these in their China-region backend:

- User identity mapping (`userId` + external identities)
- Product catalog and pricing rules
- Entitlements (what the user has access to, and until when)
- Internal orders (`bizNo`, product/plan, fulfillment status)

AppInChina Payments stores **transaction records** and provides **query endpoints** for status/history, keyed by identifiers you provide (for example `bizNo` and `customerIdentity`).

---


<!-- source: payments/guides/login-identity.md -->


This guide explains how to integrate your login system with AppInChina Payments so payments, order queries, purchase restoration, and subscriptions can be reliably associated with the correct user—**regardless of which login solution you use** (Authing, WeChat Login, custom backend, or a cloud IdP).

## Key concept: `customerIdentity`

Most AppInChina Payments flows accept a `customerIdentity` field. Treat it as **your stable internal user identifier**.

:::caution Required for account-based apps
For account-based mobile apps, `customerIdentity` is **required** unless your integration uses a documented alternate identity design. It must be a **stable, non-secret client user identifier**. It is **not** a wallet account, an email address, a payment-card number, or an arbitrary per-attempt value — an unstable or per-attempt identity breaks order queries, history, and purchase restoration.
:::

**Recommendation**: use an internal `userId` that is:

- Stable (never changes)
- Unique per user
- Not user-editable
- Not derived from a device identifier

Avoid using raw PII (plain phone/email) as the identity when possible.

## Responsibility split (who owns what)

The easiest way to avoid integration gaps is to be explicit about ownership:

| Area | Your system (client app + backend) | AppInChina Payments | WeChat Pay / Alipay |
|---|---|---|---|
| User accounts | Create accounts, login UX, account recovery, fraud/abuse controls | Not provided | Not provided |
| Identity mapping | Map provider identities (Authing/WeChat/OIDC) → internal `userId` | Records `customerIdentity` you send | Issues provider identities (e.g., WeChat `openid`) within their ecosystem |
| Sessions/tokens | Issue and validate your sessions; keep user signed-in | Not provided | Not provided |
| Product catalog & pricing | Define SKUs, pricing, eligibility, discounts | Not provided | Not provided |
| Order / entitlement model | Create internal orders, grant entitlements after payment is verified | Stores transaction record + status; returns query results | Processes the actual payment |
| Payment UI | Decide when to show paywall/checkout; start the payment flow | Starts payment via SDK/API | Displays native payment UI and authorizes payment |
| Payment confirmation | Verify final status server-side before fulfillment | Provides authoritative transaction status via query endpoints | Is the underlying payment rail |
| Refunds | Your business policy + customer support; decide eligibility | Refund operations via Dashboard (per our product scope) | May be involved as the underlying processor |

## Where `customerIdentity` is used

Depending on your integration path (SDK vs API), `customerIdentity` is typically used for:

- **Order creation metadata** (linking a payment to the user)
- **Order detail queries** (used to prevent unauthorized order lookup)
- **Order history queries**
- **Purchase restoration and subscription management**

## What `customerIdentity` should be (and should not be)

### Recommended

- Your **internal stable `userId`** (database ID or UUID), returned by your backend after login

### Not recommended

- Device identifiers
- Mutable identifiers (email/phone) unless you have a migration strategy
- External provider identifiers as your *primary* identity (WeChat `openid/unionid`, OIDC `sub`, etc.)
  - You can store them, but **map them to your internal `userId`**

## What you must implement (login-solution agnostic)

No matter what login solution you choose, your product should implement:

- **A stable internal user ID** (`userId`)
- **A mapping** from the login provider identity to your internal `userId`
- **Session/token handling** so the user remains signed in and the correct identity is available during checkout
- **Account recovery** so users can regain access and restore purchases on a new device when applicable
- **Account linking strategy** if you support multiple login methods (e.g., WeChat + phone/email)
- **A Purchase History UI (Required)**: You must provide a screen where users can view their past transactions and see their Order ID (`bizNo`). This is strictly required so users can provide their `bizNo` when requesting refunds. You can build this easily using our `/history.json` API endpoint. See [Purchase History & Refund Workflows](./purchase-history-refunds.md) for details.

## Implementation pattern (recommended)

1. **User authenticates** (Authing / WeChat / custom / OIDC).
2. Your backend resolves/creates an internal user record and returns your internal `userId`.
3. Your app stores a session token; whenever the user enters a payment flow, it fetches/uses the internal `userId`.
4. Your app/backend includes `customerIdentity = userId` in AppInChina Payments calls (create order / query / history).

## “Who stores what?” (recommended minimal data model)

At minimum, your backend should store:

- **User**
  - `userId` (internal stable ID)
  - attributes (email/phone/display name as needed)
- **External identities** (one-to-many)
  - `provider` (authing / wechat / oidc / etc.)
  - `providerUserId` (e.g., Authing user ID, WeChat `unionid`, OIDC `sub`)
  - `userId` (your internal stable ID)
- **Orders / entitlements**
  - `bizNo` (your internal order ID)
  - `userId` (who is buying)
  - product/plan identifiers
  - fulfillment status

AppInChina Payments will store the transaction record keyed by the identifiers you provide (including `bizNo` and `customerIdentity`) and allow you to query status/history.

## Provider-specific notes (how to map to internal `userId`)

### Authing

- Store Authing’s user identifier in your database (as an external identity record).
- Map it to your internal `userId`.
- Use internal `userId` as `customerIdentity`.

### WeChat Login

- WeChat identities (e.g., `openid` / `unionid`) are **provider identifiers**, not your internal ID.
- Store them as external identities and map to one internal `userId`.
- If you also support other login methods, implement **account linking** to avoid duplicate user accounts.

### Custom backend (email/phone/password/OTP)

- Generate a stable internal `userId` at account creation time.
- If you use phone/email for login, treat them as attributes, not the primary key.

### Cloud identity / OIDC (Cognito, Firebase, Auth0, Microsoft, etc.)

- Treat the IdP “subject” / provider user ID as an external identity.
- Map it to one internal `userId`.
- Use internal `userId` as `customerIdentity`.

## Common pitfalls

- **Using device ID as identity**: breaks restoration on device change.
- **Using mutable identifiers** (email/phone) as the identity: changes can orphan history unless you migrate carefully.
- **Using a provider identifier directly**: complicates account linking (and can break if you later support multiple login methods).
- **Not persisting identity across sessions**: causes mismatched `customerIdentity` during queries/restoration.

## Support / debugging checklist (what we’ll ask for)

When troubleshooting identity-related issues, have these ready:

- The `bizNo` you created
- The `customerIdentity` you sent for order creation
- The `customerIdentity` you are using for order query (must match)
- The `payChannel` and environment (test vs production)
- Timestamp + device/app version logs around CreateOrder / startPayment / query

## Related docs

- Login solutions overview: [/login/](/login/)
- High-level flow: [Understanding the IAP SDK/API Flow](../concepts/iap-flow.md)
- Payments API reference: [Payments API Reference](../api/reference.md)
- Android SDK guide: [Payments SDK Integration Guide for Android](../sdk/android.md)
- Purchase restore & SMS authentication: [Purchase Restore and SMS Authentication](./purchase-restore-sms.md)

---


<!-- source: payments/guides/purchase-history-refunds.md -->


To process refunds and handle customer support inquiries effectively, **you must implement a Purchase History screen within your app**. 

This guide explains why this is a strict requirement, the minimum UI requirements you must meet, and how to build it using the AppInChina Payments API.

## Why is a Purchase History UI required?

AppInChina Payments is designed with strict data privacy principles. **We do not store Personally Identifiable Information (PII) such as user phone numbers or email addresses in our payment records.** We only store the opaque `customerIdentity` (your internal user ID) and the `bizNo` (your internal order ID).

When a user contacts your support team or our support team to request a refund, they will not know their hidden `customerIdentity`. 

To locate the exact transaction in the AppInChina Dashboard, **support agents must search using the `bizNo` (Order ID)**. Therefore, the user must have a way to view their past transactions and copy the `bizNo` to provide to the support agent.

## Minimum UI Requirements

To ensure a smooth customer support experience, your app's Purchase History screen must include the following for every successful transaction:

1. **The Order ID (`bizNo`)**: This is the most critical piece of information. It must be clearly visible.
2. **A "Copy" Button**: Users are on mobile devices; do not force them to manually type out a long alphanumeric string. Provide a simple tap-to-copy mechanism next to the Order ID.
3. **Product Name (`goodsTitle`)**: What the user purchased (e.g., "Pro Plan - 3 Months").
4. **Purchase Date**: The date and time the transaction was completed.
5. **Amount Paid**: The final price the user paid.

### Example UI Layout

```text
-----------------------------------
Order History
-----------------------------------
Pro Plan - 3 Months
Date: 2026-03-20 14:30
Amount: ¥69.00
Order ID: ORD_20260320_A1B2C3 [COPY]
-----------------------------------
Coins Top-Up (500)
Date: 2026-03-15 09:15
Amount: ¥18.00
Order ID: ORD_20260315_X9Y8Z7 [COPY]
-----------------------------------
```

## How to build it

You can build this UI using one of two approaches:

### Approach 1: Query your own database (Recommended)

Because your backend is responsible for creating the `bizNo` and fulfilling the order, your database is the source of truth for the user's entitlements. 

*   **How:** Your app queries your backend for the user's order history.
*   **Why:** This is the fastest and most robust method. It allows you to show orders that might be "Pending" or "Failed" in addition to "Paid" orders, providing better context to the user.

### Approach 2: Use the AppInChina `/history.json` API

If you prefer not to build a custom history endpoint on your backend, you can use the AppInChina Payments API to retrieve the user's transaction history.

*   **How:** Your backend calls the `GET /history.json` endpoint, passing the user's `customerIdentity`. Your backend then formats this data and sends it to your app to display.
*   **Note:** You should filter the results to only show transactions where `paymentStatus == PAID`.

:::note Canonical method
`/history.json` uses **`GET`**. The [Payments API Reference](../api/reference.md#5-query-order-list) is the single source of truth for the method, parameters, and response schema — always derive your request from it rather than retyping from memory.
:::

## The Customer Support Workflow

Once you have implemented the Purchase History screen, the refund workflow looks like this:

1. **User experiences an issue** and contacts customer support (either your team or AppInChina's team).
2. **Support agent requests the Order ID**, instructing the user to go to *Settings > Purchase History* in the app.
3. **User copies the `bizNo`** and sends it to the support agent.
4. **Support agent searches the AppInChina Dashboard** using the exact `bizNo`.
5. **Support agent processes the refund** directly from the dashboard.

By implementing this screen, you eliminate the need to handle sensitive PII during support requests and ensure that refunds are processed for the exact transaction the user is disputing.

## `bizNo`: the correlation key you own

`bizNo` is the **client-controlled business reference** used to correlate the payment attempt, backend verification, entitlement action, purchase-history entry, and support case. Treat it as an operational requirement, not an optional field:

- **Unique** per order attempt.
- **Immutable** once created — never reassign it.
- **Retry-safe** — reusing the same `bizNo` lets you re-query the same order without creating a duplicate.
- **Stored** in your backend against the internal user and product, and surfaced in your Purchase History UI.

See [Login → Payments integration](./login-identity.md) for how `bizNo` and `customerIdentity` fit together.

## Refund limitations and reconciliation

:::caution No refund webhook
No refund webhook is currently sent unless a separately published interface explicitly states otherwise. **Do not assume a completed refund will automatically update your entitlement system.** Your backend must query/reconcile transaction status and apply the corresponding access change under your refund and subscription policy.
:::

| Section | What you must handle |
| --- | --- |
| **Refund operations** | Refunds are initiated/approved through the AppInChina Dashboard (the SDK provides no refund API). Decide which party performs the operational action. |
| **Status reconciliation** | Your backend queries for updated status (for example, `paymentStatus == REFUND` via `/detail.json` or `/history.json`). This is a client **retry/poll**, not an automatic event notification. |
| **Entitlement policy** | Define what access change happens on refund: full revocation, end-of-term access, partial adjustment, or manual review — per your product rules and applicable policy. |
| **History and support** | Ensure a refunded order appears correctly in your history UI and that support can map the user/account to the transaction via `bizNo`. |
| **Testing** | Only run a real refund test if your team can safely support one in a non-production or controlled production procedure. Do not create unnecessary refunds merely to prove the feature. |

## How your app knows a subscription is still valid

When the user opens the app again, the app should check the user's current entitlement state with **your backend**, not directly with AppInChina Payments.

Your backend should remain the source of truth for whether a user still has access. A typical app launch or resume flow is:

1. User opens the app or returns to the foreground.
2. App calls your backend with the logged-in user session.
3. Your backend checks its own entitlement record, such as subscription status, `expiresAt`, and any refunded orders already reconciled.
4. If your backend needs to confirm a payment or refund, it queries AppInChina using the relevant `bizNo` and `customerIdentity`.
5. If the AppInChina order status is `REFUND`, your backend updates or revokes the related entitlement according to your refund policy.
6. Backend returns the current entitlement state to the app, such as `active: true` or `active: false`.
7. App unlocks or locks paid features based on your backend response.

The app does **not** need to refresh all AppInChina payment history every time it comes online. Instead, it should refresh the user's entitlement state from your backend. Your backend decides how often to reconcile with AppInChina based on your product rules, support workflow, and risk tolerance.

---


<!-- source: payments/guides/purchase-restore-sms.md -->


To support cross-device access to paid content, AppInChina provides a secure authentication system based on SMS verification. This system allows customers to log in across multiple devices while preserving their purchase history. It also enforces a maximum number of simultaneously active sessions per customer.

Using SMS login and token-based authentication, your app can reliably identify returning users and restore previous purchases securely.

If you’re deciding what to use for `customerIdentity` (regardless of your login provider), start with: [Login → Payments integration (customer identity)](./login-identity.md).

## 1. Authentication flow

### 1.1 On app launch

1. Check for a stored token on the device.
2. If a token exists:
   - Call `GET /checkAuth.json` to confirm its validity.
   - If valid, skip login and proceed to the main app.
   - If invalid, proceed to the login screen.
3. If no token exists, display the login screen.

### 1.2 Login flow

1. Display a login screen prompting the user to enter their mobile number.
2. Use `GET /sms.json` to request a verification code.
3. Show a verification code input screen.
4. Authenticate the user using `POST /auth.json`.
5. Store the returned token securely.
6. Proceed to the main application.

### 1.3 UI guidelines

- **Screen 1: Phone number input**
  - Input: phone number field
  - Button: “Send Code”
  - Validation: show message if daily SMS limit is reached
- **Screen 2: Code verification**
  - Input: 6-digit verification code
  - Button: “Log In”
  - Optional: countdown timer and resend option

## 2. API endpoints

### 2.1 Request SMS code

:::note
Each user is limited to a maximum of 5 SMS code requests per day. Additional requests will be denied until the next day.
:::

#### Endpoint

```
GET /sms.json
```

#### Purpose

Send an SMS verification code to the customer.

#### Required headers

| Header | Description |
| --- | --- |
| `APP_ID` | Your AppInChina App ID |
| `APP_SECRET` | Your AppInChina App Secret |

#### Request parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `mobileNo` | string | ✅ | Customer’s phone number |

#### Success response

```json
{
  "msg": "success",
  "code": 0,
  "data": "ok"
}
```

### 2.2 Authenticate with SMS code

#### Endpoint

```
POST /auth.json
```

#### Purpose

Verify the customer’s SMS code and receive a session token.

#### Request parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `mobileNo` | string | ✅ | Customer’s phone number |
| `authCode` | string | ✅ | Verification code from SMS |

#### Success response

```json
{
  "msg": "success",
  "code": 0,
  "data": {
    "customerId": "...",
    "token": "..."
  }
}
```

:::tip
Store the returned token securely for use in future sessions.
:::

### 2.3 Check token validity

#### Endpoint

```
GET /checkAuth.json
```

#### Purpose

Verify whether a previously stored token is still valid.

#### Request parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | ✅ | Token stored on the device |

#### Success response

```json
{
  "msg": "success",
  "code": 0,
  "data": true
}
```

- `true`: token is valid
- `false`: token is no longer valid (e.g., expired or invalidated)

## 3. Token and session management

- **Token storage**: store tokens securely using encrypted local storage (e.g., EncryptedSharedPreferences on Android).
- **Validation timing**: always call `/checkAuth.json` when the app launches or resumes from background.
- **Device limit**: when a user exceeds the allowed number of active sessions, older sessions are invalidated.

## 4. Best practices

- Never expose your `APP_SECRET` in frontend code.
- Validate stored tokens before allowing access to protected functionality.
- Prompt users clearly if their session has expired or been invalidated.
- Respect the 5-per-day SMS request limit with appropriate UI feedback for users.
- Implement retry and resend timers on the verification screen.

---


<!-- source: payments/overview.md -->


This is the entry point for integrating AppInChina's payment services. It sets the **scope**, the **client/AppInChina ownership boundary**, the **prerequisites**, and the **required reading path**.

:::danger SDK setup alone is not a completed payment integration
A payment marked `PAID` in AppInChina's system is not, by itself, proof that your app has granted access. Payment acceptance is complete only when the full path — payment, server-side verification, entitlement, and refreshed access — works in your release candidate. See the [Release Checklist / Definition of Done](./release-checklist.md).
:::

## Scope and ownership

AppInChina's SDK/API **initiates and processes** supported payment transactions through the configured payment methods (WeChat Pay and Alipay). **Your app and backend** must implement the user-account, payment-result, order-verification, entitlement, purchase-history, and refund-reconciliation logic appropriate to your product.

| Capability | Client responsibility | AppInChina responsibility |
| --- | --- | --- |
| **Product and user account** | Define products, authenticate the user, supply a stable client identity, and determine access rules. | Provide configured IAP credentials / product support as agreed. |
| **Payment initiation** | Call the SDK/API with validated parameters and handle platform requirements. | Process the payment request and expose order status. |
| **Post-payment return** | Implement and package app-side callback/result handling; update UI only from authoritative entitlement state. | Return the payment result through the SDK/API contract. |
| **Order verification** | Query/verify through the client backend; implement idempotent state transitions. | Expose the documented order-status interface. |
| **Entitlement and restore** | Grant, revoke, and restore access based on your own rules and verified order data. | No ownership of the client's account or content-access system. |
| **Refunds** | Reconcile status and revoke/adjust access according to product policy. | Process supported refunds and expose updated transaction state; no refund webhook unless separately documented. |

For the detailed boundary and worked patterns, see [Capabilities and Limitations](./concepts/capabilities-limitations.md).

## Required reading path

Before beginning implementation, assign an **Android owner**, a **backend owner**, a **product/entitlement owner**, and **one designated tester for each wallet** you plan to offer. Then complete the following path **in order**:

1. **Start Here and prerequisites** (this page) → [Prerequisites & Setup](./sdk/prerequisites.md)
2. **Platform SDK guide** → [Android SDK Integration](./sdk/android.md) (or [PC & H5](./web/pc-h5.md) for web)
3. **Server verification and entitlements** → [Server Verification and Entitlements](./backend/verification-entitlements.md)
4. **Purchase history and refunds** → [Purchase History & Refund Workflows](./guides/purchase-history-refunds.md)
5. **Client payment preflight and acceptance testing** → [Client Payment Preflight and Acceptance Testing](./testing/preflight-acceptance.md)
6. **The release checklist** → [Release Checklist / Definition of Done](./release-checklist.md)

### Owners and required outcomes before handoff

| Role | Required page(s) | Required outcome before handoff |
| --- | --- | --- |
| **Technical lead** | Start Here; Release Checklist | Confirms named owners, supported payment methods, product model, and release plan. |
| **Android developer** | [Android SDK guide](./sdk/android.md); [Troubleshooting](./troubleshooting/support-evidence.md) | Build can start a payment, return safely to the app, and emit usable logs. |
| **Backend developer** | [Server Verification & Entitlements](./backend/verification-entitlements.md); [Purchase History & Refunds](./guides/purchase-history-refunds.md); [API Reference](./api/reference.md) | Backend verifies payment state, updates entitlements idempotently, and supports history/refund reconciliation. |
| **Product / membership owner** | [Server Verification & Entitlements](./backend/verification-entitlements.md); [Release Checklist](./release-checklist.md) | Entitlement rules, restore behavior, and post-payment UX are defined. |
| **Designated tester** | [Client Payment Preflight & Acceptance Testing](./testing/preflight-acceptance.md) | Wallet account and eligible card are ready for the first in-app low-value purchase. |

## Reading paths by role

The required path above is sequential. If you want a role-focused starting point, use these:

### For Product Managers

If you are defining the feature set, sales model, or integration strategy:

1.  **Choose an integration strategy**: Read [Integration Options](./concepts/integration-options.md) to decide between the unified SDK (recommended) or direct integration.
2.  **Understand the scope**: Read [Capabilities & Limitations](./concepts/capabilities-limitations.md) to understand what the system handles (payments) vs what your backend must handle (users, products, entitlements).
3.  **Define your sales model**: Read [Sales Models & Architecture](./concepts/sales-models-and-architecture.md) to understand how to implement subscriptions, one-time purchases, and cross-device entitlements in China.

### For Developers

If you are implementing the solution:

1.  **Start with the flow**: Read [Understanding the IAP Flow](./concepts/iap-flow.md) to see how the SDK wraps Alipay and WeChat Pay.
2.  **Check prerequisites**: Read [Prerequisites & Setup](./sdk/prerequisites.md) to get your credentials (`APP_ID`, `APP_SECRET`) and prepare your environment.
3.  **Handle user identity**: Read [Login & Identity](./guides/login-identity.md) to understand how to link payments to your user accounts using `customerIdentity`.
4.  **Implement the client**:
    *   **Android**: Follow the [Android SDK Guide](./sdk/android.md).
    *   **Web/H5**: Follow the [PC & H5 Guide](./web/pc-h5.md).
5.  **Verify on the server**: Use [Server Verification and Entitlements](./backend/verification-entitlements.md) and the [API Reference](./api/reference.md) to verify orders and grant access.
6.  **Preflight and ship**: Run the [Client Payment Preflight](./testing/preflight-acceptance.md) and complete the [Release Checklist](./release-checklist.md).

---


<!-- source: payments/reference/compatibility-and-changelog.md -->


This page names the SDK artifact each guide applies to, the dependency versions it was validated against, and what has changed. If your project uses a different artifact, review this page before copying behaviour from the integration guides.

## Compatibility matrix

The values below describe the **current** Android Payments SDK artifact (`payments_sdk_v20220912`).

| Item | Value |
| --- | --- |
| **AppInChina Android Payments SDK artifact** | `payments_sdk_v20220912` (internal build `acp-v20220222-prod-production`) |
| **FastJSON 1.x** (`com.alibaba:fastjson`) | Pin **`1.2.83`**. The SDK uses only stable core FastJSON 1.x APIs (no autotype), so it is compatible with **any 1.2.x**; `1.2.83` is chosen as the most-hardened 1.x release. Do **not** use old versions such as `1.1.70.android`. |
| **Alipay SDK** (`com.alipay.sdk:alipaysdk-android`) | `15.8.33` |
| **WeChat SDK** (`com.tencent.mm.opensdk:wechat-sdk-android-with-mta`) | `6.8.0` |
| **Minimum / target Android API** | `minSdkVersion 14` / `targetSdkVersion 30` (from the SDK manifest) |
| **Known device/OS exclusions or caveats** | None published. All FastJSON 1.x is end-of-life; older 1.x builds carry known CVEs. A FastJSON2 + Xiaomi/HyperOS report is under internal review and is **not** a published SDK limitation — see [FastJSON dependency issues](../troubleshooting/support-evidence.md#fastjson-dependency-issues). |

:::caution
FastJSON **1.x** (`com.alibaba:fastjson`) and **FastJSON2** (`com.alibaba.fastjson2:fastjson2`) are different dependency families. This SDK uses FastJSON 1.x. See [Prerequisites §3.2](../sdk/prerequisites.md#32-add-the-required-fastjson-1x-dependency).
:::

## Behaviour confirmed for this artifact

These points are specific to `payments_sdk_v20220912` and should be re-validated if the artifact changes:

- **`startPayment()` does not report a final result.** It creates the order (`POST /order.json`) and launches the wallet; it does not deliver a paid/failed/cancelled result to the host app. Grant access only after backend verification — see [Server Verification and Entitlements](../backend/verification-entitlements.md).
- **Alipay** opens the supplied `nextActivity` unconditionally after the wallet returns; the launch is not a payment confirmation. See [Android §7.6](../sdk/android.md#76-alipay-return-behaviour).
- **WeChat** returns to `WXPayEntryActivity`, not `nextActivity`, and requires a successful `initPayTools()` before the first payment. See [Android §7.7](../sdk/android.md#77-wechat-payment-lifecycle).
- **`queryHistoryOrder(...)`** calls `GET /history.json`. See the [API Reference](../api/reference.md#5-query-order-list).
- **Error `99999`** is a generic client-side exception path, not a specific payment decline. See [Troubleshooting](../troubleshooting/support-evidence.md#understanding-sdk-error-99999).

## Changelog

| Date | Change | Action required |
| --- | --- | --- |
| 2026-08-04 | FastJSON version confirmed: the SDK uses only stable core 1.x APIs (no autotype) → compatible with any 1.2.x; pin `1.2.83`. Android `minSdk 14` / `targetSdk 30`. | Pin `1.2.83`; remove any old FastJSON 1.x (e.g. `1.1.70.android`) from the release graph. |
| 2026-08-04 | Documented artifact-specific behaviour: authoritative payment-completion rule; Alipay `nextActivity` vs WeChat `WXPayEntryActivity` return routes; `initPayTools()` WeChat prerequisite; `99999` as a generic client-side exception. | Re-test post-payment handling against your backend; do not treat wallet return/callback as success. |
| 2026-08-04 | FastJSON guidance corrected: the dependency is required by the AppInChina SDK (legacy FastJSON 1.x), pinned, and distinct from FastJSON2. | Pin the FastJSON 1.x version; keep any FastJSON2 explicit and tested. |
| 2026-08-03 | `/history.json` method corrected to **`GET`** across the API reference, history guide, and examples. | Update any client code that used `POST /history.json`. |

:::note Publication gate
When a new SDK artifact is released, Engineering revalidates the Quick Start, payment-lifecycle diagrams, dependency block, endpoint examples, and troubleshooting/error mapping before the docs mark that version as current.
:::

---


<!-- source: payments/reference/errors.md -->


:::note Scope
This page covers **all server error codes** surfaced by the Payments & SMS API and re-emitted by the Android SDK.

Use it to decide how your app should react (retry, show a toast, log & fail, etc.).
:::

## Related docs

- Android SDK guide: [Payments SDK Integration Guide for Android](../sdk/android.md)
- Payments API reference: [Payments API Reference](../api/reference.md)
- Purchase restore & SMS: [Purchase Restore and SMS Authentication](../guides/purchase-restore-sms.md)

## Error format

All errors are returned as JSON objects in the following format:

```json
{
  "code": "error_code",
  "msg": "error_message"
}
```

## Category cheatsheet

| Category | Code ranges |
| --- | --- |
| **Success** | `0` |
| **Auth / Session** | `1`, `1001`, `10020–10023` |
| **Validation & Integration** | `10001–10019` |
| **User Account** | `20001–20003` |
| **WeChat** | `30001–30003` |
| **Alipay** | `40001` |
| **Payment** | `50001–50022` |
| **Device / Report** | `60001` |
| **Service Config** | `70002–70007` |
| **System** | `99998–99999` |


## Downloads

- [Error Codes by Category (CSV)](/downloads/error-codes-by-category.csv)

:::note
If you encounter undocumented errors or behavior, please contact support with the `traceId` of your request.
:::

---


<!-- source: payments/release-checklist.md -->


This is the final page of the Start Here path and of the acceptance-testing guide. A payment integration is **done** only when every control below passes against the **actual release candidate** — not a debug build, and not the SDK calls compiling in isolation.

SDK setup alone is not a completed payment integration.

## Related docs

- Start of the path: [Start Here: integrate AppInChina IAP](./overview.md)
- Preflight and acceptance: [Client Payment Preflight and Acceptance Testing](./testing/preflight-acceptance.md)
- Backend verification: [Server Verification and Entitlements](./backend/verification-entitlements.md)
- Android components: [Payments SDK Integration Guide for Android](./sdk/android.md)

## Definition of Done

| # | Control | Required proof / acceptance condition | Owner |
| --- | --- | --- | --- |
| 1 | **Products and identity** | Each test product is configured; the app requires a valid authenticated account; `customerIdentity` and `bizNo` handling are implemented as documented. | Client technical lead |
| 2 | **SDK and platform setup** | Dependencies (including the **pinned FastJSON 1.x** version, no dynamic selector), callback receiver(s), client result Activity (where applicable), manifest declarations, and release-artifact configuration are present in the **actual release candidate**. `initPayTools()` is confirmed to succeed before WeChat Pay is offered. | Client mobile developer |
| 3 | **Payment initiation** | The release candidate can initiate each offered payment method and creates a diagnosable order/attempt. | Client mobile developer |
| 4 | **Post-payment return** | The app returns without crash and executes the intended result/callback flow, then requests fresh entitlement state. Tested for **paid, cancelled, failed, and app-resume/relaunch** outcomes, with entitlement state read from the **client backend** — never inferred from the Activity launch or callback arrival. For WeChat, the full **`WXPayEntryActivity` → backend verification → entitlement** route is validated with a real payment. | Client mobile developer |
| 5 | **Backend verification** | The client backend verifies transaction state through the canonical documented interface and handles repeat/retry safely. | Client backend developer |
| 6 | **Entitlement** | A verified paid order grants exactly the intended access; an interrupted/repeated attempt does not create duplicate access. | Client backend / product owner |
| 7 | **Restore behavior** | Access is refreshed correctly after relaunch, login, account switch, and reinstall/restore if relevant to the app. | Client QA |
| 8 | **History and refunds** | Purchase history is implemented if in scope; `bizNo`/support correlation works; refund reconciliation behavior follows the documented limitation and product policy. | Client backend / product owner |
| 9 | **Wallet preflight** | One named tester per wallet completes the low-value release-candidate in-app preflight, or provides a complete, triage-ready failure package. | Client tester |
| 10 | **AppInChina acceptance** | Client submits the exact release candidate, test credentials/context, and preflight result. Final acceptance is completed per the agreed test matrix. | Client + AppInChina Ops |

:::tip Sign-off
Each control should be signed off in your live documentation/issue system before the release candidate is submitted for AppInChina acceptance. Keep the sign-off record with the build's audit trail.
:::

---


<!-- source: payments/sdk/android.md -->


:::info 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](../reference/compatibility-and-changelog.md) 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.

:::danger 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](../backend/verification-entitlements.md) for the authoritative flow.
:::

```text
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](./prerequisites.md)
- Login → Payments integration (identity mapping): [Login → Payments integration (customer identity)](../guides/login-identity.md)
- Payment verification and flows: [Understanding the IAP SDK/API Flow](../concepts/iap-flow.md)
- Troubleshooting errors: [Error Reference](../reference/errors.md)

:::info Download
Download the latest Android SDK package: [`payments_sdk_latest.zip`](/downloads/payments_sdk_latest.zip)
:::


## 2. Initialize the SDK

In your `Application` class:

1. Import the SDK classes:

```java
import com.mandou.acp.sdk.AcpClient;
import com.mandou.acp.sdk.AcpClientConfig;
```

2. Initialize the SDK in `onCreate()`:

```java
@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](./prerequisites.md) guide. These are distinct from your **WeChat App ID** (the WeChat Open Platform AppID used to initialize the WeChat SDK).


## 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

```java
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

```java
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
        );
    });
}
```

:::caution 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](#7-the-app-side-payment-result-activity-client-owned).

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


If you need help mapping your login system to `customerIdentity`, see: [Login → Payments integration (customer identity)](../guides/login-identity.md).

## 6. Understanding the `startPayment()` method

### Method overview

```java
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 |


## 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](../api/reference.md) 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

```bash
GET https://api.appinchinaservices.com/detail.json?bizNo=ORDER_123456789&customerIdentity=user_001
```

### 8.4 Successful response

```json
{
  "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`.
:::

---


<!-- source: payments/sdk/prerequisites.md -->


This guide outlines the environment setup and configuration steps required before integrating the AppInChina Payments SDK into your Android application.

:::caution
These steps are mandatory. The AppInChina SDK depends on the correct installation and configuration of the official WeChat Pay and Alipay SDKs.
:::

## Related docs

- Android SDK integration: [Payments SDK Integration Guide for Android](./android.md)
- Login → Payments integration (identity mapping): [Login → Payments integration (customer identity)](../guides/login-identity.md)
- Payments API reference (server-side): [Payments API Reference](../api/reference.md)
- Troubleshooting errors: [Error Reference](../reference/errors.md)

## 1. Request your credentials

Getting your credentials is a **single step**. Contact our operations team once; we issue everything you need to start — **both App IDs and the secret** — together. You do **not** need to register anything with WeChat yourself.

Provide the following details:

- **App name**
- **App package name** (e.g., `com.example.myapp`)
- **App signature** (SHA1 and MD5 fingerprints of the release signing certificate)

Once reviewed, we register your app as a **WeChat Open Platform Mobile Application** in our WeChat developer account, bind the WeChat Pay merchant account, and issue all three values:

```
APP_ID:        [AppInChina App ID — provided by AppInChina]
APP_SECRET:    [AppInChina App Secret — provided by AppInChina]
WECHAT_APP_ID: [WeChat App ID — provided by AppInChina]
```

What each value is:

| Credential | Used for |
| --- | --- |
| **AppInChina App ID** (`APP_ID`) | Authenticating SDK/API calls to the AppInChina IAP system. |
| **AppInChina App Secret** (`APP_SECRET`) | The secret paired with `APP_ID`. Keep server-side only. |
| **WeChat App ID** (`wxAppId`) | Initializing the WeChat SDK on-device ([Section 2](#2-install-the-wechat-pay-android-sdk)). This is your app's WeChat identity — the same App ID used for [WeChat Login](/login/solutions/wechat-login) and Share — **not** a payment-specific ID. |

:::caution Do not confuse the two App IDs
`APP_ID` (AppInChina) and `wxAppId` (WeChat) are separate identifiers from different platforms — even though AppInChina provides both to you. Using one where the other is expected is a common cause of initialization failures.
:::

## 2. Install the WeChat Pay Android SDK

### 2.1 Add the WeChat SDK to your `build.gradle`

```
dependencies {
    compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:6.8.0'
}
```

### 2.2 Initialize `IWXAPI` in your app

```java
private IWXAPI api;

private void initWechatPay(String wxAppId) {
    api = WXAPIFactory.createWXAPI(getApplicationContext(), wxAppId);
}
```

:::note
`wxAppId` is the **WeChat App ID** from [Section 1](#1-request-your-credentials) — not your **AppInChina App ID** (`APP_ID`).
:::

### 2.3 WeChat integration troubleshooting

- **Error: WeChat app not responding to payment request**
  - Verify WeChat app is installed and up-to-date on the test device
  - Confirm the `wxapi` package name exactly matches your app's package name
  - Ensure `WXPayEntryActivity` is properly registered in `AndroidManifest.xml`
- **Error: Payment initialization fails**
  - Double-check your WeChat App ID format
  - Verify your app's signing certificate matches the one registered with AppInChina
  - Ensure the device has a stable network connection

## 3. Install the Alipay Android SDK

### 3.1 Add the Alipay SDK via Maven Central

Add the following dependency to your app-level `build.gradle` file:

```
dependencies {
    implementation 'com.alipay.sdk:alipaysdk-android:15.8.33'
}
```

### 3.2 Add the required FastJSON 1.x dependency

The Android Payments SDK artifact currently provided by AppInChina directly depends on **legacy FastJSON 1.x** (`com.alibaba:fastjson`) for JSON parsing. This dependency is required by the **AppInChina Payments SDK itself** — not only by Alipay — and it is **not** bundled in the SDK, so your app must supply it. If it is missing or the wrong version, payment initialization can crash with errors such as:

- `NoClassDefFoundError: com/alibaba/fastjson/...`
- `ClassNotFoundException: com.alibaba.fastjson...`

Include the **exact pinned** FastJSON 1.x version in your app-level `build.gradle`. **Do not** use a dynamic version selector such as `+` or "latest":

```gradle
dependencies {
    implementation("com.alibaba:fastjson:1.2.83")

    // Only if the host app independently needs FastJSON2 (separate dependency family):
    // implementation("com.alibaba.fastjson2:fastjson2:<HOST_APP_PINNED_VERSION>")
}
```

:::caution FastJSON 1.x and FastJSON2 are different dependency families
`com.alibaba:fastjson` (FastJSON **1.x**) and `com.alibaba.fastjson2:fastjson2` (**FastJSON2**) have different package namespaces and are **not** interchangeable. The SDK uses FastJSON **1.x**. If your project also pulls in FastJSON2 through another library, keep its version explicit and test the complete release dependency graph — do **not** replace the SDK's required FastJSON 1.x with FastJSON2 unless AppInChina publishes a newer SDK that explicitly supports it.
:::

:::note Why `1.2.83`, and why any 1.2.x works
The Payments SDK uses only **stable core FastJSON 1.x APIs** (`JSON.parseObject`/`parseArray` to known classes, plus `JSONObject`/`JSONArray` getters) and does **not** use autotype deserialization. It therefore works with **any FastJSON 1.2.x** — the version is a **security choice, not a compatibility constraint**. Pin **`1.2.83`**, the final and most-hardened 1.x release, which fixes the autotype RCE issues present in older builds. Do **not** ship an old version such as `1.1.70.android`. All FastJSON 1.x is end-of-life; moving the SDK off 1.x is a separate item on our roadmap. See the [compatibility matrix](../reference/compatibility-and-changelog.md).
:::

#### Inspecting your resolved FastJSON versions

Confirm which version Gradle actually packaged into your **release** graph (adapt the module/configuration names to your project):

```bash
./gradlew :app:dependencyInsight \
  --dependency com.alibaba:fastjson \
  --configuration releaseRuntimeClasspath

./gradlew :app:dependencyInsight \
  --dependency fastjson2 \
  --configuration releaseRuntimeClasspath
```

This is a **diagnostic** check — it identifies the version resolved into the release build. It does not by itself make a version supported. For FastJSON-related crash triage, see [Troubleshooting → FastJSON](../troubleshooting/support-evidence.md#fastjson-dependency-issues).

### 3.3 Alipay integration troubleshooting

- **Error: SDK initialization failure**
  - Verify your AppInChina App ID (`APP_ID`) and App Secret (`APP_SECRET`) are correct
  - Check your network connection
  - Ensure all required permissions are properly declared in `AndroidManifest.xml`
- **Error: Payment callback not received**
  - Verify all required permissions are granted at runtime for Android 6.0+
  - Check if the device has sufficient storage space
  - Ensure Alipay app is installed and updated on the test device

## 4. Configure project repositories

Both the WeChat and Alipay SDKs are hosted on Maven Central. Be sure to include `mavenCentral()` in your root `build.gradle` file to ensure Gradle can resolve both dependencies correctly.

```
allprojects {
    repositories {
        google()
        mavenCentral()
    }
}
```

## 5. Declare required permissions

Add the following permissions to your `AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

If `targetSdkVersion >= 23`, request the following permissions **at runtime**:

- `WRITE_EXTERNAL_STORAGE`
- `READ_PHONE_STATE`

## 6. Add WeChat queries entry (Android 11+)

If `targetSdkVersion >= 30`, add this section to your `AndroidManifest.xml`:

```xml
<queries>
    <package android:name="com.tencent.mm" />
</queries>
```

This ensures your app is allowed to query for the WeChat app when launching payment intents.

## 7. Implement the WeChat Pay callback activity

WeChat Pay requires your app to implement a callback receiver for payment results.

### 7.1 Create a `wxapi` package inside your app's base package

For example: `com.yourcompany.yourapp.wxapi`

### 7.2 Add `WXPayEntryActivity` class inside `wxapi`

```java
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {

    private IWXAPI api;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        api = com.mandou.acp.sdk.PayToolInfo.getApi();
        api.handleIntent(getIntent(), this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        api.handleIntent(intent, this);
    }

    @Override
    public void onReq(BaseReq req) {
        // Handle request from WeChat if needed
    }

    @Override
    public void onResp(BaseResp resp) {
        // Handle WeChat payment response
        startActivity(new Intent(this, PayResultActivity.class));
        finish();
    }
}
```

:::note
The package name and class name must match exactly for WeChat to invoke it properly.
:::

:::caution `WXPayEntryActivity` and the result Activity are different components
`WXPayEntryActivity` above is the **WeChat callback receiver** — it must live in a `wxapi` package with the exact naming WeChat requires. The `PayResultActivity` it launches is a **separate, client-owned result Activity** that the AppInChina SDK does **not** provide. You implement, declare, and package both. See [Android SDK §7.4: the four post-payment components](./android.md#74-the-four-post-payment-components-and-who-owns-each) for the full component map.
:::

## 8. Notes and important reminders

- These configurations are **mandatory** for payments to function.
- Failure to set up the WeChat or Alipay SDKs correctly will cause `startPayment()` calls to fail.
- Missing `com.alibaba:fastjson` can cause runtime crashes during Alipay initialization on some SDK combinations.
- The AppInChina SDK depends on these lower-level SDKs to open payment apps and receive callbacks.

Next: continue with the [Android SDK integration guide](./android.md).

---


<!-- source: payments/testing/preflight-acceptance.md -->


This guide defines a normal launch prerequisite: before AppInChina performs final payment acceptance testing, a named client tester completes **one low-value real purchase** inside the release-candidate build for each payment method you intend to offer.

This is a launch readiness step, not a way to push support away. AppInChina still investigates confirmed transaction and integration issues and performs final acceptance testing after the preflight passes.

## Related docs

- Scope and ownership: [Start Here: integrate AppInChina IAP](../overview.md)
- Backend verification: [Server Verification and Entitlements](../backend/verification-entitlements.md)
- Failure triage: [Troubleshooting and Support Evidence](../troubleshooting/support-evidence.md)
- Sign-off: [Release Checklist / Definition of Done](../release-checklist.md)

## 1. Preflight policy

:::info Payment preflight
Before submitting a release-candidate APK for AppInChina payment acceptance testing, nominate **one named tester for each payment method** you intend to offer at launch. Each tester must install the relevant wallet app, complete the wallet's registration and verification steps, link an eligible card, and complete **one low-value real purchase** in the release-candidate APK. That first in-app purchase is the payment preflight; no separate real-world merchant purchase is required.
:::

### 1.1 How to triage a preflight failure

:::caution Triage
If the attempt fails **before an order appears in AppInChina's backend**, treat it initially as a client-side preflight failure. Confirm wallet/card readiness and inspect your app/SDK logs. This does **not** prove the cause is a wallet issue — it may also be an app-side setup or SDK-invocation issue.

If the order reaches AppInChina's backend and is **paid**, confirm the app return/callback, backend verification, entitlement update, and refreshed access state.
:::

### 1.2 Scope: who can test

:::note Chinese national not normally required
A Chinese national should not normally be required to run the preflight. AppInChina may assist in exceptional cases — for example, if a wallet asks the tester for account-security friend verification — but **cannot promise to override or bypass** wallet identity, card-issuer, or risk-control decisions.
:::

## 2. Test stages and ownership

| Stage | Client action | AppInChina action | Exit condition |
| --- | --- | --- | --- |
| **A. Wallet readiness** | Named tester installs WeChat Pay and/or Alipay, registers/signs in, completes prompts, and links an eligible card. | Provide resource links and clarify supported payment methods; exception support only when appropriate. | Wallet can be opened and card appears linked; no promise that a future transaction will be approved. |
| **B. Release-candidate preflight** | Run one low-value in-app transaction and collect the standard evidence package. | Check whether an order reached the backend when asked; help classify confirmed faults. | One test either reaches a clear paid/post-payment outcome or yields enough evidence for triage. |
| **C. Client flow confirmation** | If paid, confirm callback/result, backend verification, entitlement, UI refresh, relaunch behavior, and history as applicable. | Review backend payment record when escalated; investigate AppInChina-side defects. | All client-owned outcomes pass for each method/product type in scope. |
| **D. AppInChina acceptance** | Deliver the exact release-candidate build and required test context after preflight success. | Perform defined final acceptance test and report results. | Acceptance report passed or a reproducible defect is recorded. |
| **E. Release sign-off** | Confirm the submitted build is unchanged from the tested release candidate except for documented non-payment changes. | Retain acceptance evidence per internal process. | Release owner has a complete audit trail. |

The evidence package for Stage B is defined in [Troubleshooting and Support Evidence](../troubleshooting/support-evidence.md).

## 3. Wallet setup resources

The resources below help a non-Chinese tester set up a wallet. They do not imply that AppInChina can control tester eligibility or a card issuer's approval decision.

- **Government guide:** [Payment service guide for overseas visitors to China](https://english.www.gov.cn/news/202404/11/content_WS6617c858c6d0868f4e8e5f4d.html)
- **Card-brand reference:** [Overseas Bank Cards Accepted by Weixin Pay and Alipay](https://english.beijing.gov.cn/specials/paymentservices/howtopaybeijing/202404/t20240429_3647378.html)
- **Practical account/card guidance:** [Mobile Payment — A Guide to Working and Living in China as Business Expatriates](https://english.beijing.gov.cn/specials/aguidetoworkingandlivinginchinaasbusinessexpatriates2024/dailylifeservices/202404/t20240424_3635122.html)
- **WeChat:** [Official WeChat site](https://www.wechat.com/)
- **Alipay:** [Official Alipay site](https://www.alipay.com/)

:::note Eligibility and risk decisions stay with the providers
The wallet provider determines any identity-verification steps and whether a card is eligible. If card binding or a payment is declined, follow the in-app guidance and contact the card issuer where appropriate. Card brands, limits, fees, and risk controls can vary by wallet, issuing bank, user account, region, and transaction.
:::

---


<!-- source: payments/troubleshooting/support-evidence.md -->


This page maps common failures to the checks you should run first and the evidence AppInChina needs to help efficiently. The goal is a path that is neither dismissive nor open-ended.

**Do not classify a failure as a wallet defect before checking whether an AppInChina order exists** and reviewing the relevant client-side evidence. A payment attempt that never created an order is, by default, a client-side preflight matter until the evidence identifies the cause.

## Related docs

- Preflight and stages: [Client Payment Preflight and Acceptance Testing](../testing/preflight-acceptance.md)
- Backend verification: [Server Verification and Entitlements](../backend/verification-entitlements.md)
- Android components: [Payments SDK Integration Guide for Android](../sdk/android.md)
- Endpoint contract: [Payments API Reference](../api/reference.md)
- Server error codes: [Error Reference](../reference/errors.md)

## Failure classification

Use the observed state to classify the problem, run the client-side checks first, and — only if unresolved — send the listed evidence.

| Observed state | Initial classification | Client checks first | What to send AppInChina if unresolved |
| --- | --- | --- | --- |
| Wallet cannot be installed, registered, verified, or linked to a card | Wallet / card readiness | Wallet prompts; country/phone/ID/card eligibility; card issuer response. | Wallet name/version; redacted screenshots; country; card brand/issuer only if the tester is comfortable sharing; exact error and time. |
| Payment attempt does **not** create an AppInChina order | Client-side preflight | Wallet readiness; SDK invocation parameters; app logs; build/version; callback/result configuration. | APK/AAB build ID; device/OS; payment method; timestamp and time zone; sanitized logs around the attempt; screenshots; user/`bizNo` if generated. |
| Order exists but is **not paid** | Transaction state investigation | Order status; displayed wallet outcome; retry/duplicate behavior; network/app logs. | Order/`bizNo`; method; timestamp; sanitized logs; exact UI state; confirmation that the app build matches the test build. |
| Order is **paid** but app reports failure or does not return correctly | Client post-payment handling | Result Activity/callback implementation; release merged manifest; crash/error logs; app navigation. | Order/`bizNo`; APK build; exact stack/log excerpt; manifest / result-Activity verification; screen capture only if needed. |
| Order is **paid**, but entitlement/access is missing | Client backend / entitlement | Backend verification response; idempotency logic; user-to-`customerIdentity` mapping; entitlement update and app refresh. | Order/`bizNo`; sanitized verification records; entitlement audit trail; current client account state; timestamps. |
| Access was granted but history/refund state is wrong | Client history / reconciliation | Canonical history call/method; pagination; latest verified status; refund reconciliation job. | Request/response redacted of secrets; endpoint/method; user/`bizNo`; expected vs actual record; timestamps. |

## Understanding SDK error `99999`

`99999` means the Android SDK **caught an unexpected local exception** while processing the request or payment flow. It can result from a missing Activity, a dependency/configuration problem, a JSON parsing error, or another unhandled runtime failure.

:::caution `99999` is not a payment decline
`99999` is **not** enough to identify the cause and does **not**, by itself, show that AppInChina's backend or a payment provider rejected the transaction. Obtain the underlying **Android exception and surrounding logs**, then classify the failure.
:::

**Evidence to collect for `99999`:**

- Exact timestamp and time zone.
- Application ID, app version, version code, and whether the build is **debug or release**.
- Device brand/model, Android version, and wallet-app version where relevant.
- The **complete exception and surrounding Android log** — not only the error code.
- Whether an AppInChina order was created, plus `bizNo` if available.
- Which wallet/channel was selected.
- Gradle dependency report for FastJSON/FastJSON2 if the exception concerns class loading, JSON parsing, or a compatibility change (see [FastJSON dependency issues](#fastjson-dependency-issues)).
- Final merged manifest for Activity/callback errors.

**Triage rule:**

- If an order **was created or paid**, investigate the post-payment callback, verification, and entitlement path **in parallel** with any client crash.
- If **no order** reached AppInChina, first inspect the local exception, SDK setup, wallet readiness, and request parameters. Do **not** label it a backend payment failure without supporting evidence.

## WeChat-specific failures

| Symptom | First checks | Evidence for support |
| --- | --- | --- |
| WeChat option appears but no wallet handoff occurs | Confirm `initPayTools()` **succeeded in the same process** before `startPayment()`; confirm WeChat is installed and the configured channel is returned. | Timestamp; SDK log; `initPayTools()` result/error; app version/build; device/Android version. |
| Wallet completes but app does not refresh access | Confirm `WXPayEntryActivity` is correctly packaged and declared; trace its route to backend verification and entitlement refresh. | Callback logs; merged manifest; order `bizNo`; backend verification record; current entitlement response. |

See the [WeChat payment lifecycle](../sdk/android.md#77-wechat-payment-lifecycle) for the full init → callback → verification path.

## FastJSON dependency issues

The SDK requires **legacy FastJSON 1.x** (`com.alibaba:fastjson`), which is a different dependency family from **FastJSON2** (`com.alibaba.fastjson2:fastjson2`). See [Prerequisites §3.2](../sdk/prerequisites.md#32-add-the-required-fastjson-1x-dependency).

| Situation | What to do |
| --- | --- |
| `ClassNotFoundException`, `NoClassDefFoundError`, or a crash identifying `com.alibaba.fastjson` | Confirm the approved FastJSON 1.x dependency is declared and resolved into the **release** build. Provide the full exception and `dependencyInsight` output. |
| Another library resolves a different FastJSON 1.x version | Resolve to one Engineering-approved FastJSON 1.x version for that module family; retest the release artifact. Do **not** pick a version solely because it is newer. |
| The app also includes FastJSON2 | Keep FastJSON2 versioning explicit and test the whole app. Do **not** describe it as a replacement for FastJSON 1.x. |
| Device/Android-specific JSON crash | Capture device/OS, app build, SDK artifact version, full exception, and **both** FastJSON/FastJSON2 resolved versions before escalating. |

## Privacy rule

:::caution Never request sensitive data
Never ask a client — and never send AppInChina — passwords, card numbers, wallet authentication codes, full device dumps, access tokens, or production secrets. Request an **excerpt** of a sanitized log covering the attempt, not an unbounded full log, unless Engineering specifically needs it under a secure process.
:::

### Example of a sanitized log excerpt

Send the window around the attempt with secrets removed:

```text
2026-08-03 14:30:01.220 +0800  ACP/pay  startPayment channel=WECHAT bizNo=ORDER_20260803_0001 customerIdentity=user_12345
2026-08-03 14:30:02.880 +0800  ACP/pay  wallet launched
2026-08-03 14:30:19.104 +0800  ACP/pay  return-to-app; querySingleOrder bizNo=ORDER_20260803_0001
2026-08-03 14:30:19.550 +0800  ACP/pay  paymentStatus=PAID pmtDt=1754203818000
2026-08-03 14:30:19.560 +0800  APP/entitlement  grant productId=pro_3m result=OK
```

Redact `APP_SECRET`, tokens, phone numbers, and card data before sharing. Include the timestamp and time zone so the attempt can be correlated with the backend order record.

---


<!-- source: payments/web/pc-h5.md -->


## Introduction

This document explains how to integrate AppInChina’s payment services in **PC** (desktop browsers) and **H5** (mobile web browsers) environments.

Unlike mobile app payments handled through SDKs, PC and H5 payments require redirecting users to external platforms (WeChat Pay or Alipay), and managing the payment session securely.

The correct flow depends on both the payment method and the user’s device.

## Related docs

- Payments API reference (server-side): [Payments API Reference](../api/reference.md)
- Identity mapping (`customerIdentity`): [Login → Payments integration (customer identity)](../guides/login-identity.md)
- High-level flow: [Understanding the IAP SDK/API Flow](../concepts/iap-flow.md)
- Troubleshooting errors: [Error Reference](../reference/errors.md)

## Architecture overview: backend vs frontend

Understanding the separation between server-side and client-side operations is crucial for secure payment integration:

### Backend/server-side (secure operations)

- **API authentication** (APP_ID, APP_SECRET must never reach the browser)
- **CreateOrder API calls** (sensitive business logic)
- **Payment verification** (critical security operations)
- **URL/parameter building** (recommended for better security)

### Frontend/client-side (JavaScript UX)

- **Device detection** (PC vs mobile)
- **QR code display** (visual user interface)
- **Page redirections** (user navigation)
- **Form submissions** (user interactions)

:::caution Security principle
Never expose API credentials or sensitive payment data to the client-side. Always verify payments server-side.
:::


## 5. Payment verification (backend only)

Payment verification must **always** happen server-side for security.

:::danger
Never rely on frontend callbacks or return URLs for payment confirmation. Always verify server-side.
:::

### Backend: payment status check (Java)

```java
@Service
public class PaymentVerificationService {

    public PaymentStatus verifyPayment(String customerIdentity, String bizNo) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("APP_ID", appInChinaAppId);
        headers.set("APP_SECRET", appSecret);
        HttpEntity<Void> entity = new HttpEntity<>(headers);

        try {
            String url = apiEndpoint
                + "/detail.json"
                + "?bizNo=" + URLEncoder.encode(bizNo, "UTF-8")
                + "&customerIdentity=" + URLEncoder.encode(customerIdentity, "UTF-8");

            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
            );

            return parsePaymentStatus(response.getBody());

        } catch (Exception e) {
            log.error("Payment verification failed", e);
            return PaymentStatus.ERROR;
        }
    }

    private PaymentStatus parsePaymentStatus(String responseBody) {
        JSONObject result = JSON.parseObject(responseBody);

        if (result.getInteger("code") == 0) {
            JSONObject order = result.getJSONObject("data");
            if (order != null) {
                String status = order.getString("paymentStatus");

                switch (status) {
                    case "PAID": return PaymentStatus.PAID;
                    case "PENDING": return PaymentStatus.PENDING;
                    case "CLOSE": return PaymentStatus.CLOSED;
                    case "REFUND": return PaymentStatus.REFUNDED;
                    default: return PaymentStatus.UNKNOWN;
                }
            }
        }

        return PaymentStatus.ERROR;
    }
}
```


## 7. Complete working example

### Frontend: payment form (HTML)

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>AppInChina Payment Demo</title>
</head>
<body>
    <form method="post" action="/pay" id="payment-form">
        <input type="text" name="bizNo" placeholder="Order Number" required>
        <input type="text" name="goodsTitle" placeholder="Product Name" required>
        <input type="text" name="customerIdentity" placeholder="Customer ID" required>

        <select name="payChannel" required>
            <option value="ALIPAY">Alipay 支付宝</option>
            <option value="WECHAT">WeChat Pay 微信支付</option>
        </select>

        <select name="paySource" id="paySource" required>
            <option value="NATIVE">PC Desktop</option>
            <option value="H5">Mobile H5</option>
        </select>

        <input type="number" name="amount" step="1" min="1" required>
        <button type="submit">Pay Now 立即支付</button>
    </form>

    <script>
        // Auto-detect device and set paySource
        function detectDevice() {
            const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
            document.getElementById('paySource').value = isMobile ? 'H5' : 'NATIVE';
        }

        // Enhanced form validation
        document.getElementById('payment-form').addEventListener('submit', function(e) {
            const amount = parseInt(document.querySelector('[name="amount"]').value, 10);
            if (!Number.isFinite(amount) || amount < 1) {
                e.preventDefault();
                alert('Amount must be at least 1 (cent)');
            }
        });

        detectDevice();
    </script>
</body>
</html>
```

### Backend: complete controller (Java)

```java
@Controller
@Slf4j
public class PaymentController {

    @Autowired private RestTemplate restTemplate;
    @Value("${aic.endpoint}") private String apiEndpoint;
    @Value("${aic.app-id}") private String appInChinaAppId;
    @Value("${aic.app-secret}") private String appSecret;

    @PostMapping("/pay")
    public String processPayment(@Valid PayOrderDTO payOrder, Model model) {
        try {
            // Secure API call
            ResponseEntity<String> response = callCreateOrderAPI(payOrder);

            if (response.getStatusCode() != HttpStatus.OK) {
                return handlePaymentError(model, "API call failed");
            }

            JSONObject data = JSON.parseObject(response.getBody()).getJSONObject("data");

            // Route based on payment method
            if ("ALIPAY".equals(payOrder.getPayChannel())) {
                return handleAlipayResponse(data);
            } else if ("WECHAT".equals(payOrder.getPayChannel())) {
                return handleWeChatResponse(data, payOrder.getPaySource(), model);
            }

        } catch (Exception e) {
            log.error("Payment processing failed: bizNo={}", payOrder.getBizNo(), e);
            return handlePaymentError(model, "Payment processing failed");
        }

        return handlePaymentError(model, "Unknown payment method");
    }

    private ResponseEntity<String> callCreateOrderAPI(PayOrderDTO payOrder) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("APP_ID", appInChinaAppId);           // Secure server-side only
        headers.set("APP_SECRET", appSecret);   // Secure server-side only
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<String> request = new HttpEntity<>(JSON.toJSONString(payOrder), headers);

        return restTemplate.exchange(
            apiEndpoint + "/order.json",
            HttpMethod.POST,
            request,
            String.class
        );
    }

    private String handleAlipayResponse(JSONObject data) throws UnsupportedEncodingException {
        StringBuilder url = new StringBuilder("https://openapi.alipay.com/gateway.do?");

        for (Map.Entry<String, Object> entry : data.entrySet()) {
            url.append(entry.getKey())
               .append("=")
               .append(URLEncoder.encode(entry.getValue().toString(), "utf-8"))
               .append("&");
        }

        // Server-side redirect (secure)
        return "redirect:" + url.substring(0, url.length() - 1);
    }

    private String handleWeChatResponse(JSONObject data, String paySource, Model model) {
        if ("NATIVE".equals(paySource)) {
            // PC: QR code display
            model.addAttribute("qrCodeUrl", data.getString("code_url"));
            model.addAttribute("paymentType", "wechat_qr");
            return "payment_result";

        } else if ("H5".equals(paySource)) {
            // Mobile: direct redirect
            return "redirect:" + data.getString("mweb_url");
        }

        return handlePaymentError(model, "Invalid payment source");
    }

    private String handlePaymentError(Model model, String message) {
        model.addAttribute("error", message);
        return "error";
    }

    @GetMapping("/payment/status/{bizNo}")
    @ResponseBody
    public Map<String, Object> checkPaymentStatus(@PathVariable String bizNo) {
        // Secure verification (backend only)
        PaymentStatus status = verifyPaymentSecurely(bizNo);

        Map<String, Object> result = new HashMap<>();
        result.put("status", status.toString());
        result.put("timestamp", System.currentTimeMillis());

        return result;
    }
}
```

---

