mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 07:37:25 +01:00
feat(amazon-payments): working subscriptions, put donations and gifts on hold
This commit is contained in:
@@ -65,6 +65,7 @@ function($rootScope, User, $http, Content) {
|
||||
console.error(error);
|
||||
console.log(error.getErrorMessage(), error.getErrorCode());
|
||||
alert(error.getErrorMessage());
|
||||
Payments.amazonPayments.reset();
|
||||
};
|
||||
|
||||
Payments.amazonPayments = {};
|
||||
@@ -79,24 +80,30 @@ function($rootScope, User, $http, Content) {
|
||||
Payments.amazonPayments.type = null;
|
||||
Payments.amazonPayments.loggedIn = false;
|
||||
Payments.amazonPayments.gift = null;
|
||||
Payments.amazonPayments.orderReferenceId = null;
|
||||
Payments.amazonPayments.billingAgreementId = null;
|
||||
Payments.amazonPayments.paymentSelected = false;
|
||||
Payments.amazonPayments.recurringConsent = false;
|
||||
Payments.amazonPayments.subscription = null;
|
||||
Payments.amazonPayments.coupon = null;
|
||||
};
|
||||
|
||||
// Needs to be called everytime the modal/router is accessed
|
||||
Payments.amazonPayments.init = function(type, gift, giftedTo){
|
||||
if(gift){
|
||||
if(gift.gems && gift.gems.amount && gift.gems.amount <= 0) return;
|
||||
gift.uuid = giftedTo;
|
||||
Payments.amazonPayments.init = function(data){
|
||||
if(!isAmazonReady) return;
|
||||
if(data.type !== 'donation' && data.type !== 'subscription') return;
|
||||
|
||||
if(data.gift){
|
||||
if(data.gift.gems && data.gift.gems.amount && data.gift.gems.amount <= 0) return;
|
||||
gift.uuid = data.giftedTo;
|
||||
}
|
||||
|
||||
if(!isAmazonReady) return;
|
||||
if(type !== 'donation' && type !== 'subscription') return;
|
||||
if(data.subscription){
|
||||
Payments.amazonPayments.subscription = data.subscription;
|
||||
Payments.amazonPayments.coupon = data.coupon;
|
||||
}
|
||||
|
||||
Payments.amazonPayments.gift = gift;
|
||||
Payments.amazonPayments.type = type;
|
||||
Payments.amazonPayments.gift = data.gift;
|
||||
Payments.amazonPayments.type = data.type;
|
||||
|
||||
var modal = Payments.amazonPayments.modal = $rootScope.openModal('amazonPayments', {
|
||||
// Allow the modal to be closed only by pressing cancel
|
||||
@@ -153,24 +160,12 @@ function($rootScope, User, $http, Content) {
|
||||
design: {
|
||||
designMode: 'responsive'
|
||||
},
|
||||
agreementType: 'BillingAgreement',
|
||||
|
||||
onPaymentSelect: function(orderReference) {
|
||||
$rootScope.$apply(function(){
|
||||
Payments.amazonPayments.paymentSelected = true;
|
||||
});
|
||||
},
|
||||
|
||||
onError: amazonOnError
|
||||
};
|
||||
|
||||
if(Payments.amazonPayments.type === 'donation'){
|
||||
walletParams.onOrderReferenceCreate = function(orderReference) {
|
||||
Payments.amazonPayments.orderReferenceId = orderReference.getAmazonOrderReferenceId();
|
||||
}
|
||||
}else if(Payments.amazonPayments.type === 'subscription'){
|
||||
walletParams.onReady = function(billingAgreement) {
|
||||
onReady: function(billingAgreement){
|
||||
Payments.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||
|
||||
if(Payments.amazonPayments.type === 'subscription'){
|
||||
new OffAmazonPayments.Widgets.Consent({
|
||||
sellerId: window.env.AMAZON_PAYMENTS.SELLER_ID,
|
||||
amazonBillingAgreementId: Payments.amazonPayments.billingAgreementId,
|
||||
@@ -180,7 +175,8 @@ function($rootScope, User, $http, Content) {
|
||||
|
||||
onReady: function(consent){
|
||||
$rootScope.$apply(function(){
|
||||
Payments.amazonPayments.recurringConsent = consent.getConsentStatus();
|
||||
var getConsent = consent.getConsentStatus
|
||||
Payments.amazonPayments.recurringConsent = getConsent ? getConsent() : false;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -192,10 +188,17 @@ function($rootScope, User, $http, Content) {
|
||||
|
||||
onError: amazonOnError
|
||||
}).bind('AmazonPayRecurring');
|
||||
};
|
||||
|
||||
walletParams.agreementType = 'BillingAgreement';
|
||||
}
|
||||
},
|
||||
|
||||
onPaymentSelect: function() {
|
||||
$rootScope.$apply(function(){
|
||||
Payments.amazonPayments.paymentSelected = true;
|
||||
});
|
||||
},
|
||||
|
||||
onError: amazonOnError
|
||||
};
|
||||
|
||||
new OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
|
||||
}
|
||||
@@ -204,7 +207,7 @@ function($rootScope, User, $http, Content) {
|
||||
if(Payments.amazonPayments.type === 'donation'){
|
||||
var url = '/amazon/checkout'
|
||||
$http.post(url, {
|
||||
orderReferenceId: Payments.amazonPayments.orderReferenceId,
|
||||
billingAgreementId: Payments.amazonPayments.billingAgreementId,
|
||||
gift: Payments.amazonPayments.gift
|
||||
}).success(function(){
|
||||
Payments.amazonPayments.reset();
|
||||
@@ -214,13 +217,33 @@ function($rootScope, User, $http, Content) {
|
||||
Payments.amazonPayments.reset();
|
||||
});
|
||||
}else if(Payments.amazonPayments.type === 'subscription'){
|
||||
return false
|
||||
var url = '/amazon/subscribe';
|
||||
|
||||
$http.post(url, {
|
||||
billingAgreementId: Payments.amazonPayments.billingAgreementId,
|
||||
subscription: Payments.amazonPayments.subscription,
|
||||
coupon: Payments.amazonPayments.coupon
|
||||
}).success(function(){
|
||||
Payments.amazonPayments.reset();
|
||||
window.location.reload(true);
|
||||
}).error(function(res){
|
||||
alert(res.err);
|
||||
Payments.amazonPayments.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Payments.cancelSubscription = function(){
|
||||
if (!confirm(window.env.t('sureCancelSub'))) return;
|
||||
window.location.href = '/' + User.user.purchased.plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.user.apiToken;
|
||||
var paymentMethod = User.user.purchased.plan.paymentMethod;
|
||||
|
||||
if(paymentMethod === 'Amazon Payments'){
|
||||
paymentMethod = 'amazon';
|
||||
}else{
|
||||
paymentMethod = paymentMethod.toLowerCase();
|
||||
}
|
||||
|
||||
window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.user.apiToken;
|
||||
}
|
||||
|
||||
Payments.encodeGift = function(uuid, gift){
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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';
|
||||
|
||||
var amzPayment = amazonPayments.connect({
|
||||
@@ -27,24 +30,49 @@ exports.verifyAccessToken = function(req, res, next){
|
||||
};
|
||||
|
||||
exports.checkout = function(req, res, next){
|
||||
if(!req.body || !req.body['orderReferenceId']){
|
||||
return res.json(400, {err: 'Order Reference Id not supplied.'});
|
||||
if(!req.body || !req.body['billingAgreementId']){
|
||||
return res.json(400, {err: 'Billing Agreement Id not supplied.'});
|
||||
}
|
||||
|
||||
var gift = req.body.gift;
|
||||
var user = res.locals.user;
|
||||
var orderReferenceId = req.body.orderReferenceId;
|
||||
var billingAgreementId = req.body.billingAgreementId;
|
||||
var orderReferenceId;
|
||||
var 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({
|
||||
createOrderReferenceForId: function(cb){
|
||||
amzPayment.offAmazonPayments.createOrderReferenceForId({
|
||||
Id: billingAgreementId,
|
||||
IdType: 'BillingAgreement'
|
||||
}, function(err, response){
|
||||
if(err) return cb(err);
|
||||
if(!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId){
|
||||
return cb('Missing attributes in Amazon response.');
|
||||
}
|
||||
|
||||
orderReferenceId = response.OrderReferenceDetails.AmazonOrderReferenceId;
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
|
||||
setOrderReferenceDetails: function(cb){
|
||||
amzPayment.offAmazonPayments.setOrderReferenceDetails({
|
||||
AmazonOrderReferenceId: orderReferenceId,
|
||||
OrderReferenceAttributes: {
|
||||
OrderTotal: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: gift ? (gift.gems.amount/4) : 5
|
||||
Amount: amount
|
||||
},
|
||||
SellerNote: 'HabitRPG Gems',
|
||||
SellerNote: 'HabitRPG Payment',
|
||||
SellerOrderAttributes: {
|
||||
SellerOrderId: shared.uuid(),
|
||||
StoreName: 'HabitRPG'
|
||||
@@ -65,9 +93,9 @@ exports.checkout = function(req, res, next){
|
||||
AuthorizationReferenceId: shared.uuid().substring(0, 32),
|
||||
AuthorizationAmount: {
|
||||
CurrencyCode: 'USD',
|
||||
Amount: gift ? (gift.gems.amount/4) : 5
|
||||
Amount: amount
|
||||
},
|
||||
SellerAuthorizationNote: 'HabitRPG Donation',
|
||||
SellerAuthorizationNote: 'HabitRPG Payment',
|
||||
TransactionTimeout: 0,
|
||||
CaptureNow: true
|
||||
}, cb);
|
||||
@@ -87,12 +115,13 @@ exports.checkout = function(req, res, next){
|
||||
var method = 'buyGems';
|
||||
|
||||
if (gift){
|
||||
if (gift.type == 'subscription') method = 'createSubscription';
|
||||
gift.member = member;
|
||||
data.gift = gift;
|
||||
data.paymentMethod = 'Gift';
|
||||
}
|
||||
|
||||
payments.buyGems(data, cb2);
|
||||
payments[method](data, cb2);
|
||||
}
|
||||
], cb);
|
||||
}
|
||||
@@ -104,8 +133,112 @@ exports.checkout = function(req, res, next){
|
||||
|
||||
};
|
||||
|
||||
exports.setupSubscription = function(req, res, next){
|
||||
if(!req.body || !req.body['orderReferenceId']){
|
||||
exports.subscribe = function(req, res, next){
|
||||
if(!req.body || !req.body['billingAgreementId']){
|
||||
return res.json(400, {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;
|
||||
|
||||
if(!sub){
|
||||
return res.json(400, {err: 'Subscription plan not found.'});
|
||||
}
|
||||
|
||||
async.series({
|
||||
applyDiscount: function(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.'));
|
||||
cb()
|
||||
});
|
||||
},
|
||||
|
||||
setBillingAgreementDetails: function(cb){
|
||||
amzPayment.offAmazonPayments.setBillingAgreementDetails({
|
||||
AmazonBillingAgreementId: billingAgreementId,
|
||||
BillingAgreementAttributes: {
|
||||
SellerNote: 'HabitRPG Subscription',
|
||||
SellerBillingAgreementAttributes: {
|
||||
SellerBillingAgreementId: shared.uuid(),
|
||||
StoreName: 'HabitRPG',
|
||||
CustomInformation: 'HabitRPG Subscription'
|
||||
}
|
||||
}
|
||||
}, cb);
|
||||
},
|
||||
|
||||
confirmBillingAgreement: function(cb){
|
||||
amzPayment.offAmazonPayments.confirmBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId
|
||||
}, cb);
|
||||
},
|
||||
|
||||
authorizeOnBillingAgreeement: function(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'
|
||||
}
|
||||
}, cb);
|
||||
},
|
||||
|
||||
createSubscription: function(cb){
|
||||
payments.createSubscription({
|
||||
user: user,
|
||||
customerId: billingAgreementId,
|
||||
paymentMethod: 'Amazon Payments',
|
||||
sub: sub
|
||||
}, cb);
|
||||
}
|
||||
}, function(err, results){
|
||||
if(err) return next(err);
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
};
|
||||
|
||||
exports.subscribeCancel = function(req, res, next){
|
||||
var user = res.locals.user;
|
||||
if (!user.purchased.plan.customerId)
|
||||
return res.json(401, {err: "User does not have a plan subscription"});
|
||||
|
||||
var billingAgreementId = user.purchased.plan.customerId;
|
||||
|
||||
async.series({
|
||||
closeBillingAgreement: function(cb){
|
||||
amzPayment.offAmazonPayments.closeBillingAgreement({
|
||||
AmazonBillingAgreementId: billingAgreementId
|
||||
}, cb);
|
||||
},
|
||||
|
||||
cancelSubscription: function(cb){
|
||||
var data = {
|
||||
user: user,
|
||||
// Date of next bill, dateUpdated can be used because it's only updated when the user is billed
|
||||
nextBill: moment(user.purchased.plan.dateUpdated).add({days: 30}),
|
||||
paymentMethod: 'Amazon Payments'
|
||||
};
|
||||
|
||||
payments.cancelSubscription(data, cb);
|
||||
}
|
||||
}, function(err, results){
|
||||
if (err) return next(err); // don't json this, let toString() handle errors
|
||||
res.redirect('/');
|
||||
user = null;
|
||||
});
|
||||
};
|
||||
@@ -172,6 +172,8 @@ exports.paypalIPN = paypal.ipn;
|
||||
|
||||
exports.amazonVerifyAccessToken = amazon.verifyAccessToken;
|
||||
exports.amazonCheckout = amazon.checkout;
|
||||
exports.amazonSubscribe = amazon.subscribe;
|
||||
exports.amazonSubscribeCancel = amazon.subscribeCancel;
|
||||
|
||||
exports.iapAndroidVerify = iap.androidVerify;
|
||||
exports.iapIosVerify = iap.iosVerify;
|
||||
|
||||
@@ -101,8 +101,8 @@ var UserSchema = new Schema({
|
||||
mobileChat: Boolean,
|
||||
plan: {
|
||||
planId: String,
|
||||
paymentMethod: String, //enum: ['Paypal','Stripe', 'Gift', '']}
|
||||
customerId: String,
|
||||
paymentMethod: String, //enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']}
|
||||
customerId: String, // Billing Agreement Id in case of Amazon Payments
|
||||
dateCreated: Date,
|
||||
dateTerminated: Date,
|
||||
dateUpdated: Date,
|
||||
|
||||
@@ -19,6 +19,8 @@ router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, p
|
||||
|
||||
router.post('/amazon/verifyAccessToken', auth.auth, i18n.getUserLanguage, payments.amazonVerifyAccessToken);
|
||||
router.post('/amazon/checkout', auth.auth, i18n.getUserLanguage, payments.amazonCheckout);
|
||||
router.post('/amazon/subscribe', auth.auth, i18n.getUserLanguage, payments.amazonSubscribe);
|
||||
router.get('/amazon/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.amazonSubscribeCancel);
|
||||
|
||||
router.post("/iap/android/verify", auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify);
|
||||
router.post("/iap/ios/verify", auth.auth, /*i18n.getUserLanguage, */ payments.iapIosVerify);
|
||||
|
||||
@@ -180,8 +180,6 @@ script(type='text/ng-template', id='partials/options.settings.promo.html')
|
||||
button.btn.btn-primary(type='submit') Generate
|
||||
a.btn.btn-default(href='/api/v2/coupons?_id={{user._id}}&apiToken={{user.apiToken}}') Get Codes
|
||||
|
||||
|
||||
|
||||
script(type='text/ng-template', id='partials/options.settings.api.html')
|
||||
.container-fluid
|
||||
.row
|
||||
@@ -339,7 +337,7 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')= env.t("resubscribe")
|
||||
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key')= env.t('card')
|
||||
a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') PayPal
|
||||
.btn.btn-default(ng-click="Payments.amazonPayments.init('subscription')") Amazon Payments
|
||||
a.btn.btn-default(ng-click="Payments.amazonPayments.init({type: 'subscription', subscription:_subscription.key, coupon:_subscription.coupon})") Amazon Payments
|
||||
div(ng-if='user.purchased.plan.customerId')
|
||||
.btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard')
|
||||
.btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub')
|
||||
|
||||
@@ -22,7 +22,7 @@ script(id='modals/buyGems.html', type='text/ng-template')
|
||||
small.muted=env.t('paymentMethods')
|
||||
.btn.btn-primary(ng-click='Payments.showStripe({})')=env.t('card')
|
||||
a.btn.btn-warning(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal
|
||||
.btn.btn-default(ng-click="Payments.amazonPayments.init('donation')") Amazon Payments
|
||||
//.btn.btn-default(ng-click="Payments.amazonPayments.init({type: 'donation'})") Amazon Payments
|
||||
|
||||
div(ng-include="'partials/options.settings.subscription.html'")
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ script(type='text/ng-template', id='modals/send-gift.html')
|
||||
button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send")
|
||||
a.btn.btn-primary(ng-hide=fromBal, ng-click='Payments.showStripe({gift:gift, uuid:profile._id})')=env.t('card')
|
||||
a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal
|
||||
.btn.btn-default(ng-hide=fromBal, ng-click="Payments.amazonPayments.init(gift.type == 'gems' ? 'donation' : 'subscription', gift, profile._id)") Amazon Payments
|
||||
.btn.btn-default(ng-hide="true", ng-click="Payments.amazonPayments.init({type: gift.type == 'gems' ? 'donation' : 'subscription', gift: gift, giftedTo: profile._id})") Amazon Payments
|
||||
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
||||
|
||||
script(type='text/ng-template', id='modals/abuse-flag.html')
|
||||
|
||||
Reference in New Issue
Block a user