Group plan subscription (#8153)

* Added payment to groups and pay with group plan with Stripe

* Added edit card for Stripe

* Added stripe cancel

* Added subscribe with Amazon payments

* Added Amazon cancel for group subscription

* Added group subscription with paypal

* Added paypal cancel

* Added ipn cancel for Group plan

* Added a subscription tab and hid only the task tab when group is not subscribed

* Fixed linting issues

* Fixed tests

* Added payment unit tests

* Added back refresh after stripe payment

* Fixed style issues

* Limited grouop query fields and checked access

* Abstracted subscription schema

* Added year group plan and more access checks

* Maded purchase fields private

* Removed id and timestampes

* Added else checks to ensure user subscription is not altered. Removed active field from group model

* Added toJSONTransform function

* Moved plan active check to other toJson function

* Added check to see if purchaed has been populated

* Added purchase details to private

* Added correct data usage when paying for group sub
This commit is contained in:
Keith Holliday
2016-11-01 21:51:30 +01:00
committed by Matteo Pagliazzi
parent 7f38c61c70
commit d8c37f6e2d
14 changed files with 558 additions and 118 deletions

View File

@@ -1,6 +1,7 @@
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../../../libs/errors';
import amzLib from '../../../libs/amazonPayments';
import {
@@ -12,6 +13,10 @@ import payments from '../../../libs/payments';
import moment from 'moment';
import { model as Coupon } from '../../../models/coupon';
import { model as User } from '../../../models/user';
import {
model as Group,
basicFields as basicGroupFields,
} from '../../../models/group';
import cc from 'coupon-code';
let api = {};
@@ -34,6 +39,7 @@ api.verifyAccessToken = {
if (!accessToken) throw new BadRequest('Missing req.body.access_token');
await amzLib.getTokenInfo(accessToken);
res.respond(200, {});
},
};
@@ -164,6 +170,7 @@ api.subscribe = {
let sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false;
let coupon = req.body.coupon;
let user = res.locals.user;
let groupId = req.body.groupId;
if (!sub) throw new BadRequest(res.t('missingSubscriptionCode'));
if (!billingAgreementId) throw new BadRequest('Missing req.body.billingAgreementId');
@@ -213,6 +220,7 @@ api.subscribe = {
paymentMethod: 'Amazon Payments',
sub,
headers: req.headers,
groupId,
});
res.respond(200);
@@ -231,7 +239,32 @@ api.subscribeCancel = {
middlewares: [authWithUrl],
async handler (req, res) {
let user = res.locals.user;
let billingAgreementId = user.purchased.plan.customerId;
let groupId = req.query.groupId;
let billingAgreementId;
let planId;
let lastBillingDate;
if (groupId) {
let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields});
if (!group) {
throw new NotFound(res.t('groupNotFound'));
}
if (!group.leader === user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderCanManageSubscription'));
}
billingAgreementId = group.purchased.plan.customerId;
planId = group.purchased.plan.planId;
lastBillingDate = group.purchased.plan.lastBillingDate;
} else {
billingAgreementId = user.purchased.plan.customerId;
planId = user.purchased.plan.planId;
lastBillingDate = user.purchased.plan.lastBillingDate;
}
if (!billingAgreementId) throw new NotAuthorized(res.t('missingSubscription'));
@@ -245,12 +278,13 @@ api.subscribeCancel = {
});
}
let subscriptionBlock = shared.content.subscriptionBlocks[user.purchased.plan.planId];
let subscriptionBlock = shared.content.subscriptionBlocks[planId];
let subscriptionLength = subscriptionBlock.months * 30;
await payments.cancelSubscription({
user,
nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
groupId,
nextBill: moment(lastBillingDate).add({ days: subscriptionLength }),
paymentMethod: 'Amazon Payments',
headers: req.headers,
});

View File

