Skip to content

Instantly share code, notes, and snippets.

@j3k0
Last active April 15, 2024 19:26
Show Gist options
  • Save j3k0/8b7e72b6af43572651328c613ac86d59 to your computer and use it in GitHub Desktop.
Save j3k0/8b7e72b6af43572651328c613ac86d59 to your computer and use it in GitHub Desktop.
cordova-purchase-plugin v13 - micro example
document.addEventListener('deviceready', onDeviceReady);
function onDeviceReady() {
const store = CdvPurchase.store;
const { ProductType, Platform, LogLevel, Product, VerifiedReceipt } = CdvPurchase; // shortcuts
// We should first register all our products or we cannot use them in the app.
store.register([{
id: 'demo_monthly_basic',
type: ProductType.PAID_SUBSCRIPTION,
platform: Platform.GOOGLE_PLAY,
}, {
id: 'demo_weekly_basic',
type: ProductType.PAID_SUBSCRIPTION,
platform: Platform.GOOGLE_PLAY,
}]);
store.verbosity = LogLevel.DEBUG;
store.applicationUsername = () => "my_username_2"; // the plugin will hash this with md5 when needed
// For subscriptions and secured transactions, we setup a receipt validator.
store.validator = "https://staging.com/v1/validate?appName=XXX&apiKey=YYY";
store.validator_privacy_policy = ['analytics', 'support', 'tracking', 'fraud'];
// Show errors on the dedicated Div.
store.error(errorHandler);
// Define events handler for our subscription products
store.when()
.updated(object => {
// Re-render the interface on updates
log.info('Updated: ' + JSON.stringify(object));
renderUI();
})
.approved(transaction => {
// verify approved transactions
store.verify(transaction);
})
.verified(receipt => {
// finish transactions from verified receipts
store.finish(receipt);
renderUI();
});
// Load informations about products and purchases
store.initialize([
Platform.APPLE_APPSTORE,
Platform.GOOGLE_PLAY,
{
platform: Platform.BRAINTREE,
options: {
tokenizationKey: 'sandbox_xyz',
nonceProvider: (type, callback) => {
callback({ // only 3D secure supported.
type: CdvPurchase.Braintree.PaymentMethod.THREE_D_SECURE,
value: 'fake-valid-nonce',
});
}
}
}
]);
// Updates the user interface to reflect the initial state
renderUI();
}
// Perform a full render of the user interface
function renderUI() {
const store = CdvPurchase.store;
// When either of our susbscription products is owned, display "Subscribed".
// If one of them is being purchased or validated, display "Processing".
// In all other cases, display "Not Subscribed".
const subscriptions = store.products.filter(p => p.type === CdvPurchase.ProductType.PAID_SUBSCRIPTION);
const statusElement = document.getElementById('status');
const productsElement = document.getElementById('products');
if (!statusElement || !productsElement) return;
if (isOwned(subscriptions))
statusElement.textContent = 'Subscribed';
else if (isApproved(subscriptions) || isInitiated(subscriptions))
statusElement.textContent = 'Processing...';
else
statusElement.textContent = 'Not Subscribed';
const validProducts = store.products.filter(product => product.offers.length > 0);
productsElement.innerHTML =
validProducts
.map(product => `<div id="${product.id}-purchase" style="margin-top: 30px">...</div>`)
.join('');
// Render the products' DOM elements
validProducts.forEach(renderProductUI);
// Find a verified purchase for one of the provided products that passes the given filter.
function findVerifiedPurchase(products: CdvPurchase.Product[], filter: (purchase: CdvPurchase.VerifiedPurchase) => boolean): CdvPurchase.VerifiedPurchase | undefined {
for (const product of products) {
const purchase = store.findInVerifiedReceipts(product);
if (!purchase) continue;
if (filter(purchase)) return purchase;
}
}
// Find a local transaction for one of the provided products that passes the given filter.
function findLocalTransaction(products: CdvPurchase.Product[], filter: (transaction: CdvPurchase.Transaction) => boolean): CdvPurchase.Transaction | undefined {
// find if some of those products are part of a receipt
for (const product of products) {
const transaction = store.findInLocalReceipts(product);
if (!transaction) continue;
if (filter(transaction)) return transaction;
}
}
function isOwned(products: CdvPurchase.Product[]): boolean {
return !!findVerifiedPurchase(products, p => !p.isExpired);
}
function isApproved(products: CdvPurchase.Product[]) {
return !!findLocalTransaction(products, t => t.state === CdvPurchase.TransactionState.APPROVED);
}
function isInitiated(products: CdvPurchase.Product[]) {
return !!findLocalTransaction(products, t => t.state === CdvPurchase.TransactionState.INITIATED);
}
/**
* Refresh the displayed details about a product in the DOM
*/
function renderProductUI(product: CdvPurchase.Product) {
const productId = product.id;
const el = document.getElementById(`${productId}-purchase`);
if (!el) {
log.error(`HTML element ${productId}-purchase does not exists`);
return;
}
function strikeIf(when: boolean) { return when ? '<strike>' : ''; }
function strikeEnd(when: boolean) { return when ? '</strike>' : ''; }
// Create and update the HTML content
const id = `id: ${product.id}<br/>`;
const info =
(`title: ${product.title || ''}<br/>`) +
(product.description ? `desc: ${product.description || ''}<br/>` : '');
const offers = product.offers ? 'offers:<ul>' + product.offers.map(offer => {
return '<li>' + (offer.pricingPhases || []).map(pricingPhase => {
const cycles =
pricingPhase.recurrenceMode === 'FINITE_RECURRING'
? `${pricingPhase.billingCycles}x `
: pricingPhase.recurrenceMode === 'NON_RECURRING' ? '1x '
: 'every '; // INFINITE_RECURRING
return `${pricingPhase.price} (${cycles}${formatDuration(pricingPhase.billingPeriod)})`;
}).join(' then ') + ` <button onclick="orderOffer('${product.platform}', '${product.id}', '${offer.id}')">Buy</button></li>`;
}).join('') + '</ul>' : '';
el.innerHTML = id + info + /* discounts + subInfo + */ offers;
}
}
function orderOffer(platform: CdvPurchase.Platform, productId: string, offerId: string) {
const store = CdvPurchase.store;
const offer = store.get(productId, platform)?.getOffer(offerId);
if (offer) store.order(offer);
}
function formatDuration(iso: string | undefined): string {
if (!iso) return '';
const l = iso.length;
const n = iso.slice(1, l - 1);
if (n === '1') {
return ({ 'D': 'Day', 'W': 'Week', 'M': 'Month', 'Y': 'Year', }[iso[l - 1]]) || iso[l - 1];
}
else {
const u = ({ 'D': 'Days', 'W': 'Weeks', 'M': 'Months', 'Y': 'Years', }[iso[l - 1]]) || iso[l - 1];
return `${n} ${u}`;
}
}
function errorHandler(error: CdvPurchase.IError) {
const errorElement = document.getElementById('error');
if (!errorElement) return;
errorElement.textContent = `ERROR ${error.code}: ${error.message}`;
setTimeout(() => {
errorElement.innerHTML = '<br/>';
}, 10000);
if (error.code === CdvPurchase.ErrorCode.LOAD_RECEIPTS) {
// Cannot load receipt, ask user to refresh purchases.
setTimeout(() => {
alert('Cannot access purchase information. Use "Refresh" to try again.');
}, 1);
}
}
function restorePurchases() {
log.info('restorePurchases()');
CdvPurchase.store.restorePurchases();
}
function launchBraintreePayment() {
CdvPurchase.store.requestPayment({
platform: CdvPurchase.Platform.BRAINTREE,
amountMicros: 1990000,
currency: 'USD',
description: 'This is the description of the payment request',
}).then((result) => {
if (result && result.code !== CdvPurchase.ErrorCode.PAYMENT_CANCELLED) {
alert(result.message);
}
})
}
@jrecho
Copy link

jrecho commented Apr 15, 2024

When we get the webhook its is sending all products registered and not just the product purchased. PLease help

@jrecho
Copy link

jrecho commented Apr 15, 2024

And the webhook does not work all the time we see the receipt then no webhook. This Is intermittent. Are you guys having issues with your servers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment