I have a TypeScript React project that is built in Vite. This is an Ionic React project that I build apps for Android and iOS as well as a PWA for the web.
I'm trying to use the latest version (13) of the cordova-purchase-plugin in my app. This version adds TypeScript support but it is not a module, so I'm confused about how to input it correctly (everything else in my app that I import is a module).
A very simple code example:
import 'cordova-plugin-purchase';
const store = new CdvPurchase.Store();
When I build this in Vite, it compiles with no errors. In VSCode, I can manipulate the store
object and the plugin's built-in types are shown correctly.
However, when I open the PWA in my web browser, I get an error:
Can't find variable: CdvPurchase
So the import is failing somehow.
cordova-plugin-purchase
includes a single JS file, store.js
.
To get my compiled app to load, I can copy this store.js
file into the assets directory of my app and then add it via the <script>
tag in index.html
. This puts CdvPurchase
in global scope and allows my app to load. However, I obviously don't want to be manually adding scripts from node_modules
to index.html
-- that's what a build tool is for.
So how can I make sure the variable is imported/resolve this error?
Previously, I was using the awesome-cordova-plugins wrapper to install the cordova-purchase-plugin. This works, but awesome-cordova-plugins is limited to cordova-purchase-plugin version 11, and I am trying to find a way to use version 13 in my app.
Here's how to use cordova-purchase-plugin with React 18 and Ionic React 7, TypeScript 5.
Partial example:
import React from 'react';
import 'cordova-plugin-purchase';
// Some imports omitted.
const PageStoreAppFovea: React.FC = () => {
const history = useHistory();
// These are wrappers for useState() hooks.
const [isLoading, setIsLoading] = useLoading();
const [showErrorAlert, setShowErrorAlert] = useAlert();
const [showCancelledAlert, setShowCancelledAlert] = useAlert();
const [showSuccessAlert, setShowSuccessAlert] = useAlert();
const [isVerifying, setIsVerifying] = useStateBoolean();
const userObject = useUserObject();
const queryClient = useQueryClient();
const { store } = CdvPurchase;
const monthly = store.get(MyProduct.SubMonthly);
const annual = store.get(MyProduct.SubAnnual);
const buySub = (sub: CdvPurchase.Offer) => {
const productId = sub.id;
setIsLoading(true);
// https://bobbyhadz.com/blog/typescript-check-if-value-exists-in-enum
const allProductsValues = Object.values(MyProduct);
if (allProductsValues.includes(productId)) {
// console.log('placing order for ', productId);
store.applicationUsername = () => userObject.id;
store
.order(sub)
.then(() => {
console.log('order placed', store.get(productId));
})
.catch((error: Error) => {
console.log('error purchased failed', error);
setShowErrorAlert(true);
});
} else {
const errorMessage = `Product is invalid: ${productId}`;
throw new Error(errorMessage);
}
};
// User closed the native purchase dialog
store.when().productUpdated((product) => {
console.log('Purchase cancelled', product);
setIsLoading(false);
setShowCancelledAlert(true);
});
// Upon approval, show a different message.
store.when().approved((product) => {
console.log('Purchase approved', product);
setIsVerifying(true);
});
// Upon the subscription becoming owned.
store.when().finished((product) => {
console.log('Purchase now owned', product);
queryClient
.invalidateQueries({ queryKey: ['myKey'] })
.then(() => setShowSuccessAlert(true))
});
const onClickCancelNotDuringVerify = () => {
setIsLoading(false);
};
const onClickCancelDuringVerify = () => {
setIsVerifying(false);
setIsLoading(false);
};
// console.log('monthly', monthly);
// console.log('annual', annual);
// Todo: Show a message if the free trial is in progress.
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref={'my-route'} />
</IonButtons>
<IonTitle>
<TTitleStore />
</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
{!userObject.hasAccessPaidFeatures(myCookie) && (
<BlockSubscriptionExpired />
)}
<p>Mobile store</p>
{isLoading && !isVerifying && (
<>
<p>Please wait...</p>
<ButtonStoreCancel onClick={onClickCancelNotDuringVerify} />
</>
)}
{isLoading && isVerifying && (
<>
<p>Please wait...</p>
<ButtonStoreCancel onClick={onClickCancelDuringVerify} />
</>
)}
{!isLoading && !isVerifying && monthly && annual && (
<ListSubscriptions
monthly={monthly}
annual={annual}
buySub={buySub}
setIsLoading={setIsLoading}
/>
)}
<IonAlert
isOpen={showErrorAlert}
onDidDismiss={() => setShowErrorAlert(false)}
message={tAlertMessagePurchaseFailed}
buttons={['OK']}
/>
<IonAlert
isOpen={showCancelledAlert}
onDidDismiss={() => setShowCancelledAlert(false)}
message={tAlertMessagePurchaseCancelled}
buttons={['OK']}
/>
<IonAlert
isOpen={showSuccessAlert}
onDidDismiss={() => {
history.push(routeTabWelcome);
}}
message={tAlertMessagePurchaseSuccess}
buttons={['OK']}
/>
</IonContent>
</IonPage>
);
};
export default PageStoreAppFovea;