@@ -11,6 +11,10 @@ import cc from 'coupon-code';
import Bluebird from 'bluebird';
import { model as Coupon } from '../../../models/coupon';
import { model as User } from '../../../models/user';
import {
model as Group,
basicFields as basicGroupFields,
} from '../../../models/group';
import {
authWithUrl,
authWithSession,
@@ -18,6 +22,7 @@ import {
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../../../libs/errors';
const BASE_URL = nconf.get('BASE_URL');
@@ -178,6 +183,7 @@ api.subscribe = {
let billingAgreement = await paypalBillingAgreementCreate(billingAgreementAttributes);
req.session.paypalBlock = req.query.sub;
req.session.groupId = req.query.groupId;
let link = _.find(billingAgreement.links, { rel: 'approval_url' }).href;
res.redirect(link);
},
@@ -196,11 +202,15 @@ api.subscribeSuccess = {
async handler (req, res) {
let user = res.locals.user;
let block = shared.content.subscriptionBlocks[req.session.paypalBlock];
let groupId = req.session.groupId;
delete req.session.paypalBlock;
delete req.session.groupId;
let result = await paypalBillingAgreementExecute(req.query.token, {});
await payments.createSubscription({
user,
groupId,
customerId: result.id,
paymentMethod: 'Paypal',
sub: block,
@@ -223,8 +233,26 @@ api.subscribeCancel = {
middlewares: [authWithUrl],
async handler (req, res) {
let user = res.locals.user;
let customerId = user.purchased.plan.customerId;
if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription'));
let groupId = req.query.groupId;
let customerId;
if (groupId) {
let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields});
if (!group) {
throw new NotFound(res.t('groupNotFound'));
}
if (!group.leader === user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderCanManageSubscription'));
}
customerId = group.purchased.plan.customerId;
} else {
customerId = user.purchased.plan.customerId;
}
if (!customerId) throw new NotAuthorized(res.t('missingSubscription'));
let customer = await paypalBillingAgreementGet(customerId);
@@ -236,6 +264,7 @@ api.subscribeCancel = {
await paypalBillingAgreementCancel(customerId, { note: res.t('cancelingSubscription') });
await payments.cancelSubscription({
user,
groupId,
paymentMethod: 'Paypal',
nextBill: nextBillingDate,
});
@@ -265,6 +294,13 @@ api.ipn = {
let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id });
if (user) {
await payments.cancelSubscription({ user, paymentMethod: 'Paypal' });
return;
}
let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }).select(groupFields).exec();
if (group) {
await payments.cancelSubscription({ groupId: group._id, paymentMethod: 'Paypal' });
}
}
},

View File

@@ -3,11 +3,16 @@ import shared from '../../../../common';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../../../libs/errors';
import { model as Coupon } from '../../../models/coupon';
import payments from '../../../libs/payments';
import nconf from 'nconf';
import { model as User } from '../../../models/user';
import {
model as Group,
basicFields as basicGroupFields,
} from '../../../models/group';
import cc from 'coupon-code';
import {
authWithHeaders,
@@ -41,6 +46,7 @@ api.checkout = {
let user = res.locals.user;
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
let sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false;
let groupId = req.query.groupId;
let coupon;
let response;
@@ -84,6 +90,7 @@ api.checkout = {
paymentMethod: 'Stripe',
sub,
headers: req.headers,
groupId,
});
} else {
let method = 'buyGems';
@@ -124,8 +131,26 @@ api.subscribeEdit = {
middlewares: [authWithHeaders()],
async handler (req, res) {
let token = req.body.id;
let groupId = req.body.groupId;
let user = res.locals.user;
let customerId = user.purchased.plan.customerId;
let customerId;
// If we are buying a group subscription
if (groupId) {
let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields});
if (!group) {
throw new NotFound(res.t('groupNotFound'));
}
if (!group.leader === user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderCanManageSubscription'));
}
customerId = group.purchased.plan.customerId;
} else {
customerId = user.purchased.plan.customerId;
}
if (!customerId) throw new NotAuthorized(res.t('missingSubscription'));
if (!token) throw new BadRequest('Missing req.body.id');
@@ -150,13 +175,39 @@ api.subscribeCancel = {
middlewares: [authWithUrl],
async handler (req, res) {
let user = res.locals.user;
if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription'));
let groupId = req.query.groupId;
let customerId;
let customer = await stripe.customers.retrieve(user.purchased.plan.customerId);
await stripe.customers.del(user.purchased.plan.customerId);
if (groupId) {
let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields});
if (!group) {
throw new NotFound(res.t('groupNotFound'));
}
if (!group.leader === user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderCanManageSubscription'));
}
customerId = group.purchased.plan.customerId;
} else {
customerId = user.purchased.plan.customerId;
}
if (!customerId) throw new NotAuthorized(res.t('missingSubscription'));
let customer = await stripe.customers.retrieve(customerId);
let subscription = customer.subscription;
if (!subscription) {
subscription = customer.subscriptions.data[0];
}
await stripe.customers.del(customerId);
await payments.cancelSubscription({
user,
nextBill: customer.subscription.current_period_end * 1000, // timestamp in seconds
groupId,
nextBill: subscription.current_period_end * 1000, // timestamp in seconds
paymentMethod: 'Stripe',
});