import cc from 'coupon-code'; import moment from 'moment'; import logger from '../../logger'; import { model as Coupon } from '../../../models/coupon'; import shared from '../../../../common'; import payments from '../payments'; // eslint-disable-line import/no-cycle import stripeConstants from './constants'; import { model as User } from '../../../models/user'; // eslint-disable-line import/no-cycle import { getStripeApi } from './api'; import { // eslint-disable-line import/no-cycle model as Group, basicFields as basicGroupFields, } from '../../../models/group'; import { NotAuthorized, BadRequest, NotFound, } from '../../errors'; export async function checkSubData (sub, isGroup = false, coupon) { if (!sub || !sub.canSubscribe) throw new BadRequest(shared.i18n.t('missingSubscriptionCode')); if ( (sub.target === 'group' && !isGroup) || (sub.target === 'user' && isGroup) ) throw new BadRequest(shared.i18n.t('missingSubscriptionCode')); 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')); } } export async function applySubscription (session) { const { metadata, customer: customerId, subscription: subscriptionId } = session; const { sub: subStringified, userId, groupId, gift, } = metadata; const sub = subStringified ? JSON.parse(subStringified) : undefined; const user = await User.findById(metadata.userId).exec(); if (!user) throw new NotFound(shared.i18n.t('userWithIDNotFound', { userId })); await payments.createSubscription({ user, customerId, paymentMethod: stripeConstants.PAYMENT_METHOD, sub, groupId, subscriptionId, autoRenews: !gift, }); } export async function handlePaymentMethodChange (session, stripeInc) { // @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; const { setup_intent: setupIntent } = session; const intent = await stripeApi.setupIntents.retrieve(setupIntent); const { payment_method: paymentMethodId } = intent; const subscriptionId = intent.metadata.subscription_id; // Update the payment method on the subscription await stripeApi.subscriptions.update(subscriptionId, { default_payment_method: paymentMethodId, }); } export async function chargeForAdditionalGroupMember (group, stripeInc) { // @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; const plan = shared.content.subscriptionBlocks.group_monthly; await stripeApi.subscriptions.update( group.purchased.plan.subscriptionId, { plan: plan.key, quantity: group.memberCount + plan.quantity - 1, }, ); group.purchased.plan.quantity = group.memberCount + plan.quantity - 1; } export async function cancelSubscription (options, stripeInc) { const { groupId, user, cancellationReason } = options; let customerId; // @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 (groupId) { const groupFields = basicGroupFields.concat(' purchased'); const group = await Group.getGroup({ user, groupId, populateLeader: false, groupFields, }); if (!group) { throw new NotFound(shared.i18n.t('groupNotFound')); } const allowedManagers = [group.leader, group.purchased.plan.owner]; if (allowedManagers.indexOf(user._id) === -1) { throw new NotAuthorized(shared.i18n.t('onlyGroupLeaderCanManageSubscription')); } customerId = group.purchased.plan.customerId; } else { customerId = user.purchased.plan.customerId; } if (!customerId) throw new NotAuthorized(shared.i18n.t('missingSubscription')); const customer = await stripeApi .customers.retrieve(customerId, { expand: ['subscriptions'] }) .catch(err => logger.error(err, 'Error retrieving customer from Stripe (was likely deleted).')); let nextBill = moment().add(30, 'days').unix() * 1000; if (customer && (customer.subscription || customer.subscriptions)) { let { subscription } = customer; if (!subscription && customer.subscriptions) { [subscription] = customer.subscriptions.data; } await stripeApi.customers.del(customerId); if (subscription && subscription.current_period_end) { nextBill = subscription.current_period_end * 1000; // timestamp in seconds } } await payments.cancelSubscription({ user, groupId, nextBill, paymentMethod: this.constants.PAYMENT_METHOD, cancellationReason, }); }