Skip to content

Instantly share code, notes, and snippets.

@mrhieu
Forked from j3k0/index.ts
Created May 6, 2025 10:54
Show Gist options
  • Select an option

  • Save mrhieu/7bed35eb139e13fedaadba845c2020e7 to your computer and use it in GitHub Desktop.

Select an option

Save mrhieu/7bed35eb139e13fedaadba845c2020e7 to your computer and use it in GitHub Desktop.

Revisions

  1. @j3k0 j3k0 revised this gist Oct 7, 2022. No changes.
  2. @j3k0 j3k0 revised this gist Oct 7, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.ts
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ document.addEventListener('deviceready', onDeviceReady);
    function onDeviceReady() {

    const store = CdvPurchase.store;
    const { ProductType, Platform, LogLevel, Product, VerifiedReceipt } = CdvPurchase;
    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([{
  3. @j3k0 j3k0 created this gist Oct 7, 2022.
    215 changes: 215 additions & 0 deletions index.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,215 @@
    document.addEventListener('deviceready', onDeviceReady);

    function onDeviceReady() {

    const store = CdvPurchase.store;
    const { ProductType, Platform, LogLevel, Product, VerifiedReceipt } = CdvPurchase;

    // 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);
    }
    })
    }