mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
working on promisifying amazonPayments
This commit is contained in:
@@ -170,5 +170,10 @@
|
||||
"pushDeviceAdded": "Push device added successfully",
|
||||
"pushDeviceAlreadyAdded": "The user already has the push device",
|
||||
"resetComplete": "Reset completed",
|
||||
"lvl10ChangeClass": "To change class you must be at least level 10."
|
||||
"lvl10ChangeClass": "To change class you must be at least level 10.",
|
||||
"resetComplete": "Reset has completed",
|
||||
"missingAccessToken": "The request is missing a required parameter : access_token",
|
||||
"missingBillingAgreementId": "Missing billing agreement id",
|
||||
"missingAttributesFromAmazon": "Missing attributes from Amazon",
|
||||
"errorFromAmazon": "Error from Amazon"
|
||||
}
|
||||
|
||||
@@ -358,6 +358,10 @@ gulp.task('test:api-v3:unit', (done) => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:unit:watch', () => {
|
||||
gulp.watch(['website/src/libs/api-v3/*', 'test/api/v3/unit/libs/*'], ['test:api-v3:unit']);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v3/integration --recursive'),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('payments : amazon', () => {
|
||||
let endpoint = '/payments/amazon/verifyAccessToken';
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verify access token', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingAccessToken'),
|
||||
});
|
||||
});
|
||||
});
|
||||
24
test/api/v3/unit/libs/amazonPayments.test.js
Normal file
24
test/api/v3/unit/libs/amazonPayments.test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as amz from '../../../../../website/src/libs/api-v3/amazonPayments';
|
||||
|
||||
describe.only('amazonPayments', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('#getTokenInfo', () => {
|
||||
it('validates access_token parameter', async (done) => {
|
||||
try {
|
||||
let result = await amz.getTokenInfo();
|
||||
} catch (e) {
|
||||
expect(e.type).to.eql('invalid_request');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createOrderReferenceId', () => {
|
||||
it('is sane', () => {
|
||||
expect(false).to.eql(true); // @TODO
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,277 +0,0 @@
|
||||
import amazonPayments from 'amazon-payments';
|
||||
import async from 'async';
|
||||
import cc from 'coupon-code';
|
||||
import mongoose from 'mongoose';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import payments from './index';
|
||||
import shared from '../../../../../common';
|
||||
import { model as User } from '../../models/user';
|
||||
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
let api = {};
|
||||
|
||||
let amzPayment = amazonPayments.connect({
|
||||
environment: amazonPayments.Environment[IS_PROD ? 'Production' : 'Sandbox'],
|
||||
sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
|
||||
mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'),
|
||||
mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'),
|
||||
clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'),
|
||||
});
|
||||
|
||||
api.verifyAccessToken = function verifyAccessToken (req, res) {
|
||||
if (!req.body || !req.body.access_token) {
|
||||
return res.status(400).json({err: 'Access token not supplied.'});
|
||||
}
|
||||
|
||||
amzPayment.api.getTokenInfo(req.body.access_token, function getTokenInfo (err) {
|
||||
if (err) return res.status(400).json({err});
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
};
|
||||
|
||||
api.createOrderReferenceId = function createOrderReferenceId (req, res, next) {
|
||||
if (!req.body || !req.body.billingAgreementId) {
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
amzPayment.offAmazonPayments.createOrderReferenceForId({
|
||||
Id: req.body.billingAgreementId,
|
||||
IdType: 'BillingAgreement',
|
||||
ConfirmNow: false,
|
||||
}, function createOrderReferenceForId (err, response) {
|
||||
if (err) return next(err);
|
||||
if (!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId) {
|
||||
return next(new Error('Missing attributes in Amazon response.'));
|
||||
}
|
||||
|
||||
res.json({
|
||||
orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
api.checkout = function checkout (req, res, next) {
|
||||
if (!req.body || !req.body.orderReferenceId) {
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
let gift = req.body.gift;
|
||||
let user = res.locals.user;
|
||||
let orderReferenceId = req.body.orderReferenceId;
|
||||
let amount = 5;
|
||||
|
||||
if (gift) {
|
||||
if (gift.type === 'gems') {
|
||||
amount = gift.gems.amount / 4;
|
||||
} else if (gift.type === 'subscription') {
|
||||
amount = shared.content.subscriptionBlocks[gift.subscription.key].price;
|
||||
}
|
||||
}
|
||||
|
||||
async.series({
|
||||
setOrderReferenceDetails (cb) {
|
||||
amzPayment.offAmazonPayments.setOrderReferenceDetails({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
OrderReferenceAttributes: {
|
||||
OrderTotal: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: amount,
|
||||
},
|
||||
SellerNote: 'HabitRPG Payment',
|
||||
SellerOrderAttributes: {
|
||||
SellerOrderId: shared.uuid(),
|
||||
StoreName: 'HabitRPG',
|
||||
},
|
||||
},
|
||||
}, cb);
|
||||
},
|
||||
|
||||
confirmOrderReference (cb) {
|
||||
amzPayment.offAmazonPayments.confirmOrderReference({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
authorize (cb) {
|
||||
amzPayment.offAmazonPayments.authorize({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
AuthorizationReferenceId: shared.uuid().substring(0, 32),
|
||||
AuthorizationAmount: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: amount,
|
||||
},
|
||||
SellerAuthorizationNote: 'HabitRPG Payment',
|
||||
TransactionTimeout: 0,
|
||||
CaptureNow: true,
|
||||
}, function checkAuthorizationStatus (err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (res.AuthorizationDetails.AuthorizationStatus.State === 'Declined') {
|
||||
return cb(new Error('The payment was not successfull.'));
|
||||
}
|
||||
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
|
||||
closeOrderReference (cb) {
|
||||
amzPayment.offAmazonPayments.closeOrderReference({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
executePayment (cb) {
|
||||
async.waterfall([
|
||||
function findUser (cb2) {
|
||||
User.findById(gift ? gift.uuid : undefined, cb2);
|
||||
},
|
||||
function executeAmazonPayment (member, cb2) {
|
||||
let data = {user, paymentMethod: 'Amazon Payments'};
|
||||
let method = 'buyGems';
|
||||
|
||||
if (gift) {
|
||||
if (gift.type === 'subscription') method = 'createSubscription';
|
||||
gift.member = member;
|
||||
data.gift = gift;
|
||||
data.paymentMethod = 'Gift';
|
||||
}
|
||||
|
||||
payments[method](data, cb2);
|
||||
},
|
||||
], cb);
|
||||
},
|
||||
}, function result (err) {
|
||||
if (err) return next(err);
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
};
|
||||
|
||||
api.subscribe = function subscribe (req, res, next) {
|
||||
if (!req.body || !req.body.billingAgreementId) {
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
let billingAgreementId = req.body.billingAgreementId;
|
||||
let sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false;
|
||||
let coupon = req.body.coupon;
|
||||
let user = res.locals.user;
|
||||
|
||||
if (!sub) {
|
||||
return res.status(400).json({err: 'Subscription plan not found.'});
|
||||
}
|
||||
|
||||
async.series({
|
||||
applyDiscount (cb) {
|
||||
if (!sub.discount) return cb();
|
||||
if (!coupon) return cb(new Error('Please provide a coupon code for this plan.'));
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(coupon), event: sub.key}, function couponResult (err) {
|
||||
if (err) return cb(err);
|
||||
if (!coupon) return cb(new Error('Coupon code not found.'));
|
||||
cb();
|
||||
});
|
||||
},
|
||||
|
||||
setBillingAgreementDetails (cb) {
|
||||
amzPayment.offAmazonPayments.setBillingAgreementDetails({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
BillingAgreementAttributes: {
|
||||
SellerNote: 'HabitRPG Subscription',
|
||||
SellerBillingAgreementAttributes: {
|
||||
SellerBillingAgreementId: shared.uuid(),
|
||||
StoreName: 'HabitRPG',
|
||||
CustomInformation: 'HabitRPG Subscription',
|
||||
},
|
||||
},
|
||||
}, cb);
|
||||
},
|
||||
|
||||
confirmBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.confirmBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
authorizeOnBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.authorizeOnBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
AuthorizationReferenceId: shared.uuid().substring(0, 32),
|
||||
AuthorizationAmount: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: sub.price,
|
||||
},
|
||||
SellerAuthorizationNote: 'HabitRPG Subscription Payment',
|
||||
TransactionTimeout: 0,
|
||||
CaptureNow: true,
|
||||
SellerNote: 'HabitRPG Subscription Payment',
|
||||
SellerOrderAttributes: {
|
||||
SellerOrderId: shared.uuid(),
|
||||
StoreName: 'HabitRPG',
|
||||
},
|
||||
}, function billingAgreementResult (err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (res.AuthorizationDetails.AuthorizationStatus.State === 'Declined') {
|
||||
return cb(new Error('The payment was not successful.'));
|
||||
}
|
||||
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
|
||||
createSubscription (cb) {
|
||||
payments.createSubscription({
|
||||
user,
|
||||
customerId: billingAgreementId,
|
||||
paymentMethod: 'Amazon Payments',
|
||||
sub,
|
||||
}, cb);
|
||||
},
|
||||
}, function subscribeResult (err) {
|
||||
if (err) return next(err);
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
};
|
||||
|
||||
api.subscribeCancel = function subscribeCancel (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
if (!user.purchased.plan.customerId)
|
||||
return res.status(401).json({err: 'User does not have a plan subscription'});
|
||||
|
||||
let billingAgreementId = user.purchased.plan.customerId;
|
||||
|
||||
async.series({
|
||||
closeBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.closeBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
cancelSubscription (cb) {
|
||||
let data = {
|
||||
user,
|
||||
// Date of next bill
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({days: 30}),
|
||||
paymentMethod: 'Amazon Payments',
|
||||
};
|
||||
|
||||
payments.cancelSubscription(data, cb);
|
||||
},
|
||||
}, function subscribeCancelResult (err) {
|
||||
if (err) return next(err); // don't json this, let toString() handle errors
|
||||
|
||||
if (req.query.noRedirect) {
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
res.redirect('/');
|
||||
}
|
||||
|
||||
user = null;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
@@ -1,158 +0,0 @@
|
||||
import {
|
||||
iap,
|
||||
inAppPurchase, }
|
||||
from 'in-app-purchase';
|
||||
import payments from './index';
|
||||
import nconf from 'nconf';
|
||||
|
||||
inAppPurchase.config({
|
||||
// this is the path to the directory containing iap-sanbox/iap-live files
|
||||
googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR'),
|
||||
});
|
||||
|
||||
// Validation ERROR Codes
|
||||
const INVALID_PAYLOAD = 6778001;
|
||||
/* const CONNECTION_FAILED = 6778002;
|
||||
const PURCHASE_EXPIRED = 6778003; */ // These variables were never used??
|
||||
|
||||
let api = {};
|
||||
|
||||
api.androidVerify = function androidVerify (req, res) {
|
||||
let iapBody = req.body;
|
||||
let user = res.locals.user;
|
||||
|
||||
iap.setup(function googleSetupResult (error) {
|
||||
if (error) {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error',
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
/*
|
||||
google receipt must be provided as an object
|
||||
{
|
||||
"data": "{stringified data object}",
|
||||
"signature": "signature from google"
|
||||
}
|
||||
*/
|
||||
let testObj = {
|
||||
data: iapBody.transaction.receipt,
|
||||
signature: iapBody.transaction.signature,
|
||||
};
|
||||
|
||||
// iap is ready
|
||||
iap.validate(iap.GOOGLE, testObj, function googleValidateResult (err, googleRes) {
|
||||
if (err) {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString(),
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
if (iap.isValidated(googleRes)) {
|
||||
let resObj = {
|
||||
ok: true,
|
||||
data: googleRes,
|
||||
};
|
||||
|
||||
payments.buyGems({user, paymentMethod: 'IAP GooglePlay', amount: 5.25});
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.iosVerify = function iosVerify (req, res) {
|
||||
let iapBody = req.body;
|
||||
let user = res.locals.user;
|
||||
|
||||
iap.setup(function iosSetupResult (error) {
|
||||
if (error) {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error',
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
// iap is ready
|
||||
iap.validate(iap.APPLE, iapBody.transaction.receipt, function iosValidateResult (err, appleRes) {
|
||||
if (err) {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString(),
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
if (iap.isValidated(appleRes)) {
|
||||
let purchaseDataList = iap.getPurchaseData(appleRes);
|
||||
if (purchaseDataList.length > 0) {
|
||||
let correctReceipt = true;
|
||||
for (let index of purchaseDataList) {
|
||||
switch (purchaseDataList[index].productId) {
|
||||
case 'com.habitrpg.ios.Habitica.4gems':
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.8gems':
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.20gems':
|
||||
case 'com.habitrpg.ios.Habitica.21gems':
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.42gems':
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5});
|
||||
break;
|
||||
default:
|
||||
correctReceipt = false;
|
||||
}
|
||||
}
|
||||
if (correctReceipt) {
|
||||
let resObj = {
|
||||
ok: true,
|
||||
data: appleRes,
|
||||
};
|
||||
// yay good!
|
||||
return res.json(resObj);
|
||||
}
|
||||
}
|
||||
// wrong receipt content
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: 'Incorrect receipt content',
|
||||
},
|
||||
};
|
||||
return res.json(resObj);
|
||||
}
|
||||
// invalid receipt
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: 'Invalid receipt',
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
@@ -1,232 +0,0 @@
|
||||
import _ from 'lodash' ;
|
||||
import analytics from '../../../libs/api-v3/analyticsService';
|
||||
import async from 'async';
|
||||
import cc from 'coupon-code';
|
||||
import {
|
||||
getUserInfo,
|
||||
sendTxn as txnEmail,
|
||||
} from '../../../libs/api-v3/email';
|
||||
import members from '../members';
|
||||
import moment from 'moment';
|
||||
import mongoose from 'mongoose';
|
||||
import nconf from 'nconf';
|
||||
import pushNotify from '../../../libs/api-v3/pushNotifications';
|
||||
import shared from '../../../../../common' ;
|
||||
|
||||
import amazon from './amazon';
|
||||
import iap from './iap';
|
||||
import paypal from './paypal';
|
||||
import stripe from './stripe';
|
||||
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
let api = {};
|
||||
|
||||
function revealMysteryItems (user) {
|
||||
_.each(shared.content.gear.flat, function findMysteryItems (item) {
|
||||
if (
|
||||
item.klass === 'mystery' &&
|
||||
moment().isAfter(shared.content.mystery[item.mystery].start) &&
|
||||
moment().isBefore(shared.content.mystery[item.mystery].end) &&
|
||||
!user.items.gear.owned[item.key] &&
|
||||
user.purchased.plan.mysteryItems.indexOf(item.key) !== -1
|
||||
) {
|
||||
user.purchased.plan.mysteryItems.push(item.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
api.createSubscription = function createSubscription (data, cb) {
|
||||
let recipient = data.gift ? data.gift.member : data.user;
|
||||
let plan = recipient.purchased.plan;
|
||||
let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
|
||||
let months = Number(block.months);
|
||||
|
||||
if (data.gift) {
|
||||
if (plan.customerId && !plan.dateTerminated) { // User has active plan
|
||||
plan.extraMonths += months;
|
||||
} else {
|
||||
plan.dateTerminated = moment(plan.dateTerminated).add({months}).toDate();
|
||||
if (!plan.dateUpdated) plan.dateUpdated = new Date();
|
||||
}
|
||||
if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId
|
||||
} else {
|
||||
_(plan).merge({ // override with these values
|
||||
planId: block.key,
|
||||
customerId: data.customerId,
|
||||
dateUpdated: new Date(),
|
||||
gemsBought: 0,
|
||||
paymentMethod: data.paymentMethod,
|
||||
extraMonths: Number(plan.extraMonths) +
|
||||
Number(plan.dateTerminated ? moment(plan.dateTerminated).diff(new Date(), 'months', true) : 0),
|
||||
dateTerminated: null,
|
||||
// Specify a lastBillingDate just for Amazon Payments
|
||||
// Resetted every time the subscription restarts
|
||||
lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined,
|
||||
}).defaults({ // allow non-override if a plan was previously used
|
||||
dateCreated: new Date(),
|
||||
mysteryItems: [],
|
||||
}).value();
|
||||
}
|
||||
|
||||
// Block sub perks
|
||||
let perks = Math.floor(months / 3);
|
||||
if (perks) {
|
||||
plan.consecutive.offset += months;
|
||||
plan.consecutive.gemCapExtra += perks * 5;
|
||||
if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25;
|
||||
plan.consecutive.trinkets += perks;
|
||||
}
|
||||
revealMysteryItems(recipient);
|
||||
if (IS_PROD) {
|
||||
if (!data.gift) txnEmail(data.user, 'subscription-begins');
|
||||
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Subscription',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-subscription`,
|
||||
purchaseType: 'subscribe',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: Boolean(data.gift),
|
||||
purchaseValue: block.price,
|
||||
};
|
||||
analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
data.user.purchased.txnCount++;
|
||||
if (data.gift) {
|
||||
members.sendMessage(data.user, data.gift.member, data.gift);
|
||||
|
||||
let byUserName = getUserInfo(data.user, ['name']).name;
|
||||
|
||||
if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) {
|
||||
txnEmail(data.gift.member, 'gifted-subscription', [
|
||||
{name: 'GIFTER', content: byUserName},
|
||||
{name: 'X_MONTHS_SUBSCRIPTION', content: months},
|
||||
]);
|
||||
}
|
||||
|
||||
if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`);
|
||||
}
|
||||
}
|
||||
async.parallel([
|
||||
function saveGiftingUserData (cb2) {
|
||||
data.user.save(cb2);
|
||||
},
|
||||
function saveRecipientUserData (cb2) {
|
||||
if (data.gift) {
|
||||
data.gift.member.save(cb2);
|
||||
} else {
|
||||
cb2(null);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets their subscription to be cancelled later
|
||||
*/
|
||||
api.cancelSubscription = function cancelSubscription (data, cb) {
|
||||
let plan = data.user.purchased.plan;
|
||||
let now = moment();
|
||||
let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30;
|
||||
|
||||
plan.dateTerminated =
|
||||
moment(`${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`)
|
||||
.add({days: remaining}) // end their subscription 1mo from their last payment
|
||||
.add({months: Math.ceil(plan.extraMonths)})// plus any extra time (carry-over, gifted subscription, etc) they have. FIXME: moment can't add months in fractions...
|
||||
.toDate();
|
||||
plan.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
||||
|
||||
data.user.save(cb);
|
||||
txnEmail(data.user, 'cancel-subscription');
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
gaCategory: 'commerce',
|
||||
gaLabel: data.paymentMethod,
|
||||
paymentMethod: data.paymentMethod,
|
||||
};
|
||||
analytics.track('unsubscribe', analyticsData);
|
||||
};
|
||||
|
||||
api.buyGems = function buyGems (data, cb) {
|
||||
let amt = data.amount || 5;
|
||||
amt = data.gift ? data.gift.gems.amount / 4 : amt;
|
||||
(data.gift ? data.gift.member : data.user).balance += amt;
|
||||
data.user.purchased.txnCount++;
|
||||
if (IS_PROD) {
|
||||
if (!data.gift) txnEmail(data.user, 'donation');
|
||||
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gems',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-checkout`,
|
||||
purchaseType: 'checkout',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: Boolean(data.gift),
|
||||
purchaseValue: amt,
|
||||
};
|
||||
analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
|
||||
if (data.gift) {
|
||||
let byUsername = getUserInfo(data.user, ['name']).name;
|
||||
let gemAmount = data.gift.gems.amount || 20;
|
||||
|
||||
members.sendMessage(data.user, data.gift.member, data.gift);
|
||||
if (data.gift.member.preferences.emailNotifications.giftedGems !== false) {
|
||||
txnEmail(data.gift.member, 'gifted-gems', [
|
||||
{name: 'GIFTER', content: byUsername},
|
||||
{name: 'X_GEMS_GIFTED', content: gemAmount},
|
||||
]);
|
||||
}
|
||||
|
||||
if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`);
|
||||
}
|
||||
}
|
||||
async.parallel([
|
||||
function saveGiftingUserData (cb2) {
|
||||
data.user.save(cb2);
|
||||
},
|
||||
function saveRecipientUserData (cb2) {
|
||||
if (data.gift) {
|
||||
data.gift.member.save(cb2);
|
||||
} else {
|
||||
cb2(null);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
};
|
||||
|
||||
api.validCoupon = function validCoupon (req, res, next) {
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(req.params.code), event: 'google_6mo'}, function couponErrorCheck (err, coupon) {
|
||||
if (err) return next(err);
|
||||
if (!coupon) return res.status(401).json({err: 'Invalid coupon code'});
|
||||
return res.sendStatus(200);
|
||||
});
|
||||
};
|
||||
|
||||
api.stripeCheckout = stripe.checkout;
|
||||
api.stripeSubscribeCancel = stripe.subscribeCancel;
|
||||
api.stripeSubscribeEdit = stripe.subscribeEdit;
|
||||
|
||||
api.paypalSubscribe = paypal.createBillingAgreement;
|
||||
api.paypalSubscribeSuccess = paypal.executeBillingAgreement;
|
||||
api.paypalSubscribeCancel = paypal.cancelSubscription;
|
||||
api.paypalCheckout = paypal.createPayment;
|
||||
api.paypalCheckoutSuccess = paypal.executePayment;
|
||||
api.paypalIPN = paypal.ipn;
|
||||
|
||||
api.amazonVerifyAccessToken = amazon.verifyAccessToken;
|
||||
api.amazonCreateOrderReferenceId = amazon.createOrderReferenceId;
|
||||
api.amazonCheckout = amazon.checkout;
|
||||
api.amazonSubscribe = amazon.subscribe;
|
||||
api.amazonSubscribeCancel = amazon.subscribeCancel;
|
||||
|
||||
api.iapAndroidVerify = iap.androidVerify;
|
||||
api.iapIosVerify = iap.iosVerify;
|
||||
|
||||
module.exports = api;
|
||||
@@ -1,135 +0,0 @@
|
||||
import nconf from 'nconf';
|
||||
import stripeModule from 'stripe';
|
||||
import async from 'async';
|
||||
import payments from './index';
|
||||
import { model as User } from '../../models/user';
|
||||
import shared from '../../../../../common';
|
||||
import mongoose from 'mongoose';
|
||||
import cc from 'coupon-code';
|
||||
|
||||
const stripe = stripeModule(nconf.get('STRIPE_API_KEY'));
|
||||
|
||||
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) {
|
||||
if (sub) {
|
||||
async.waterfall([
|
||||
function handleCoupon (cb2) {
|
||||
if (!sub.discount) return cb2(null, null);
|
||||
if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.');
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(req.query.coupon), event: sub.key}, cb2);
|
||||
},
|
||||
function createCustomer (coupon, cb2) {
|
||||
if (sub.discount && !coupon) return cb2('Invalid coupon code.');
|
||||
let customer = {
|
||||
email: req.body.email,
|
||||
metadata: {uuid: user._id},
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
};
|
||||
stripe.customers.create(customer, cb2);
|
||||
},
|
||||
], cb);
|
||||
} else {
|
||||
let amount;
|
||||
if (!gift) {
|
||||
amount = '500';
|
||||
} else if (gift.type === 'subscription') {
|
||||
amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`;
|
||||
} else {
|
||||
amount = `${gift.gems.amount / 4 * 100}`;
|
||||
}
|
||||
stripe.charges.create({
|
||||
amount,
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
}, cb);
|
||||
}
|
||||
},
|
||||
function saveUserData (response, cb) {
|
||||
if (sub) return payments.createSubscription({user, customerId: response.id, paymentMethod: 'Stripe', sub}, cb);
|
||||
async.waterfall([
|
||||
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) {
|
||||
let user = res.locals.user;
|
||||
if (!user.purchased.plan.customerId) {
|
||||
return res.status(401).json({err: 'User does not have a plan subscription'});
|
||||
}
|
||||
|
||||
async.auto({
|
||||
getCustomer: function getCustomer (cb) {
|
||||
stripe.customers.retrieve(user.purchased.plan.customerId, cb);
|
||||
},
|
||||
deleteCustomer: ['getCustomer', function deleteCustomer (cb) {
|
||||
stripe.customers.del(user.purchased.plan.customerId, cb);
|
||||
}],
|
||||
cancelSubscription: ['getCustomer', function cancelSubscription (cb, results) {
|
||||
let data = {
|
||||
user,
|
||||
nextBill: results.get_cus.subscription.current_period_end * 1000, // timestamp is in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
};
|
||||
payments.cancelSubscription(data, cb);
|
||||
}],
|
||||
}, function handleResponse (err) {
|
||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
||||
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;
|
||||
@@ -1,112 +1,128 @@
|
||||
var amazonPayments = require('amazon-payments');
|
||||
var mongoose = require('mongoose');
|
||||
var moment = require('moment');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var User = require('mongoose').model('User');
|
||||
var shared = require('../../../../common');
|
||||
var payments = require('./index');
|
||||
var cc = require('coupon-code');
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
import async from 'async';
|
||||
import cc from 'coupon-code';
|
||||
import mongoose from 'mongoose';
|
||||
import moment from 'moment';
|
||||
import payments from './index';
|
||||
import shared from '../../../../../common';
|
||||
import { model as User } from '../../../models/user';
|
||||
import {
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
BadRequest,
|
||||
} from '../../../libs/api-v3/errors';
|
||||
import amz from '../../../libs/api-v3/amazonPayments';
|
||||
|
||||
var amzPayment = amazonPayments.connect({
|
||||
environment: amazonPayments.Environment[isProd ? 'Production' : 'Sandbox'],
|
||||
sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
|
||||
mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'),
|
||||
mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'),
|
||||
clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
|
||||
});
|
||||
let api = {};
|
||||
|
||||
exports.verifyAccessToken = function(req, res, next){
|
||||
if(!req.body || !req.body['access_token']){
|
||||
return res.status(400).json({err: 'Access token not supplied.'});
|
||||
}
|
||||
|
||||
amzPayment.api.getTokenInfo(req.body['access_token'], function(err, tokenInfo){
|
||||
if(err) return res.status(400).json({err:err});
|
||||
|
||||
res.sendStatus(200);
|
||||
/**
|
||||
* @api {post} /api/v3/payments/amazon/verifyAccessToken verify access token
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName AmazonVerifyAccessToken
|
||||
* @apiGroup Payments
|
||||
* @apiParam {string} access_token the access token
|
||||
* @apiSuccess {} empty
|
||||
**/
|
||||
api.verifyAccessToken = {
|
||||
method: 'POST',
|
||||
url: '/payments/amazon/verifyAccessToken',
|
||||
async handler (req, res) {
|
||||
await amz.getTokenInfo(req.body.access_token)
|
||||
.then(() => {
|
||||
res.respond(200, {});
|
||||
}).catch( (error) => {
|
||||
throw new BadRequest(error.body.error_description);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
exports.createOrderReferenceId = function(req, res, next){
|
||||
if(!req.body || !req.body.billingAgreementId){
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
/**
|
||||
* @api {post} /api/v3/payments/amazon/createOrderReferenceId create order reference id
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName AmazonCreateOrderReferenceId
|
||||
* @apiGroup Payments
|
||||
* @apiParam {string} billingAgreementId billing agreement id
|
||||
* @apiSuccess {object} object containing { orderReferenceId }
|
||||
**/
|
||||
api.createOrderReferenceId = {
|
||||
method: 'POST',
|
||||
url: '/payments/amazon/createOrderReferenceId',
|
||||
async handler (req, res) {
|
||||
if (!req.body.billingAgreementId) {
|
||||
throw new BadRequest(res.t('missingBillingAgreementId'));
|
||||
}
|
||||
|
||||
amzPayment.offAmazonPayments.createOrderReferenceForId({
|
||||
let response = await amz.createOrderReferenceId({
|
||||
Id: req.body.billingAgreementId,
|
||||
IdType: 'BillingAgreement',
|
||||
ConfirmNow: false
|
||||
}, function(err, response){
|
||||
if(err) return next(err);
|
||||
if(!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId){
|
||||
return next(new Error('Missing attributes in Amazon response.'));
|
||||
}
|
||||
|
||||
res.json({
|
||||
orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId
|
||||
ConfirmNow: false,
|
||||
}).then(() => {
|
||||
res.respond(200, {
|
||||
orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId,
|
||||
});
|
||||
}).catch(errStr => {
|
||||
throw new BadRequest(res.t(errStr));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
exports.checkout = function(req, res, next){
|
||||
if(!req.body || !req.body.orderReferenceId){
|
||||
/*
|
||||
api.checkout = function checkout (req, res, next) {
|
||||
if (!req.body || !req.body.orderReferenceId) {
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
var gift = req.body.gift;
|
||||
var user = res.locals.user;
|
||||
var orderReferenceId = req.body.orderReferenceId;
|
||||
var amount = 5;
|
||||
let gift = req.body.gift;
|
||||
let user = res.locals.user;
|
||||
let orderReferenceId = req.body.orderReferenceId;
|
||||
let amount = 5;
|
||||
|
||||
if(gift){
|
||||
if(gift.type === 'gems'){
|
||||
amount = gift.gems.amount/4;
|
||||
}else if(gift.type === 'subscription'){
|
||||
if (gift) {
|
||||
if (gift.type === 'gems') {
|
||||
amount = gift.gems.amount / 4;
|
||||
} else if (gift.type === 'subscription') {
|
||||
amount = shared.content.subscriptionBlocks[gift.subscription.key].price;
|
||||
}
|
||||
}
|
||||
|
||||
async.series({
|
||||
setOrderReferenceDetails: function(cb){
|
||||
setOrderReferenceDetails (cb) {
|
||||
amzPayment.offAmazonPayments.setOrderReferenceDetails({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
OrderReferenceAttributes: {
|
||||
OrderTotal: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: amount
|
||||
Amount: amount,
|
||||
},
|
||||
SellerNote: 'HabitRPG Payment',
|
||||
SellerOrderAttributes: {
|
||||
SellerOrderId: shared.uuid(),
|
||||
StoreName: 'HabitRPG'
|
||||
}
|
||||
}
|
||||
StoreName: 'HabitRPG',
|
||||
},
|
||||
},
|
||||
}, cb);
|
||||
},
|
||||
|
||||
confirmOrderReference: function(cb){
|
||||
confirmOrderReference (cb) {
|
||||
amzPayment.offAmazonPayments.confirmOrderReference({
|
||||
AmazonOrderReferenceId: orderReferenceId
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
authorize: function(cb){
|
||||
authorize (cb) {
|
||||
amzPayment.offAmazonPayments.authorize({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
AuthorizationReferenceId: shared.uuid().substring(0, 32),
|
||||
AuthorizationAmount: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: amount
|
||||
Amount: amount,
|
||||
},
|
||||
SellerAuthorizationNote: 'HabitRPG Payment',
|
||||
TransactionTimeout: 0,
|
||||
CaptureNow: true
|
||||
}, function(err, res){
|
||||
if(err) return cb(err);
|
||||
CaptureNow: true,
|
||||
}, function checkAuthorizationStatus (err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){
|
||||
if (res.AuthorizationDetails.AuthorizationStatus.State === 'Declined') {
|
||||
return cb(new Error('The payment was not successfull.'));
|
||||
}
|
||||
|
||||
@@ -114,64 +130,65 @@ exports.checkout = function(req, res, next){
|
||||
});
|
||||
},
|
||||
|
||||
closeOrderReference: function(cb){
|
||||
closeOrderReference (cb) {
|
||||
amzPayment.offAmazonPayments.closeOrderReference({
|
||||
AmazonOrderReferenceId: orderReferenceId
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
executePayment: function(cb){
|
||||
executePayment (cb) {
|
||||
async.waterfall([
|
||||
function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); },
|
||||
function(member, cb2){
|
||||
var data = {user:user, paymentMethod:'Amazon Payments'};
|
||||
var method = 'buyGems';
|
||||
function findUser (cb2) {
|
||||
User.findById(gift ? gift.uuid : undefined, cb2);
|
||||
},
|
||||
function executeAmazonPayment (member, cb2) {
|
||||
let data = {user, paymentMethod: 'Amazon Payments'};
|
||||
let method = 'buyGems';
|
||||
|
||||
if (gift){
|
||||
if (gift.type == 'subscription') method = 'createSubscription';
|
||||
if (gift) {
|
||||
if (gift.type === 'subscription') method = 'createSubscription';
|
||||
gift.member = member;
|
||||
data.gift = gift;
|
||||
data.paymentMethod = 'Gift';
|
||||
}
|
||||
|
||||
payments[method](data, cb2);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
}
|
||||
}, function(err, results){
|
||||
if(err) return next(err);
|
||||
},
|
||||
}, function result (err) {
|
||||
if (err) return next(err);
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
exports.subscribe = function(req, res, next){
|
||||
if(!req.body || !req.body['billingAgreementId']){
|
||||
api.subscribe = function subscribe (req, res, next) {
|
||||
if (!req.body || !req.body.billingAgreementId) {
|
||||
return res.status(400).json({err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
var billingAgreementId = req.body.billingAgreementId;
|
||||
var sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false;
|
||||
var coupon = req.body.coupon;
|
||||
var user = res.locals.user;
|
||||
let billingAgreementId = req.body.billingAgreementId;
|
||||
let sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false;
|
||||
let coupon = req.body.coupon;
|
||||
let user = res.locals.user;
|
||||
|
||||
if(!sub){
|
||||
if (!sub) {
|
||||
return res.status(400).json({err: 'Subscription plan not found.'});
|
||||
}
|
||||
|
||||
async.series({
|
||||
applyDiscount: function(cb){
|
||||
applyDiscount (cb) {
|
||||
if (!sub.discount) return cb();
|
||||
if (!coupon) return cb(new Error('Please provide a coupon code for this plan.'));
|
||||
mongoose.model('Coupon').findOne({_id:cc.validate(coupon), event:sub.key}, function(err, coupon){
|
||||
if(err) return cb(err);
|
||||
if(!coupon) return cb(new Error('Coupon code not found.'));
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(coupon), event: sub.key}, function couponResult (err) {
|
||||
if (err) return cb(err);
|
||||
if (!coupon) return cb(new Error('Coupon code not found.'));
|
||||
cb();
|
||||
});
|
||||
},
|
||||
|
||||
setBillingAgreementDetails: function(cb){
|
||||
setBillingAgreementDetails (cb) {
|
||||
amzPayment.offAmazonPayments.setBillingAgreementDetails({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
BillingAgreementAttributes: {
|
||||
@@ -179,25 +196,25 @@ exports.subscribe = function(req, res, next){
|
||||
SellerBillingAgreementAttributes: {
|
||||
SellerBillingAgreementId: shared.uuid(),
|
||||
StoreName: 'HabitRPG',
|
||||
CustomInformation: 'HabitRPG Subscription'
|
||||
}
|
||||
}
|
||||
CustomInformation: 'HabitRPG Subscription',
|
||||
},
|
||||
},
|
||||
}, cb);
|
||||
},
|
||||
|
||||
confirmBillingAgreement: function(cb){
|
||||
confirmBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.confirmBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
authorizeOnBillingAgreeement: function(cb){
|
||||
authorizeOnBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.authorizeOnBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
AuthorizationReferenceId: shared.uuid().substring(0, 32),
|
||||
AuthorizationAmount: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: sub.price
|
||||
Amount: sub.price,
|
||||
},
|
||||
SellerAuthorizationNote: 'HabitRPG Subscription Payment',
|
||||
TransactionTimeout: 0,
|
||||
@@ -205,67 +222,70 @@ exports.subscribe = function(req, res, next){
|
||||
SellerNote: 'HabitRPG Subscription Payment',
|
||||
SellerOrderAttributes: {
|
||||
SellerOrderId: shared.uuid(),
|
||||
StoreName: 'HabitRPG'
|
||||
}
|
||||
}, function(err, res){
|
||||
if(err) return cb(err);
|
||||
StoreName: 'HabitRPG',
|
||||
},
|
||||
}, function billingAgreementResult (err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){
|
||||
return cb(new Error('The payment was not successfull.'));
|
||||
if (res.AuthorizationDetails.AuthorizationStatus.State === 'Declined') {
|
||||
return cb(new Error('The payment was not successful.'));
|
||||
}
|
||||
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
|
||||
createSubscription: function(cb){
|
||||
createSubscription (cb) {
|
||||
payments.createSubscription({
|
||||
user: user,
|
||||
user,
|
||||
customerId: billingAgreementId,
|
||||
paymentMethod: 'Amazon Payments',
|
||||
sub: sub
|
||||
sub,
|
||||
}, cb);
|
||||
}
|
||||
}, function(err, results){
|
||||
if(err) return next(err);
|
||||
},
|
||||
}, function subscribeResult (err) {
|
||||
if (err) return next(err);
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
};
|
||||
|
||||
exports.subscribeCancel = function(req, res, next){
|
||||
var user = res.locals.user;
|
||||
api.subscribeCancel = function subscribeCancel (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
if (!user.purchased.plan.customerId)
|
||||
return res.status(401).json({err: 'User does not have a plan subscription'});
|
||||
|
||||
var billingAgreementId = user.purchased.plan.customerId;
|
||||
let billingAgreementId = user.purchased.plan.customerId;
|
||||
|
||||
async.series({
|
||||
closeBillingAgreement: function(cb){
|
||||
closeBillingAgreement (cb) {
|
||||
amzPayment.offAmazonPayments.closeBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
}, cb);
|
||||
},
|
||||
|
||||
cancelSubscription: function(cb){
|
||||
var data = {
|
||||
user: user,
|
||||
cancelSubscription (cb) {
|
||||
let data = {
|
||||
user,
|
||||
// Date of next bill
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({days: 30}),
|
||||
paymentMethod: 'Amazon Payments'
|
||||
paymentMethod: 'Amazon Payments',
|
||||
};
|
||||
|
||||
payments.cancelSubscription(data, cb);
|
||||
}
|
||||
}, function(err, results){
|
||||
},
|
||||
}, function subscribeCancelResult (err) {
|
||||
if (err) return next(err); // don't json this, let toString() handle errors
|
||||
|
||||
if(req.query.noRedirect){
|
||||
if (req.query.noRedirect) {
|
||||
res.sendStatus(200);
|
||||
}else{
|
||||
} else {
|
||||
res.redirect('/');
|
||||
}
|
||||
|
||||
user = null;
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
module.exports = api;
|
||||
|
||||
@@ -1,67 +1,66 @@
|
||||
var iap = require('in-app-purchase');
|
||||
var async = require('async');
|
||||
var payments = require('./index');
|
||||
var nconf = require('nconf');
|
||||
import iap from 'in-app-purchase';
|
||||
import whatThis from 'in-app-purchase';
|
||||
import payments from './index';
|
||||
import nconf from 'nconf';
|
||||
|
||||
var inAppPurchase = require('in-app-purchase');
|
||||
inAppPurchase.config({
|
||||
iap.config({
|
||||
// this is the path to the directory containing iap-sanbox/iap-live files
|
||||
googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR')
|
||||
googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR'),
|
||||
});
|
||||
|
||||
// Validation ERROR Codes
|
||||
var INVALID_PAYLOAD = 6778001;
|
||||
var CONNECTION_FAILED = 6778002;
|
||||
var PURCHASE_EXPIRED = 6778003;
|
||||
const INVALID_PAYLOAD = 6778001;
|
||||
/* const CONNECTION_FAILED = 6778002;
|
||||
const PURCHASE_EXPIRED = 6778003; */ // These variables were never used??
|
||||
|
||||
exports.androidVerify = function(req, res, next) {
|
||||
var iapBody = req.body;
|
||||
var user = res.locals.user;
|
||||
let api = {};
|
||||
|
||||
iap.setup(function (error) {
|
||||
/*
|
||||
api.androidVerify = function androidVerify (req, res) {
|
||||
let iapBody = req.body;
|
||||
let user = res.locals.user;
|
||||
|
||||
iap.setup(function googleSetupResult (error) {
|
||||
if (error) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
data: 'IAP Error',
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
google receipt must be provided as an object
|
||||
{
|
||||
"data": "{stringified data object}",
|
||||
"signature": "signature from google"
|
||||
}
|
||||
*/
|
||||
var testObj = {
|
||||
// google receipt must be provided as an object
|
||||
// {
|
||||
// "data": "{stringified data object}",
|
||||
// "signature": "signature from google"
|
||||
// }
|
||||
let testObj = {
|
||||
data: iapBody.transaction.receipt,
|
||||
signature: iapBody.transaction.signature
|
||||
signature: iapBody.transaction.signature,
|
||||
};
|
||||
|
||||
// iap is ready
|
||||
iap.validate(iap.GOOGLE, testObj, function (err, googleRes) {
|
||||
iap.validate(iap.GOOGLE, testObj, function googleValidateResult (err, googleRes) {
|
||||
if (err) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString()
|
||||
}
|
||||
message: err.toString(),
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
if (iap.isValidated(googleRes)) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: true,
|
||||
data: googleRes
|
||||
data: googleRes,
|
||||
};
|
||||
|
||||
payments.buyGems({user:user, paymentMethod:'IAP GooglePlay', amount: 5.25});
|
||||
payments.buyGems({user, paymentMethod: 'IAP GooglePlay', amount: 5.25});
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
@@ -69,87 +68,89 @@ exports.androidVerify = function(req, res, next) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.iosVerify = function(req, res, next) {
|
||||
var iapBody = req.body;
|
||||
var user = res.locals.user;
|
||||
exports.iosVerify = function iosVerify (req, res) {
|
||||
let iapBody = req.body;
|
||||
let user = res.locals.user;
|
||||
|
||||
iap.setup(function (error) {
|
||||
iap.setup(function iosSetupResult (error) {
|
||||
if (error) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
data: 'IAP Error',
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
|
||||
}
|
||||
|
||||
//iap is ready
|
||||
iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) {
|
||||
// iap is ready
|
||||
iap.validate(iap.APPLE, iapBody.transaction.receipt, function iosValidateResult (err, appleRes) {
|
||||
if (err) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString()
|
||||
}
|
||||
message: err.toString(),
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
}
|
||||
|
||||
if (iap.isValidated(appleRes)) {
|
||||
var purchaseDataList = iap.getPurchaseData(appleRes);
|
||||
let purchaseDataList = iap.getPurchaseData(appleRes);
|
||||
if (purchaseDataList.length > 0) {
|
||||
var correctReceipt = true;
|
||||
for (var index in purchaseDataList) {
|
||||
let correctReceipt = true;
|
||||
for (let index of purchaseDataList) {
|
||||
switch (purchaseDataList[index].productId) {
|
||||
case 'com.habitrpg.ios.Habitica.4gems':
|
||||
payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 1});
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.8gems':
|
||||
payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 2});
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.20gems':
|
||||
case 'com.habitrpg.ios.Habitica.21gems':
|
||||
payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 5.25});
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25});
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.42gems':
|
||||
payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 10.5});
|
||||
payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5});
|
||||
break;
|
||||
default:
|
||||
correctReceipt = false;
|
||||
}
|
||||
}
|
||||
if (correctReceipt) {
|
||||
var resObj = {
|
||||
let resObj = {
|
||||
ok: true,
|
||||
data: appleRes
|
||||
data: appleRes,
|
||||
};
|
||||
// yay good!
|
||||
return res.json(resObj);
|
||||
}
|
||||
}
|
||||
//wrong receipt content
|
||||
var resObj = {
|
||||
// wrong receipt content
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: 'Incorrect receipt content'
|
||||
}
|
||||
message: 'Incorrect receipt content',
|
||||
},
|
||||
};
|
||||
return res.json(resObj);
|
||||
}
|
||||
//invalid receipt
|
||||
var resObj = {
|
||||
// invalid receipt
|
||||
let resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: 'Invalid receipt'
|
||||
}
|
||||
message: 'Invalid receipt',
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(resObj);
|
||||
});
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
module.exports = api;
|
||||
|
||||
@@ -1,207 +1,233 @@
|
||||
var _ = require('lodash');
|
||||
var shared = require('../../../../common');
|
||||
var nconf = require('nconf');
|
||||
var utils = require('./../../libs/api-v2/utils');
|
||||
var moment = require('moment');
|
||||
var isProduction = nconf.get("NODE_ENV") === "production";
|
||||
var stripe = require('./stripe');
|
||||
var paypal = require('./paypal');
|
||||
var amazon = require('./amazon');
|
||||
var members = require('../api-v2/members')
|
||||
var async = require('async');
|
||||
var iap = require('./iap');
|
||||
var mongoose= require('mongoose');
|
||||
var cc = require('coupon-code');
|
||||
var pushNotify = require('./../api-v2/pushNotifications');
|
||||
import _ from 'lodash' ;
|
||||
import analytics from '../../../libs/api-v3/analyticsService';
|
||||
import async from 'async';
|
||||
import cc from 'coupon-code';
|
||||
import {
|
||||
getUserInfo,
|
||||
sendTxn as txnEmail,
|
||||
} from '../../../libs/api-v3/email';
|
||||
import members from '../members';
|
||||
import moment from 'moment';
|
||||
import mongoose from 'mongoose';
|
||||
import nconf from 'nconf';
|
||||
import pushNotify from '../../../libs/api-v3/pushNotifications';
|
||||
import shared from '../../../../../common' ;
|
||||
|
||||
function revealMysteryItems(user) {
|
||||
_.each(shared.content.gear.flat, function(item) {
|
||||
import amazon from './amazon';
|
||||
import iap from './iap';
|
||||
import paypal from './paypal';
|
||||
import stripe from './stripe';
|
||||
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
let api = {};
|
||||
|
||||
function revealMysteryItems (user) {
|
||||
_.each(shared.content.gear.flat, function findMysteryItems (item) {
|
||||
if (
|
||||
item.klass === 'mystery' &&
|
||||
moment().isAfter(shared.content.mystery[item.mystery].start) &&
|
||||
moment().isBefore(shared.content.mystery[item.mystery].end) &&
|
||||
!user.items.gear.owned[item.key] &&
|
||||
!~user.purchased.plan.mysteryItems.indexOf(item.key)
|
||||
user.purchased.plan.mysteryItems.indexOf(item.key) !== -1
|
||||
) {
|
||||
user.purchased.plan.mysteryItems.push(item.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.createSubscription = function(data, cb) {
|
||||
var recipient = data.gift ? data.gift.member : data.user;
|
||||
//if (!recipient.purchased.plan) recipient.purchased.plan = {}; // TODO double-check, this should never be the case
|
||||
var p = recipient.purchased.plan;
|
||||
var block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
|
||||
var months = +block.months;
|
||||
api.createSubscription = function createSubscription (data, cb) {
|
||||
let recipient = data.gift ? data.gift.member : data.user;
|
||||
let plan = recipient.purchased.plan;
|
||||
let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
|
||||
let months = Number(block.months);
|
||||
|
||||
if (data.gift) {
|
||||
if (p.customerId && !p.dateTerminated) { // User has active plan
|
||||
p.extraMonths += months;
|
||||
if (plan.customerId && !plan.dateTerminated) { // User has active plan
|
||||
plan.extraMonths += months;
|
||||
} else {
|
||||
p.dateTerminated = moment(p.dateTerminated).add({months: months}).toDate();
|
||||
if (!p.dateUpdated) p.dateUpdated = new Date();
|
||||
plan.dateTerminated = moment(plan.dateTerminated).add({months}).toDate();
|
||||
if (!plan.dateUpdated) plan.dateUpdated = new Date();
|
||||
}
|
||||
if (!p.customerId) p.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId
|
||||
if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId
|
||||
} else {
|
||||
_(p).merge({ // override with these values
|
||||
_(plan).merge({ // override with these values
|
||||
planId: block.key,
|
||||
customerId: data.customerId,
|
||||
dateUpdated: new Date(),
|
||||
gemsBought: 0,
|
||||
paymentMethod: data.paymentMethod,
|
||||
extraMonths: +p.extraMonths
|
||||
+ +(p.dateTerminated ? moment(p.dateTerminated).diff(new Date(),'months',true) : 0),
|
||||
extraMonths: Number(plan.extraMonths) +
|
||||
Number(plan.dateTerminated ? moment(plan.dateTerminated).diff(new Date(), 'months', true) : 0),
|
||||
dateTerminated: null,
|
||||
// Specify a lastBillingDate just for Amazon Payments
|
||||
// Resetted every time the subscription restarts
|
||||
lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined
|
||||
lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined,
|
||||
}).defaults({ // allow non-override if a plan was previously used
|
||||
dateCreated: new Date(),
|
||||
mysteryItems: []
|
||||
mysteryItems: [],
|
||||
}).value();
|
||||
}
|
||||
|
||||
// Block sub perks
|
||||
var perks = Math.floor(months/3);
|
||||
let perks = Math.floor(months / 3);
|
||||
if (perks) {
|
||||
p.consecutive.offset += months;
|
||||
p.consecutive.gemCapExtra += perks*5;
|
||||
if (p.consecutive.gemCapExtra > 25) p.consecutive.gemCapExtra = 25;
|
||||
p.consecutive.trinkets += perks;
|
||||
plan.consecutive.offset += months;
|
||||
plan.consecutive.gemCapExtra += perks * 5;
|
||||
if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25;
|
||||
plan.consecutive.trinkets += perks;
|
||||
}
|
||||
revealMysteryItems(recipient);
|
||||
if(isProduction) {
|
||||
if (!data.gift) utils.txnEmail(data.user, 'subscription-begins');
|
||||
if (IS_PROD) {
|
||||
if (!data.gift) txnEmail(data.user, 'subscription-begins');
|
||||
|
||||
var analyticsData = {
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Subscription',
|
||||
sku: data.paymentMethod.toLowerCase() + '-subscription',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-subscription`,
|
||||
purchaseType: 'subscribe',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: !!data.gift, // coerced into a boolean
|
||||
purchaseValue: block.price
|
||||
}
|
||||
utils.analytics.trackPurchase(analyticsData);
|
||||
gift: Boolean(data.gift),
|
||||
purchaseValue: block.price,
|
||||
};
|
||||
analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
data.user.purchased.txnCount++;
|
||||
if (data.gift){
|
||||
if (data.gift) {
|
||||
members.sendMessage(data.user, data.gift.member, data.gift);
|
||||
|
||||
var byUserName = utils.getUserInfo(data.user, ['name']).name;
|
||||
let byUserName = getUserInfo(data.user, ['name']).name;
|
||||
|
||||
if(data.gift.member.preferences.emailNotifications.giftedSubscription !== false){
|
||||
utils.txnEmail(data.gift.member, 'gifted-subscription', [
|
||||
if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) {
|
||||
txnEmail(data.gift.member, 'gifted-subscription', [
|
||||
{name: 'GIFTER', content: byUserName},
|
||||
{name: 'X_MONTHS_SUBSCRIPTION', content: months}
|
||||
{name: 'X_MONTHS_SUBSCRIPTION', content: months},
|
||||
]);
|
||||
}
|
||||
|
||||
if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), months + " months - by "+ byUserName);
|
||||
if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`);
|
||||
}
|
||||
}
|
||||
async.parallel([
|
||||
function(cb2){data.user.save(cb2)},
|
||||
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
||||
function saveGiftingUserData (cb2) {
|
||||
data.user.save(cb2);
|
||||
},
|
||||
function saveRecipientUserData (cb2) {
|
||||
if (data.gift) {
|
||||
data.gift.member.save(cb2);
|
||||
} else {
|
||||
cb2(null);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets their subscription to be cancelled later
|
||||
*/
|
||||
exports.cancelSubscription = function(data, cb) {
|
||||
var p = data.user.purchased.plan,
|
||||
now = moment(),
|
||||
remaining = data.nextBill ? moment(data.nextBill).diff(new Date, 'days') : 30;
|
||||
api.cancelSubscription = function cancelSubscription (data, cb) {
|
||||
let plan = data.user.purchased.plan;
|
||||
let now = moment();
|
||||
let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30;
|
||||
|
||||
p.dateTerminated =
|
||||
moment( now.format('MM') + '/' + moment(p.dateUpdated).format('DD') + '/' + now.format('YYYY') )
|
||||
plan.dateTerminated =
|
||||
moment(`${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`)
|
||||
.add({days: remaining}) // end their subscription 1mo from their last payment
|
||||
.add({months: Math.ceil(p.extraMonths)})// plus any extra time (carry-over, gifted subscription, etc) they have. TODO: moment can't add months in fractions...
|
||||
.add({months: Math.ceil(plan.extraMonths)})// plus any extra time (carry-over, gifted subscription, etc) they have. FIXME: moment can't add months in fractions...
|
||||
.toDate();
|
||||
p.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
||||
plan.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
||||
|
||||
data.user.save(cb);
|
||||
utils.txnEmail(data.user, 'cancel-subscription');
|
||||
var analyticsData = {
|
||||
txnEmail(data.user, 'cancel-subscription');
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
gaCategory: 'commerce',
|
||||
gaLabel: data.paymentMethod,
|
||||
paymentMethod: data.paymentMethod
|
||||
}
|
||||
utils.analytics.track('unsubscribe', analyticsData);
|
||||
}
|
||||
paymentMethod: data.paymentMethod,
|
||||
};
|
||||
analytics.track('unsubscribe', analyticsData);
|
||||
};
|
||||
|
||||
exports.buyGems = function(data, cb) {
|
||||
var amt = data.amount || 5;
|
||||
amt = data.gift ? data.gift.gems.amount/4 : amt;
|
||||
api.buyGems = function buyGems (data, cb) {
|
||||
let amt = data.amount || 5;
|
||||
amt = data.gift ? data.gift.gems.amount / 4 : amt;
|
||||
(data.gift ? data.gift.member : data.user).balance += amt;
|
||||
data.user.purchased.txnCount++;
|
||||
if(isProduction) {
|
||||
if (!data.gift) utils.txnEmail(data.user, 'donation');
|
||||
if (IS_PROD) {
|
||||
if (!data.gift) txnEmail(data.user, 'donation');
|
||||
|
||||
var analyticsData = {
|
||||
let analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gems',
|
||||
sku: data.paymentMethod.toLowerCase() + '-checkout',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-checkout`,
|
||||
purchaseType: 'checkout',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: !!data.gift, // coerced into a boolean
|
||||
purchaseValue: amt
|
||||
}
|
||||
utils.analytics.trackPurchase(analyticsData);
|
||||
gift: Boolean(data.gift),
|
||||
purchaseValue: amt,
|
||||
};
|
||||
analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
|
||||
if (data.gift){
|
||||
var byUsername = utils.getUserInfo(data.user, ['name']).name;
|
||||
var gemAmount = data.gift.gems.amount || 20;
|
||||
if (data.gift) {
|
||||
let byUsername = getUserInfo(data.user, ['name']).name;
|
||||
let gemAmount = data.gift.gems.amount || 20;
|
||||
|
||||
members.sendMessage(data.user, data.gift.member, data.gift);
|
||||
if(data.gift.member.preferences.emailNotifications.giftedGems !== false){
|
||||
utils.txnEmail(data.gift.member, 'gifted-gems', [
|
||||
if (data.gift.member.preferences.emailNotifications.giftedGems !== false) {
|
||||
txnEmail(data.gift.member, 'gifted-gems', [
|
||||
{name: 'GIFTER', content: byUsername},
|
||||
{name: 'X_GEMS_GIFTED', content: gemAmount}
|
||||
{name: 'X_GEMS_GIFTED', content: gemAmount},
|
||||
]);
|
||||
}
|
||||
|
||||
if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), gemAmount + ' Gems - by '+byUsername);
|
||||
if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
|
||||
pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`);
|
||||
}
|
||||
}
|
||||
async.parallel([
|
||||
function(cb2){data.user.save(cb2)},
|
||||
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
||||
function saveGiftingUserData (cb2) {
|
||||
data.user.save(cb2);
|
||||
},
|
||||
function saveRecipientUserData (cb2) {
|
||||
if (data.gift) {
|
||||
data.gift.member.save(cb2);
|
||||
} else {
|
||||
cb2(null);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
}
|
||||
};
|
||||
|
||||
exports.validCoupon = function(req, res, next){
|
||||
mongoose.model('Coupon').findOne({_id:cc.validate(req.params.code), event:'google_6mo'}, function(err, coupon){
|
||||
api.validCoupon = function validCoupon (req, res, next) {
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(req.params.code), event: 'google_6mo'}, function couponErrorCheck (err, coupon) {
|
||||
if (err) return next(err);
|
||||
if (!coupon) return res.status(401).json({err:"Invalid coupon code"});
|
||||
if (!coupon) return res.status(401).json({err: 'Invalid coupon code'});
|
||||
return res.sendStatus(200);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.stripeCheckout = stripe.checkout;
|
||||
exports.stripeSubscribeCancel = stripe.subscribeCancel;
|
||||
exports.stripeSubscribeEdit = stripe.subscribeEdit;
|
||||
api.stripeCheckout = stripe.checkout;
|
||||
api.stripeSubscribeCancel = stripe.subscribeCancel;
|
||||
api.stripeSubscribeEdit = stripe.subscribeEdit;
|
||||
|
||||
exports.paypalSubscribe = paypal.createBillingAgreement;
|
||||
exports.paypalSubscribeSuccess = paypal.executeBillingAgreement;
|
||||
exports.paypalSubscribeCancel = paypal.cancelSubscription;
|
||||
exports.paypalCheckout = paypal.createPayment;
|
||||
exports.paypalCheckoutSuccess = paypal.executePayment;
|
||||
exports.paypalIPN = paypal.ipn;
|
||||
api.paypalSubscribe = paypal.createBillingAgreement;
|
||||
api.paypalSubscribeSuccess = paypal.executeBillingAgreement;
|
||||
api.paypalSubscribeCancel = paypal.cancelSubscription;
|
||||
api.paypalCheckout = paypal.createPayment;
|
||||
api.paypalCheckoutSuccess = paypal.executePayment;
|
||||
api.paypalIPN = paypal.ipn;
|
||||
|
||||
exports.amazonVerifyAccessToken = amazon.verifyAccessToken;
|
||||
exports.amazonCreateOrderReferenceId = amazon.createOrderReferenceId;
|
||||
exports.amazonCheckout = amazon.checkout;
|
||||
exports.amazonSubscribe = amazon.subscribe;
|
||||
exports.amazonSubscribeCancel = amazon.subscribeCancel;
|
||||
api.amazonVerifyAccessToken = amazon.verifyAccessToken;
|
||||
api.amazonCreateOrderReferenceId = amazon.createOrderReferenceId;
|
||||
api.amazonCheckout = amazon.checkout;
|
||||
api.amazonSubscribe = amazon.subscribe;
|
||||
api.amazonSubscribeCancel = amazon.subscribeCancel;
|
||||
|
||||
exports.iapAndroidVerify = iap.androidVerify;
|
||||
exports.iapIosVerify = iap.iosVerify;
|
||||
api.iapAndroidVerify = iap.androidVerify;
|
||||
api.iapIosVerify = iap.iosVerify;
|
||||
|
||||
// module.exports = api;
|
||||
module.exports = {}; // @TODO HEREHERE
|
||||
|
||||
@@ -5,10 +5,10 @@ var _ = require('lodash');
|
||||
var url = require('url');
|
||||
var User = require('mongoose').model('User');
|
||||
var payments = require('./index');
|
||||
var logger = require('../../libs/api-v2/logging');
|
||||
var logger = require('../../../libs/api-v2/logging');
|
||||
var ipn = require('paypal-ipn');
|
||||
var paypal = require('paypal-rest-sdk');
|
||||
var shared = require('../../../../common');
|
||||
var shared = require('../../../../../common');
|
||||
var mongoose = require('mongoose');
|
||||
var cc = require('coupon-code');
|
||||
|
||||
@@ -31,6 +31,7 @@ var parseErr = function(res, err){
|
||||
return res.status(400).json({err:error});
|
||||
}
|
||||
|
||||
/*
|
||||
exports.createBillingAgreement = function(req,res,next){
|
||||
var sub = shared.content.subscriptionBlocks[req.query.sub];
|
||||
async.waterfall([
|
||||
@@ -190,11 +191,13 @@ exports.cancelSubscription = function(req, res, next){
|
||||
user = null;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their
|
||||
* recurring paypal payments in their paypal dashboard. Remove this when we can move to webhooks or some other solution
|
||||
*/
|
||||
/*
|
||||
exports.ipn = function(req, res, next) {
|
||||
console.log('IPN Called');
|
||||
res.sendStatus(200); // Must respond to PayPal IPN request with an empty 200 first
|
||||
@@ -213,4 +216,4 @@ exports.ipn = function(req, res, next) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
@@ -1,123 +1,137 @@
|
||||
var nconf = require('nconf');
|
||||
var stripe = require('stripe')(nconf.get('STRIPE_API_KEY'));
|
||||
var async = require('async');
|
||||
var payments = require('./index');
|
||||
var User = require('mongoose').model('User');
|
||||
var shared = require('../../../../common');
|
||||
var mongoose = require('mongoose');
|
||||
var cc = require('coupon-code');
|
||||
import nconf from 'nconf';
|
||||
import stripeModule from 'stripe';
|
||||
import async from 'async';
|
||||
import payments from './index';
|
||||
import { model as User } from '../../../models/user';
|
||||
import shared from '../../../../../common';
|
||||
import mongoose from 'mongoose';
|
||||
import cc from 'coupon-code';
|
||||
|
||||
const stripe = stripeModule(nconf.get('STRIPE_API_KEY'));
|
||||
|
||||
let api = {};
|
||||
/*
|
||||
Setup Stripe response when posting payment
|
||||
*/
|
||||
exports.checkout = function(req, res, next) {
|
||||
var token = req.body.id;
|
||||
var user = res.locals.user;
|
||||
var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||
var sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false;
|
||||
/*
|
||||
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(cb){
|
||||
function stripeCharge (cb) {
|
||||
if (sub) {
|
||||
async.waterfall([
|
||||
function(cb2){
|
||||
function handleCoupon (cb2) {
|
||||
if (!sub.discount) return cb2(null, null);
|
||||
if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.');
|
||||
mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb2);
|
||||
mongoose.model('Coupon').findOne({_id: cc.validate(req.query.coupon), event: sub.key}, cb2);
|
||||
},
|
||||
function(coupon, cb2){
|
||||
function createCustomer (coupon, cb2) {
|
||||
if (sub.discount && !coupon) return cb2('Invalid coupon code.');
|
||||
var customer = {
|
||||
let customer = {
|
||||
email: req.body.email,
|
||||
metadata: {uuid: user._id},
|
||||
card: token,
|
||||
plan: sub.key
|
||||
plan: sub.key,
|
||||
};
|
||||
stripe.customers.create(customer, cb2);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
} else {
|
||||
let amount;
|
||||
if (!gift) {
|
||||
amount = '500';
|
||||
} else if (gift.type === 'subscription') {
|
||||
amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`;
|
||||
} else {
|
||||
amount = `${gift.gems.amount / 4 * 100}`;
|
||||
}
|
||||
stripe.charges.create({
|
||||
amount: !gift ? '500' //"500" = $5
|
||||
: gift.type=='subscription' ? ''+shared.content.subscriptionBlocks[gift.subscription.key].price*100
|
||||
: ''+gift.gems.amount/4*100,
|
||||
amount,
|
||||
currency: 'usd',
|
||||
card: token
|
||||
card: token,
|
||||
}, cb);
|
||||
}
|
||||
},
|
||||
function(response, cb) {
|
||||
if (sub) return payments.createSubscription({user:user, customerId:response.id, paymentMethod:'Stripe', sub:sub}, cb);
|
||||
function saveUserData (response, cb) {
|
||||
if (sub) return payments.createSubscription({user, customerId: response.id, paymentMethod: 'Stripe', sub}, cb);
|
||||
async.waterfall([
|
||||
function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); },
|
||||
function(member, cb2){
|
||||
var data = {user:user, customerId:response.id, paymentMethod:'Stripe', gift:gift};
|
||||
var method = 'buyGems';
|
||||
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';
|
||||
if (gift.type === 'subscription') method = 'createSubscription';
|
||||
data.paymentMethod = 'Gift';
|
||||
}
|
||||
payments[method](data, cb2);
|
||||
}
|
||||
},
|
||||
], cb);
|
||||
}
|
||||
], function(err){
|
||||
},
|
||||
], 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;
|
||||
});
|
||||
};
|
||||
|
||||
exports.subscribeCancel = function(req, res, next) {
|
||||
var user = res.locals.user;
|
||||
if (!user.purchased.plan.customerId)
|
||||
api.subscribeCancel = function subscribeCancel (req, res) {
|
||||
let 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){
|
||||
getCustomer: function getCustomer (cb) {
|
||||
stripe.customers.retrieve(user.purchased.plan.customerId, cb);
|
||||
},
|
||||
del_cus: ['get_cus', function(cb, results){
|
||||
deleteCustomer: ['getCustomer', function deleteCustomer (cb) {
|
||||
stripe.customers.del(user.purchased.plan.customerId, cb);
|
||||
}],
|
||||
cancel_sub: ['get_cus', function(cb, results) {
|
||||
var data = {
|
||||
user: user,
|
||||
nextBill: results.get_cus.subscription.current_period_end*1000, // timestamp is in seconds
|
||||
paymentMethod: 'Stripe'
|
||||
cancelSubscription: ['getCustomer', function cancelSubscription (cb, results) {
|
||||
let data = {
|
||||
user,
|
||||
nextBill: results.get_cus.subscription.current_period_end * 1000, // timestamp is in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
};
|
||||
payments.cancelSubscription(data, cb);
|
||||
}]
|
||||
}, function(err, results){
|
||||
}],
|
||||
}, function handleResponse (err) {
|
||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
||||
res.redirect('/');
|
||||
user = null;
|
||||
});
|
||||
};
|
||||
|
||||
exports.subscribeEdit = function(req, res, next) {
|
||||
var token = req.body.id;
|
||||
var user = res.locals.user;
|
||||
var user_id = user.purchased.plan.customerId;
|
||||
var sub_id;
|
||||
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(cb){
|
||||
stripe.customers.listSubscriptions(user_id, cb);
|
||||
function listSubscriptions (cb) {
|
||||
stripe.customers.listSubscriptions(userId, cb);
|
||||
},
|
||||
function(response, cb) {
|
||||
sub_id = response.data[0].id;
|
||||
console.warn(sub_id);
|
||||
console.warn([user_id, sub_id, { card: token }]);
|
||||
stripe.customers.updateSubscription(user_id, sub_id, { card: token }, cb);
|
||||
function updateSubscription (response, cb) {
|
||||
subscriptionId = response.data[0].id;
|
||||
stripe.customers.updateSubscription(userId, subscriptionId, { card: token }, cb);
|
||||
},
|
||||
function(response, cb) {
|
||||
function saveUser (response, cb) {
|
||||
user.save(cb);
|
||||
}
|
||||
], function(err, saved){
|
||||
},
|
||||
], function handleResponse (err) {
|
||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
||||
res.sendStatus(200);
|
||||
token = user = user_id = sub_id;
|
||||
token = user = userId = subscriptionId;
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
module.exports = api;
|
||||
|
||||
38
website/src/libs/api-v3/amazonPayments.js
Normal file
38
website/src/libs/api-v3/amazonPayments.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import amazonPayments from 'amazon-payments';
|
||||
import nconf from 'nconf';
|
||||
import Q from 'q';
|
||||
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
let api = {};
|
||||
|
||||
let amzPayment = amazonPayments.connect({
|
||||
environment: amazonPayments.Environment[IS_PROD ? 'Production' : 'Sandbox'],
|
||||
sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
|
||||
mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'),
|
||||
mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'),
|
||||
clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'),
|
||||
});
|
||||
|
||||
api.getTokenInfo = (token) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
amzPayment.api.getTokenInfo(token, (err, tokenInfo) => {
|
||||
if (err) return reject(err);
|
||||
return resolve(tokenInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
api.createOrderReferenceId = (inputSet) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
amzPayment.offAmazonPayments.createOrderReferenceForId(inputSet, (err, response) => {
|
||||
if (err) return reject(err);
|
||||
if (!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId) {
|
||||
return reject('missingAttributesFromAmazon');
|
||||
}
|
||||
return resolve(response);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
Reference in New Issue
Block a user