[Huawei Developers] Freemind R:
Subscriptions are mostly used for video, music, and reading apps. Once a user has purchased a subscription, it can be automatically renewed on a periodic basis.

The graph below shows the process for purchasing a subscription.

This guide tells you how to integrate HUAWEI In-App Purchases' (IAP) product management function so you can manage subscriptions in your apps. It will also provide solutions for problems that can occur during the integration.

Key development steps:

1. Create a subscription in the product management system (PMS) and add it to your app. You can do this by referring to Adding a Product.

Notes:

(1) Before you create a subscription, you need to create a subscription group to manage subscriptions of the same type.

(2) A new subscription in PMS will be automatically allocated to a default group, so you cannot choose a specific group for it. The subscription also cannot be deleted or re-created. Fixing such problems takes a great deal of effort, especially when the product, whose ID stays the same, is released on multiple platforms.

(3) When you add a subscription, its status will be set to Invalid by default. You need to activate it before you can start selling it. You can present invalid subscriptions in your app, but users will be unable to purchase them.

2. How the Code Works

a. Check whether the country or region of the signed-in HUAWEI ID is supported by HUAWEI IAP.

/**

* Check whether the country or region of the signed-in HUAWEI ID is included in the countries or regions

* supported by HUAWEI IAP.

* @param activity indicates the activity object that initiates a request.

*/

public static void isBillingSupported(final Activity activity) {

// Get the Activity instance that calls this API.

Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady();

task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {

@Override

public void onSuccess(IsEnvReadyResult result) {

// Obtain the execution result.

}

}).addOnFailureListener(new OnFailureListener() {

@Override

public void onFailure(Exception e) {

if (e instanceof IapApiException) {

IapApiException apiException = (IapApiException) e;

Status status = apiException.getStatus();

if (status.getStatusCode() == OrderStatusCode.ORDER_HWID_NOT_LOGIN) {

// Not signed in.

if (status.hasResolution()) {

try {

// 3333 is an int constant defined by the developer.

status.startResolutionForResult(activity, 3333);

} catch (IntentSender.SendIntentException exp) {

}

}

} else if (status.getStatusCode() == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) {

// The current country or region is not supported by HUAWEI IAP.

}

}

}

});

}

Notes:

(1) If a user tries to purchase a subscription in your app without first signing in to their HUAWEI ID, an error code will be reported to this API. Set the API to display the sign-in page upon receiving this code.

(2) If a user tries to purchase a subscription of your app from a location where HUAWEI IAP payment is not supported, an error code will be reported to this API.

(3) It is recommended that the context of the main activity be used, because this allows you to directly display the sign-in page and process subsequent services after sign-in.

It is best to call this API before the subscription list is displayed to users.

b. Obtain in-app product details configured in AppGallery Connect.

//**

* Obtain in-app product details configured in AppGallery Connect.

* @param productIds ID list of products to be queried.

* Each product ID must exist and be unique in the current app.

* @param type In-app product type.

* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription

*/

private void queryProducts(final Activity activity) {

// Pass in the productId list of products to be queried.

List<String> productIdList = new ArrayList<>();

// The product ID is the same as that set by a developer when configuring product information

// in AppGallery Connect.

productIdList.add("SubProduct1001");

ProductInfoReq req = new ProductInfoReq();

// priceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription

req.setPriceType(IapClient.PriceType.IN_APP_SUBSCRIPTION);

req.setProductIds(productIdList);

// Call the obtainProductInfo API.

Task<ProductInfoResult> task = Iap.getIapClient(activity).obtainProductInfo(req);

task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {

@Override

public void onSuccess(ProductInfoResult result) {

// Obtain the result.

List<ProductInfo> productList = result.getProductInfoList();

}

}).addOnFailureListener(new OnFailureListener() {

@Override

public void onFailure(Exception e) {

if (e instanceof IapApiException) {

IapApiException apiException = (IapApiException) e;

int returnCode = apiException.getStatusCode();

} else {

// Other external errors.

}

}

});

}

When calling this API, errors can occur if:

