j3k0 / cordova-plugin-purchase

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

[DOC] Example of use with React components #1169

Open ptmkenny opened 3 years ago

ptmkenny commented 3 years ago

I'm learning React and I've built an app in Ionic / Capacitor. Now I'm trying to use this plugin in my app, but I'm having a lot of trouble figuring out how to configure it. Does anyone have an example of implementing this in React?

mchambaud commented 3 years ago

+1

robmarshall commented 3 years ago

+1

kenkurata commented 3 years ago

same here, anyone got this working in React ?

mchambaud commented 3 years ago

I still need this, I will start implementing tomorrow probably

danielp982 commented 3 years ago

From my understanding, you need to install @ionic-native/core and @ionic-native/in-app-purchase-2 in addition to the plugin, then you can import it into React like so:

Disregard, I misread the Ionic documentation. While it technically works, the code in the comment below is the advised method.

import { InAppPurchase2 } from '@ionic-native/in-app-purchase-2/ngx'
const store = new InAppPurchase2()
mchambaud commented 3 years ago
import { InAppPurchase2 } from '@ionic-native/in-app-purchase-2'
InAppPurchase2.register([{id: 'toto', type :'...']);
AlvinStefanus commented 3 years ago

Any of you guys successfully implement it? I cannot make the store fire the ready event. Also products are not loaded and can be purchased.

ptmkenny commented 3 years ago

I did actually get this working. I'm using Ionic React v5 with Capacitor v3.

My store sells two subscriptions (monthly and annual) as IAPs. The backend is Drupal 9.

I am just cutting and pasting the code from my actual app below, minus the stuff that is unique to my site.

Hopefully it can give people some ideas.

index.tsx

You have to initialize the store at the root of your app. I only do this on mobile because I also have a PWA on my website that uses a different payment system.

import { IAPProduct, InAppPurchase2 } from '@ionic-native/in-app-purchase-2';

const startApp = () => {
  ReactDOM.render(<App />, document.getElementById('root'));
};

if (isPlatformMobile()) {
    const store = InAppPurchase2;
    // Needed to use IAP + cordova plugins.

    // Set debug messages.
    // Default.
    store.verbosity = store.QUIET;
    // store.verbosity = store.DEBUG;

    store.register([{
      id: subMonthly,
      type: store.PAID_SUBSCRIPTION,
    }, {
      id: subAnnual,
      type: store.PAID_SUBSCRIPTION,
    }]);

    // Register event handlers for subscriptions.
    // store.when(subMonthly).updated((product: IAPProduct) => {
    //   console.log(`Registered: ${JSON.stringify(product)}`);
    // });
    // store.when(subAnnual).updated((product: IAPProduct) => {
    //   console.log(`Registered: ${JSON.stringify(product)}`);
    // });

    // Updated
    // store.when(subMonthly).updated((product: IAPProduct) => {
    //   console.log(`Updated${JSON.stringify(product)}`);
    // });
    // store.when(subAnnual).updated((product: IAPProduct) => {
    //   console.log(`Updated${JSON.stringify(product)}`);
    // });

    // Upon approval, verify the receipt.
    store.when(subMonthly).approved((product: IAPProduct) => {
      // console.log('DOING VERIFICATION MONTHLY');
      product.verify();
    });
    store.when(subAnnual).approved((product: IAPProduct) => {
      // console.log('DOING VERIFICATION ANNUAL');
      product.verify();
    });

    // Upon receipt validation, mark the subscription as owned.
    store.when(subMonthly).verified((product: IAPProduct) => {
      // console.log('DOING FINISH MONTHLY');
      product.finish();
      // console.log('User now owns: ', product.title);
    });
    store.when(subAnnual).verified((product: IAPProduct) => {
      // console.log('DOING FINISH ANNUAL');
      product.finish();
      // console.log('User now owns: ', product.title);
    });

    // https://billing-dashboard.fovea.cc/setup/cordova
    store.validator = mySecretCode;
    store.refresh();

    // console.log('store', store);
    startApp();

And here's my actual store page component:

const PageStoreApp: React.VFC<MyProps> = ({ pageTitle }: MyProps) => {
  const store = InAppPurchase2;

  const history = useHistory();

  const [isLoading, setIsLoading] = useState(false);
  const [showErrorAlert, setShowErrorAlert] = useState(false);
  const [showCancelledAlert, setShowCancelledAlert] = useState(false);
  const [showSuccessAlert, setShowSuccessAlert] = useState(false);

  const [isVerifying, setIsVerifying] = useState(false);

  const { userObject } = useContext(UserContext);
  const queryClient = useQueryClient();

  const monthly = store.get(subMonthly);
  const annual = store.get(subAnnual);

  const restorePurchases = () => {
    setIsLoading(true);
    store.refresh().finished(() => {
      queryClient.invalidateQueries(queryKeyUseUser)
        .then(() => {
          debugLog('restorePurchases down, shutting off loading');
          setIsLoading(false);
        });
    });
  };

  const buySub = (sub: IAPProduct) => {
    const productId = sub.id;
    setIsLoading(true);

    debugLog('attempting to purchase ', productId);
    const allProducts = [subMonthly, subAnnual];
    if (allProducts.includes(productId)) {
      // console.log('placing order for ', productId);
      // Uuid must be set for processing by drupal.
      store.applicationUsername = () => (userObject.id);
      debugLog('setting application username', store.applicationUsername);
      store.order(productId)
        .then(() => {
          debugLog('order placed', store.get(productId));
        })
        // Drupal should receive the new sub data.
        .then(() => queryClient.invalidateQueries(queryKeyUseUser))
        .error((error: Error) => {
          debugLog('error purchased failed', error);
          setShowErrorAlert(true);
        });
    } else {
      const error = Error(`Product is invalid: ${productId}`);
    }
  };
  // User closed the native purchase dialog
  store.when(subMonthly).cancelled((product: IAPProduct) => {
    debugLog('Purchase monthly was cancelled');
    setIsLoading(false);
    setShowCancelledAlert(true);
  });
  store.when(subAnnual).cancelled((product: IAPProduct) => {
    debugLog('Purchase annual was Cancelled');
    setIsLoading(false);
    setShowCancelledAlert(true);
  });

  // Upon approval, show a different message.
  store.when(subMonthly).approved((product: IAPProduct) => {
    debugLog('monthly approved', product);
    setIsVerifying(true);
  });
  store.when(subAnnual).approved((product: IAPProduct) => {
    debugLog('annual approved', product);
    setIsVerifying(true);
  });

  // Upon the subscription becoming owned.
  store.when(subMonthly).finished((product: IAPProduct) => {
    queryClient.invalidateQueries(queryKeyUseUser)
      .then(() => setShowSuccessAlert(true));
  });
  store.when(subAnnual).finished((product: IAPProduct) => {
    queryClient.invalidateQueries(queryKeyUseUser)
      .then(() => setShowSuccessAlert(true));
  });

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonBackButton />
          </IonButtons>
          <IonTitle>
            { pageTitle === ''
              ? <Trans id="page.store.title">My Store</Trans>
              : pageTitle}
          </IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent className={classesPageWithBodyText}>
        <p>Choose your subscription length.</p>
        <p>Subscribe yearly and get two months free.</p>
        { isLoading && !isVerifying && (
          <>
            <p><Trans id="store.order.please_wait_processing">Please wait while we process your order...</Trans></p>
              <IonButton
                size="small"
                onClick={() => {
                  setIsLoading(false);
                }}
                color="danger"
              >
                <Trans id="button.cancel">Cancel</Trans>
              </IonButton>
          </>
        ) }
        { isLoading && isVerifying && (
          <>
            <p><Trans id="store.order.verifying">Verifying order...</Trans></p>
              <IonButton
                size="small"
                onClick={() => {
                  setIsVerifying(false);
                  setIsLoading(false);
                }}
                color="danger"
              >
                <Trans id="button.cancel">Cancel</Trans>
              </IonButton>
          </>
        ) }
        { !isLoading && !isVerifying && (
          <>
            <IonCard>
              <IonCardContent>
                <IonCardTitle>
                  {monthly.title}
                </IonCardTitle>
                <p>{monthly.description}</p>
                <p>{monthly.price}</p>
                  <IonButton
                    size="small"
                    onClick={() => {
                      buySub(monthly);
                    }}
                  >
                    <Trans id="button.store.buy.monthly">
                      Buy a Monthly Subscription
                    </Trans>
                  </IonButton>
              </IonCardContent>
            </IonCard>
            <IonCard>
              <IonCardContent>
                <IonCardTitle>
                  {annual.title}
                </IonCardTitle>
                <p>{annual.description}</p>
                <p>{annual.price}</p>
                  <IonButton
                    size="small"
                    onClick={() => {
                      buySub(annual);
                    }}
                  >
                    <Trans id="button.store.buy.annual">
                      Buy an Annual Subscription
                    </Trans>
                  </IonButton>
              </IonCardContent>
            </IonCard>
            <IonGridTwoItems
              primary={(
                <ButtonContact />
              )}
              secondary={(
                <IonButton
                  size="small"
                  onClick={() => {
                    restorePurchases();
                  }}
                >
                  <Trans id="button.store.restore_purchases">Restore Purchases</Trans>
                </IonButton>
              )}
            />
          </>
        ) }
        <IonAlert
          isOpen={showErrorAlert}
          onDidDismiss={() => setShowErrorAlert(false)}
          message={t({ id: 'alert.purchase_failed', message: 'Your purchase could not be completed. Please try again.' })}
          buttons={['OK']}
        />
        <IonAlert
          isOpen={showCancelledAlert}
          onDidDismiss={() => setShowCancelledAlert(false)}
          message={t({ id: 'alert.purchase_cancelled', message: 'Your purchase was cancelled. You have not been charged.' })}
          buttons={['OK']}
        />
        <IonAlert
          isOpen={showSuccessAlert}
          onDidDismiss={() => {
            history.push(routeTabWelcome);
          }}
          message={t({ id: 'alert.purchase_success', message: 'Thanks for purchasing!' })}
          buttons={['OK']}
        />
      </IonContent>
    </IonPage>
  );
};

PageStoreApp.defaultProps = {
  pageTitle: '',
};

export default PageStoreApp;
stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.