Files
habitica/test/api/v3/unit/libs/stripePayments.test.js
Keith Holliday 638525f8d8 Stripe refactor to lib (#8417)
* Moved stripe tests to folder

* Abstracted stripe payments logic to lib

* Added initial unit test for stripe payment

* Added subscription tests

* Added tests for regulare purchases

* Added tests for edit subscription

* Added cancel tests

* Added integration tests

* Fixed lint issues

* Fixed lint issue
2017-01-20 16:08:52 -07:00

662 lines
18 KiB
JavaScript

import stripeModule from 'stripe';
import cc from 'coupon-code';
import {
generateGroup,
} from '../../../../helpers/api-unit.helper.js';
import { model as User } from '../../../../../website/server/models/user';
import { model as Coupon } from '../../../../../website/server/models/coupon';
import stripePayments from '../../../../../website/server/libs/stripePayments';
import payments from '../../../../../website/server/libs/payments';
import common from '../../../../../website/common';
const i18n = common.i18n;
describe('Stripe Payments', () => {
let subKey = 'basic_3mo';
let stripe = stripeModule('test');
describe('checkout', () => {
let stripeChargeStub, paymentBuyGemsStub, paymentCreateSubscritionStub;
let user, gift, groupId, email, headers, coupon, customerIdResponse, token;
beforeEach(() => {
user = new User();
user.profile.name = 'sender';
user.purchased.plan.customerId = 'customer-id';
user.purchased.plan.planId = subKey;
user.purchased.plan.lastBillingDate = new Date();
token = 'test-token';
customerIdResponse = 'example-customerIdResponse';
let stripCustomerResponse = {
id: customerIdResponse,
};
stripeChargeStub = sinon.stub(stripe.charges, 'create').returnsPromise().resolves(stripCustomerResponse);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
});
afterEach(() => {
stripe.charges.create.restore();
payments.buyGems.restore();
payments.createSubscription.restore();
});
it('should purchase gems', async () => {
await stripePayments.checkout({
token,
user,
gift,
groupId,
email,
headers,
coupon,
}, stripe);
expect(stripeChargeStub).to.be.calledOnce;
expect(stripeChargeStub).to.be.calledWith({
amount: 500,
currency: 'usd',
card: token,
});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Stripe',
gift,
});
});
it('should gift gems', async () => {
let receivingUser = new User();
receivingUser.save();
gift = {
type: 'gems',
gems: {
amount: 16,
uuid: receivingUser._id,
},
};
await stripePayments.checkout({
token,
user,
gift,
groupId,
email,
headers,
coupon,
}, stripe);
gift.member = receivingUser;
expect(stripeChargeStub).to.be.calledOnce;
expect(stripeChargeStub).to.be.calledWith({
amount: '400',
currency: 'usd',
card: token,
});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Gift',
gift,
});
});
it('should gift a subscription', async () => {
let receivingUser = new User();
receivingUser.save();
gift = {
type: 'subscription',
subscription: {
key: subKey,
uuid: receivingUser._id,
},
};
await stripePayments.checkout({
token,
user,
gift,
groupId,
email,
headers,
coupon,
}, stripe);
gift.member = receivingUser;
expect(stripeChargeStub).to.be.calledOnce;
expect(stripeChargeStub).to.be.calledWith({
amount: '1500',
currency: 'usd',
card: token,
});
expect(paymentCreateSubscritionStub).to.be.calledOnce;
expect(paymentCreateSubscritionStub).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Gift',
gift,
});
});
});
describe('checkout with subscription', () => {
let user, group, data, gift, sub, groupId, email, headers, coupon, customerIdResponse, subscriptionId, token;
let spy;
let stripeCreateCustomerSpy;
let stripePaymentsCreateSubSpy;
beforeEach(async () => {
user = new User();
user.profile.name = 'sender';
user.purchased.plan.customerId = 'customer-id';
user.purchased.plan.planId = subKey;
user.purchased.plan.lastBillingDate = new Date();
group = generateGroup({
name: 'test group',
type: 'guild',
privacy: 'public',
leader: user._id,
});
group.purchased.plan.customerId = 'customer-id';
group.purchased.plan.planId = subKey;
await group.save();
sub = {
key: 'basic_3mo',
};
data = {
user,
sub,
customerId: 'customer-id',
paymentMethod: 'Payment Method',
};
email = 'example@example.com';
customerIdResponse = 'test-id';
subscriptionId = 'test-sub-id';
token = 'test-token';
spy = sinon.stub(stripe.subscriptions, 'update');
spy.returnsPromise().resolves;
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
let stripCustomerResponse = {
id: customerIdResponse,
subscriptions: {
data: [{id: subscriptionId}],
},
};
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
stripePaymentsCreateSubSpy.returnsPromise().resolves({});
data.groupId = group._id;
data.sub.quantity = 3;
});
afterEach(function () {
sinon.restore(stripe.subscriptions.update);
stripe.customers.create.restore();
payments.createSubscription.restore();
});
it('should throw an error if we are missing a token', async () => {
await expect(stripePayments.checkout({
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
name: 'BadRequest',
message: 'Missing req.body.id',
});
});
it('should throw an error when coupon code is missing', async () => {
sub.discount = 40;
await expect(stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
name: 'BadRequest',
message: i18n.t('couponCodeRequired'),
});
});
it('should throw an error when coupon code is invalid', async () => {
sub.discount = 40;
sub.key = 'google_6mo';
coupon = 'example-coupon';
let couponModel = new Coupon();
couponModel.event = 'google_6mo';
await couponModel.save();
sinon.stub(cc, 'validate').returns('invalid');
await expect(stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
name: 'BadRequest',
message: i18n.t('invalidCoupon'),
});
cc.validate.restore();
});
it('subscribes with amazon with a coupon', async () => {
sub.discount = 40;
sub.key = 'google_6mo';
coupon = 'example-coupon';
let couponModel = new Coupon();
couponModel.event = 'google_6mo';
let updatedCouponModel = await couponModel.save();
sinon.stub(cc, 'validate').returns(updatedCouponModel._id);
await stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}, stripe);
expect(stripeCreateCustomerSpy).to.be.calledOnce;
expect(stripeCreateCustomerSpy).to.be.calledWith({
email,
metadata: { uuid: user._id },
card: token,
plan: sub.key,
});
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Stripe',
sub,
headers,
groupId: undefined,
subscriptionId: undefined,
});
cc.validate.restore();
});
it('subscribes a user', async () => {
sub = data.sub;
await stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}, stripe);
expect(stripeCreateCustomerSpy).to.be.calledOnce;
expect(stripeCreateCustomerSpy).to.be.calledWith({
email,
metadata: { uuid: user._id },
card: token,
plan: sub.key,
});
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Stripe',
sub,
headers,
groupId: undefined,
subscriptionId: undefined,
});
});
it('subscribes a group', async () => {
token = 'test-token';
sub = data.sub;
groupId = group._id;
email = 'test@test.com';
headers = {};
await stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
}, stripe);
expect(stripeCreateCustomerSpy).to.be.calledOnce;
expect(stripeCreateCustomerSpy).to.be.calledWith({
email,
metadata: { uuid: user._id },
card: token,
plan: sub.key,
quantity: 3,
});
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
user,
customerId: customerIdResponse,
paymentMethod: 'Stripe',
sub,
headers,
groupId,
subscriptionId,
});
});
});
describe('edit subscription', () => {
let user, groupId, group, token;
beforeEach(async () => {
user = new User();
user.profile.name = 'sender';
user.purchased.plan.customerId = 'customer-id';
user.purchased.plan.planId = subKey;
user.purchased.plan.lastBillingDate = new Date();
group = generateGroup({
name: 'test group',
type: 'guild',
privacy: 'public',
leader: user._id,
});
group.purchased.plan.customerId = 'customer-id';
group.purchased.plan.planId = subKey;
await group.save();
groupId = group._id;
token = 'test-token';
});
it('throws an error if there is no customer id', async () => {
user.purchased.plan.customerId = undefined;
await expect(stripePayments.editSubscription({
user,
groupId: undefined,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('missingSubscription'),
});
});
it('throws an error if a token is not provided', async () => {
await expect(stripePayments.editSubscription({
user,
groupId: undefined,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
name: 'BadRequest',
message: 'Missing req.body.id',
});
});
it('throws an error if the group is not found', async () => {
await expect(stripePayments.editSubscription({
token,
user,
groupId: 'fake-group',
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 404,
name: 'NotFound',
message: i18n.t('groupNotFound'),
});
});
it('throws an error if user is not the group leader', async () => {
let nonLeader = new User();
nonLeader.guilds.push(groupId);
await nonLeader.save();
await expect(stripePayments.editSubscription({
token,
user: nonLeader,
groupId,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
});
});
describe('success', () => {
let stripeListSubscriptionStub, stripeUpdateSubscriptionStub, subscriptionId;
beforeEach(() => {
subscriptionId = 'subId';
stripeListSubscriptionStub = sinon.stub(stripe.customers, 'listSubscriptions')
.returnsPromise().resolves({
data: [{id: subscriptionId}],
});
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').returnsPromise().resolves({});
});
afterEach(() => {
stripe.customers.listSubscriptions.restore();
stripe.customers.updateSubscription.restore();
});
it('edits a user subscription', async () => {
await stripePayments.editSubscription({
token,
user,
groupId: undefined,
}, stripe);
expect(stripeListSubscriptionStub).to.be.calledOnce;
expect(stripeListSubscriptionStub).to.be.calledWith(user.purchased.plan.customerId);
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
user.purchased.plan.customerId,
subscriptionId,
{ card: token }
);
});
it('edits a group subscription', async () => {
await stripePayments.editSubscription({
token,
user,
groupId,
}, stripe);
expect(stripeListSubscriptionStub).to.be.calledOnce;
expect(stripeListSubscriptionStub).to.be.calledWith(group.purchased.plan.customerId);
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
group.purchased.plan.customerId,
subscriptionId,
{ card: token }
);
});
});
});
describe('cancel subscription', () => {
let user, groupId, group;
beforeEach(async () => {
user = new User();
user.profile.name = 'sender';
user.purchased.plan.customerId = 'customer-id';
user.purchased.plan.planId = subKey;
user.purchased.plan.lastBillingDate = new Date();
group = generateGroup({
name: 'test group',
type: 'guild',
privacy: 'public',
leader: user._id,
});
group.purchased.plan.customerId = 'customer-id';
group.purchased.plan.planId = subKey;
await group.save();
groupId = group._id;
});
it('throws an error if there is no customer id', async () => {
user.purchased.plan.customerId = undefined;
await expect(stripePayments.cancelSubscription({
user,
groupId: undefined,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('missingSubscription'),
});
});
it('throws an error if the group is not found', async () => {
await expect(stripePayments.cancelSubscription({
user,
groupId: 'fake-group',
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 404,
name: 'NotFound',
message: i18n.t('groupNotFound'),
});
});
it('throws an error if user is not the group leader', async () => {
let nonLeader = new User();
nonLeader.guilds.push(groupId);
await nonLeader.save();
await expect(stripePayments.cancelSubscription({
user: nonLeader,
groupId,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
});
});
describe('success', () => {
let stripeDeleteCustomerStub, paymentsCancelSubStub, stripeRetrieveStub, subscriptionId, currentPeriodEndTimeStamp;
beforeEach(() => {
subscriptionId = 'subId';
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
currentPeriodEndTimeStamp = (new Date()).getTime();
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
.returnsPromise().resolves({
subscriptions: {
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
},
});
});
afterEach(() => {
stripe.customers.del.restore();
stripe.customers.retrieve.restore();
payments.cancelSubscription.restore();
});
it('cancels a user subscription', async () => {
await stripePayments.cancelSubscription({
user,
groupId: undefined,
}, stripe);
expect(stripeDeleteCustomerStub).to.be.calledOnce;
expect(stripeDeleteCustomerStub).to.be.calledWith(user.purchased.plan.customerId);
expect(stripeRetrieveStub).to.be.calledOnce;
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
expect(paymentsCancelSubStub).to.be.calledOnce;
expect(paymentsCancelSubStub).to.be.calledWith({
user,
groupId: undefined,
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
paymentMethod: 'Stripe',
});
});
it('cancels a group subscription', async () => {
await stripePayments.cancelSubscription({
user,
groupId,
}, stripe);
expect(stripeDeleteCustomerStub).to.be.calledOnce;
expect(stripeDeleteCustomerStub).to.be.calledWith(group.purchased.plan.customerId);
expect(stripeRetrieveStub).to.be.calledOnce;
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
expect(paymentsCancelSubStub).to.be.calledOnce;
expect(paymentsCancelSubStub).to.be.calledWith({
user,
groupId,
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
paymentMethod: 'Stripe',
});
});
});
});
});