Support subscription payment through Google Play Store (#8437)

* Support subscription payment through Google Play Store

* minor fixes to iap subscriptions

* Support subscription payment through Google Play Store

* minor fixes to iap subscriptions

* revert change to test

* add unit tests for google payments

* add integration tests for google payments

* change config formatting for play api

* fix typo in file name

* fix typo in example config

* Improve google payment tests

* fix linter errors
This commit is contained in:
Phillip Thelen
2017-02-02 01:39:37 +01:00
committed by Sabe Jones
parent a002bc5e20
commit 4d0295a60d
10 changed files with 630 additions and 54 deletions

View File

@@ -5,10 +5,11 @@ import {
import iap from '../../../libs/inAppPurchases';
import payments from '../../../libs/payments';
import {
NotAuthorized,
BadRequest,
} from '../../../libs/errors';
import { model as IapPurchaseReceipt } from '../../../models/iapPurchaseReceipt';
import logger from '../../../libs/logger';
import googlePayments from '../../../libs/googlePayments';
let api = {};
@@ -28,63 +29,56 @@ api.iapAndroidVerify = {
let user = res.locals.user;
let iapBody = req.body;
await iap.setup();
let testObj = {
data: iapBody.transaction.receipt,
signature: iapBody.transaction.signature,
};
let googleRes = await iap.validate(iap.GOOGLE, testObj);
let isValidated = iap.isValidated(googleRes);
if (!isValidated) throw new NotAuthorized('INVALID_RECEIPT');
let receiptObj = JSON.parse(testObj.data); // passed as a string
let token = receiptObj.token || receiptObj.purchaseToken;
let existingReceipt = await IapPurchaseReceipt.findOne({
_id: token,
}).exec();
if (existingReceipt) throw new NotAuthorized('RECEIPT_ALREADY_USED');
await IapPurchaseReceipt.create({
_id: token,
consumed: true,
userId: user._id,
});
let amount;
switch (receiptObj.productId) {
case 'com.habitrpg.android.habitica.iap.4gems':
amount = 1;
break;
case 'com.habitrpg.android.habitica.iap.20.gems':
case 'com.habitrpg.android.habitica.iap.21gems':
amount = 5.25;
break;
case 'com.habitrpg.android.habitica.iap.42gems':
amount = 10.5;
break;
case 'com.habitrpg.android.habitica.iap.84gems':
amount = 21;
break;
}
if (!amount) throw new Error('INVALID_ITEM_PURCHASED');
await payments.buyGems({
user,
paymentMethod: 'IAP GooglePlay',
amount,
headers: req.headers,
});
let googleRes = await googlePayments.verifyGemPurchase(user, iapBody.transaction.receipt, iapBody.transaction.signature, req.headers);
res.respond(200, googleRes);
},
};
/**
* @apiIgnore Payments are considered part of the private API
* @api {post} /iap/android/subscription Android Subscribe
* @apiName IapAndroidSubscribe
* @apiGroup Payments
**/
api.iapSubscriptionAndroid = {
method: 'POST',
url: '/iap/android/subscribe',
middlewares: [authWithUrl],
async handler (req, res) {
if (!req.body.sku) throw new BadRequest(res.t('missingSubscriptionCode'));
let user = res.locals.user;
let iapBody = req.body;
await googlePayments.subscribe(req.body.sku, user, iapBody.transaction.receipt, iapBody.transaction.signature, req.headers);
res.respond(200);
},
};
/**
* @apiIgnore Payments are considered part of the private API
* @api {get} /iap/android/subscribe/cancel Google Payments: subscribe cancel
* @apiName AndroidSubscribeCancel
* @apiGroup Payments
**/
api.iapCancelSubscriptionAndroid = {
method: 'GET',
url: '/iap/android/subscribe/cancel',
middlewares: [authWithUrl],
async handler (req, res) {
let user = res.locals.user;
await googlePayments.cancelSubscribe(user, req.headers);
if (req.query.noRedirect) {
res.respond(200);
} else {
res.redirect('/');
}
},
};
// IMPORTANT: NOT PORTED TO v3 standards (not using res.respond)
/**