j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.3k stars 537 forks source link

Problem on iOS with Ionic/Capacitor app #1416

Open petrot opened 1 year ago

petrot commented 1 year ago

Observed behavior

Include logs with store.verbosity = store.DEBUG

Expected behavior

System Info

I use Capacitor 5.0.4 with Ionic 7.0.9

Description

I have a working implementation with my old Ionic/Cordova based code. Now I try to use the plugin in my new Capacitor based app.

My code:


// I use this import because I read in an issue, that the simple import may causes double plugin initialization
// import 'cordova-plugin-purchase';
import 'cordova-plugin-purchase/www/store.d';

private _initializeStore() {
    CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG;

    CdvPurchase.store.register([
        {
            id: 'com.myname.myapp.monthly', // It's just a fake name, I have real ids
            type: CdvPurchase.ProductType.PAID_SUBSCRIPTION,
            platform: CdvPurchase.Platform.APPLE_APPSTORE,
          },
          {
            id: 'com.myname.myapp.onetime',  // It's just a fake name, I have real ids
            type: CdvPurchase.ProductType.NON_CONSUMABLE,
            platform: CdvPurchase.Platform.APPLE_APPSTORE,
          },
    ]);

    CdvPurchase.store
      .when()
      .approved((transaction) => {
        // I call finish here, because calling verify never runs
        transaction.finish();
        // transaction.verify();
      })
      .verified((receipt) => {
        // This block never runs

        if (CdvPurchase.store.owned(receipt.id)) {
          LogService.log(`[IAP] ${receipt.id} is owned`);
        }

        receipt.collection.forEach((purchase) => {
          if (CdvPurchase.store.owned(purchase)) {
            LogService.log(`[IAP] ${purchase.id} is owned.`);
          }
        });

        receipt.finish();
      })
      .finished((transaction) => {
        LogService.log(
          `[IAP] transaction finished: ${JSON.stringify(transaction)}`
        );
      })
      .receiptUpdated((receipt) => {
        LogService.log(
          `[IAP] receipt updated: ${JSON.stringify(receipt.transactions)}`
        );

        receipt.transactions.forEach((transaction) => {
          transaction.products.forEach(this._updatedCallback);
        });
      })
      .productUpdated((product: CdvPurchase.Product) => {
        // I had a special logic here to fill subscribtion data to my app
        // With the old version it worked here.

        LogService.log(`[IAP] product updated: ${product.id}`);
      });

    // Track all store errors
    CdvPurchase.store.error((err) =>
      LogService.error(`[IAP] error ${JSON.stringify(err)}`)
    );

    await CdvPurchase.store.initialize([CdvPurchase.Platform.APPLE_APPSTORE]);
    await CdvPurchase.store.update();
}

When I run the app, the initialization logic runs fine, but the cached (?) receipts contain invalid data, the id is just the lead part of my products (com.myname.myapp instead of com.myname.myapp.onetime):

from receiptUpdated event (just the transaction part of the receipt):

[
{"className":"Transaction",
"transactionId":"appstore.application",
"state":"approved",
"products":[
{"id":"com.myname.myapp"}
],
"platform":"ios-appstore"}
] 

I also have a fired event from the finished event, but with the same problem:

[
{"className":"Transaction",
"transactionId":"appstore.application",
"state":"approved",
"products":[
{"id":"com.myname.myapp"}
],
"platform":"ios-appstore"}
] 

PROBLEM: If I buy a product, it works (I have correct id-s in the transactions), but I get the previously bought products only with the restorePurchase function (in that case the transactions are fine). With my old Cordova app, I got by old purchases right after the plugin initialization.

My ordering code with some RxJs magic:

public order$(code: string) {
    const myProduct = CdvPurchase.store.get(code, this._cdvPlatform);

    LogService.log(`[IAP] Order product: ${JSON.stringify(myProduct)}`);

    const offer = myProduct.getOffer();

    LogService.log(`[IAP] Order offer: ${JSON.stringify(offer)}`);

    return iif(
      () => Capacitor.isNativePlatform(),
      defer(() =>
        this._utilsService.checkNetworkAndCall$(
          () =>
            from(offer.order()).pipe(
              tap((response) => {
                LogService.log(
                  `[IAP] Order success: ${JSON.stringify(response)}`
                );
              }),
              switchMap(() => this.update$(true)),
              catchError((err) => {
                LogService.error(`[IAP] buyProduct error: ${err}`);
                return ofFailed$();
              })
            ),
          ofFailed$
        )
      ),
      defer(() => ofSuccess$())
    ).pipe(take(1));
  }
kinggolf commented 1 year ago

I am seeing same transaction.verify(); within the approved callback not triggering verified.

kinggolf commented 1 year ago

Looks like verified is not triggered w/out a receipt validator, https://github.com/j3k0/cordova-plugin-purchase/wiki/HOWTO:-Migrate-to-v13#2-using-receipt-validation-with-no-back-end-server. BTW - on Android calling transaction.finish() in the approved callback does not work for us.