From 3ee3734fd5d4bee92b11c93338e1e345ff75d817 Mon Sep 17 00:00:00 2001 From: Antonio Marino Date: Fri, 4 Jan 2019 15:47:49 +0000 Subject: [PATCH 1/7] Added defer subscription for google play --- README.md | 15 +++++++++++++ index.js | 32 +++++++++++++++++++++++++++ lib/google/index.js | 53 +++++++++++++++++++++++++++++++++++++++++++++ lib/google/urls.js | 14 ++++++++++-- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6660a5f..9e0fd6d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,21 @@ iap.cancelSubscription("google", payment, function (error, response) { }); ``` +### Subscription deferral ( Google Play only ) + +Google exposes [an API for deferral](https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/defer) of recurring suscriptions. This might be +be used to extend a user's subscription purchase until a specified future expiration time ( useful to grant your users some free days or months ). + +```javascript +var deferralInfo = { + expectedExpiryTimeMillis: 1546616722237, + desiredExpiryTimeMillis: 1547716722237, +}; +iap.deferSubscription("google", payment, deferralInfo, function (error, response) { + /* your code */ +}); +``` + ## Supported platforms ### Amazon diff --git a/index.js b/index.js index fa8a17b..c495bef 100644 --- a/index.js +++ b/index.js @@ -67,3 +67,35 @@ exports.cancelSubscription = function (platform, payment, cb) { cb(null, result); }); }; + + +exports.deferSubscription = function (platform, payment, deferralInfo, cb) { + function syncError(error) { + process.nextTick(function () { + cb(error); + }); + } + + if (!payment) { + return syncError(new Error('No payment given')); + } + + var engine = platforms[platform]; + + if (!engine) { + return syncError(new Error('Platform ' + platform + ' not recognized')); + } + + if (!engine.deferSubscription) { + return syncError(new Error('Platform ' + platform + + ' does not have deferSubscription method')); + } + + engine.deferSubscription(payment, deferralInfo, function (error, result) { + if (error) { + return cb(error); + } + + cb(null, result); + }); +}; diff --git a/lib/google/index.js b/lib/google/index.js index 07b621a..d49c60a 100644 --- a/lib/google/index.js +++ b/lib/google/index.js @@ -41,6 +41,14 @@ function validatePaymentAndParseKeyObject(payment) { return keyObject; } +function validateDeferralInfo(deferralInfo) { + assert.equal(typeof deferralInfo, 'object', 'deferralInfo must be an object'); + assert.equal(typeof deferralInfo.expectedExpiryTimeMillis, 'number', 'expectedExpiryTimeMillis must be a number'); + assert.equal(typeof deferralInfo.desiredExpiryTimeMillis, 'number', 'desiredExpiryTimeMillis must be a number'); + assert(deferralInfo.desiredExpiryTimeMillis > deferralInfo.expectedExpiryTimeMillis, 'desiredExpiryTimeMillis must be greater than expectedExpiryTimeMillis'); +} + + exports.verifyPayment = function (payment, cb) { var keyObject; @@ -133,3 +141,48 @@ exports.cancelSubscription = function (payment, cb) { }); }); }; + + +exports.cancelSubscription = function (payment, deferralInfo, cb) { + var keyObject; + + try { + keyObject = validatePaymentAndParseKeyObject(payment); + validateDeferralInfo(deferralInfo); + } catch (error) { + return process.nextTick(function () { + cb(error); + }); + } + + jwt.getToken(keyObject.client_email, keyObject.private_key, apiUrls.publisherScope, function (error, token) { + if (error) { + return cb(error); + } + + var requestUrl = apiUrls.purchasesSubscriptionsDefer( + payment.packageName, + payment.productId, + payment.receipt, + token + ); + + https.post(requestUrl, null, function (error, res, resultString) { + if (error) { + return cb(error); + } + + if (res.statusCode !== 200) { + return cb(new Error('Received ' + res.statusCode + ' status code with body: ' + resultString)); + } + + try { + var resultObject = JSON.parse(resultString); + } catch (e) { + return cb(e); + } + + return cb(null, resultObject); + }); + }); +}; diff --git a/lib/google/urls.js b/lib/google/urls.js index 4534949..13943ed 100644 --- a/lib/google/urls.js +++ b/lib/google/urls.js @@ -38,8 +38,6 @@ exports.purchasesSubscriptionsGet = function (packageName, productId, receipt, a ); }; - -// Android Subscriptions URLs & generators exports.purchasesSubscriptionsCancel = function (packageName, productId, receipt, accessToken) { var urlFormat = 'https://www.googleapis.com/androidpublisher/v2/applications/%s/purchases/subscriptions/%s/tokens/%s:cancel?access_token=%s'; @@ -51,3 +49,15 @@ exports.purchasesSubscriptionsCancel = function (packageName, productId, receipt encodeURIComponent(accessToken) // API access token ); }; + +exports.purchasesSubscriptionsDefer = function (packageName, productId, receipt, accessToken) { + var urlFormat = 'https://www.googleapis.com/androidpublisher/v2/applications/%s/purchases/subscriptions/%s/tokens/%s:defer?access_token=%s'; + + return util.format( + urlFormat, + encodeURIComponent(packageName), // application package name + encodeURIComponent(productId), // productId + encodeURIComponent(receipt), // purchase token + encodeURIComponent(accessToken) // API access token + ); +}; From bfdbc8fae8b0a88e2d6a289b276c585feba96503 Mon Sep 17 00:00:00 2001 From: Antonio Marino Date: Fri, 4 Jan 2019 17:36:22 +0000 Subject: [PATCH 2/7] fix --- lib/google/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/google/index.js b/lib/google/index.js index d49c60a..81dbebb 100644 --- a/lib/google/index.js +++ b/lib/google/index.js @@ -143,7 +143,7 @@ exports.cancelSubscription = function (payment, cb) { }; -exports.cancelSubscription = function (payment, deferralInfo, cb) { +exports.deferSubscription = function (payment, deferralInfo, cb) { var keyObject; try { From e1f1b6c4e86d402de1a2010373d02d0ffd076131 Mon Sep 17 00:00:00 2001 From: Antonio Marino Date: Sun, 6 Jan 2019 19:02:11 +0000 Subject: [PATCH 3/7] passing deferralInfo --- lib/google/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/google/index.js b/lib/google/index.js index 81dbebb..54d6ae1 100644 --- a/lib/google/index.js +++ b/lib/google/index.js @@ -167,7 +167,12 @@ exports.deferSubscription = function (payment, deferralInfo, cb) { token ); - https.post(requestUrl, null, function (error, res, resultString) { + var options = { + json: { + deferralInfo, + } + } + https.post(requestUrl, options, function (error, res, resultString) { if (error) { return cb(error); } From 7e7043e208fbebf8f23a3142c0fac70cdaaa710f Mon Sep 17 00:00:00 2001 From: Antonio Marino Date: Sun, 6 Jan 2019 19:32:27 +0000 Subject: [PATCH 4/7] fix lint --- lib/google/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/google/index.js b/lib/google/index.js index 54d6ae1..f10ed37 100644 --- a/lib/google/index.js +++ b/lib/google/index.js @@ -169,9 +169,9 @@ exports.deferSubscription = function (payment, deferralInfo, cb) { var options = { json: { - deferralInfo, + deferralInfo } - } + }; https.post(requestUrl, options, function (error, res, resultString) { if (error) { return cb(error); @@ -181,8 +181,9 @@ exports.deferSubscription = function (payment, deferralInfo, cb) { return cb(new Error('Received ' + res.statusCode + ' status code with body: ' + resultString)); } + var resultObject; try { - var resultObject = JSON.parse(resultString); + resultObject = JSON.parse(resultString); } catch (e) { return cb(e); } From 63b7f6404a816c11190f943b30a82311ef8e9a0a Mon Sep 17 00:00:00 2001 From: Antonio Marino Date: Mon, 3 Jun 2019 12:53:52 +0100 Subject: [PATCH 5/7] fixed indentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e0fd6d..31dc075 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ be used to extend a user's subscription purchase until a specified future expira ```javascript var deferralInfo = { expectedExpiryTimeMillis: 1546616722237, - desiredExpiryTimeMillis: 1547716722237, + desiredExpiryTimeMillis: 1547716722237, }; iap.deferSubscription("google", payment, deferralInfo, function (error, response) { /* your code */ From 0cbbbb416b8cd84ae78aa1b321ed2272992d6ab9 Mon Sep 17 00:00:00 2001 From: Varadhan Kalidasan Date: Tue, 5 Nov 2019 12:43:56 +0000 Subject: [PATCH 6/7] google deferral --- README.md | 14 +++++++++++ index.js | 36 +++++++++++++++++++++++++++ lib/google/index.js | 60 +++++++++++++++++++++++++++++++++++++++++++++ lib/google/urls.js | 14 +++++++++++ 4 files changed, 124 insertions(+) diff --git a/README.md b/README.md index 6660a5f..efc542c 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,20 @@ iap.cancelSubscription("google", payment, function (error, response) { }); ``` +### Subscription deferral ( Google Play only ) + +Google exposes [an API for deferral](https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/defer) of recurring suscriptions. This might be used to extend a user's subscription purchase until a specified future expiration time ( useful to grant your users some free days or months ). + +```javascript +var deferralInfo = { + expectedExpiryTimeMillis: 1546616722237, + desiredExpiryTimeMillis: 1547716722237, +}; +iap.deferSubscription("google", payment, deferralInfo, function (error, response) { + /* your code */ +}); +``` + ## Supported platforms ### Amazon diff --git a/index.js b/index.js index e7220b4..af022ad 100644 --- a/index.js +++ b/index.js @@ -67,3 +67,39 @@ exports.cancelSubscription = function (platform, payment, cb) { cb(null, result); }); }; + + +exports.deferSubscription = function (platform, payment, deferralInfo, cb) { + function syncError(error) { + process.nextTick(function () { + cb(error); + }); + } + + if (!payment) { + return syncError(new Error('No payment given')); + } + + if (!deferralInfo) { + return syncError(new Error('No deferralInfo given')); + } + + const engine = platforms[platform]; + + if (!engine) { + return syncError(new Error(`Platform ${platform} not recognized`)); + } + + if (!engine.deferSubscription) { + return syncError(new Error(`Platform ${platform + } does not have deferSubscription method`)); + } + + engine.deferSubscription(payment, deferralInfo, function (error, result) { + if (error) { + return cb(error); + } + + cb(null, result); + }); +}; diff --git a/lib/google/index.js b/lib/google/index.js index ca4ae9e..9bc27cd 100644 --- a/lib/google/index.js +++ b/lib/google/index.js @@ -41,6 +41,17 @@ function validatePaymentAndParseKeyObject(payment) { return keyObject; } +function validateDeferralInfo(deferralInfo) { + assert.equal(typeof deferralInfo, 'object', 'deferralInfo must be an object'); + assert.equal(typeof deferralInfo.expectedExpiryTimeMillis, 'number', 'expectedExpiryTimeMillis must be a number'); + assert.equal(typeof deferralInfo.desiredExpiryTimeMillis, 'number', 'desiredExpiryTimeMillis must be a number'); + + assert(deferralInfo.desiredExpiryTimeMillis > deferralInfo.expectedExpiryTimeMillis, 'desiredExpiryTimeMillis must be greater than expectedExpiryTimeMillis'); + + return deferralInfo; +} + + exports.verifyPayment = function (payment, cb) { let keyObject; @@ -133,3 +144,52 @@ exports.cancelSubscription = function (payment, cb) { }); }); }; + + +exports.deferSubscription = function (payment, deferralInfo, cb) { + let keyObject; + let options; + + try { + keyObject = validatePaymentAndParseKeyObject(payment); + options.json = { + deferralInfo: validateDeferralInfo(deferralInfo) + }; + } catch (error) { + return process.nextTick(function () { + cb(error); + }); + } + + jwt.getToken(keyObject.client_email, keyObject.private_key, apiUrls.publisherScope, function (error, token) { + if (error) { + return cb(error); + } + + const requestUrl = apiUrls.purchasesSubscriptionsDefer( + payment.packageName, + payment.productId, + payment.receipt, + token + ); + + https.post(requestUrl, options, function (error, res, resultString) { + if (error) { + return cb(error); + } + + if (res.statusCode !== 200) { + return cb(new Error(`Received ${res.statusCode} status code with body: ${resultString}`)); + } + + var resultObject; + try { + resultObject = JSON.parse(resultString); + } catch (e) { + return cb(e); + } + + return cb(null, resultObject); + }); + }); +}; diff --git a/lib/google/urls.js b/lib/google/urls.js index ed23521..69eca14 100644 --- a/lib/google/urls.js +++ b/lib/google/urls.js @@ -51,3 +51,17 @@ exports.purchasesSubscriptionsCancel = function (packageName, productId, receipt encodeURIComponent(accessToken) // API access token ); }; + + +// Android Subscriptions URLs & generators +exports.purchasesSubscriptionsDefer = function (packageName, productId, receipt, accessToken) { + const urlFormat = 'https://www.googleapis.com/androidpublisher/v3/applications/%s/purchases/subscriptions/%s/tokens/%s:defer?access_token=%s'; + + return util.format( + urlFormat, + encodeURIComponent(packageName), // application package name + encodeURIComponent(productId), // productId + encodeURIComponent(receipt), // purchase token + encodeURIComponent(accessToken) // API access token + ); +}; From b61a8137ccfc3842914ba758dc71cb1f8b590a76 Mon Sep 17 00:00:00 2001 From: Varadhan Kalidasan Date: Fri, 15 Nov 2019 15:25:14 +0000 Subject: [PATCH 7/7] updated package json --- package.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d97eff6..551b1ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iap", - "version": "1.1.0", + "version": "1.1.1", "description": "In-app purchase validation for Apple, Google, Amazon, Roku", "main": "index.js", "scripts": { @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/Wizcorp/node-iap" + "url": "https://github.com/emma-app/node-iap" }, "keywords": [ "iap", @@ -21,7 +21,16 @@ "purchase", "itunes" ], - "author": "Ron Korving ", + "author": { + "name": "Ron Korving", + "email": "rkorving@wizcorp.jp" + }, + "contributors": [ + { + "name": "Varadh Kalidasan", + "email": "varadh@emma-app.com" + } + ], "license": "MIT", "dependencies": { "jwt-simple": "0.5.6",