├── index.js ├── license.txt ├── package.json └── readme.md /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2017 (c) Peter Širka 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | const Parser = require('url'); 23 | const Https = require('https'); 24 | const Qs = require('querystring'); 25 | const HEADERS = {}; 26 | 27 | HEADERS['User-Agent'] = 'Total.js PayPal Express Checkout'; 28 | HEADERS['Accept'] = '*/*'; 29 | 30 | function Paypal(username, password, signature, returnUrl, cancelUrl, debug) { 31 | this.username = username; 32 | this.password = password; 33 | this.solutiontype = 'Mark'; 34 | this.signature = signature; 35 | this.debug = debug || false; 36 | this.returnUrl = returnUrl; 37 | this.cancelUrl = cancelUrl; 38 | this.url = 'https://' + (debug ? 'api-3t.sandbox.paypal.com' : 'api-3t.paypal.com') + '/nvp'; 39 | this.redirect = 'https://' + (debug ? 'www.sandbox.paypal.com/cgi-bin/webscr' : 'www.paypal.com/cgi-bin/webscr'); 40 | this.locale = ''; 41 | } 42 | 43 | Paypal.prototype.params = function() { 44 | var PARAMS = {}; 45 | PARAMS.VERSION = '204.0'; 46 | PARAMS.USER = this.username; 47 | PARAMS.PWD = this.password; 48 | PARAMS.SIGNATURE = this.signature; 49 | return PARAMS; 50 | }; 51 | 52 | Paypal.prototype.detail = function(token, payer, callback) { 53 | 54 | // Total.js 55 | if (token.query && typeof(payer) === 'function') { 56 | callback = payer; 57 | payer = token.query.PayerID || token.query.PAYERID; 58 | token = token.query.token || token.query.TOKEN; 59 | } 60 | 61 | var self = this; 62 | var params = self.params(); 63 | 64 | params.TOKEN = token; 65 | params.METHOD = 'GetExpressCheckoutDetails'; 66 | 67 | self.request(self.url, 'POST', params, function(err, data) { 68 | 69 | if (err || !data.PAYMENTREQUEST_0_CUSTOM) { 70 | callback(null, data, 0); 71 | return; 72 | } 73 | 74 | var custom = data.CUSTOM.split('|'); 75 | var params = self.params(); 76 | var prevData = data; 77 | 78 | params.PAYERID = payer; 79 | params.TOKEN = token; 80 | params.PAYMENTREQUEST_0_PAYMENTACTION = 'Sale'; 81 | params.PAYMENTREQUEST_0_INVNUM = custom[0]; 82 | params.PAYMENTREQUEST_0_AMT = custom[1]; 83 | params.PAYMENTREQUEST_0_CURRENCYCODE = custom[2]; 84 | params.METHOD = 'DoExpressCheckoutPayment'; 85 | 86 | self.request(self.url, 'POST', params, function(err, data) { 87 | if (err) 88 | return callback(err, data); 89 | data.ACK2 = data.ACK; 90 | data.ACK = data.PAYMENTINFO_0_ACK; // Backward compatibility 91 | data.PAYMENTSTATUS = data.PAYMENTINFO_0_PAYMENTSTATUS; 92 | var is = (data.PAYMENTINFO_0_PAYMENTSTATUS || '').toLowerCase(); 93 | data.success = (data.ACK || 'failure').toLowerCase() === 'success' && (is === 'completed' || is === 'processed' || is === 'pending'); 94 | callback(null, Object.assign(prevData, data), custom[0], custom[1], custom); 95 | }); 96 | }); 97 | 98 | return self; 99 | }; 100 | 101 | Paypal.prototype.pay = function(invoiceNumber, amount, description, currency, requireAddress, customData = [], callback) { 102 | 103 | // Backward compatibility 104 | if (typeof(requireAddress) === 'function') { 105 | callback = requireAddress; 106 | requireAddress = false; 107 | } 108 | 109 | var self = this; 110 | var params = self.params(); 111 | 112 | params.PAYMENTREQUEST_0_PAYMENTACTION = 'SALE'; 113 | params.PAYMENTREQUEST_0_AMT = prepareNumber(amount); 114 | params.PAYMENTREQUEST_0_CURRENCYCODE = currency; 115 | params.PAYMENTREQUEST_0_DESC = description; 116 | 117 | const customField = [invoiceNumber, params.PAYMENTREQUEST_0_AMT, params.PAYMENTREQUEST_0_CURRENCYCODE, ...customData]; 118 | 119 | params.PAYMENTREQUEST_0_CUSTOM = customField.join('|'); 120 | params.PAYMENTREQUEST_0_INVNUM = invoiceNumber; 121 | params.returnUrl = self.returnUrl; 122 | params.cancelUrl = self.cancelUrl; 123 | params.NOSHIPPING = requireAddress ? 0 : 1; 124 | params.ALLOWNOTE = 1; 125 | 126 | if (self.locale) 127 | params.LOCALECODE = self.locale; 128 | 129 | params.METHOD = 'SetExpressCheckout'; 130 | 131 | self.request(self.url, 'POST', params, function(err, data) { 132 | if (err) 133 | return callback(err, null); 134 | 135 | if (data.ACK === 'Success') 136 | callback(null, self.redirect + '?cmd=_express-checkout&useraction=commit&token=' + data.TOKEN); 137 | else 138 | callback(new Error('ACK ' + data.ACK + ': ' + data.L_LONGMESSAGE0), null); 139 | }); 140 | 141 | return self; 142 | }; 143 | 144 | Paypal.prototype.request = function(url, method, data, callback) { 145 | 146 | var self = this; 147 | var params = Qs.stringify(data); 148 | 149 | if (method === 'GET') 150 | url += '?' + params; 151 | 152 | var uri = Parser.parse(url); 153 | 154 | HEADERS['Content-Type'] = method === 'POST' ? 'application/x-www-form-urlencoded' : 'text/plain'; 155 | HEADERS['Content-Length'] = new Buffer(params).length; 156 | 157 | var options = { protocol: uri.protocol, auth: uri.auth, method: method || 'GET', hostname: uri.hostname, port: uri.port, path: uri.path, agent: false, headers: HEADERS }; 158 | 159 | var response = function(res) { 160 | 161 | var buffer = new Buffer(0); 162 | 163 | res.on('data', (chunk) => buffer = Buffer.concat([buffer, chunk])); 164 | req.setTimeout(exports.timeout, () => callback(new Error('timeout'), null)); 165 | res.on('end', function() { 166 | 167 | var error; 168 | var data; 169 | 170 | if (res.statusCode > 200) { 171 | error = res.statusCode + ': ' + buffer.toString('utf8'); 172 | data = ''; 173 | } else 174 | data = Qs.parse(buffer.toString('utf8')); 175 | 176 | callback(error, data); 177 | }); 178 | }; 179 | 180 | var req = Https.request(options, response); 181 | req.on('error', (err) => callback(err, null)); 182 | req.end(params); 183 | return self; 184 | }; 185 | 186 | function prepareNumber(num, doubleZero) { 187 | var str = num.toString().replace(',', '.'); 188 | var index = str.indexOf('.'); 189 | if (index > -1) { 190 | var len = str.substring(index + 1).length; 191 | if (len === 1) 192 | str += '0'; 193 | if (len > 2) 194 | str = str.substring(0, index + 3); 195 | } else { 196 | if (doubleZero || true) 197 | str += '.00'; 198 | } 199 | return str; 200 | } 201 | 202 | exports.timeout = 10000; 203 | exports.Paypal = Paypal; 204 | 205 | exports.init = function(username, password, signature, returnUrl, cancelUrl, debug) { 206 | return new Paypal(username, password, signature, returnUrl, cancelUrl, debug); 207 | }; 208 | 209 | exports.create = function(username, password, signature, returnUrl, cancelUrl, debug) { 210 | return exports.init(username, password, signature, returnUrl, cancelUrl, debug); 211 | }; -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | Copyright 2012-2016 (c) Peter Širka 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the 10 | following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 18 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paypal-express-checkout", 3 | "version": "1.6.3", 4 | "description": "node.js paypal express checkout", 5 | "main": "./index.js", 6 | "engines": { 7 | "node": ">=0.8.x" 8 | }, 9 | "readmeFilename": "readme.md", 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/petersirka/node-paypal-express-checkout" 17 | }, 18 | "author": { 19 | "name": "Peter Širka" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Peter Širka", 24 | "email": "petersirka@gmail.com" 25 | }, 26 | { 27 | "name": "Martin Smola", 28 | "email": "smola.martin@gmail.com" 29 | }, 30 | { 31 | "name": "Johann Botha", 32 | "email": "johannbbotha@gmail.com" 33 | }, 34 | { 35 | "name": "Umar Hansa", 36 | "email": "umar.hansa@gmail.com" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![NPM version][npm-version-image]][npm-url] [![NPM quality][npm-quality]](http://packagequality.com/#?package=total.js) [![NPM downloads][npm-downloads-image]][npm-url] [![MIT License][license-image]][license-url] 2 | 3 | - [__Live chat with professional support__](https://messenger.totaljs.com) 4 | - [__HelpDesk with professional support__](https://helpdesk.totaljs.com) 5 | 6 | Node.js PayPal Express Checkout 7 | =============================== 8 | 9 | * Easy to use 10 | * __No dependencies__ 11 | * [Full source-code of the eshop with PayPal written in node.js](http://www.totaljs.com/eshop/) 12 | 13 | PayPal account 14 | -------------- 15 | 16 | Read here: 17 | 18 | - Log in to your PayPal business account at www.paypal.com. Click the profile icon ( Profile menu ) on the top right side of the page. From the Business Profile menu, select Profile and Settings. 19 | - From the left menu, click My selling tools. 20 | - In the Selling online section, click the Update link for the API access item. 21 | - To generate the API signature, click Request API Credentials on the API Access page. ![Request API credentials](https://www.paypalobjects.com/webstatic/en_US/developer/docs/api/classicApiCerts/requestApiCreds.png) 22 | - Select Request API signature and click Agree and Submit to generate the API signature. ![Signature](https://www.paypalobjects.com/webstatic/en_US/developer/docs/api/classicApiCerts/signatureCredentials.png) 23 | 24 | *** 25 | 26 | ```text 27 | $ npm install paypal-express-checkout 28 | ``` 29 | 30 | *** 31 | 32 | ```javascript 33 | var Paypal = require('paypal-express-checkout'); 34 | // debug = optional, defaults to false, if true then paypal's sandbox url is used 35 | // paypal.init('some username', 'some password', 'signature', 'return url', 'cancel url', debug); 36 | var paypal = Paypal.init('username', 'password', 'signature', 'http://www.example.com/return', 'http://www.example.com/cancel', true); 37 | 38 | // Localization (OPTIONAL): https://developer.paypal.com/docs/classic/api/locale_codes/ 39 | // paypal.locale = 'SK'; 40 | // or 41 | // paypal.locale = 'en_US'; 42 | 43 | // checkout 44 | // requireAddress = optional, defaults to false 45 | // paypal.pay('Invoice number', amount, 'description', 'currency', requireAddress, customData, callback); 46 | // paypal.pay('20130001', 123.23, 'iPad', 'EUR', function(err, url) { 47 | // or with "requireAddress": true 48 | paypal.pay('20130001', 123.23, 'iPad', 'EUR', true, ['custom', 'data'], function(err, url) { 49 | if (err) { 50 | console.log(err); 51 | return; 52 | } 53 | 54 | // redirect to paypal webpage 55 | response.redirect(url); 56 | }); 57 | 58 | // result in GET method 59 | // paypal.detail('token', 'PayerID', callback); 60 | // or 61 | // paypal.detail(totaljs.controller, callback); 62 | 63 | paypal.detail('EC-788441863R616634K', '9TM892TKTDWCE', function(err, data, invoiceNumber, price, custom_data_array) { 64 | 65 | // custom_data_array {String Array} - supported in +v1.6.3 66 | 67 | if (err) { 68 | console.log(err); 69 | return; 70 | } 71 | 72 | // data.success == {Boolean} 73 | 74 | if (data.success) 75 | console.log('DONE, PAYMENT IS COMPLETED.'); 76 | else 77 | console.log('SOME PROBLEM:', data); 78 | 79 | /* 80 | data (object) = 81 | { TOKEN: 'EC-35S39602J3144082X', 82 | TIMESTAMP: '2013-01-27T08:47:50Z', 83 | CORRELATIONID: 'e51b76c4b3dc1', 84 | ACK: 'Success', 85 | VERSION: '52.0', 86 | BUILD: '4181146', 87 | TRANSACTIONID: '87S10228Y4778651P', 88 | TRANSACTIONTYPE: 'expresscheckout', 89 | PAYMENTTYPE: 'instant', 90 | ORDERTIME: '2013-01-27T08:47:49Z', 91 | AMT: '10.00', 92 | TAXAMT: '0.00', 93 | CURRENCYCODE: 'EUR', 94 | PAYMENTSTATUS: 'Pending', 95 | PENDINGREASON: 'multicurrency', 96 | REASONCODE: 'None' }; 97 | */ 98 | 99 | }); 100 | 101 | ``` 102 | 103 | ## PayPal PAYMENTSTATUS 104 | 105 | ``` 106 | Canceled_Reversal: A reversal has been canceled. For example, you won a dispute with the customer, and the funds for the transaction that was reversed have been returned to you. 107 | Completed: The payment has been completed, and the funds have been added successfully to your account balance. 108 | Created: A German ELV payment is made using Express Checkout. 109 | Denied: You denied the payment. This happens only if the payment was previously pending because of possible reasons described for the pending_reason variable or the Fraud_Management_Filters_x variable. 110 | Expired: This authorization has expired and cannot be captured. 111 | Failed: The payment has failed. This happens only if the payment was made from your customer’s bank account. 112 | Pending: The payment is pending. See pending_reason for more information. 113 | Refunded: You refunded the payment. 114 | Reversed: A payment was reversed due to a chargeback or other type of reversal. The funds have been removed from your account balance and returned to the buyer. The reason for the reversal is specified in the ReasonCode element. 115 | Processed: A payment has been accepted. 116 | Voided: This authorization has been voided. 117 | ``` 118 | 119 | ## How to prevent of pending paymentstatus? 120 | 121 | > Login into your bussiness account and click here: https://www.sandbox.paypal.com/ca/cgi-bin/?cmd=_profile-pref&source=acct_setup&fli=true 122 | 123 | ## Contributors 124 | 125 | | Contributor | Type | E-mail | 126 | |-------------|------|--------| 127 | | [Peter Širka](https://www.petersirka.eu) | author + support | | 128 | | [Martin Smola](https://github.com/molda) | support + contributor | | 129 | | [Johann Botha](https://github.com/johannbotha) | contributor | | 130 | 131 | ## Images 132 | 133 | ![Payment Card Types](https://www.paypalobjects.com/webstatic/en_US/i/buttons/cc-badges-ppmcvdam.png) 134 | 135 | ![PayPal Express Checkout Button](https://www.paypalobjects.com/webstatic/en_US/i/btn/png/gold-pill-paypalcheckout-34px.png) 136 | 137 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 138 | [license-url]: license.txt 139 | 140 | [npm-url]: https://npmjs.org/package/total.js 141 | [npm-version-image]: https://img.shields.io/npm/v/total.js.svg?style=flat 142 | [npm-downloads-image]: https://img.shields.io/npm/dm/total.js.svg?style=flat 143 | [npm-quality]: http://npm.packagequality.com/shield/total.js.svg 144 | --------------------------------------------------------------------------------