(1) The product type parameter does not match the product. For example, the value 0 (consumable) is passed to the parameter type for a subscription.

(2) More than 200 products are imported in one batch.

(3) The product ID is different from the one configured in the PMS.

(4) The product has not been activated.

c. Create orders for in-app products in the PMS.

/**

* Create orders for in-app products in the PMS.

*

* @param productId ID of the in-app product to be paid.

* The in-app product ID is the product ID you set during in-app product

* configuration in AppGallery Connect.

* @param type In-app product type.

* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription

*/

private void buy(final Activity activity, String productId, int type) {

// Construct a PurchaseIntentReq object.

PurchaseIntentReq req = new PurchaseIntentReq();

// The product ID is the same as that set by a developer when configuring product information in AppGallery Connect.

req.setProductId("productId");

// In-app product type contains:

// priceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription

// type:IapClient.PriceType.IN_APP_SUBSCRIPTION

req.setPriceType(type);

req.setDeveloperPayload("developer define info");

// Call the createPurchaseIntent API.

Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntent(req);

task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {

@Override

public void onSuccess(PurchaseIntentResult result) {

// Obtain the payment result.

Status status = result.getStatus();

if (status.hasResolution()) {

try {

// 6666 is an int constant defined by the developer.

status.startResolutionForResult(activity,status.startResolutionForResult(activity, Constants.REQ_CODE_BUY));

} catch (IntentSender.SendIntentException exp) {

}

}

}

}).addOnFailureListener(new OnFailureListener() {

@Override

public void onFailure(Exception e) {

if (e instanceof IapApiException) {

IapApiException apiException = (IapApiException) e;

Status status = apiException.getStatus();

int returnCode = apiException.getStatusCode();

} else {

// Other external errors.

}

}

});

}

When calling this API, errors can occur if:

(1) The product is still invalid after it has been purchased.

(2) The payment page does not display. This can happen if the parameter or context is incorrect. We recommend you use the context of the main activity, because this will enable your app to provide services after the user has made a successful payment.

(3) The user has to sign the payment agreement because they are purchasing a subscription for the first time.

d. HUAWEI IAP returns the payment result to your app through the main activity's onActivityResult callback.

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if (requestCode == Constants.REQ_CODE_LOGIN || requestCode == Constants.REQ_CODE_BUYWITHPRICE_CONTINUE) {

int returnCode = IapClientHelper.parseRespCodeFromIntent(data);

Log.i(TAG,"onActivityResult, returnCode: " + returnCode);

if (returnCode == OrderStatusCode.ORDER_STATE_SUCCESS) {

// If success is returned, you can call the buyWithPrice API again.

//buyWithPrice(productId);

} else if(returnCode == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED){

Log.e(TAG,"This is unavailable in your country/region." );

} else {

Log.e(TAG,"user cancel login" );

}

return;

}

if (requestCode == Constants.REQ_CODE_BUY) {

if (data == null) {

Log.e("onActivityResult", "data is null");

return;

}

PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data);

switch(purchaseResultInfo.getReturnCode()) {

case OrderStatusCode.ORDER_STATE_CANCEL:

// The user cancels payment.

break;

case OrderStatusCode.ORDER_STATE_FAILED:

case OrderStatusCode.ORDER_PRODUCT_OWNED:

// Check if there exists undelivered products.

break;

case OrderStatusCode.ORDER_STATE_SUCCESS:

// The payment is successful.

String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData();

String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature();

// Use the public key of your app to verify the signature.

// If the signature is correct, you can deliver your products.

// If the user purchased a consumable product, call the consumeOwnedPurchase

// API to consume it after successfully delivering the product.

break;

default:

break;

}

}

}

Notes:

(1) This API is used for payment results like payment cancellations, abnormal purchasing activity, payment successes, or when the user tries to purchase a product they have already purchased.

(2) If a user tries to purchase a subscription without first signing in to their HUAWEI ID, set this API to resume processing after they have signed in.

(3) If a user tries to purchase a subscription without having first signed the payment agreement, set this API to enable your app to continue the purchasing process after the user has signed the agreement.

