From 09cd13242022a80498f544d793d181a2d4aa4595 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 6 Oct 2013 16:52:54 -0700 Subject: [PATCH 1/3] paypal: begin adding paypal alternative to credit-card payments --- bower.json | 6 +++--- package.json | 3 ++- src/controllers/user.js | 26 ++++++++++++++++++++++++++ src/middleware.js | 1 + src/routes/api.js | 1 + views/shared/modals/why-ads.jade | 7 ++++--- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/bower.json b/bower.json index 60228a30c9..358ec0dfa0 100644 --- a/bower.json +++ b/bower.json @@ -32,12 +32,12 @@ "angular-route": "1.2.0-rc.2", "angular-ui-utils": "~0.0.4", "angular-sanitize": "1.2.0-rc.2", - "marked": "~0.2.9" + "marked": "~0.2.9", + "JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master" }, "resolutions": { "jquery": "~2.0.3", - "angular": "1.2.0-rc.2", - "angular-mocks": "1.2.0-rc.2" + "angular": "1.2.0-rc.2" }, "devDependencies": { "angular-mocks": "1.2.0-rc.2" diff --git a/package.json b/package.json index 1939c440e2..7ef714c5fa 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "passport": "~0.1.17", "validator": "~1.5.1", "nodemailer": "~0.5.2", - "grunt-cli": "~0.1.9" + "grunt-cli": "~0.1.9", + "paypal-ipn": "~1.0.1" }, "private": true, "subdomain": "habitrpg", diff --git a/src/controllers/user.js b/src/controllers/user.js index c1373777d2..6970a89e35 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -3,6 +3,7 @@ // fixme remove this junk, was coffeescript compiled (probably for IE8 compat) var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; +var url = require('url'); var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); @@ -591,6 +592,31 @@ api.buyGems = function(req, res) { }); }; +api.buyGemsPaypalIPN = function(req, res) { + var ipn = require('paypal-ipn'); + + ipn.verify(req.body, function callback(err, msg) { + if (err) { + console.error(msg); + res.send(500, msg); + } else { + if (req.body.payment_status == 'Completed') { + //Payment has been confirmed as completed + var parts = url.parse(req.body.custom, true); + var uid = parts.query.uid; //, apiToken = query.apiToken; + if (!uid) throw new Error("uuid or apiToken not found when completing paypal transaction"); + User.findById(uid, function(err, user) { + if (err) throw err; + if (_.isEmpty(user)) throw "user not found with uuid " + uuid + " when completing paypal transaction" + user.balance += 5; + user.flags.ads = 'hide'; + user.save(); + }); + } + } + }); +} + /* ------------------------------------------------------------------------ Tags diff --git a/src/middleware.js b/src/middleware.js index e2e5543098..1b4cadd396 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -63,6 +63,7 @@ module.exports.locals = function(req, res, next) { res.locals.habitrpg = res.locals.habitrpg || {} _.defaults(res.locals.habitrpg, { NODE_ENV: nconf.get('NODE_ENV'), + BASE_URL: nconf.get('BASE_URL'), IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), getBuildUrl: getBuildUrl diff --git a/src/routes/api.js b/src/routes/api.js index 91346a424b..86f995239c 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -47,6 +47,7 @@ router.post('/user/revive', auth.auth, cron, user.revive); router.post('/user/batch-update', auth.auth, cron, user.batchUpdate); router.post('/user/reroll', auth.auth, cron, user.reroll); router.post('/user/buy-gems', auth.auth, user.buyGems); +router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN); router.post('/user/reset', auth.auth, user.reset); router['delete']('/user', auth.auth, user['delete']); diff --git a/views/shared/modals/why-ads.jade b/views/shared/modals/why-ads.jade index 38dd5a8214..2e35033178 100644 --- a/views/shared/modals/why-ads.jade +++ b/views/shared/modals/why-ads.jade @@ -4,8 +4,9 @@ div(modal='modals.whyAds') .modal-body p. Habit is an open source project, and can use all the help it can get - consider this a donation to the contributors. You also get 20 Gems from the purchase, which you can use to buy special items. - p - | "Hey, I backed the Kickstarter!" - follow - a(target='_blank', href='http://community.habitrpg.com/node/22') these instructions. + p. + "Hey, I backed the Kickstarter!" - follow these instructions. + script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=tylerrenelle-facilitator@gmail.com', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='.1', data-currency='USD', data-shipping='0', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/user/buy-gems/paypal-ipn', data-env='sandbox', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}') + .modal-footer button.btn.btn-default.cancel(ng-click='modals.whyAds = false') Ok \ No newline at end of file From 5de0c928fe423b183beac951406ae1ad8f2ca51f Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 12 Oct 2013 16:47:49 -0700 Subject: [PATCH 2/3] paypal: conslidate buy gems modals --- public/css/index.styl | 6 ++++- public/js/controllers/groupsCtrl.js | 2 +- public/js/controllers/marketCtrl.js | 2 +- public/js/controllers/rootCtrl.js | 37 ++++++++++++++--------------- views/shared/gems.jade | 2 +- views/shared/modals/buy-gems.jade | 21 ++++++++++++++++ views/shared/modals/index.jade | 3 +-- views/shared/modals/more-gems.jade | 10 -------- views/shared/modals/reroll.jade | 2 +- views/shared/modals/why-ads.jade | 12 ---------- views/tasks/ads.jade | 5 +--- 11 files changed, 50 insertions(+), 52 deletions(-) create mode 100644 views/shared/modals/buy-gems.jade delete mode 100644 views/shared/modals/more-gems.jade delete mode 100644 views/shared/modals/why-ads.jade diff --git a/public/css/index.styl b/public/css/index.styl index 48530c87da..729328f19d 100644 --- a/public/css/index.styl +++ b/public/css/index.styl @@ -168,4 +168,8 @@ a word-wrap: break-word .btn - margin-right: 5px \ No newline at end of file + margin-right: 5px + +.buy-gems-list + margin-left: 10px; + padding-left: 10px; \ No newline at end of file diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index f52291cf82..81b18f8dde 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -114,7 +114,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A $scope.create = function(group){ if (User.user.balance < 1) { - return $rootScope.modals.moreGems = true; + return $rootScope.modals.buyGems = true; // $('#more-gems-modal').modal('show'); } diff --git a/public/js/controllers/marketCtrl.js b/public/js/controllers/marketCtrl.js index 68b11b79bc..c0cf429539 100644 --- a/public/js/controllers/marketCtrl.js +++ b/public/js/controllers/marketCtrl.js @@ -12,7 +12,7 @@ habitrpg.controller("MarketCtrl", ['$rootScope', '$scope', 'User', 'API_URL', '$ storePath = type === 'egg' ? 'items.eggs' : 'items.hatchingPotions' if(gems < item.value){ - return $rootScope.modals.moreGems = true; + return $rootScope.modals.buyGems = true; } var message = "Buy this " + (type == 'egg' ? 'egg' : 'hatching potion') + " with " + item.value + " of your " + gems + " Gems?" diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index d73cdfe6c4..052a52b778 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -42,25 +42,24 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ } $rootScope.showStripe = function() { - var disableAds = User.user.flags.ads == 'hide' ? '' : 'Disable Ads, '; - StripeCheckout.open({ - key: window.env.STRIPE_PUB_KEY, - address: false, - amount: 500, - name: "Checkout", - description: "Buy 20 Gems, " + disableAds + "Support the Developers", - panelLabel: "Checkout", - token: function(data) { - $scope.$apply(function(){ - $http.post("/api/v1/user/buy-gems", data) - .success(function() { - window.location.href = "/"; - }).error(function(err) { - alert(err); - }); - }) - } - }); + StripeCheckout.open({ + key: window.env.STRIPE_PUB_KEY, + address: false, + amount: 500, + name: "Checkout", + //description: "Buy 20 Gems, Disable Ads, Support the Developers", + panelLabel: "Checkout", + token: function(data) { + $scope.$apply(function(){ + $http.post("/api/v1/user/buy-gems", data) + .success(function() { + window.location.href = "/"; + }).error(function(err) { + alert(err); + }); + }) + } + }); } } ]); diff --git a/views/shared/gems.jade b/views/shared/gems.jade index f276a47528..af75287f13 100644 --- a/views/shared/gems.jade +++ b/views/shared/gems.jade @@ -1,5 +1,5 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Gems', popover="Used for buying special items (reroll, eggs, hatching potions, etc). You'll need to unlock those features before being able to use Gems.", popover-placement='bottom') - span.task-action-btn.tile.flush.bright.add-gems-btn(ng-click='showStripe()') + + span.task-action-btn.tile.flush.bright.add-gems-btn(ng-click='modals.buyGems = true') + span.task-action-btn.tile.flush.neutral .Gems | {{user.balance * 4 | number:0}} Gems \ No newline at end of file diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade new file mode 100644 index 0000000000..8562d0e754 --- /dev/null +++ b/views/shared/modals/buy-gems.jade @@ -0,0 +1,21 @@ +div(modal='modals.buyGems') + .modal-header + h3 Buy Gems / Donate + + .modal-body + include ../gems + p $5 USD will: + ul.buy-gems-list + li Add 20 gems to your account, which are used to buy special items. + li Disable ads. + li Donate to the developers (as an open source project, we can us all the help we can get). + .row-fluid + .span6.well + h3 Pay with Card + .btn.btn-primary(ng-click='showStripe()') Pay with Card + .span6.well + h3 Pay with PayPal + script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=tylerrenelle-facilitator@gmail.com', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='.1', data-currency='USD', data-shipping='0', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/user/buy-gems/paypal-ipn', data-env='sandbox', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}') + + .modal-footer + button.btn.btn-default.cancel(ng-click='modals.buyGems = false') Cancel \ No newline at end of file diff --git a/views/shared/modals/index.jade b/views/shared/modals/index.jade index 4bf003b160..6d15f517b7 100644 --- a/views/shared/modals/index.jade +++ b/views/shared/modals/index.jade @@ -3,8 +3,7 @@ include ./login include ./reroll include ./death include ./new-stuff -include ./why-ads -include ./more-gems +include ./buy-gems include ./members include ./settings include ./pets \ No newline at end of file diff --git a/views/shared/modals/more-gems.jade b/views/shared/modals/more-gems.jade deleted file mode 100644 index b33d4dd87f..0000000000 --- a/views/shared/modals/more-gems.jade +++ /dev/null @@ -1,10 +0,0 @@ -div(modal='modals.moreGems', header='Out Of Gems') - .modal-header - h3 Out Of Gems - .modal-body - include ../gems - p. - Oops, out of Gems, which are used to buy special items! Habit is an open source project, and can use all the help - it can get - buy more Gems to receive this pet, and consider it a donation to the contributors. - .modal-footer - button.btn.btn-default.cancel(ng-click='modals.moreGems = false') Cancel \ No newline at end of file diff --git a/views/shared/modals/reroll.jade b/views/shared/modals/reroll.jade index 4f48f8f4a6..0941593b32 100644 --- a/views/shared/modals/reroll.jade +++ b/views/shared/modals/reroll.jade @@ -11,7 +11,7 @@ div(modal='modals.reroll') .modal-footer span(ng-if='user.balance < 1') - a.btn.btn-success.btn-large(ng-click="showStripe()") Buy More Gems + a.btn.btn-success.btn-large(ng-click="modals.buyGems = true") Buy More Gems span.gem-cost Not enough Gems span(ng-if='user.balance >= 1', ng-controller='SettingsCtrl') a.btn.btn-danger.btn-large(ng-click='reroll()') Re-Roll diff --git a/views/shared/modals/why-ads.jade b/views/shared/modals/why-ads.jade deleted file mode 100644 index 2e35033178..0000000000 --- a/views/shared/modals/why-ads.jade +++ /dev/null @@ -1,12 +0,0 @@ -div(modal='modals.whyAds') - .modal-header - h3 Why Ads? - .modal-body - p. - Habit is an open source project, and can use all the help it can get - consider this a donation to the contributors. You also get 20 Gems from the purchase, which you can use to buy special items. - p. - "Hey, I backed the Kickstarter!" - follow these instructions. - script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=tylerrenelle-facilitator@gmail.com', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='.1', data-currency='USD', data-shipping='0', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/user/buy-gems/paypal-ipn', data-env='sandbox', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}') - - .modal-footer - button.btn.btn-default.cancel(ng-click='modals.whyAds = false') Ok \ No newline at end of file diff --git a/views/tasks/ads.jade b/views/tasks/ads.jade index 4337c96f8f..f86d0bb5f6 100644 --- a/views/tasks/ads.jade +++ b/views/tasks/ads.jade @@ -1,10 +1,7 @@ div(ng-if='authenticated() && user.flags.ads!="hide"') span.pull-right(ng-if='list.type!="reward"') - a(ng-click='showStripe()', tooltip='Remove Ads') + a(ng-click='modals.buyGems=true', tooltip='Remove Ads') i.icon-remove - br - a(ng-click='modals.whyAds=true', tooltip='Why Ads?') - i.icon-question-sign div(ng-if='list.type=="habit"', habitrpg-adsense) script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js') From f8328d3eeb29991639c87eb9923d773a0d5dc9b5 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 12 Oct 2013 17:05:21 -0700 Subject: [PATCH 3/3] paypal: add configurable merchant and sandbox/production environment --- config.json.example | 1 + public/js/controllers/rootCtrl.js | 2 +- src/middleware.js | 1 + views/shared/modals/buy-gems.jade | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.json.example b/config.json.example index c1cc52ad11..3bbcde4cc0 100644 --- a/config.json.example +++ b/config.json.example @@ -13,4 +13,5 @@ "SMTP_SERVICE":"Gmail", "STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111", "STRIPE_PUB_KEY":"22223333444455556666777788889999" + "PAYPAL_MERCHANT":"paypal-merchant@gmail.com" } \ No newline at end of file diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 052a52b778..a298188db3 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -47,7 +47,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ address: false, amount: 500, name: "Checkout", - //description: "Buy 20 Gems, Disable Ads, Support the Developers", + description: "Buy 20 Gems, Disable Ads, Support the Developers", panelLabel: "Checkout", token: function(data) { $scope.$apply(function(){ diff --git a/src/middleware.js b/src/middleware.js index 1b4cadd396..45209e6375 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -64,6 +64,7 @@ module.exports.locals = function(req, res, next) { _.defaults(res.locals.habitrpg, { NODE_ENV: nconf.get('NODE_ENV'), BASE_URL: nconf.get('BASE_URL'), + PAYPAL_MERCHANT: nconf.get('PAYPAL_MERCHANT'), IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), getBuildUrl: getBuildUrl diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index 8562d0e754..b727065599 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -15,7 +15,7 @@ div(modal='modals.buyGems') .btn.btn-primary(ng-click='showStripe()') Pay with Card .span6.well h3 Pay with PayPal - script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=tylerrenelle-facilitator@gmail.com', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='.1', data-currency='USD', data-shipping='0', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/user/buy-gems/paypal-ipn', data-env='sandbox', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}') + script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=#{env.PAYPAL_MERCHANT}', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='5', data-currency='USD', data-shipping='0', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/user/buy-gems/paypal-ipn', data-env="#{env.NODE_ENV == 'production' ? '' : 'sandbox'}", data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}', data-return='#{env.BASE_URL}', data-no_shipping='1') .modal-footer button.btn.btn-default.cancel(ng-click='modals.buyGems = false') Cancel \ No newline at end of file