diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js index 49b4bd5361..5f048083f3 100644 --- a/website/server/controllers/top-level/payments/iap.js +++ b/website/server/controllers/top-level/payments/iap.js @@ -8,6 +8,7 @@ import { NotAuthorized, } from '../../../libs/api-v3/errors'; import { model as IapPurchaseReceipt } from '../../../models/iapPurchaseReceipt'; +import logger from '../../../libs/api-v3/logger'; let api = {}; @@ -35,32 +36,28 @@ api.iapAndroidVerify = { let googleRes = await iap.validate(iap.GOOGLE, testObj); - if (iap.isValidated(googleRes)) { - let receiptObj = JSON.parse(testObj.data); // passed as a string - let token = receiptObj.token || receiptObj.purchaseToken; + let isValidated = iap.isValidated(googleRes); + if (!isValidated) throw new NotAuthorized('INVALID_RECEIPT'); - let existingReceipt = await IapPurchaseReceipt.findOne({ - _id: token, - }).exec(); + let receiptObj = JSON.parse(testObj.data); // passed as a string + let token = receiptObj.token || receiptObj.purchaseToken; - if (!existingReceipt) { - await IapPurchaseReceipt.create({ - _id: token, - consumed: true, - userId: user._id, - }); + let existingReceipt = await IapPurchaseReceipt.findOne({ + _id: token, + }).exec(); + if (existingReceipt) throw new NotAuthorized('RECEIPT_ALREADY_USED'); - await payments.buyGems({ - user, - paymentMethod: 'IAP GooglePlay', - amount: 5.25, - }); - } else { - throw new NotAuthorized('RECEIPT_ALREADY_USED'); - } - } else { - throw new NotAuthorized('INVALID_RECEIPT'); - } + await IapPurchaseReceipt.create({ + _id: token, + consumed: true, + userId: user._id, + }); + + await payments.buyGems({ + user, + paymentMethod: 'IAP GooglePlay', + amount: 5.25, + }); res.respond(200, googleRes); }, @@ -80,11 +77,79 @@ api.iapiOSVerify = { url: '/iap/ios/verify', middlewares: [authWithHeaders()], async handler (req, res) { - let resObject = await iapIOSVerify(res.locals.user, req.body); + let user = res.locals.user; + let iapBody = req.body; - return res - .status(resObject.ok === true ? 200 : 500) - .json(resObject); + let appleRes; + let token; + + try { + await iap.setup(); + + appleRes = await iap.validate(iap.APPLE, iapBody.transaction.receipt); + let isValidated = iap.isValidated(appleRes); + if (!isValidated) throw new Error('INVALID_RECEIPT'); + + let purchaseDataList = iap.getPurchaseData(appleRes); + if (purchaseDataList.length === 0) throw new Error('NO_ITEM_PURCHASED'); + + let correctReceipt = true; + + // Purchasing one item at a time (processing of await(s) below is sequential not parallel) + for (let purchaseData of purchaseDataList) { + token = purchaseData.transactionId; + + let existingReceipt = await IapPurchaseReceipt.findOne({ // eslint-disable-line babel/no-await-in-loop + _id: token, + }).exec(); + + if (!existingReceipt) { + await IapPurchaseReceipt.create({ // eslint-disable-line babel/no-await-in-loop + _id: token, + consumed: true, + userId: user._id, + }); + } else { + throw new Error('RECEIPT_ALREADY_USED'); + } + + switch (purchaseData.productId) { + case 'com.habitrpg.ios.Habitica.4gems': + await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1}); // eslint-disable-line babel/no-await-in-loop + break; + case 'com.habitrpg.ios.Habitica.8gems': + await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2}); // eslint-disable-line babel/no-await-in-loop + break; + case 'com.habitrpg.ios.Habitica.20gems': + case 'com.habitrpg.ios.Habitica.21gems': + await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25}); // eslint-disable-line babel/no-await-in-loop + break; + case 'com.habitrpg.ios.Habitica.42gems': + await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5}); // eslint-disable-line babel/no-await-in-loop + break; + default: + correctReceipt = false; + } + } + + if (!correctReceipt) throw new Error('INVALID_ITEM_PURCHASED'); + + return res.status(200).json({ + ok: true, + data: appleRes, + }); + } catch (err) { + logger.error(err, { + userId: user._id, + iapBody, + appleRes, + }); + + return res.status(500).json({ + ok: false, + data: 'An error occurred while processing the purchase.', + }); + } }, }; diff --git a/website/server/libs/api-v3/inAppPurchases.js b/website/server/libs/api-v3/inAppPurchases.js index 3fb47427e5..5f1d16b135 100644 --- a/website/server/libs/api-v3/inAppPurchases.js +++ b/website/server/libs/api-v3/inAppPurchases.js @@ -1,9 +1,6 @@ import nconf from 'nconf'; import iap from 'in-app-purchase'; -import payments from './payments'; -import { model as IapPurchaseReceipt } from '../../models/iapPurchaseReceipt'; import Bluebird from 'bluebird'; -import logger from './logger'; // Validation ERROR Codes // const INVALID_PAYLOAD = 6778001; @@ -18,78 +15,8 @@ iap.config({ module.exports = { setup: Bluebird.promisify(iap.setup, { context: iap }), validate: Bluebird.promisify(iap.validate, { context: iap }), + isValidated: iap.isValidated, + getPurchaseData: iap.getPurchaseData, GOOGLE: iap.GOOGLE, -}; - -async function iapIOSVerify (user, iapBody) { - // Defining these 2 variables here so they can be logged in case of error - let token; - let appleRes; - - try { - await iapSetup(); - appleRes = await iapValidate(iap.APPLE, iapBody.transaction.receipt); - - if (iap.isValidated(appleRes)) { - token = appleRes.receipt.transactionIdentifier; - - console.log(JSON.stringify(appleRes), JSON.stringify(appleRes.receipt)); - - let existingReceipt = await IapPurchaseReceipt.findOne({ - _id: token, - }).exec(); - - if (!existingReceipt) { - await IapPurchaseReceipt.create({ - _id: token, - consumed: true, - userId: user._id, - }); - } else { - throw new Error('RECEIPT_ALREADY_USED'); - } - - let purchaseDataList = iap.getPurchaseData(appleRes); - if (purchaseDataList.length === 0) throw new Error('NO_ITEM_PURCHASED'); - - let correctReceipt = true; - - // Purchasing one item at a time (processing of await(s) below is sequential not parallel) - for (let index in purchaseDataList) { - switch (purchaseDataList[index].productId) { - case 'com.habitrpg.ios.Habitica.4gems': - await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1}); // eslint-disable-line babel/no-await-in-loop - break; - case 'com.habitrpg.ios.Habitica.8gems': - await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2}); // eslint-disable-line babel/no-await-in-loop - break; - case 'com.habitrpg.ios.Habitica.20gems': - case 'com.habitrpg.ios.Habitica.21gems': - await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25}); // eslint-disable-line babel/no-await-in-loop - break; - case 'com.habitrpg.ios.Habitica.42gems': - await payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5}); // eslint-disable-line babel/no-await-in-loop - break; - default: - correctReceipt = false; - } - } - - if (!correctReceipt) throw new Error('INVALID_ITEM_PURCHASED'); - } else { - throw new Error('INVALID_RECEIPT'); - } - } catch (err) { - logger.error(err, { - userId: user._id, - iapBody, - appleRes, - token, - }); - - return { - ok: false, - data: 'An error occurred while processing the purchase.', - }; - } -} \ No newline at end of file + APPLE: iap.APPLE, +}; \ No newline at end of file