e. Once a user has successfully purchased a subscription, verify the purchase on your app to check that the subscription is valid and provides its service.

/**

* Deliver products for the user .

*/

private void deliverProduct(final String inAppPurchaseDataStr, final String inAppPurchaseDataSignature) {

if (TextUtils.isEmpty(inAppPurchaseDataStr) || TextUtils.isEmpty(inAppPurchaseDataSignature)) {

Utils.showMessage(this, "purchase data is error");

return;

}

if (CipherUtil.doCheck(inAppPurchaseDataStr, inAppPurchaseDataSignature, Key.getPublicKey())) {

try {

InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseDataStr);

if(inAppPurchaseDataBean.isSubValid())

{

// Provide the user with the service.

}

} catch (JSONException e) {

Log.e(TAG, "delivery:" + e.getMessage());

}

} else {

Log.e(TAG, "delivery:" + getString(R.string.verify_signature_fail));

}

}

Notes:

(1) You need to generate the public key used for verifying signatures by going to Project Setting > In-App Purchases > Settings in AppGallery Connect.

(2) After a successful verification, for services (such as video app memberships) that are only available when they have been renewed in time, if InApppurchaseData.subIsvalid is true, you need to keep providing the services for the user.

(3) If possible, store the delivered products' purchaseToken on your app server. This can then be used as a credential when a user switches between subscriptions or accounts.

f. Redelivery. This mechanism reduces unsuccessful deliveries.

Generally, if an exception (such as a network error or process failure) occurs after the subscription has been paid for, your app will be unable to detect whether the payment was successful. This means the purchased product may not be delivered as expected.

However, with HUAWEI IAP, you can redeliver products.

/**

* Query information about all subscribed in-app products, including consumables, non-consumables,

* and auto-renewable subscriptions.

* If consumables are returned, the system needs to deliver them and calls the consumeOwnedPurchase

* API to consume the products.

* If non-consumables are returned, the in-app products do not need to be consumed.

* If subscriptions are returned, all existing subscription relationships of the user under the

* app are returned.

* @param mClient IapClient instance to call the obtainOwnedPurchases API.

* @param type In-app product type.

* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription

*/

public static void obtainOwnedPurchases(IapClient mClient, final int type) {

Log.i(TAG, "call obtainOwnedPurchases");

String continuationToken = null;

// Construct a OwnedPurchasesReq object.

OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq();

// In-app product type contains:

// priceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription

ownedPurchasesReq.setPriceType(2);

// Call the obtainOwnedPurchases API.

// Get the Activity instance that calls this API.

Task<OwnedPurchasesResult> task = mClient.obtainOwnedPurchases(ownedPurchasesReq);

task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {

@Override

public void onSuccess(OwnedPurchasesResult result) {

// Obtain the execution result.

if (result != null && result.getInAppPurchaseDataList() != null) {

for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) {

String inAppPurchaseData = result.getInAppPurchaseDataList().get(i);

String InAppSignature = result.getInAppSignature().get(i);

// Use the payment public key to verify the signature of the inAppPurchaseData.

deliverProduct(inAppPurchaseData,InAppSignature);

// The verification is successful.

try {

InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);

int purchaseState = inAppPurchaseDataBean.getPurchaseState();

} catch (JSONException e) {

}

}

}

}

}).addOnFailureListener(new OnFailureListener() {

@Override

public void onFailure(Exception e) {

if (e instanceof IapApiException) {

IapApiException apiException = (IapApiException) e;

Status status = apiException.getStatus();

int returnCode = apiException.getStatusCode();

} else {

// Other external errors.

}

}

});

}

Notes:

(1) This mechanism works by either querying the IAP server for information about all valid, in-app product subscriptions, and then reporting this to your app server and providing services, or by providing services directly from the client.

(2) For services (such as video app memberships) that are only available when they have been renewed in time, if InApppurchaseData.subIsvalid is true, you need to keep providing the services for the user. For other custom services (such as magazine subscriptions), if you require the subscription renewal data for previous periods, you can query the receipts for all subscriptions through IapClient.obtainOwnedPurchaseRecord. The receipt format is the same as it is in InAppPurchaseData.

