androidtypescriptfluttergoogle-cloud-functionsin-app-purchase

In App Purchase Subscription error on Google API Validation (Non Renewing subscription / Prepaid)


I want to offer non renew subscription for users inside my app and google play purchases. I have all set but related transactions can't be verified properly by my cloud function.

Im using androidpublisher_v3 from "googleapis" inside a typescript/nodejs function.

When I try to validate a non renew subscription, the server verification (Purchases.products:get) raise 404 error:

could not verify the purchase because of error GaxiosError: The document type is not supported. 
at Gaxios._request (/workspace/node_modules/gaxios/build/src/gaxios.js:136:23) 
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) 
at async JWT.requestAsync (/workspace/node_modules/google-auth-library/build/src/auth/oauth2client.js:408:18) 
at async GooglePlayPurchaseHandler.handleNonSubscription (/workspace/lib/google-play.purchase-handler.js:85:30)  
at async /workspace/node_modules/firebase-functions/lib/common/providers/https.js:458:26  
.
.
.
code: 404,
errors: [ 
     { 
    message: 'The document type is not supported.', 
    domain: 'androidpublisher', 
        reason: 'unsupportedDocType', 
        location: 'token', 
    locationType: 'parameter' 
} 
[Symbol(gaxios-gaxios-error)]: '6.5.0'"

The token is valid, same of Google Order Management which purchase appears charged.

If I try to validate the non renew subscription using Purchases.subscription:get, it raise 404 error "Your request is invalid for this subscription purchase.", which seems okay, once is expected to use Purchases.products:get for Prepaid subscriptions, according to docs.

Ive been struggling for a week and tried different ways to handle it with no success.

The renewable subscription and one time non consumable products works properly (Purchases.subscription:get / Purchases.products:get).

My code:

export class GooglePlayPurchaseHandler extends PurchaseHandler {
  private androidPublisher: AndroidPublisherApi.Androidpublisher;

  constructor(private dbCalls: dbCalls) {
    super();
    this.androidPublisher = new AndroidPublisherApi.Androidpublisher({
      auth: new GoogleAuth({
        credentials,
        scopes: ["https://www.googleapis.com/auth/androidpublisher"],
      }),
    });
  }

  async handleSubscription(
      userId: string, productData: ProductData, token: string, productID: string
  ) : Promise<boolean> {
    console.warn(
        "called Google handleSubscription"
    );
    console.warn(
        `ANDROID_PACKAGE_ID: ("${ANDROID_PACKAGE_ID}")`
    );
    console.warn(
        `productID: ("${productID}")`
    );
    console.warn(
        `token: ("${token}")`
    );
    try {
      const response = await this.androidPublisher.purchases.subscriptions.get({
        packageName: ANDROID_PACKAGE_ID,
        subscriptionId: productID,
        token,
      });

      if (!response.data.orderId) {
        console.error("Could not handle purchase without order id");
        return false;
      }

      console.log("mapped orderId handleSubscription");
      // Update order id if necessary
      let orderId = response.data.orderId;
      const orderIdMatch = /^(.+)?[.]{2}[0-9]+$/g.exec(orderId);
      if (orderIdMatch) {
        orderId = orderIdMatch[1];
      }

      console.log("called SubscriptionPurchase handleSubscription Google Play");
      const purchaseData: SubscriptionPurchase = {
        iapSource: "google_play",
        orderId: orderId,
        productId: productID,
        userId: userId,
        purchaseDate: new Date(parseInt(response.data.startTimeMillis ?? "0", 10)),
        type: "SUBSCRIPTION",
        expiryDate: new Date(parseInt(response.data.expiryTimeMillis ?? "0", 10)),
        status: [
          "PENDING",
          "ACTIVE",
          "ACTIVE",
          "PENDING",
          "EXPIRED",
        ][response.data.paymentState ?? 4] as SubscriptionStatus,
      };
      if (userId) {
        await this.dbCalls.insertPurchaseInfo(
            {...purchaseData, userId} as Purchase
        );
      } else {
        await this.dbCalls.updatePurchaseInfo(
          purchaseData as Purchase
        );
      }
      return true;
    } catch (e) {
      console.log("could not verify the purchase because of error", e);
      return false;
    }
  }

My dependencies:

"dependencies": {
    "firebase-admin": "^12.1.0",
    "firebase-functions": "^4.9.0",
    "google-auth-library": "^9.8.0",
    "googleapis": "^134.0.0",
    "node-apple-receipt-verify": "^1.13.0"
  },

Thank you all in advance.


Solution

  • After a month google answered my ticket:

    This should be working as intended, as the Prepaid type subscription plan was part of the new Subscriptions migrated workflow indicated here. The old Subscription API is not compatible to return payload responses for those products that use the new attributes or features. To mitigate the issue, please make sure to use the SubscriptionV2 API in this case.

    For more details about the changes, please check our documentation.

    I didn't test that due the delay (I've opted to proceed with renew subscription), but hope to work for those which face the same that I did.