Featured image of post Handling Apple Payment Server Callbacks Like WeChat Pay

Handling Apple Payment Server Callbacks Like WeChat Pay

Processing Apple Payments Similar to WeChat Pay

Client-side implementation should use StoreKit2. For server-side notifications, refer to Apple’s official documentation.

Database Design (Simplified)

# Order Table
mysql> desc orders;
+------------------+---------------------+------+-----+---------+----------------+
| Field            | Type                | Null | Key | Default | Extra          |
+------------------+---------------------+------+-----+---------+----------------+
| id               | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| no               | varchar(255)        | NO   | UNI | NULL    |                |
| trade_no         | varchar(255)        | YES  |     | NULL    |                |
| status           | tinyint(4)          | NO   |     | NULL    |                |
| origin_no        | varchar(255)        | YES  |     | NULL    |                |
+------------------+---------------------+------+-----+---------+----------------+

# User Subscription Contracts Table
mysql> desc member_contracts;
+-----------------+---------------------+------+-----+---------+-------+
| Field           | Type                | Null | Key | Default | Extra |
+-----------------+---------------------+------+-----+---------+-------+
| user_id         | bigint(20) unsigned | NO   | PRI | NULL    |       |
| status          | tinyint(4)          | NO   |     | NULL    |       |
| contract_id     | varchar(255)        | NO   | MUL | NULL    |       |
+-----------------+---------------------+------+-----+---------+-------+

Server-Side Processing Logic

  • Resubscription after expiration changes both appAccountToken and originalTransactionId (after subscription cancellation and expiration)
  • Resubscription during active period retains the same appAccountToken and originalTransactionId

Client-Side Order Creation

## Client requests order creation API to generate order number `$uuid`
## Return this value to client for `appAccountToken` usage
INSERT INTO orders(no) VALUES('$uuid');

Subscription Event Callback

  • Handling Apple Server notificationV2 callback && client-side verification (a JWS string)
## Parse server callback, verify notificationType=SUBSCRIBED and subtype in (INITIAL_BUY, RESUBSCRIBE, AUTO_RENEW_ENABLED)
## Key fields: appAccountToken, transactionId, originalTransactionId, expiresDate
## 1. Check if appAccountToken order exists
select * from orders where no='{$appAccountToken}' limit 1 => $id;
## 2. Update order status and payment information
update orders set trace_no='$transactionId', origin_no='$originalTransactionId' where id=$id;
## 3. Update user subscription status
update member_contracts set contract_id='$originalTransactionId';
## ... Additional membership benefits processing

Renewal Event Handling

## notificationType=DID_RENEW
## 1. Check if transactionId exists
select * from orders where trade_no='{$transactionId}' limit 1 => $id
## 2. If not exists, find subscription via originalTransactionId
select * from member_contracts where contract_id='$originalTransactionId' limit 1;
## 3. Create new paid order record
INSERT INTO orders(no, trade_no, origin_no) VALUES('$uuid', '$transactionId', '$originalTransactionId');
## ... Membership extension processing

Unsubscription Event

update member_contracts set status=xxx where contract_id='$originalTransactionId';

Client-Side Verification

  • The JWS string from client callback contains identical fields to Apple server callback’s .Data.SignedTransactionInfo
  • Reuse the same event handling logic for both server callbacks and client verification
// Retrieve product information
products = try await Product.products(for: Set(productIds))

// Initiate payment with server-generated UUID
let uuid = Product.PurchaseOption.appAccountToken(UUID.init(uuidString: "$uuid")!)
let result = try await product.purchase(options: [uuid])

// Handle payment result
switch result {
    case .success(let verificationResult):
        // Send verificationResult to server for immediate validation
        // (Avoid waiting for Apple server callback delay)
    case .userCancelled:
        // Handle cancellation
    case .pending:
        // Handle pending transactions via Transaction.updates
}

Additional Event Handling

Implement corresponding logic for other notification types (DID_FAIL_TO_RENEW, REFUND, etc.) as needed.