(3) If InApppurchaseData.purchaseState is set to 0, the subscription is in purchased state. The time segment from InApppurchaseData.purchaseTime to InApppurchaseData.expirationDate indicates a subscription's validity period.

(4) If a subscription is in purchased state and its validity period matches your app server's valid provisioning period, your app needs to provide the relevant services. For all subscriptions, your app determines whether to provide services in this way.

3. Key subscription event callback from the Huawei IAP Server

(1) Before you request callback, you need to sign in to AppGallery Connect and go to Project Setting > In-App Purchases > Settings to generate a public key and app server callback address.

(2) Phases of the subscription process:

Promotional period: The user enjoys a subscription for free or at a promotional price. This period will begin when the user successfully subscribes.

Renewal:

First renewal: The system renews the subscription at the standard price. During this period, the subscription is valid and the user is entitled to the service provided by the subscription.

Second and subsequent renewals: For each of these renewals, HUAWEI IAP renews the subscription by periodically deducting fees from the user's account. It obtains the fee and product status 10 days before the subscription expires. HUAWEI IAP will attempt to deduct fees from the user's account 24 hours before the subscription renews. If this fails, the lapse period will start. During this period, HUAWEI IAP will try to deduct fees once a day. It will stop trying when this period ends.

Expiring: The subscription is valid and services are available. However, because the user has canceled the subscription, no fee will be deducted for the next renewal.

Expired: The subscription has expired and services are no longer available. There are several possible causes for this: HUAWEI IAP was unable to deduct a fee for the last renewal; the user has canceled the subscription; the user has not agreed to a price increase; the fee deducted account is abnormal.

To-be-effective: The subscription has not taken effect, and the user cannot use the service provided by the subscription. This period occurs when a user switches between subscriptions in the same subscription group. For example, a user may have switched to a yearly subscription from a monthly one which is still valid, in which case the yearly subscription would be in the to-be-effective period.

Suspended: Users can decide on a suspension plan for a subscription which is under renewal on the subscription management screen of HMS Core (APK). Once this plan is completed, the subscription renewal will be suspended, but the suspension will not take effect and users can still use the services provided by the subscription. Once the subscription validity period has ended, the subscription renewal will be suspended, and the user will no longer be able to use the services. Then, after the suspension period ends, the subscription will be automatically renewed. If the renewal is successful, the subscription can be used. If the renewal fails, the subscription will expire. Users can adjust suspension plans at any time, and the maximum suspension period is three months. Users can cancel the suspension plan at any time to revert to the auto-renewal status and enjoy the services immediately.

Grace period (coming soon): If you enable a grace period for your app, it will activate when HUAWEI IAP is unable to deduct a fee at the end of the subscription validity period. During the grace period, the user can still use the subscription while the HUAWEI IAP server attempts to deduct fees. If the fee deduction is successful within the grace period, the renewal will go ahead as planned. If the fee deduction is successful once the grace period has passed, the renewal will be interrupted. Then, the renewal start time will be the actual fee deduction time.

Lapse period: If a subscription is not renewed (for example, the user proactively cancels a renewal) or the renewal fails after the grace period has passed, a subscription enters the lapse period. For a subscription in the lapse period, there is a 30-day retention period when services are unavailable but a user can still resume the subscription. During this period, the subscription can be considered as having expired.

Key subscription event callback returned to your app server:

POST /subscription/notify/address

Content-Type: application/json; charset=UTF-8

Accept: application/json

Content-Length: 2754

