├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.md ├── lib └── paypal-adaptive.js ├── package.json └── test └── api.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | lib-cov -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gonzalo Aguirre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Adaptive Payments and Adaptive Accounts SDK 2 | 3 | Node.js sdk for Paypal Adaptive Payments and Paypal Adaptive Accounts APIs, without dependencies 4 | 5 | ### Usage 6 | * Add dependency 'paypal-adaptive' in your package.json file. 7 | * Require 'paypal-adaptive' in your file. 8 | ```js 9 | var Paypal = require('paypal-adaptive'); 10 | 11 | var paypalSdk = new Paypal({ 12 | userId: 'userId', 13 | password: 'password', 14 | signature: 'signature', 15 | sandbox: true //defaults to false 16 | }); 17 | ``` 18 | * Call to sdk methods or to the generic method callApi. If you get an error, you can check the response too for better error handling. 19 | ```js 20 | var requestData = { 21 | requestEnvelope: { 22 | errorLanguage: 'en_US', 23 | detailLevel: 'ReturnAll' 24 | }, 25 | payKey: 'AP-1234567890' 26 | }; 27 | 28 | paypalSdk.callApi('AdaptivePayments/PaymentDetails', requestData, function (err, response) { 29 | if (err) { 30 | // You can see the error 31 | console.log(err); 32 | //And the original Paypal API response too 33 | console.log(response); 34 | } else { 35 | // Successful response 36 | console.log(response); 37 | } 38 | }); 39 | ``` 40 | 41 | ### API 42 | #### GetPaymentOptions 43 | 44 | ```js 45 | var payKey = 'AP-1234567890'; 46 | 47 | paypalSdk.getPaymentOptions(payKey, function (err, response) { 48 | if (err) { 49 | console.log(err); 50 | } else { 51 | // payments options for this payKey 52 | console.log(response); 53 | } 54 | }); 55 | ``` 56 | 57 | #### PaymentDetails 58 | ```js 59 | // One of this params is required 60 | // The payKey 61 | var params = { 62 | payKey: 'AP-1234567890' 63 | }; 64 | // Or the transactionId 65 | var params = { 66 | transactionId: 'AP-1234567890' 67 | }; 68 | // Or the trackingId 69 | var params = { 70 | trackingId: 'AP-1234567890' 71 | }; 72 | 73 | paypalSdk.paymentDetails(params, function (err, response) { 74 | if (err) { 75 | console.log(err); 76 | } else { 77 | // payments details for this payKey, transactionId or trackingId 78 | console.log(response); 79 | } 80 | }); 81 | ``` 82 | 83 | #### Pay 84 | ```js 85 | var payload = { 86 | requestEnvelope: { 87 | errorLanguage: 'en_US' 88 | }, 89 | actionType: 'PAY', 90 | currencyCode: 'USD', 91 | feesPayer: 'EACHRECEIVER', 92 | memo: 'Chained payment example', 93 | cancelUrl: 'http://test.com/cancel', 94 | returnUrl: 'http://test.com/success', 95 | receiverList: { 96 | receiver: [ 97 | { 98 | email: 'primary@test.com', 99 | amount: '100.00', 100 | primary:'true' 101 | }, 102 | { 103 | email: 'secondary@test.com', 104 | amount: '10.00', 105 | primary:'false' 106 | } 107 | ] 108 | } 109 | }; 110 | 111 | paypalSdk.pay(payload, function (err, response) { 112 | if (err) { 113 | console.log(err); 114 | } else { 115 | // Response will have the original Paypal API response 116 | console.log(response); 117 | // But also a paymentApprovalUrl, so you can redirect the sender to checkout easily 118 | console.log('Redirect to %s', response.paymentApprovalUrl); 119 | } 120 | }); 121 | ``` 122 | 123 | #### Preapproval 124 | ```js 125 | var payload = { 126 | currencyCode: 'USD', 127 | startingDate: new Date().toISOString(), 128 | endingDate: new Date('2020-01-01').toISOString(), 129 | returnUrl: 'http://your-website.com', 130 | cancelUrl: 'http://your-website.com', 131 | ipnNotificationUrl: 'http://your-ipn-listener.com', 132 | maxNumberOfPayments: 1, 133 | displayMaxTotalAmount: true, 134 | maxTotalAmountOfAllPayments: '100.00', 135 | requestEnvelope: { 136 | errorLanguage: 'en_US' 137 | } 138 | } 139 | 140 | paypalSdk.preapproval(payload, function (err, response) { 141 | if (err) { 142 | console.log(err); 143 | } else { 144 | // Response will have the original Paypal API response 145 | console.log(response); 146 | // But also a preapprovalUrl, so you can redirect the sender to approve the payment easily 147 | console.log('Redirect to %s', response.preapprovalUrl); 148 | } 149 | }); 150 | ``` 151 | 152 | **Note:** 153 | The other API methods has default behavior by now: you send a payload and obtains the Paypal original response. 154 | 155 | ```js 156 | var payload = { 157 | requestEnvelope: { 158 | errorLanguage: 'en_US' 159 | }, 160 | // another data required by API method 161 | }; 162 | 163 | var callback = function (err, response) { 164 | if (err) { 165 | // Handle error 166 | console.log(err); 167 | } else { 168 | // Paypal response 169 | console.log(response) 170 | } 171 | }; 172 | 173 | // For Adaptive Payments 174 | paypalSdk.cancelPreapproval(payload, callback); 175 | 176 | paypalSdk.convertCurrency(payload, callback); 177 | 178 | paypalSdk.executePayment(payload, callback); 179 | 180 | paypalSdk.getFundingPlans(payload, callback); 181 | 182 | paypalSdk.getShippingAddresses(payload, callback); 183 | 184 | paypalSdk.preapprovalDetails(payload, callback); 185 | 186 | paypalSdk.setPaymentOptions(payload, callback); 187 | 188 | // For Adaptive Accounts 189 | paypalSdk.addBankAccount(payload, callback); 190 | 191 | paypalSdk.addPaymentCard(payload, callback); 192 | 193 | paypalSdk.checkComplianceStatus(payload, callback); 194 | 195 | paypalSdk.createAccount(payload, callback); 196 | // To use this method you can set X-PAYPAL-SANDBOX-EMAIL-ADDRESS and X-PAYPAL-DEVICE-IPADDRESS headers passing 'sandboxEmailAddress' and 'deviceIpAddress' properties on config 197 | 198 | paypalSdk.getUserAgreement(payload, callback); 199 | 200 | paypalSdk.getVerifiedStatus(payload, callback); 201 | 202 | paypalSdk.setFundingSourceConfirmed(payload, callback); 203 | 204 | paypalSdk.updateComplianceStatus(payload, callback); 205 | ``` 206 | 207 | ### Tests 208 | Tests can be ran with: 209 | 210 | ```sh 211 | mocha 212 | ``` 213 | 214 | ### Reference 215 | Paypal Adaptive Payments 216 | Paypal Adaptive Accounts 217 | 218 | ### License 219 | Copyright (c) 2014 Gonzalo Aguirre. See the LICENSE file for license rights and limitations (MIT). -------------------------------------------------------------------------------- /lib/paypal-adaptive.js: -------------------------------------------------------------------------------- 1 | var https = require('https') 2 | , util = require('util'); 3 | 4 | var adaptiveAccountsMethods = [ 5 | 'AddBankAccount', 6 | 'AddPaymentCard', 7 | 'CheckComplianceStatus', 8 | 'CreateAccount', 9 | 'GetUserAgreement', 10 | 'GetVerifiedStatus', 11 | 'SetFundingSourceConfirmed', 12 | 'UpdateComplianceStatus' 13 | ]; 14 | 15 | var adaptivePaymentsMethods = [ 16 | 'CancelPreapproval', 17 | 'ConvertCurrency', 18 | 'ExecutePayment', 19 | 'GetFundingPlans', 20 | 'GetShippingAddresses', 21 | 'PreapprovalDetails', 22 | 'SetPaymentOptions' 23 | ]; 24 | 25 | function merge(a, b) { 26 | for (var p in b) { 27 | try { 28 | if (b[p].constructor === Object) { 29 | a[p] = merge(a[p], b[p]); 30 | } else { 31 | a[p] = b[p]; 32 | } 33 | } catch (e) { 34 | a[p] = b[p]; 35 | } 36 | } 37 | return a; 38 | } 39 | 40 | function defaultPayload() { 41 | return { 42 | requestEnvelope: { 43 | errorLanguage: 'en_US', 44 | detailLevel: 'ReturnAll' 45 | } 46 | }; 47 | } 48 | 49 | function httpsPost(options, callback) { 50 | options.method = 'POST'; 51 | options.headers = options.headers || {}; 52 | 53 | var data = (typeof options.data !== 'string') ? JSON.stringify(options.data) : options.data; 54 | 55 | options.headers['Content-Length'] = Buffer.byteLength(data); 56 | 57 | var req = https.request(options); 58 | 59 | req.on('response', function (res) { 60 | var response = ''; 61 | //do not setEndcoding with browserify 62 | if (res.setEncoding) { 63 | res.setEncoding('utf8'); 64 | } 65 | 66 | res.on('data', function (chunk) { 67 | response += chunk; 68 | }); 69 | 70 | res.on('end', function () { 71 | return callback(null, { 72 | statusCode: res.statusCode, 73 | body: response 74 | }); 75 | }); 76 | }); 77 | 78 | req.on('error', function (e) { 79 | callback(e); 80 | }); 81 | 82 | if (data) { 83 | req.write(data); 84 | } 85 | 86 | req.end(); 87 | } 88 | 89 | var Paypal = function (config) { 90 | if (!config) throw new Error('Config is required'); 91 | if (!config.userId) throw new Error('Config must have userId'); 92 | if (!config.password) throw new Error('Config must have password'); 93 | if (!config.signature) throw new Error('Config must have signature'); 94 | if (!config.appId && !config.sandbox) throw new Error('Config must have appId'); 95 | 96 | var defaultConfig = { 97 | requestFormat: 'JSON', 98 | responseFormat: 'JSON', 99 | sandbox: false, 100 | productionHostname: 'svcs.paypal.com', 101 | sandboxHostname: 'svcs.sandbox.paypal.com', 102 | appId: 'APP-80W284485P519543T', 103 | approvalUrl: 'https://www.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=%s', 104 | sandboxApprovalUrl: 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=%s', 105 | preapprovalUrl: 'https://www.paypal.com/webscr?cmd=_ap-preapproval&preapprovalkey=%s', 106 | sandboxPreapprovalUrl: 'https://www.sandbox.paypal.com/webscr?cmd=_ap-preapproval&preapprovalkey=%s' 107 | }; 108 | 109 | this.config = merge(defaultConfig, config); 110 | }; 111 | 112 | Paypal.prototype.callApi = function (apiMethod, data, callback) { 113 | var config = this.config; 114 | 115 | var options = { 116 | hostname: config.sandbox ? config.sandboxHostname : config.productionHostname, 117 | port: 443, 118 | path: '/' + apiMethod, 119 | data: data, 120 | headers: { 121 | 'X-PAYPAL-SECURITY-USERID': config.userId, 122 | 'X-PAYPAL-SECURITY-PASSWORD': config.password, 123 | 'X-PAYPAL-SECURITY-SIGNATURE': config.signature, 124 | 'X-PAYPAL-APPLICATION-ID': config.appId, 125 | 'X-PAYPAL-REQUEST-DATA-FORMAT': config.requestFormat, 126 | 'X-PAYPAL-RESPONSE-DATA-FORMAT': config.responseFormat 127 | } 128 | }; 129 | 130 | if (config.sandboxEmailAddress) 131 | options.headers['X-PAYPAL-SANDBOX-EMAIL-ADDRESS'] = config.sandboxEmailAddress; 132 | 133 | if (config.deviceIpAddress) 134 | options.headers['X-PAYPAL-DEVICE-IPADDRESS'] = config.deviceIpAddress; 135 | 136 | if (config.subject) 137 | options.headers['X-PAYPAL-SECURITY-SUBJECT'] = config.subject; 138 | 139 | httpsPost(options, function (error, response) { 140 | if (error) { return callback(error); } 141 | 142 | var body = response.body; 143 | var statusCode = response.statusCode; 144 | 145 | if (config.responseFormat === 'JSON') { 146 | try { 147 | body = JSON.parse(body); 148 | } catch (e) { 149 | var err = new Error('Invalid JSON Response Received'); 150 | err.response = body; 151 | err.httpStatusCode = response.statusCode; 152 | return callback(err); 153 | } 154 | } 155 | 156 | if (statusCode < 200 || statusCode >= 300) { 157 | error = new Error('Response Status: ' + statusCode); 158 | error.response = body; 159 | error.httpStatusCode = statusCode; 160 | return callback(error); 161 | } 162 | 163 | body.httpStatusCode = statusCode; 164 | 165 | if (/^(Success|SuccessWithWarning)$/.test(body.responseEnvelope.ack)) { 166 | callback(null, body); 167 | } else { 168 | var err = new Error('Response ack is ' + body.responseEnvelope.ack + '. Check the response for more info'); 169 | return callback(err, body); 170 | } 171 | }); 172 | }; 173 | 174 | // Paypal Adaptive Payments API methods 175 | Paypal.prototype.getPaymentOptions = function (payKey, callback) { 176 | if (!payKey) { 177 | return callback(new Error('Required "payKey"')); 178 | } 179 | 180 | var data = defaultPayload(); 181 | data.payKey = payKey; 182 | 183 | this.callApi('AdaptivePayments/GetPaymentOptions', data, callback); 184 | }; 185 | 186 | Paypal.prototype.paymentDetails = function (params, callback) { 187 | if (!params.payKey && !params.transactionId && !params.trackingId) { 188 | return callback(new Error('Required "payKey" or "transactionId" or "trackingId" on first param')); 189 | } 190 | 191 | var data = merge(defaultPayload(), params); 192 | 193 | this.callApi('AdaptivePayments/PaymentDetails', data, callback); 194 | }; 195 | 196 | Paypal.prototype.pay = function (data, callback) { 197 | var config = this.config; 198 | 199 | this.callApi('AdaptivePayments/Pay', data, function (err, res) { 200 | if (err) { return callback(err, res); } 201 | 202 | if (res.paymentExecStatus === 'CREATED') { 203 | var url = config.sandbox ? config.sandboxApprovalUrl : config.approvalUrl; 204 | res.paymentApprovalUrl = util.format(url, res.payKey); 205 | } 206 | 207 | return callback(null, res); 208 | }); 209 | }; 210 | 211 | Paypal.prototype.preapproval = function (data, callback) { 212 | var config = this.config; 213 | 214 | this.callApi('AdaptivePayments/Preapproval', data, function (err, res) { 215 | if (err) { return callback(err, res); } 216 | 217 | if (res.preapprovalKey) { 218 | var url = config.sandbox ? config.sandboxPreapprovalUrl : config.preapprovalUrl; 219 | res.preapprovalUrl = util.format(url, res.preapprovalKey); 220 | } 221 | 222 | return callback(null, res); 223 | }); 224 | }; 225 | 226 | Paypal.prototype.refund = function (params, callback) { 227 | if (!params.payKey && !params.transactionId && !params.trackingId) { 228 | return callback(new Error('Required "payKey" or "transactionId" or "trackingId" on first param')); 229 | } 230 | 231 | var data = merge(defaultPayload(), params); 232 | 233 | this.callApi('AdaptivePayments/Refund', data, callback); 234 | }; 235 | 236 | adaptivePaymentsMethods.forEach(function (method) { 237 | var prototypeMethodName = method.charAt(0).toLowerCase() + method.slice(1); 238 | var apiMethodName = 'AdaptivePayments/' + method; 239 | Paypal.prototype[prototypeMethodName] = function (data, callback) { 240 | this.callApi(apiMethodName, data, callback); 241 | }; 242 | }); 243 | 244 | adaptiveAccountsMethods.forEach(function (method) { 245 | var prototypeMethodName = method.charAt(0).toLowerCase() + method.slice(1); 246 | var apiMethodName = 'AdaptiveAccounts/' + method; 247 | Paypal.prototype[prototypeMethodName] = function (data, callback) { 248 | this.callApi(apiMethodName, data, callback); 249 | }; 250 | }); 251 | 252 | module.exports = Paypal; 253 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "paypal-adaptive", 3 | "version": "0.4.1", 4 | "author" : "Gonzalo Aguirre", 5 | "description" : "Paypal Adaptive (Payments & Accounts) SDK in node.js", 6 | "main": "./lib/paypal-adaptive.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/gaguirre/paypal-adaptive-sdk-nodejs.git" 10 | }, 11 | "keywords": [ 12 | "api" 13 | , "paypal" 14 | , "adaptive" 15 | , "payments" 16 | , "accounts" 17 | , "sdk" 18 | , "node" 19 | ], 20 | "devDependencies": { 21 | "mocha": "1.10.0", 22 | "nock": "0.18.2" 23 | }, 24 | "engine" : { 25 | "node": ">=0.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | var nock = require('nock') 2 | , assert = require('assert') 3 | , Paypal = require('..'); 4 | 5 | var paypalConfig = { 6 | userId: 'mockUserId', 7 | password: 'mockPassword', 8 | signature: 'mockSignature', 9 | sandbox: true 10 | }; 11 | 12 | describe('callApi method', function() { 13 | it('should POST with correct header', function(done) { 14 | var mockResponse = { jjx: 'jjx' }; 15 | 16 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 17 | .matchHeader('X-PAYPAL-SECURITY-USERID', 'mockUserId') 18 | .matchHeader('X-PAYPAL-SECURITY-PASSWORD', 'mockPassword') 19 | .matchHeader('X-PAYPAL-SECURITY-SIGNATURE', 'mockSignature') 20 | .matchHeader('X-PAYPAL-APPLICATION-ID', 'APP-80W284485P519543T') 21 | .matchHeader('X-PAYPAL-REQUEST-DATA-FORMAT', 'JSON') 22 | .matchHeader('X-PAYPAL-RESPONSE-DATA-FORMAT', 'JSON') 23 | .post('/just-for-check-header', {}) 24 | .reply(400, mockResponse); 25 | 26 | var api = new Paypal(paypalConfig); 27 | 28 | api.callApi('just-for-check-header', {}, function(err, res) { 29 | assert.equal(err.httpStatusCode, 400); 30 | assert.deepEqual(err.response, mockResponse); 31 | 32 | mockHttp.done(); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('should POST with header additional headers X-PAYPAL-SANDBOX-EMAIL-ADDRESS and X-PAYPAL-DEVICE-IPADDRESS if were provided on config', function(done) { 38 | var mockResponse = { jjx: 'jjx' }; 39 | 40 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 41 | .matchHeader('X-PAYPAL-SECURITY-USERID', 'mockUserId') 42 | .matchHeader('X-PAYPAL-SECURITY-PASSWORD', 'mockPassword') 43 | .matchHeader('X-PAYPAL-SECURITY-SIGNATURE', 'mockSignature') 44 | .matchHeader('X-PAYPAL-APPLICATION-ID', 'APP-80W284485P519543T') 45 | .matchHeader('X-PAYPAL-REQUEST-DATA-FORMAT', 'JSON') 46 | .matchHeader('X-PAYPAL-RESPONSE-DATA-FORMAT', 'JSON') 47 | .matchHeader('X-PAYPAL-SANDBOX-EMAIL-ADDRESS', 'mockEmailAddress') 48 | .matchHeader('X-PAYPAL-DEVICE-IPADDRESS', 'mockIpAddress') 49 | .post('/just-for-check-extra-headers', {}) 50 | .reply(400, mockResponse); 51 | 52 | var api = new Paypal({ 53 | userId: 'mockUserId', 54 | password: 'mockPassword', 55 | signature: 'mockSignature', 56 | sandbox: true, 57 | sandboxEmailAddress: 'mockEmailAddress', 58 | deviceIpAddress: 'mockIpAddress' 59 | }); 60 | 61 | api.callApi('just-for-check-extra-headers', {}, function(err, res) { 62 | assert.equal(err.httpStatusCode, 400); 63 | assert.deepEqual(err.response, mockResponse); 64 | 65 | mockHttp.done(); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('should return error when status is not 200', function(done) { 71 | var mockResponse = { jjx: 'jjx' }; 72 | 73 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 74 | .post('/not-200', {}) 75 | .reply(400, mockResponse); 76 | 77 | 78 | var api = new Paypal(paypalConfig); 79 | 80 | api.callApi('not-200', {}, function(err, res) { 81 | assert.equal(err.httpStatusCode, 400); 82 | assert.deepEqual(err.response, mockResponse); 83 | 84 | mockHttp.done(); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('should return error when Paypal response ack is not Success or SuccessWithWarning', function(done) { 90 | var failureResponse = { 91 | responseEnvelope: { 92 | ack: 'NotSuccess' 93 | }, 94 | error: 'errorMock' 95 | }; 96 | 97 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 98 | .post('/failure-response', {}) 99 | .reply(200, failureResponse); 100 | 101 | 102 | var api = new Paypal(paypalConfig); 103 | 104 | api.callApi('failure-response', {}, function(err, res) { 105 | assert.notEqual(err, null); 106 | 107 | failureResponse.httpStatusCode = 200; 108 | assert.deepEqual(res, failureResponse); 109 | 110 | mockHttp.done(); 111 | done(); 112 | }); 113 | }); 114 | 115 | it('should return OK when Paypal response ack is Success', function(done) { 116 | var okResponse = { 117 | responseEnvelope: { 118 | ack: 'Success' 119 | }, 120 | mock: 'mock' 121 | }; 122 | 123 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 124 | .post('/ok-response', {}) 125 | .reply(200, okResponse); 126 | 127 | 128 | var api = new Paypal(paypalConfig); 129 | 130 | api.callApi('ok-response', {}, function(err, res) { 131 | assert.equal(err, null); 132 | 133 | okResponse.httpStatusCode = 200; 134 | assert.deepEqual(res, okResponse); 135 | 136 | mockHttp.done(); 137 | done(); 138 | }); 139 | }); 140 | 141 | it('should return OK when Paypal response ack is SuccessWithWarning', function(done) { 142 | var okResponse = { 143 | responseEnvelope: { 144 | ack: 'SuccessWithWarning' 145 | }, 146 | mock: 'mock' 147 | }; 148 | 149 | var mockHttp = nock('https://svcs.sandbox.paypal.com') 150 | .post('/ok-response', {}) 151 | .reply(200, okResponse); 152 | 153 | 154 | var api = new Paypal(paypalConfig); 155 | 156 | api.callApi('ok-response', {}, function(err, res) { 157 | assert.equal(err, null); 158 | 159 | okResponse.httpStatusCode = 200; 160 | assert.deepEqual(res, okResponse); 161 | 162 | mockHttp.done(); 163 | done(); 164 | }); 165 | }); 166 | }); 167 | --------------------------------------------------------------------------------