Files
habitica/website/server/libs/payments/stripe/checkout.js
Matteo Pagliazzi 83aca20ce5 Fall Festival Gem Promo (#138)
* content: add gems blocks

* gemsBlocks: include ios and android identifiers

* wip: promo code

* split common constants into multiple files

* add second promo part

* geCurrentEvent, refactor promo

* fix lint

* fix exports, use world state api

* start adding world state tests

* remove console.log

* use gems block for purchases

* remove comments

* fix most unit tests

* restore comment

* fix lint

* prevent apple/google gift tests from breaking other tests when stub is not reset

* fix unit tests, clarify tests names

* iap: use gift object when gifting gems

* allow gift object with less data

* fix iap tests, remove findById stubs

* iap: require less data from the mobile apps

* apply discounts

* add missing worldState file

* fix lint

* add test event

* start removing 20 gems option for web

* start adding support for all gems packages on web

* fix unit tests for apple, stripe and google

* amazon: support all gems blocks

* paypal: support all gems blocks

* fix payments unit tests, add tests for getGemsBlock

* web: add gems plans with discounts, update stripe

* fix amazon and paypal clients, payments success modals

* amazon pay: disabled state

* update icons, start abstracting payments buttons

* begin redesign

* redesign gems modal

* fix buttons

* fix hover color for gems modal close icon

* add key to world state current event

* extend test event length

* implement gems modals designs

* early test fall2020

* fix header banner position

* add missing files

* use iso 8601 for dates, minor ui fixes

* fix time zones

* events: fix ISO8601 format

* fix css indentation

* start abstracting banners

* refactor payments buttons

* test spooky, fix group plans box

* implement gems promo banners, refactor banners, fixes

* fix lint

* fix dates

* remove unused i18n strings

* fix stripe integration test

* fix world state integration tests

* the current active event

* add missing unit tests

* add storybook story for payments buttons component

* fix typo

* fix(stripe): correct label when gifting subscriptions
2020-09-21 16:22:13 +02:00

162 lines
4.2 KiB
JavaScript

import cc from 'coupon-code';
import { getStripeApi } from './api';
import { model as User } from '../../../models/user'; // eslint-disable-line import/no-cycle
import { model as Coupon } from '../../../models/coupon';
import { // eslint-disable-line import/no-cycle
model as Group,
basicFields as basicGroupFields,
} from '../../../models/group';
import shared from '../../../../common';
import {
BadRequest,
NotAuthorized,
} from '../../errors';
import payments from '../payments'; // eslint-disable-line import/no-cycle
import { getGemsBlock } from '../gems'; // eslint-disable-line import/no-cycle
import stripeConstants from './constants';
function getGiftAmount (gift) {
if (gift.type === 'subscription') {
return `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`;
}
if (gift.gems.amount <= 0) {
throw new BadRequest(shared.i18n.t('badAmountOfGemsToPurchase'));
}
return `${(gift.gems.amount / 4) * 100}`;
}
async function buyGems (gemsBlock, gift, user, token, stripeApi) {
let amount;
if (gift) {
amount = getGiftAmount(gift);
} else {
amount = gemsBlock.price;
}
if (!gift || gift.type === 'gems') {
const receiver = gift ? gift.member : user;
const receiverCanGetGems = await receiver.canGetGems();
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
}
const response = await stripeApi.charges.create({
amount,
currency: 'usd',
card: token,
});
return response;
}
async function buySubscription (sub, coupon, email, user, token, groupId, stripeApi) {
if (sub.discount) {
if (!coupon) throw new BadRequest(shared.i18n.t('couponCodeRequired'));
coupon = await Coupon // eslint-disable-line no-param-reassign
.findOne({ _id: cc.validate(coupon), event: sub.key }).exec();
if (!coupon) throw new BadRequest(shared.i18n.t('invalidCoupon'));
}
const customerObject = {
email,
metadata: { uuid: user._id },
card: token,
plan: sub.key,
};
if (groupId) {
customerObject.quantity = sub.quantity;
const groupFields = basicGroupFields.concat(' purchased');
const group = await Group.getGroup({
user, groupId, populateLeader: false, groupFields,
});
const membersCount = await group.getMemberCount();
customerObject.quantity = membersCount + sub.quantity - 1;
}
const response = await stripeApi.customers.create(customerObject);
let subscriptionId;
if (groupId) subscriptionId = response.subscriptions.data[0].id;
return { subResponse: response, subId: subscriptionId };
}
async function applyGemPayment (user, response, gemsBlock, gift) {
let method = 'buyGems';
const data = {
user,
customerId: response.id,
paymentMethod: stripeConstants.PAYMENT_METHOD,
gemsBlock,
gift,
};
if (gift) {
if (gift.type === 'subscription') method = 'createSubscription';
data.paymentMethod = 'Gift';
}
await payments[method](data);
}
export async function checkout (options, stripeInc) {
const {
token,
user,
gift,
gemsBlock,
sub,
groupId,
email,
headers,
coupon,
} = options;
let response;
let subscriptionId;
// @TODO: We need to mock this, but curently we don't have correct
// Dependency Injection. And the Stripe Api doesn't seem to be a singleton?
let stripeApi = getStripeApi();
if (stripeInc) stripeApi = stripeInc;
if (!token) throw new BadRequest('Missing req.body.id');
if (gift) {
const member = await User.findById(gift.uuid).exec();
gift.member = member;
}
let block;
if (!sub && !gift) {
block = getGemsBlock(gemsBlock);
}
if (sub) {
const { subId, subResponse } = await buySubscription(
sub, coupon, email, user, token, groupId, stripeApi,
);
subscriptionId = subId;
response = subResponse;
} else {
response = await buyGems(block, gift, user, token, stripeApi);
}
if (sub) {
await payments.createSubscription({
user,
customerId: response.id,
paymentMethod: this.constants.PAYMENT_METHOD,
sub,
headers,
groupId,
subscriptionId,
});
} else {
await applyGemPayment(user, response, block, gift);
}
}