{

"statusUpdateNotification": "{\"environment\":\"Sandbox\",\"notificationType\":7,\"subscriptionId\":\"1581789719266.D40972AC.3089\",\"orderId\":\"1581789719266.148748E7.3089\",\"latestReceipt\":\"00000173741056a37eef310dff9c6a86fec57efafe318ae478e52d9c4261994d64c8f6fc8ea1abbdx5347.5.3089\",\"latestReceiptInfo\":\"{\\\"autoRenewing\\\":true,\\\"subIsvalid\\\":true,\\\"orderId\\\":\\\"1581789719266.148748E7.3089\\\",\\\"lastOrderId\\\":\\\"1581790584090.DDB1EE93.5610\\\",\\\"packageName\\\":\\\"com.example.hwinappdemo\\\",\\\"applicationId\\\":123456,\\\"productId\\\":\\\"monthly_subscription2\\\",\\\"kind\\\":2,\\\"productName\\\":\\\"monthly_subscription2\\\",\\\"productGroup\\\":\\\"185F2E6A04BB4299B82C2525D3D68EBE\\\",\\\"purchaseTime\\\":1582791622434,\\\"oriPurchaseTime\\\":1582790122434,\\\"purchaseState\\\":0,\\\"developerPayload\\\":\\\"test\\\",\\\"purchaseToken\\\":\\\"00230173741056a37eef310dff9c6a86fec57efafe318ae478e52d9c4261994d64c8f6fc8ea1abbdx5347.5.3089\\\",\\\"purchaseType\\\":0,\\\"currency\\\":\\\"TRY\\\",\\\"price\\\":2,\\\"country\\\":\\\"TR\\\",\\\"subscriptionId\\\":\\\"1581789719266.D40972AC.3089\\\",\\\"quantity\\\":1,\\\"daysLasted\\\":26,\\\"numOfPeriods\\\":6,\\\"numOfDiscount\\\":0,\\\"expirationDate\\\":1582791922434,\\\"retryFlag\\\":1,\\\"introductoryFlag\\\":0,\\\"trialFlag\\\":0,\\\"renewStatus\\\":1,\\\"cancelledSubKeepDays\\\":30,\\\"payOrderId\\\":\\\"SandBox_1581789719266.148748E7.3089\\\",\\\"payType\\\":\\\"0\\\",\\\"acknowledged\\\":1,\\\"cancelWay\\\":0,\\\"cancellationTime\\\":1582789943392}\",\"latestReceiptInfoSignature\":\"S94effXhWisNXNE+lFBPv5DHGixYDY+3aI8fNDA+/wGQA+gZEx76Vilf0OqmW7zWorenMp98ra98nO7zJwmoZIF7R8/ZEd0V7dWiMOU+iMkhfDpfjAmtkraj5tjTYJR+QIhrQQWIxXg1AhqmGBuTyBdy0TVrfsVoKfCnh5oFZ5H7UB7jZA3KJtIHqsziAFdPEP67oE9kf4eHNsYAw/xSl4wTPXgsRJINu6SmCWcUzZ2UNMN/zWVgiqYlc9CGu6jParOfYeEnUCspdyZrsdWkmh2IB3pXOV8JxVsinzkklPwjotU21Oja46mjW9E+WSEomSjk9dop/iA0SjbzuH8pSogu0UyeO9FelFf/ZsaVQZLLjCHBK6pQ3SuHD3amqRB0GP3lGKdD1z5N7N5rO8ngS+mmlPi1gZwj+FiXdScVbmX6fOpvP+PFhyOginZp84EenKYx9Z9asXH+cJxmw9usN95cL7gAFhk+pu19Ng0Ro+t0+DPuj+cTOzyhVy7FTcDM\",\"autoRenewStatus\":1,\"productId\":\"monthly_subscription2\",\"applicationId\":\"123456\"}",

"notifycationSignature": "lD8NiUCrH+TK7gWBJB6XP3ZG9L2/TZvAGPIvRetAus9ZrKLjIzeagGjfi9rW9f/wEctIrp2gtDZ+k9xEyFT9apAqEdp629pUmufKe8jWlj6ZrHxqABcBQ2+ZFcDQH2u1Vm8tsEsk0ApQ4Z/nY2GCpifU/L6ITqT/Gn5hEBm7aCBEzOzg+SOBlCxqMOehusgPQTss32Y+E7KAPFy3SzH7o4/mMWOBoqvhZQrs/t9hlS8oamnsm3MmT3fNkoV8W/H7ckZhA62bfryjO8B/RVmz69x4CbvuIXj5Uo459aaIX6wE/5gPEqnnj1QrzYd10arNk6u0/nPUoKllm1NJ56qH/i9SLX+fRVnRnhvM/6t01IXcfxOg5Nx4pT2R/UkOEdcZs7lRrnWnVY7VsMQVZAkxbPAe1eSyzhGnW7s0mr1VfdMmr56eQ3zxNeQ2KXzeVQkBCb4EiY0MB2swFMzsWlo/OMBrUjG7ojflq0Shww45bSOBNO0nlz9B/keFj17fUKFt"

}

