Files
habitica/test/api/unit/libs/payments/google.test.js
Matteo Pagliazzi 6d34319455 Stripe: upgrade module and API, switch to Checkout (#12785)
* upgrade stripe module

* switch stripe api to latest version

* fix api version in tests

* start upgrading client and server

* client: switch to redirect

* implement checkout session creation for gems, start implementing webhooks

* stripe: start refactoring one time payments

* working gems and gift payments

* start adding support for subscriptions

* stripe: migrate subscriptions and fix cancelling sub

* allow upgrading group plans

* remove console.log statements

* group plans: upgrade from static page / create new one

* fix #11885, correct group plan modal title

* silence more stripe webhooks

* fix group plans redirects

* implement editing payment method

* start cleaning up code

* fix(stripe): update in-code docs, fix eslint issues

* subscriptions tests

* remove and skip old tests

* skip integration tests

* fix client build

* stripe webhooks: throw error if request fails

* subscriptions: correctly pass groupId

* remove console.log

* stripe: add unit tests for one time payments

* wip: stripe checkout tests

* stripe createCheckoutSession unit tests

* stripe createCheckoutSession unit tests

* stripe createCheckoutSession unit tests (editing card)

* fix existing webhooks tests

* add new webhooks tests

* add more webhooks tests

* fix lint

* stripe integration tests

* better error handling when retrieving customer from stripe

* client: remove unused strings and improve error handling

* payments: limit gift message length (server)

* payments: limit gift message length (client)

* fix redirects when payment is cancelled

* add back "subUpdateCard" string

* fix redirects when editing a sub card, use proper names for products, check subs when gifting
2020-12-14 15:59:17 +01:00

330 lines
11 KiB
JavaScript

/* eslint-disable camelcase */
import moment from 'moment';
import payments from '../../../../../website/server/libs/payments/payments';
import googlePayments from '../../../../../website/server/libs/payments/google';
import iap from '../../../../../website/server/libs/inAppPurchases';
import { model as User } from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import * as gems from '../../../../../website/server/libs/payments/gems';
const { i18n } = common;
describe('Google Payments', () => {
const subKey = 'basic_3mo';
describe('verifyGemPurchase', () => {
let sku; let user; let token; let receipt; let signature; let
headers; const gemsBlock = common.content.gems['21gems'];
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentBuyGemsStub; let validateGiftMessageStub;
beforeEach(() => {
sku = 'com.habitrpg.android.habitica.iap.21gems';
user = new User();
receipt = `{"token": "${token}", "productId": "${sku}"}`;
signature = '';
headers = {};
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
payments.buyGems.restore();
gems.validateGiftMessage.restore();
});
it('should throw an error if receipt is invalid', async () => {
iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false);
await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_RECEIPT,
});
});
it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`;
await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
});
});
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('groupPolicyCannotGetGems'),
});
user.canGetGems.restore();
});
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
});
expect(validateGiftMessageStub).to.not.be.called;
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
gemsBlock,
headers,
gift: undefined,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
const gift = { uuid: receivingUser._id };
await googlePayments.verifyGemPurchase({
user, gift, receipt, signature, headers,
});
expect(validateGiftMessageStub).to.be.calledOnce;
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
gemsBlock,
headers,
gift: {
type: 'gems',
gems: { amount: 21 },
member: sinon.match({ _id: receivingUser._id }),
uuid: receivingUser._id,
},
});
});
});
describe('subscribe', () => {
let sub; let sku; let user; let token; let receipt; let signature; let headers; let
nextPaymentProcessing;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentsCreateSubscritionStub;
beforeEach(() => {
sub = common.content.subscriptionBlocks[subKey];
sku = 'com.habitrpg.android.habitica.subscription.3month';
token = 'test-token';
headers = {};
receipt = `{"token": "${token}"}`;
signature = '';
nextPaymentProcessing = moment.utc().add({ days: 2 });
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
payments.createSubscription.restore();
});
it('should throw an error if receipt is invalid', async () => {
iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false);
await expect(googlePayments
.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_RECEIPT,
});
});
it('should throw an error if sku is invalid', async () => {
sku = 'invalid';
await expect(googlePayments
.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
});
});
it('creates a user subscription', async () => {
await googlePayments.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
expect(paymentsCreateSubscritionStub).to.be.calledWith({
user,
customerId: token,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
sub,
headers,
additionalData: { data: receipt, signature },
nextPaymentProcessing,
});
});
});
describe('cancelSubscribe ', () => {
let user; let token; let receipt; let signature; let headers; let customerId; let
expirationDate;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
paymentCancelSubscriptionSpy;
beforeEach(async () => {
token = 'test-token';
headers = {};
receipt = `{"token": "${token}"}`;
signature = '';
customerId = 'test-customerId';
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: expirationDate.toDate() }]);
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
user = new User();
user.profile.name = 'sender';
user.purchased.plan.customerId = customerId;
user.purchased.plan.paymentMethod = googlePayments.constants.PAYMENT_METHOD_GOOGLE;
user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = { data: receipt, signature };
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.getPurchaseData.restore();
payments.cancelSubscription.restore();
});
it('should throw an error if we are missing a subscription', async () => {
user.purchased.plan.paymentMethod = undefined;
await expect(googlePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('missingSubscription'),
});
});
it('should throw an error if receipt is invalid', async () => {
iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false);
await expect(googlePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_RECEIPT,
});
});
it('should cancel a user subscription', async () => {
await googlePayments.cancelSubscribe(user, headers);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({
expirationDate,
});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledWith({
user,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
nextBill: expirationDate.toDate(),
headers,
});
});
});
});