Allow gems and subs to be gifted through in-app-purchases (#10892)

* Allow gems to be gifted through IAPs

* implement non recurring IAP subscriptions

* fix localization issue in error

* fix non renewing subscription handling

* Fix lint error

* fix tests

* move findbyId mock to helper file

* undo package-lock changes

* Fix lint error
This commit is contained in:
Phillip Thelen
2018-12-23 19:20:14 +01:00
committed by Matteo Pagliazzi
parent 88f28188a1
commit cfbfec34aa
12 changed files with 557 additions and 40 deletions

View File

@@ -13,6 +13,7 @@ let api = {};
api.constants = {
PAYMENT_METHOD_APPLE: 'Apple',
PAYMENT_METHOD_GIFT: 'Apple (Gift)',
RESPONSE_INVALID_RECEIPT: 'INVALID_RECEIPT',
RESPONSE_ALREADY_USED: 'RECEIPT_ALREADY_USED',
RESPONSE_INVALID_ITEM: 'INVALID_ITEM_PURCHASED',
@@ -20,9 +21,15 @@ api.constants = {
RESPONSE_NO_ITEM_PURCHASED: 'NO_ITEM_PURCHASED',
};
api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers) {
const userCanGetGems = await user.canGetGems();
if (!userCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', user.preferences.language));
api.verifyGemPurchase = async function verifyGemPurchase (options) {
let {gift, user, receipt, headers} = options;
if (gift) {
gift.member = await User.findById(gift.uuid).exec();
}
const receiver = gift ? gift.member : user;
const receiverCanGetGems = await receiver.canGetGems();
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', user.preferences.language));
await iap.setup();
let appleRes = await iap.validate(iap.APPLE, receipt);
@@ -45,6 +52,7 @@ api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers
await IapPurchaseReceipt.create({ // eslint-disable-line no-await-in-loop
_id: token,
consumed: true,
// This should always be the buying user even for a gift.
userId: user._id,
});
@@ -67,7 +75,7 @@ api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers
if (amount) {
correctReceipt = true;
await payments.buyGems({ // eslint-disable-line no-await-in-loop
user,
user: receiver,
paymentMethod: api.constants.PAYMENT_METHOD_APPLE,
amount,
headers,
@@ -148,6 +156,81 @@ api.subscribe = async function subscribe (sku, user, receipt, headers, nextPayme
}
};
api.noRenewSubscribe = async function noRenewSubscribe (options) {
let {sku, gift, user, receipt, headers} = options;
if (!sku) throw new BadRequest(shared.i18n.t('missingSubscriptionCode'));
let subCode;
switch (sku) {
case 'com.habitrpg.ios.habitica.norenew_subscription.1month':
subCode = 'basic_earned';
break;
case 'com.habitrpg.ios.habitica.norenew_subscription.3month':
subCode = 'basic_3mo';
break;
case 'com.habitrpg.ios.habitica.norenew_subscription.6month':
subCode = 'basic_6mo';
break;
case 'com.habitrpg.ios.habitica.norenew_subscription.12month':
subCode = 'basic_12mo';
break;
}
const sub = subCode ? shared.content.subscriptionBlocks[subCode] : false;
if (!sub) throw new NotAuthorized(this.constants.RESPONSE_INVALID_ITEM);
await iap.setup();
let appleRes = await iap.validate(iap.APPLE, receipt);
const isValidated = iap.isValidated(appleRes);
if (!isValidated) throw new NotAuthorized(api.constants.RESPONSE_INVALID_RECEIPT);
let purchaseDataList = iap.getPurchaseData(appleRes);
if (purchaseDataList.length === 0) throw new NotAuthorized(api.constants.RESPONSE_NO_ITEM_PURCHASED);
let transactionId;
for (let index in purchaseDataList) {
let purchaseData = purchaseDataList[index];
let dateTerminated = new Date(Number(purchaseData.expirationDate));
if (purchaseData.productId === sku && dateTerminated > new Date()) {
transactionId = purchaseData.transactionId;
break;
}
}
if (transactionId) {
let existingReceipt = await IapPurchaseReceipt.findOne({ // eslint-disable-line no-await-in-loop
_id: transactionId,
}).exec();
if (existingReceipt) throw new NotAuthorized(this.constants.RESPONSE_ALREADY_USED);
await IapPurchaseReceipt.create({ // eslint-disable-line no-await-in-loop
_id: transactionId,
consumed: true,
// This should always be the buying user even for a gift.
userId: user._id,
});
let data = {
user,
paymentMethod: this.constants.PAYMENT_METHOD_APPLE,
headers,
sub,
autoRenews: false,
};
if (gift) {
gift.member = await User.findById(gift.uuid).exec();
gift.subscription = sub;
data.gift = gift;
data.paymentMethod = this.constants.PAYMENT_METHOD_GIFT;
}
await payments.createSubscription(data);
} else {
throw new NotAuthorized(api.constants.RESPONSE_INVALID_RECEIPT);
}
};
api.cancelSubscribe = async function cancelSubscribe (user, headers) {
let plan = user.purchased.plan;