Case study:

In this example, a social media app integrates HUAWEI IAP. Below, you can see the kinds of major problems which may arise during the integration process, when they might occur, and how they can be solved.

1. Is there a redelivery mechanism available for subscriptions?

Scenario: The redelivery mechanism should take effect after a successful payment, but sometimes the purchased product is not delivered as expected because an exception occurs. For example, your app may fail to receive or correctly handle notifications about key subscription events, or the process may be interrupted by an unexpected app exit caused by a program error.

Solution: When your app restarts, set it to call obtainOwnedPurchases(OwnedPurchasesReq ownedPurchasesReq) to query information about all the auto-renewal subscriptions the user has purchased, and return the results. This information can be obtained from InAppPurchaseData and returned to your app server. It ensures your app can provide users with uninterrupted services. The redelivery mechanism can be implemented for all subscriptions using this kind of API.

2. When does the parameter subscriptionId change? Will it change when a user switches between subscriptions, or when a user resumes a subscription?

Scenario: This parameter is used to provide service credentials to users whom you want to be able to enjoy your service.

Solution: The subscription ID of a product remains the same when the subscription renewal is normal.

Currently, the subscription ID only changes in the following two scenarios:

l A user purchases a new product.

l A user resumes a subscription after the grace period. If this happens, the subscription ID changes because the user's previous subscription has become invalid. However, if a user proactively resumes a subscription before it enters the lapse period, the subscription ID will not change.

This is also true of the subscription's purchaseToken.

3. What notifications about key subscription events indicate the subscription is valid?

Scenario: Your app needs to be able to deliver a product to the user based on the notification types it receives.

Solution:

(1) For details about the notification types, refer to the Table above.

(2) The app will provide users with the services they are entitled once it has checked that InApppurchaseData.subIsvalid is set to true and the period from InApppurchaseData.purchaseTime to InApppurchaseData.expirationDate indicates that the subscription is valid.

4. What should I do with the subscriptions when my app supports multiple accounts for the same user?

Scenario 1: Your app has its own account system. For example, a user has two accounts, A and B. If this user purchases a monthly subscription for A, will this subscription be available for B?

Solution: HUAWEI IAP only distinguishes between HUAWEI IDs, and not user accounts registered to your app. Therefore, the purchased subscription is shared by all accounts as long as they are related to the same HUAWEI ID. In this scenario, we recommend that the user still be entitled to the service when signing in with B, up until the subscription they purchased for A has expired. Since the subscription ID stays the same, you can build an agreement mechanism according to your needs.

Scenario 2: A user purchases a monthly subscription for account A and then a yearly subscription for account B. Which of these subscriptions should my app provide them with?

Solution: In this scenario, if the user switches between subscriptions using HUAWEI IAP's subscription management feature, you can use setDeveloperPayLoad("xxxx") to set user tags and provide account B with the service when the yearly subscription becomes valid. However, when the user switches between subscriptions using the subscription switching function, your app cannot tell which account switches the subscription. Therefore, your app will provide the yearly subscription to account A, which the user used first to purchase a subscription in your app.

Scenario 3: What happens when a user disables switching between accounts A and B, cancels a subscription of A, and resumes the subscription for B?

Solution: The subscription's service will be provided to whichever user account is bound to the subscription ID. In this scenario, since this ID remains unchanged, the service could be provided to either A or B. To avoid this issue, your app server needs to unbind the subscription from A and provide the service to B.