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