mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
V3 payments 7 stripe (#7124)
* payments api: cancelSubscription * some more tests for amazon payments * promisifying amazon payments * somehow payment stub is not working * cleaning up tests * renaming tests in api/v3/integration/payments * improvements * cleanup, lint * fixes as per comments * moment.zone() is back in. * basic controller for stripe payments * authWithUrl is in * stripe cleanup * making tests pass * stripe bug fixes * 400 error is right * cleanup of sinon spy for fakeSend * paypal payments * lint of paypal * require -> import
This commit is contained in:
committed by
Matteo Pagliazzi
parent
fa21577c46
commit
a567476bb7
@@ -19,7 +19,6 @@ website/src/routes/payments.js
|
|||||||
website/src/routes/pages.js
|
website/src/routes/pages.js
|
||||||
website/src/middlewares/apiThrottle.js
|
website/src/middlewares/apiThrottle.js
|
||||||
website/src/middlewares/forceRefresh.js
|
website/src/middlewares/forceRefresh.js
|
||||||
website/src/controllers/top-level/payments/paypal.js
|
|
||||||
|
|
||||||
debug-scripts/*
|
debug-scripts/*
|
||||||
tasks/*.js
|
tasks/*.js
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"missingAuthHeaders": "Missing authentication headers.",
|
"missingAuthHeaders": "Missing authentication headers.",
|
||||||
|
"missingAuthParams": "Missing authentication parameters.",
|
||||||
"missingUsernameEmail": "Missing username or email.",
|
"missingUsernameEmail": "Missing username or email.",
|
||||||
"missingEmail": "Missing email.",
|
"missingEmail": "Missing email.",
|
||||||
"missingUsername": "Missing username.",
|
"missingUsername": "Missing username.",
|
||||||
@@ -175,5 +176,7 @@
|
|||||||
"equipmentAlreadyOwned": "You already own that piece of equipment",
|
"equipmentAlreadyOwned": "You already own that piece of equipment",
|
||||||
"missingAccessToken": "The request is missing a required parameter : access_token",
|
"missingAccessToken": "The request is missing a required parameter : access_token",
|
||||||
"missingBillingAgreementId": "Missing billing agreement id",
|
"missingBillingAgreementId": "Missing billing agreement id",
|
||||||
"paymentNotSuccessful": "The payment was not successful"
|
"paymentNotSuccessful": "The payment was not successful",
|
||||||
|
"planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
|
||||||
|
"cancelingSubscription": "Canceling the subscription"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ gulp.task('test:api-v3:unit', (done) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:api-v3:unit:watch', () => {
|
gulp.task('test:api-v3:unit:watch', () => {
|
||||||
gulp.watch(['website/src/libs/api-v3/*', 'test/api/v3/unit/libs/*', 'website/src/controllers/**/*'], ['test:api-v3:unit']);
|
gulp.watch(['website/src/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/src/controllers/**/*'], ['test:api-v3:unit']);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration', (done) => {
|
gulp.task('test:api-v3:integration', (done) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('payments : amazon #subscribeCancel', () => {
|
describe('payments : amazon #subscribeCancel', () => {
|
||||||
let endpoint = '/payments/amazon/subscribeCancel';
|
let endpoint = '/payments/amazon/subscribe/cancel';
|
||||||
let user;
|
let user;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -13,9 +13,9 @@ describe('payments : amazon #subscribeCancel', () => {
|
|||||||
|
|
||||||
it('verifies subscription', async () => {
|
it('verifies subscription', async () => {
|
||||||
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 401,
|
||||||
error: 'BadRequest',
|
error: 'NotAuthorized',
|
||||||
message: t('missingSubscription'),
|
message: t('missingAuthParams'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments : paypal #checkout', () => {
|
||||||
|
let endpoint = '/payments/paypal/checkout';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies subscription', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('missingAuthParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments : paypal #checkoutSuccess', () => {
|
||||||
|
let endpoint = '/payments/paypal/checkout/success';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies subscription', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('invalidCredentials'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments : paypal #subscribe', () => {
|
||||||
|
let endpoint = '/payments/paypal/subscribe';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('missingAuthParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments : paypal #subscribeCancel', () => {
|
||||||
|
let endpoint = '/payments/paypal/subscribe/cancel';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('missingAuthParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments : paypal #subscribeSuccess', () => {
|
||||||
|
let endpoint = '/payments/paypal/subscribe/success';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('invalidCredentials'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments - stripe - #subscribeCancel', () => {
|
||||||
|
let endpoint = '/payments/stripe/subscribe/cancel';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('missingAuthParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments - paypal - #ipn', () => {
|
||||||
|
let endpoint = '/payments/paypal/ipn';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
let result = await user.post(endpoint);
|
||||||
|
expect(result).to.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments - stripe - #checkout', () => {
|
||||||
|
let endpoint = '/payments/stripe/checkout';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'Error',
|
||||||
|
message: 'Invalid API Key provided: ****************************1111',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('payments - stripe - #subscribeEdit', () => {
|
||||||
|
let endpoint = '/payments/stripe/subscribe/edit';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies credentials', async () => {
|
||||||
|
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('missingSubscription'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -66,10 +66,7 @@ describe('payments/index', () => {
|
|||||||
|
|
||||||
it('sends a text', async () => {
|
it('sends a text', async () => {
|
||||||
await api.cancelSubscription(data);
|
await api.cancelSubscription(data);
|
||||||
sinon.assert.calledOnce(fakeSend);
|
sinon.assert.called(fakeSend);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#buyGems', async () => {
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import validator from 'validator';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
import setupNconf from '../../libs/api-v3/setupNconf';
|
||||||
|
setupNconf();
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authWithHeaders,
|
authWithHeaders,
|
||||||
} from '../../middlewares/api-v3/auth';
|
} from '../../middlewares/api-v3/auth';
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import _ from 'lodash';
|
|||||||
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
|
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
|
||||||
import { sendTxn } from '../../libs/api-v3/email';
|
import { sendTxn } from '../../libs/api-v3/email';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
import setupNconf from '../../libs/api-v3/setupNconf';
|
||||||
|
setupNconf();
|
||||||
|
|
||||||
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
|
||||||
return { email, canSend: true };
|
return { email, canSend: true };
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
|
||||||
import mongoose from 'mongoose';
|
|
||||||
import { model as User } from '../../../models/user'; */
|
|
||||||
import {
|
import {
|
||||||
BadRequest,
|
BadRequest,
|
||||||
} from '../../../libs/api-v3/errors';
|
} from '../../../libs/api-v3/errors';
|
||||||
import amzLib from '../../../libs/api-v3/amazonPayments';
|
import amzLib from '../../../libs/api-v3/amazonPayments';
|
||||||
import { authWithHeaders } from '../../../middlewares/api-v3/auth';
|
import {
|
||||||
|
authWithHeaders,
|
||||||
|
authWithUrl,
|
||||||
|
} from '../../../middlewares/api-v3/auth';
|
||||||
import shared from '../../../../../common';
|
import shared from '../../../../../common';
|
||||||
import payments from '../../../libs/api-v3/payments';
|
import payments from '../../../libs/api-v3/payments';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -16,7 +16,7 @@ import cc from 'coupon-code';
|
|||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/payments/amazon/verifyAccessToken verify access token
|
* @api {post} /amazon/verifyAccessToken verify access token
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AmazonVerifyAccessToken
|
* @apiName AmazonVerifyAccessToken
|
||||||
* @apiGroup Payments
|
* @apiGroup Payments
|
||||||
@@ -40,7 +40,7 @@ api.verifyAccessToken = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/payments/amazon/createOrderReferenceId create order reference id
|
* @api {post} /amazon/createOrderReferenceId create order reference id
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AmazonCreateOrderReferenceId
|
* @apiName AmazonCreateOrderReferenceId
|
||||||
* @apiGroup Payments
|
* @apiGroup Payments
|
||||||
@@ -70,7 +70,7 @@ api.createOrderReferenceId = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/payments/amazon/checkout do checkout
|
* @api {post} /amazon/checkout do checkout
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AmazonCheckout
|
* @apiName AmazonCheckout
|
||||||
* @apiGroup Payments
|
* @apiGroup Payments
|
||||||
@@ -148,7 +148,7 @@ api.checkout = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/payments/amazon/subscribe Subscribe
|
* @api {post} /amazon/subscribe Subscribe
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AmazonSubscribe
|
* @apiName AmazonSubscribe
|
||||||
* @apiGroup Payments
|
* @apiGroup Payments
|
||||||
@@ -228,7 +228,7 @@ api.subscribe = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /api/v3/payments/amazon/subscribe/cancel SubscribeCancel
|
* @api {get} /amazon/subscribe/cancel SubscribeCancel
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AmazonSubscribe
|
* @apiName AmazonSubscribe
|
||||||
* @apiGroup Payments
|
* @apiGroup Payments
|
||||||
@@ -238,7 +238,7 @@ api.subscribe = {
|
|||||||
api.subscribeCancel = {
|
api.subscribeCancel = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/payments/amazon/subscribe/cancel',
|
url: '/payments/amazon/subscribe/cancel',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithUrl],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
let billingAgreementId = user.purchased.plan.customerId;
|
let billingAgreementId = user.purchased.plan.customerId;
|
||||||
|
|||||||
@@ -1,218 +1,296 @@
|
|||||||
var nconf = require('nconf');
|
import nconf from 'nconf';
|
||||||
var moment = require('moment');
|
import moment from 'moment';
|
||||||
var async = require('async');
|
import _ from 'lodash';
|
||||||
var _ = require('lodash');
|
import payments from '../../../libs/api-v3/payments';
|
||||||
var url = require('url');
|
import ipn from 'paypal-ipn';
|
||||||
var User = require('mongoose').model('User');
|
import paypal from 'paypal-rest-sdk';
|
||||||
var payments = require('../../../libs/api-v3/payments');
|
import shared from '../../../../../common';
|
||||||
var logger = require('../../../libs/api-v2/logging');
|
import cc from 'coupon-code';
|
||||||
var ipn = require('paypal-ipn');
|
import { model as Coupon } from '../../../models/coupon';
|
||||||
var paypal = require('paypal-rest-sdk');
|
import { model as User } from '../../../models/user';
|
||||||
var shared = require('../../../../../common');
|
import {
|
||||||
var mongoose = require('mongoose');
|
authWithUrl,
|
||||||
var cc = require('coupon-code');
|
authWithSession,
|
||||||
|
} from '../../../middlewares/api-v3/auth';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
} from '../../../libs/api-v3/errors';
|
||||||
|
import * as logger from '../../../libs/api-v3/logger';
|
||||||
|
|
||||||
// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have
|
// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have
|
||||||
// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created
|
// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created
|
||||||
// there, get it's plan.id and store it in config.json
|
// there, get it's plan.id and store it in config.json
|
||||||
_.each(shared.content.subscriptionBlocks, function(block){
|
_.each(shared.content.subscriptionBlocks, (block) => {
|
||||||
block.paypalKey = nconf.get("PAYPAL:billing_plans:"+block.key);
|
block.paypalKey = nconf.get(`PAYPAL:billing_plans:${block.key}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
paypal.configure({
|
paypal.configure({
|
||||||
'mode': nconf.get("PAYPAL:mode"), //sandbox or live
|
mode: nconf.get('PAYPAL:mode'), // sandbox or live
|
||||||
'client_id': nconf.get("PAYPAL:client_id"),
|
client_id: nconf.get('PAYPAL:client_id'),
|
||||||
'client_secret': nconf.get("PAYPAL:client_secret")
|
client_secret: nconf.get('PAYPAL:client_secret'),
|
||||||
});
|
});
|
||||||
|
|
||||||
var parseErr = function (res, err) {
|
let api = {};
|
||||||
//var error = err.response ? err.response.message || err.response.details[0].issue : err;
|
|
||||||
var error = JSON.stringify(err);
|
|
||||||
return res.status(400).json({err:error});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
exports.createBillingAgreement = function(req,res,next){
|
|
||||||
var sub = shared.content.subscriptionBlocks[req.query.sub];
|
|
||||||
async.waterfall([
|
|
||||||
function(cb){
|
|
||||||
if (!sub.discount) return cb(null, null);
|
|
||||||
if (!req.query.coupon) return cb('Please provide a coupon code for this plan.');
|
|
||||||
mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb);
|
|
||||||
},
|
|
||||||
function(coupon, cb){
|
|
||||||
if (sub.discount && !coupon) return cb('Invalid coupon code.');
|
|
||||||
var billingPlanTitle = "HabitRPG Subscription" + ' ($'+sub.price+' every '+sub.months+' months, recurring)';
|
|
||||||
var billingAgreementAttributes = {
|
|
||||||
"name": billingPlanTitle,
|
|
||||||
"description": billingPlanTitle,
|
|
||||||
"start_date": moment().add({minutes:5}).format(),
|
|
||||||
"plan": {
|
|
||||||
"id": sub.paypalKey
|
|
||||||
},
|
|
||||||
"payer": {
|
|
||||||
"payment_method": "paypal"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
paypal.billingAgreement.create(billingAgreementAttributes, cb);
|
|
||||||
}
|
|
||||||
], function(err, billingAgreement){
|
|
||||||
if (err) return parseErr(res, err);
|
|
||||||
// For approving subscription via Paypal, first redirect user to: approval_url
|
|
||||||
req.session.paypalBlock = req.query.sub;
|
|
||||||
var approval_url = _.find(billingAgreement.links, {rel:'approval_url'}).href;
|
|
||||||
res.redirect(approval_url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.executeBillingAgreement = function(req,res,next){
|
|
||||||
var block = shared.content.subscriptionBlocks[req.session.paypalBlock];
|
|
||||||
delete req.session.paypalBlock;
|
|
||||||
async.auto({
|
|
||||||
exec: function (cb) {
|
|
||||||
paypal.billingAgreement.execute(req.query.token, {}, cb);
|
|
||||||
},
|
|
||||||
get_user: function (cb) {
|
|
||||||
User.findById(req.session.userId, cb);
|
|
||||||
},
|
|
||||||
create_sub: ['exec', 'get_user', function (cb, results) {
|
|
||||||
payments.createSubscription({
|
|
||||||
user: results.get_user,
|
|
||||||
customerId: results.exec.id,
|
|
||||||
paymentMethod: 'Paypal',
|
|
||||||
sub: block
|
|
||||||
}, cb);
|
|
||||||
}]
|
|
||||||
},function(err){
|
|
||||||
if (err) return parseErr(res, err);
|
|
||||||
res.redirect('/');
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createPayment = function(req, res) {
|
|
||||||
// if we're gifting to a user, put it in session for the `execute()`
|
|
||||||
req.session.gift = req.query.gift || undefined;
|
|
||||||
var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
|
||||||
var price = !gift ? 5.00
|
|
||||||
: gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2)
|
|
||||||
: Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2);
|
|
||||||
var description = !gift ? "HabitRPG Gems"
|
|
||||||
: gift.type=='gems' ? "HabitRPG Gems (Gift)"
|
|
||||||
: shared.content.subscriptionBlocks[gift.subscription.key].months + "mo. HabitRPG Subscription (Gift)";
|
|
||||||
var create_payment = {
|
|
||||||
"intent": "sale",
|
|
||||||
"payer": {
|
|
||||||
"payment_method": "paypal"
|
|
||||||
},
|
|
||||||
"redirect_urls": {
|
|
||||||
"return_url": nconf.get('BASE_URL') + '/paypal/checkout/success',
|
|
||||||
"cancel_url": nconf.get('BASE_URL')
|
|
||||||
},
|
|
||||||
"transactions": [{
|
|
||||||
"item_list": {
|
|
||||||
"items": [{
|
|
||||||
"name": description,
|
|
||||||
//"sku": "1",
|
|
||||||
"price": price,
|
|
||||||
"currency": "USD",
|
|
||||||
"quantity": 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"currency": "USD",
|
|
||||||
"total": price
|
|
||||||
},
|
|
||||||
"description": description
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
paypal.payment.create(create_payment, function (err, payment) {
|
|
||||||
if (err) return parseErr(res, err);
|
|
||||||
var link = _.find(payment.links, {rel: 'approval_url'}).href;
|
|
||||||
res.redirect(link);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.executePayment = function(req, res) {
|
|
||||||
var paymentId = req.query.paymentId,
|
|
||||||
PayerID = req.query.PayerID,
|
|
||||||
gift = req.session.gift ? JSON.parse(req.session.gift) : undefined;
|
|
||||||
delete req.session.gift;
|
|
||||||
async.waterfall([
|
|
||||||
function(cb){
|
|
||||||
paypal.payment.execute(paymentId, {payer_id: PayerID}, cb);
|
|
||||||
},
|
|
||||||
function(payment, cb){
|
|
||||||
async.parallel([
|
|
||||||
function(cb2){ User.findById(req.session.userId, cb2); },
|
|
||||||
function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); }
|
|
||||||
], cb);
|
|
||||||
},
|
|
||||||
function(results, cb){
|
|
||||||
if (_.isEmpty(results[0])) return cb("User not found when completing paypal transaction");
|
|
||||||
var data = {user:results[0], customerId:PayerID, paymentMethod:'Paypal', gift:gift}
|
|
||||||
var method = 'buyGems';
|
|
||||||
if (gift) {
|
|
||||||
gift.member = results[1];
|
|
||||||
if (gift.type=='subscription') method = 'createSubscription';
|
|
||||||
data.paymentMethod = 'Gift';
|
|
||||||
}
|
|
||||||
payments[method](data, cb);
|
|
||||||
}
|
|
||||||
],function(err){
|
|
||||||
if (err) return parseErr(res, err);
|
|
||||||
res.redirect('/');
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.cancelSubscription = function(req, res, next){
|
|
||||||
var user = res.locals.user;
|
|
||||||
if (!user.purchased.plan.customerId)
|
|
||||||
return res.status(401).json({err: "User does not have a plan subscription"});
|
|
||||||
async.auto({
|
|
||||||
get_cus: function(cb){
|
|
||||||
paypal.billingAgreement.get(user.purchased.plan.customerId, cb);
|
|
||||||
},
|
|
||||||
verify_cus: ['get_cus', function(cb, results){
|
|
||||||
var hasntBilledYet = results.get_cus.agreement_details.cycles_completed == "0";
|
|
||||||
if (hasntBilledYet)
|
|
||||||
return cb("The plan hasn't activated yet (due to a PayPal bug). It will begin "+results.get_cus.agreement_details.next_billing_date+", after which you can cancel to retain your full benefits");
|
|
||||||
cb();
|
|
||||||
}],
|
|
||||||
del_cus: ['verify_cus', function(cb, results){
|
|
||||||
paypal.billingAgreement.cancel(user.purchased.plan.customerId, {note: "Canceling the subscription"}, cb);
|
|
||||||
}],
|
|
||||||
cancel_sub: ['get_cus', 'verify_cus', function(cb, results){
|
|
||||||
var data = {user: user, paymentMethod: 'Paypal', nextBill: results.get_cus.agreement_details.next_billing_date};
|
|
||||||
payments.cancelSubscription(data, cb)
|
|
||||||
}]
|
|
||||||
}, function(err){
|
|
||||||
if (err) return parseErr(res, err);
|
|
||||||
res.redirect('/');
|
|
||||||
user = null;
|
|
||||||
});
|
|
||||||
} // */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their
|
* @api {get} /paypal/checkout checkout
|
||||||
* recurring paypal payments in their paypal dashboard. Remove this when we can move to webhooks or some other solution
|
* @apiVersion 3.0.0
|
||||||
*/
|
* @apiName PaypalCheckout
|
||||||
/*
|
* @apiGroup Payments
|
||||||
exports.ipn = function(req, res, next) {
|
*
|
||||||
console.log('IPN Called');
|
* @apiParam {string} gift The stringified object representing the user, the gift recepient.
|
||||||
res.sendStatus(200); // Must respond to PayPal IPN request with an empty 200 first
|
*
|
||||||
ipn.verify(req.body, function(err, msg) {
|
* @apiSuccess {} redirect
|
||||||
if (err) return logger.error(msg);
|
**/
|
||||||
switch (req.body.txn_type) {
|
api.checkout = {
|
||||||
// TODO what's the diff b/w the two data.txn_types below? The docs recommend subscr_cancel, but I'm getting the other one instead...
|
method: 'GET',
|
||||||
case 'recurring_payment_profile_cancel':
|
url: '/payments/paypal/checkout',
|
||||||
case 'subscr_cancel':
|
middlewares: [authWithUrl],
|
||||||
User.findOne({'purchased.plan.customerId':req.body.recurring_payment_id},function(err, user){
|
async handler (req, res) {
|
||||||
if (err) return logger.error(err);
|
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||||
if (_.isEmpty(user)) return; // looks like the cancellation was already handled properly above (see api.paypalSubscribeCancel)
|
req.session.gift = req.query.gift;
|
||||||
payments.cancelSubscription({user:user, paymentMethod: 'Paypal'});
|
|
||||||
});
|
let amount = 5.00;
|
||||||
break;
|
let description = 'HabitRPG gems';
|
||||||
|
if (gift) {
|
||||||
|
if (gift.type === 'gems') {
|
||||||
|
amount = Number(gift.gems.amount / 4).toFixed(2);
|
||||||
|
description = `${description} (Gift)`;
|
||||||
|
} else {
|
||||||
|
amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2);
|
||||||
|
description = 'monthly HabitRPG Subscription (Gift)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
let createPayment = {
|
||||||
|
intent: 'sale',
|
||||||
|
payer: { payment_method: 'Paypal' },
|
||||||
|
redirect_urls: {
|
||||||
|
return_url: `${nconf.get('BASE_URL')}/paypal/checkout/success`,
|
||||||
|
cancel_url: `${nconf.get('BASE_URL')}`,
|
||||||
|
},
|
||||||
|
transactions: [{
|
||||||
|
item_list: {
|
||||||
|
items: [{
|
||||||
|
name: description,
|
||||||
|
price: amount,
|
||||||
|
currency: 'USD',
|
||||||
|
quality: 1,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
currency: 'USD',
|
||||||
|
total: amount,
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
let result = await paypal.payment.create(createPayment);
|
||||||
|
let link = _.find(result.links, { rel: 'approval_url' }).href;
|
||||||
|
res.redirect(link);
|
||||||
|
} catch (e) {
|
||||||
|
throw new BadRequest(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* @api {get} /paypal/checkout/success Paypal checkout success
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName PaypalCheckoutSuccess
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} paymentId The payment id
|
||||||
|
* @apiParam {string} payerID The payer id, notice ID not id
|
||||||
|
*
|
||||||
|
* @apiSuccess {} redirect
|
||||||
|
**/
|
||||||
|
api.checkoutSuccess = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/payments/paypal/checkout/success',
|
||||||
|
middlewares: [authWithSession],
|
||||||
|
async handler (req, res) {
|
||||||
|
let paymentId = req.query.paymentId;
|
||||||
|
let customerId = req.query.payerID;
|
||||||
|
let method = 'buyGems';
|
||||||
|
let data = {
|
||||||
|
user: res.locals.user,
|
||||||
|
customerId,
|
||||||
|
paymentMethod: 'Paypal',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let gift = req.session.gift ? JSON.parse(req.session.gift) : undefined;
|
||||||
|
delete req.session.gift;
|
||||||
|
if (gift) {
|
||||||
|
gift.member = await User.findById(gift.uuid);
|
||||||
|
if (gift.type === 'subscription') {
|
||||||
|
method = 'createSubscription';
|
||||||
|
data.paymentMethod = 'Gift';
|
||||||
|
}
|
||||||
|
data.gift = gift;
|
||||||
|
}
|
||||||
|
|
||||||
|
await paypal.payment.execute(paymentId, { payer_id: customerId });
|
||||||
|
await payments[method](data);
|
||||||
|
res.redirect('/');
|
||||||
|
} catch (e) {
|
||||||
|
throw new BadRequest(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /paypal/subscribe Paypal subscribe
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName PaypalSubscribe
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} sub subscription, possible values are: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo
|
||||||
|
* @apiParam {string} coupon coupon for the matching subscription, required only for certain subscriptions
|
||||||
|
*
|
||||||
|
* @apiSuccess {} empty object
|
||||||
|
**/
|
||||||
|
api.subscribe = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/payments/paypal/subscribe',
|
||||||
|
middlewares: [authWithUrl],
|
||||||
|
async handler (req, res) {
|
||||||
|
let sub = shared.content.subscriptionBlocks[req.query.sub];
|
||||||
|
if (sub.discount) {
|
||||||
|
if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired'));
|
||||||
|
let coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key});
|
||||||
|
if (!coupon) throw new BadRequest(res.t('invalidCoupon'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let billingPlanTitle = `HabitRPG Subscription ($${sub.price} every ${sub.months} months, recurring)`;
|
||||||
|
let billingAgreementAttributes = {
|
||||||
|
name: billingPlanTitle,
|
||||||
|
description: billingPlanTitle,
|
||||||
|
start_date: moment().add({ minutes: 5}).format(),
|
||||||
|
plan: {
|
||||||
|
id: sub.paypalKey,
|
||||||
|
},
|
||||||
|
payer: {
|
||||||
|
payment_method: 'Paypal',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
let billingAgreement = await paypal.billingAgreement.create(billingAgreementAttributes);
|
||||||
|
req.session.paypalBlock = req.query.sub;
|
||||||
|
let link = _.find(billingAgreement.links, { rel: 'approval_url' }).href;
|
||||||
|
res.redirect(link);
|
||||||
|
} catch (e) {
|
||||||
|
throw new BadRequest(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /paypal/subscribe/success Paypal subscribe success
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName PaypalSubscribeSuccess
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} token The token in query
|
||||||
|
*
|
||||||
|
* @apiSuccess {} redirect
|
||||||
|
**/
|
||||||
|
api.subscribeSuccess = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/payments/paypal/subscribe/success',
|
||||||
|
middlewares: [authWithSession],
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
let block = shared.content.subscriptionBlocks[req.session.paypalBlock];
|
||||||
|
delete req.session.paypalBlock;
|
||||||
|
try {
|
||||||
|
let result = await paypal.billingAgreement.execute(req.query.token, {});
|
||||||
|
await payments.createSubscription({
|
||||||
|
user,
|
||||||
|
customerId: result.id,
|
||||||
|
paymentMethod: 'Paypal',
|
||||||
|
sub: block,
|
||||||
|
});
|
||||||
|
res.redirect('/');
|
||||||
|
} catch (e) {
|
||||||
|
throw new BadRequest(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /paypal/subscribe/cancel Paypal subscribe cancel
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName PaypalSubscribeCancel
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} token The token in query
|
||||||
|
*
|
||||||
|
* @apiSuccess {} redirect
|
||||||
|
**/
|
||||||
|
api.subscribeCancel = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/payments/paypal/subscribe/cancel',
|
||||||
|
middlewares: [authWithUrl],
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
let customerId = user.purchased.plan.customerId;
|
||||||
|
if (!user.purchased.plan.customerId) throw new BadRequest(res.t('missingSubscription'));
|
||||||
|
try {
|
||||||
|
let customer = await paypal.billingAgreement.get(customerId);
|
||||||
|
let nextBillingDate = customer.agreement_details.next_billing_date;
|
||||||
|
if (customer.agreement_details.cycles_completed === '0') { // hasn't billed yet
|
||||||
|
throw new BadRequest(res.t('planNotActive', { nextBillingDate }));
|
||||||
|
}
|
||||||
|
await paypal.billingAgreement.cancel(customerId, { note: res.t('cancelingSubscription') });
|
||||||
|
let data = {
|
||||||
|
user,
|
||||||
|
paymentMethod: 'Paypal',
|
||||||
|
nextBill: nextBillingDate,
|
||||||
|
};
|
||||||
|
await payments.cancelSubscription(data);
|
||||||
|
res.redirect('/');
|
||||||
|
} catch (e) {
|
||||||
|
throw new BadRequest(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /paypal/ipn Paypal IPN
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName PaypalIpn
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} txn_type txn_type
|
||||||
|
* @apiParam {string} recurring_payment_id recurring_payment_id
|
||||||
|
*
|
||||||
|
* @apiSuccess {} empty object
|
||||||
|
**/
|
||||||
|
api.ipn = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/payments/paypal/ipn',
|
||||||
|
middlewares: [],
|
||||||
|
async handler (req, res) {
|
||||||
|
res.respond(200);
|
||||||
|
try {
|
||||||
|
await ipn.verify(req.body);
|
||||||
|
if (req.body.txn_type === 'recurring_payment_profile_cancel' || req.body.txn_type === 'subscr_cancel') {
|
||||||
|
let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id });
|
||||||
|
if (user) {
|
||||||
|
payments.cancelSubscriptoin({ user, paymentMethod: 'Paypal' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
module.exports = api;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ let billingPlanAttributes = {
|
|||||||
cycles: '0',
|
cycles: '0',
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
_.each(blocks, function defineBlock (block) {
|
_.each(blocks, function defineBlock (block) {
|
||||||
block.definition = _.cloneDeep(billingPlanAttributes);
|
block.definition = _.cloneDeep(billingPlanAttributes);
|
||||||
_.merge(block.definition.payment_definitions[0], {
|
_.merge(block.definition.payment_definitions[0], {
|
||||||
|
|||||||
@@ -1,137 +1,167 @@
|
|||||||
/* import nconf from 'nconf';
|
|
||||||
import stripeModule from 'stripe';
|
import stripeModule from 'stripe';
|
||||||
import async from 'async';
|
|
||||||
import payments from '../../../libs/api-v3/payments';
|
|
||||||
import { model as User } from '../../../models/user';
|
|
||||||
import shared from '../../../../../common';
|
import shared from '../../../../../common';
|
||||||
import mongoose from 'mongoose';
|
import {
|
||||||
import cc from 'coupon-code'; */
|
BadRequest,
|
||||||
|
} from '../../../libs/api-v3/errors';
|
||||||
|
import { model as Coupon } from '../../../models/coupon';
|
||||||
|
import payments from '../../../libs/api-v3/payments';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import { model as User } from '../../../models/user';
|
||||||
|
import cc from 'coupon-code';
|
||||||
|
import {
|
||||||
|
authWithHeaders,
|
||||||
|
authWithUrl,
|
||||||
|
} from '../../../middlewares/api-v3/auth';
|
||||||
|
|
||||||
// const stripe = stripeModule(nconf.get('STRIPE_API_KEY'));
|
const stripe = stripeModule(nconf.get('STRIPE_API_KEY'));
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
/*
|
|
||||||
Setup Stripe response when posting payment
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
api.checkout = function checkout (req, res) {
|
|
||||||
let token = req.body.id;
|
|
||||||
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;
|
|
||||||
|
|
||||||
async.waterfall([
|
/**
|
||||||
function stripeCharge (cb) {
|
* @api {post} /stripe/checkout Stripe checkout
|
||||||
if (sub) {
|
* @apiVersion 3.0.0
|
||||||
async.waterfall([
|
* @apiName StripeCheckout
|
||||||
function handleCoupon (cb2) {
|
* @apiGroup Payments
|
||||||
if (!sub.discount) return cb2(null, null);
|
*
|
||||||
if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.');
|
* @apiParam {string} id The token
|
||||||
mongoose.model('Coupon').findOne({_id: cc.validate(req.query.coupon), event: sub.key}, cb2);
|
* @apiParam {string} gift stringified json object, gift
|
||||||
},
|
* @apiParam {string} sub subscription, possible values are: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo
|
||||||
function createCustomer (coupon, cb2) {
|
* @apiParam {string} coupon coupon for the matching subscription, required only for certain subscriptions
|
||||||
if (sub.discount && !coupon) return cb2('Invalid coupon code.');
|
* @apiParam {string} email the customer email
|
||||||
let customer = {
|
*
|
||||||
email: req.body.email,
|
* @apiSuccess {} empty object
|
||||||
metadata: {uuid: user._id},
|
**/
|
||||||
card: token,
|
api.checkout = {
|
||||||
plan: sub.key,
|
method: 'POST',
|
||||||
};
|
url: '/payments/stripe/checkout',
|
||||||
stripe.customers.create(customer, cb2);
|
middlewares: [authWithHeaders()],
|
||||||
},
|
async handler (req, res) {
|
||||||
], cb);
|
let token = req.body.id;
|
||||||
} else {
|
let user = res.locals.user;
|
||||||
let amount;
|
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||||
if (!gift) {
|
let sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false;
|
||||||
amount = '500';
|
let coupon;
|
||||||
} else if (gift.type === 'subscription') {
|
let response;
|
||||||
|
|
||||||
|
if (sub) {
|
||||||
|
if (sub.discount) {
|
||||||
|
if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired'));
|
||||||
|
coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key});
|
||||||
|
if (!coupon) throw new BadRequest(res.t('invalidCoupon'));
|
||||||
|
}
|
||||||
|
let customer = {
|
||||||
|
email: req.body.email,
|
||||||
|
metadata: { uuid: user._id },
|
||||||
|
card: token,
|
||||||
|
plan: sub.key,
|
||||||
|
};
|
||||||
|
response = await stripe.customers.create(customer);
|
||||||
|
} else {
|
||||||
|
let amount = 500; // $5
|
||||||
|
if (gift) {
|
||||||
|
if (gift.type === 'subscription') {
|
||||||
amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`;
|
amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`;
|
||||||
} else {
|
} else {
|
||||||
amount = `${gift.gems.amount / 4 * 100}`;
|
amount = `${gift.gems.amount / 4 * 100}`;
|
||||||
}
|
}
|
||||||
stripe.charges.create({
|
|
||||||
amount,
|
|
||||||
currency: 'usd',
|
|
||||||
card: token,
|
|
||||||
}, cb);
|
|
||||||
}
|
}
|
||||||
},
|
response = await stripe.charges.create({
|
||||||
function saveUserData (response, cb) {
|
amount,
|
||||||
if (sub) return payments.createSubscription({user, customerId: response.id, paymentMethod: 'Stripe', sub}, cb);
|
currency: 'usd',
|
||||||
async.waterfall([
|
card: token,
|
||||||
function findUser (cb2) {
|
});
|
||||||
User.findById(gift ? gift.uuid : undefined, cb2);
|
}
|
||||||
},
|
|
||||||
function prepData (member, cb2) {
|
|
||||||
let data = {user, customerId: response.id, paymentMethod: 'Stripe', gift};
|
|
||||||
let method = 'buyGems';
|
|
||||||
if (gift) {
|
|
||||||
gift.member = member;
|
|
||||||
if (gift.type === 'subscription') method = 'createSubscription';
|
|
||||||
data.paymentMethod = 'Gift';
|
|
||||||
}
|
|
||||||
payments[method](data, cb2);
|
|
||||||
},
|
|
||||||
], cb);
|
|
||||||
},
|
|
||||||
], function handleResponse (err) {
|
|
||||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
|
||||||
res.sendStatus(200);
|
|
||||||
user = token = null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
api.subscribeCancel = function subscribeCancel (req, res) {
|
if (sub) {
|
||||||
let user = res.locals.user;
|
await payments.createSubscription({
|
||||||
if (!user.purchased.plan.customerId) {
|
user,
|
||||||
return res.status(401).json({err: 'User does not have a plan subscription'});
|
customerId: response.id,
|
||||||
}
|
paymentMethod: 'Stripe',
|
||||||
|
sub,
|
||||||
async.auto({
|
});
|
||||||
getCustomer: function getCustomer (cb) {
|
} else {
|
||||||
stripe.customers.retrieve(user.purchased.plan.customerId, cb);
|
let method = 'buyGems';
|
||||||
},
|
|
||||||
deleteCustomer: ['getCustomer', function deleteCustomer (cb) {
|
|
||||||
stripe.customers.del(user.purchased.plan.customerId, cb);
|
|
||||||
}],
|
|
||||||
cancelSubscription: ['getCustomer', function cancelSubscription (cb, results) {
|
|
||||||
let data = {
|
let data = {
|
||||||
user,
|
user,
|
||||||
nextBill: results.get_cus.subscription.current_period_end * 1000, // timestamp is in seconds
|
customerId: response.id,
|
||||||
|
paymentMethod: 'Stripe',
|
||||||
|
gift,
|
||||||
|
};
|
||||||
|
if (gift) {
|
||||||
|
let member = await User.findById(gift.uuid);
|
||||||
|
gift.member = member;
|
||||||
|
if (gift.type === 'subscription') method = 'createSubscription';
|
||||||
|
data.paymentMethod = 'Gift';
|
||||||
|
}
|
||||||
|
await payments[method](data);
|
||||||
|
}
|
||||||
|
res.respond(200, {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /stripe/subscribe/edit Stripe subscribeEdit
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName StripeSubscribeEdit
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam {string} id The token
|
||||||
|
*
|
||||||
|
* @apiSuccess {}
|
||||||
|
**/
|
||||||
|
api.subscribeEdit = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/payments/stripe/subscribe/edit',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
async handler (req, res) {
|
||||||
|
let token = req.body.id;
|
||||||
|
let user = res.locals.user;
|
||||||
|
let customerId = user.purchased.plan.customerId;
|
||||||
|
|
||||||
|
if (!customerId) throw new BadRequest(res.t('missingSubscription'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
let subscriptions = await stripe.customers.listSubscriptions(customerId);
|
||||||
|
let subscriptionId = subscriptions.data[0].id;
|
||||||
|
await stripe.customers.updateSubscription(customerId, subscriptionId, { card: token });
|
||||||
|
res.respond(200, {});
|
||||||
|
} catch (error) {
|
||||||
|
throw new BadRequest(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /stripe/subscribe/cancel Stripe subscribeCancel
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName StripeSubscribeCancel
|
||||||
|
* @apiGroup Payments
|
||||||
|
*
|
||||||
|
* @apiParam
|
||||||
|
*
|
||||||
|
* @apiSuccess {}
|
||||||
|
**/
|
||||||
|
api.subscribeCancel = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/payments/stripe/subscribe/cancel',
|
||||||
|
middlewares: [authWithUrl],
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
if (!user.purchased.plan.customerId) throw new BadRequest(res.t('missingSubscription'));
|
||||||
|
try {
|
||||||
|
let customer = await stripe.customers.retrieve(user.purchased.plan.customeerId);
|
||||||
|
await stripe.customers.del(user.purchased.plan.customerId);
|
||||||
|
let data = {
|
||||||
|
user,
|
||||||
|
nextBill: customer.subscription.current_period_end * 1000, // timestamp in seconds
|
||||||
paymentMethod: 'Stripe',
|
paymentMethod: 'Stripe',
|
||||||
};
|
};
|
||||||
payments.cancelSubscription(data, cb);
|
await payments.cancelSubscriptoin(data);
|
||||||
}],
|
res.respond(200, {});
|
||||||
}, function handleResponse (err) {
|
} catch (e) {
|
||||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
throw new BadRequest(e);
|
||||||
res.redirect('/');
|
}
|
||||||
user = null;
|
},
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
api.subscribeEdit = function subscribeEdit (req, res) {
|
|
||||||
let token = req.body.id;
|
|
||||||
let user = res.locals.user;
|
|
||||||
let userId = user.purchased.plan.customerId;
|
|
||||||
let subscriptionId;
|
|
||||||
|
|
||||||
async.waterfall([
|
|
||||||
function listSubscriptions (cb) {
|
|
||||||
stripe.customers.listSubscriptions(userId, cb);
|
|
||||||
},
|
|
||||||
function updateSubscription (response, cb) {
|
|
||||||
subscriptionId = response.data[0].id;
|
|
||||||
stripe.customers.updateSubscription(userId, subscriptionId, { card: token }, cb);
|
|
||||||
},
|
|
||||||
function saveUser (response, cb) {
|
|
||||||
user.save(cb);
|
|
||||||
},
|
|
||||||
], function handleResponse (err) {
|
|
||||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
|
||||||
res.sendStatus(200);
|
|
||||||
token = user = userId = subscriptionId;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import _ from 'lodash' ;
|
import _ from 'lodash' ;
|
||||||
import analytics from './analyticsService';
|
import analytics from './analyticsService';
|
||||||
import cc from 'coupon-code';
|
|
||||||
import {
|
import {
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
sendTxn as txnEmail,
|
sendTxn as txnEmail,
|
||||||
} from './email';
|
} from './email';
|
||||||
import members from '../../controllers/api-v3/members';
|
import members from '../../controllers/api-v3/members';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import mongoose from 'mongoose';
|
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import pushNotify from './pushNotifications';
|
import pushNotify from './pushNotifications';
|
||||||
import shared from '../../../../common' ;
|
import shared from '../../../../common' ;
|
||||||
|
|||||||
@@ -55,3 +55,21 @@ export function authWithSession (req, res, next) {
|
|||||||
})
|
})
|
||||||
.catch(next);
|
.catch(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function authWithUrl (req, res, next) {
|
||||||
|
let userId = req.query._id;
|
||||||
|
let apiToken = req.query.apiToken;
|
||||||
|
|
||||||
|
if (!userId || !apiToken) {
|
||||||
|
throw new NotAuthorized(res.t('missingAuthParams'));
|
||||||
|
}
|
||||||
|
|
||||||
|
User.findOne({ _id: userId, apiToken }).exec()
|
||||||
|
.then((user) => {
|
||||||
|
if (!user) throw new NotAuthorized(res.t('invalidCredentials'));
|
||||||
|
|
||||||
|
res.locals.user = user;
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user