├── .editorconfig
├── .github
└── pull_request_template.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.js
├── package.json
├── src
├── PayStack
│ ├── extension
│ │ ├── Mockable.js
│ │ └── facade
│ │ │ └── MockFactory.js
│ └── index.js
└── endpoints
│ ├── balance_history.js
│ ├── bulk_charges.js
│ ├── charges.js
│ ├── control_panel_for_sessions.js
│ ├── customers.js
│ ├── dedicated_nuban.js
│ ├── disputes.js
│ ├── invoices.js
│ ├── miscellaneous.js
│ ├── pages.js
│ ├── plans.js
│ ├── products.js
│ ├── refunds.js
│ ├── settlements.js
│ ├── subaccounts.js
│ ├── subscriptions.js
│ ├── transactions.js
│ ├── transfers.js
│ ├── transfers_recipients.js
│ └── verifications.js
└── test
├── .gitkeep
└── paystack-object.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_size = 2
6 | indent_style = space
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Closes #
9 |
10 | ## 📑 Description
11 |
12 |
13 |
17 |
18 | ## ✅ Checks
19 |
20 | - [ ] My pull request adheres to the code style of this project
21 | - [ ] My code requires changes to the documentation
22 | - [ ] I have updated the documentation as required
23 | - [ ] All the tests have passed
24 |
25 | ## ℹ Additional Information
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules
37 | node_modules/
38 | jspm_packages/
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # package manager files
59 | package-lock.json
60 | yarn.lock
61 |
62 | # dotenv environment variables file
63 | .env
64 |
65 | # next.js build output
66 | .next
67 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage
2 | .github
3 | node_modules
4 | .DS_Store
5 | npm-debug.log
6 | test
7 | .travis.yml
8 | .editorconfig
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - node
5 | - 8.4.0
6 | - 9.0.0
7 | - 10.0.0
8 | - 11.0.0
9 | - 12.0.0
10 |
11 | sudo: false
12 |
13 | install:
14 | - npm install
15 |
16 | cache:
17 | directories:
18 | - ~/.npm # cache npm's cache
19 | - ~/npm # cache latest npm
20 |
21 | notifications:
22 | email:
23 | - stitchnig@gmail.com
24 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # 0.3.0 (2021-03-23)
3 |
4 | ### Added Features
5 | - Added the **submitAddress()** Endpoint method
6 | - Added the **chargeUssd()** Endpoint method
7 | - Added the **chargeMobileMoney()** Endpoint method
8 | - Added the **resolveBVNPremium()** Endpoint method
9 | - Added the **listProviders()** Endpoint method
10 |
11 | ### Bug Fixes
12 | - Fixed error _"TypeError: function is not a function"_ when mock is enabled
13 |
14 |
15 | # 0.2.6 (2021-02-27)
16 |
17 | ### Features Added
18 | - Updated `lodash` dependency to _v4.17.21_ from _v4.17.19_ to fix ReDoS vulnerability
19 | - Beta: Added ability to mock `PayStack` instance by [@isocroft](https://github.com/isocroft)
20 | - Added _Dedicated Nuban_ API Endpoint methods by [@isocroft](https://github.com/isocroft)
21 |
22 |
23 | # 0.2.5 (2020-07-18)
24 |
25 | ### Bug Fixes
26 | - Fixed outdated endpoints issue raised by [@Tetranyble](https://github.com/Tetranyble)
27 | - Fixed errors on **chargeCard()** Endpoint method
28 |
29 |
30 | # 0.2.4 (2020-02-25)
31 |
32 | ### Bug Fixes
33 | - Fixed outdated endpoints issue raised by [@navicstein](https://github.com/navicstein)
34 |
35 |
36 | # 0.2.3 (2020-02-04)
37 |
38 | ### Bug Fixes
39 | - Fixed errors on **listBanks()** Endpoint method
40 |
41 | ### Feature Added
42 | - Updated _Miscellaneous_ API Endpoint methods
43 | - Updated _Pages_ API Endpoint methods
44 | - Updated _Verifications_ API Endpoint methods
45 | - Added _Disputes_ API Endpoint methods by [@isocroft](https://github.com/isocroft)
46 |
47 |
48 | # 0.2.2 (2019-11-23)
49 |
50 | ### Feature Added
51 | - Added _Transfer Recipients_ API Endpoint methods by [@adekoyejoakinhanmi](https://github.com/adekoyejoakinhanmi)
52 |
53 |
54 | # 0.2.1 (2019-10-13)
55 |
56 | ### Bug Fixes
57 | - Fixed `SyntaxError: Unexpected end of JSON input` by [@HopePraise5](https://github.com/HopePraise5)
58 |
59 | ### Feature Added
60 | - Added _Products_ API Endpoint methods
61 |
62 |
63 | # 0.2.0 (2019-04-14)
64 |
65 | ### Feature Added
66 | - Added 'Control-Cache' header to limit HTTP caching
67 | - Added _Control Panel Payment Sessions_ API Endpoint methods
68 |
69 |
70 | # 0.1.0 (2019-03-14)
71 |
72 | ### Bug Fixes
73 | - None
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this [StitchNG Paystack Library][project], please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
11 | 2. Update the README.md with details of changes to the interface, this includes new environment
12 | variables, exposed ports, useful file locations and container parameters.
13 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
14 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
15 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
16 | do not have permission to do that, you may request the second reviewer to merge it for you.
17 |
18 | ## Code of Conduct
19 |
20 | - If you are contributing to the repo, kindly update the necessary test file in `/test` or add a new one and ensure all tests are passed before sending a PR.
21 |
22 | - All code sent in MUST pass both linting & testing requirements
23 |
24 | - Code Reviews are done in order to ensure that only quality work is put forward and eventually merged
25 |
26 | ### Our Pledge
27 |
28 | In the interest of fostering an open and welcoming environment, we as
29 | contributors and maintainers pledge to making participation in our project and
30 | our community a harassment-free experience for everyone, regardless of age, body
31 | size, disability, ethnicity, gender identity and expression, level of experience,
32 | nationality, personal appearance, race, religion, or sexual identity and
33 | orientation.
34 |
35 | ### Our Standards
36 |
37 | Examples of behavior that contributes to creating a positive environment
38 | include:
39 |
40 | * Using welcoming and inclusive language
41 | * Being respectful of differing viewpoints and experiences
42 | * Gracefully accepting constructive criticism
43 | * Focusing on what is best for the community
44 | * Showing empathy towards other community members
45 |
46 | Examples of unacceptable behavior by participants include:
47 |
48 | * The use of sexualized language or imagery and unwelcome sexual attention or
49 | advances
50 | * Trolling, insulting/derogatory comments, and personal or political attacks
51 | * Public or private harassment
52 | * Publishing others' private information, such as a physical or electronic
53 | address, without explicit permission
54 | * Other conduct which could reasonably be considered inappropriate in a
55 | professional setting
56 |
57 | ### Our Responsibilities
58 |
59 | Project maintainers are responsible for clarifying the standards of acceptable
60 | behavior and are expected to take appropriate and fair corrective action in
61 | response to any instances of unacceptable behavior.
62 |
63 | Project maintainers have the right and responsibility to remove, edit, or
64 | reject comments, commits, code, wiki edits, issues, and other contributions
65 | that are not aligned to this Code of Conduct, or to ban temporarily or
66 | permanently any contributor for other behaviors that they deem inappropriate,
67 | threatening, offensive, or harmful.
68 |
69 | ### Scope
70 |
71 | This Code of Conduct applies both within project spaces and in public spaces
72 | when an individual is representing the project or its community. Examples of
73 | representing a project or community include using an official project e-mail
74 | address, posting via an official social media account, or acting as an appointed
75 | representative at an online or offline event. Representation of a project may be
76 | further defined and clarified by project maintainers.
77 |
78 | ### Enforcement
79 |
80 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
81 | reported by contacting the project maintainer at [this email](mailto:stitchnig@gmail.com). All
82 | complaints will be reviewed and investigated and will result in a response that
83 | is deemed necessary and appropriate to the circumstances. The project team is
84 | obligated to maintain confidentiality with regard to the reporter of an incident.
85 | Further details of specific enforcement policies may be posted separately.
86 |
87 | Project maintainers who do not follow or enforce the Code of Conduct in good
88 | faith may face temporary or permanent repercussions as determined by other
89 | members of the project's leadership.
90 |
91 | ### Attribution
92 |
93 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1,
94 | available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct][version]
95 |
96 | [project]: https://github.com/stitchng/paystack
97 | [homepage]: https://contributor-covenant.org
98 | [version]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
99 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 StitchNG
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 | # Paystack
2 |
3 | [![NPM Version][npm-image]][npm-url]
4 | [![Build Status][travis-image]][travis-url]
5 |
6 | A NodeJS Wrapper for [Paystack](https://www.paystack.com)
7 |
8 | ## Overview
9 | This project provides an easy-to-use object-oriented API to access endpoints delineated at https://developers.paystack.co/reference
10 |
11 | ## Getting Started
12 |
13 | >Install from the NPM Registry
14 |
15 | ```bash
16 |
17 | $ npm i --save paystack-node
18 |
19 | ```
20 |
21 | # Usage
22 |
23 | ```js
24 |
25 | let PayStack = require('paystack-node')
26 |
27 | let APIKEY = 'sk_live_2hWyQ6HW73jS8p1IkXmSWOlE4y9Inhgyd6g5f2R7'
28 | const environment = process.env.NODE_ENV
29 |
30 | const paystack = new PayStack(APIKEY, environment)
31 |
32 | const feesCalculator = new PayStack.Fees();
33 | const feeCharge = feesCalculator.calculateFor(250000) // 2,500 Naira
34 |
35 | /*
36 | NOTE: All fields/params that require dates should be set to
37 | instances of the `Date()` constructor as they will
38 | eventually be turned into the ISO 8601 format (String)
39 | using `toJSON()` method for date instances/objects
40 | */
41 |
42 | const promise_0 = paystack.getSettlements({
43 | from:new Date("2017-02-09"),
44 | to:new Date()
45 | })
46 |
47 | promise_0.then(function(response){
48 | var data = response.body.data;
49 | }).catch(function (error){
50 | // deal with error
51 | })
52 |
53 | // listBanks
54 |
55 | try {
56 | let { body: { status, message, data } } = await paystack.listBanks({
57 | currency: 'NGN'
58 | });
59 |
60 | if(status === false){
61 | throw new Error(message);
62 | }
63 | }catch(ex){
64 | console.error(ex.message);
65 | }
66 |
67 | // addPageProduct
68 |
69 | const promise1 = paystack.addPageProduct({
70 | id: '0826739',
71 | products: [473, 292]
72 | })
73 |
74 | promise1.then(function(response){
75 | // Error Handling
76 | if(response.body.status === false){
77 | console.error(response.body.message);
78 | }
79 | var data = response.body.data;
80 | }).catch(function (error) {
81 | // deal with error
82 | })
83 |
84 | // getCustomer
85 |
86 | const promise2 = paystack.getCustomer({
87 | customer_id:'CUS_e24m6SqA6g3Jk889o21'
88 | })
89 |
90 | promise2.then(function(response){
91 | var data = response.body
92 |
93 | }).catch(function(error){
94 | // deal with error
95 | })
96 |
97 | // createCustomer
98 |
99 | const promise3 = paystack.createCustomer({
100 | email:'malik.onyemah@gmail.com',
101 | first_name:'Malik',
102 | last_name:'Onyemah',
103 | phone:'+2347135548369'
104 | })
105 |
106 | promise3.then(function(response){
107 | return response.body
108 | }).then( body => {
109 | return res.status(200).json({id:body.data.id})
110 | })
111 |
112 | // setRiskActionOnCustomer
113 |
114 | const promise4 = paystack.setRiskActionOnCustomer({
115 | risk_action:'deny',
116 | customer_id:'CUS_e24m6SqA6g3Jk889o21'
117 | })
118 |
119 | promise4.then(function (response){
120 | const result = response.body
121 | }).catch(function (error){
122 | // deal with error
123 | })
124 |
125 | // createPage
126 |
127 | const promise5 = paystack.createPage({
128 | name:'DoorPost Pay',
129 | description:'This is payment for every ',
130 | amount:300000, // Amount in kobo
131 | slug:'5nApBwZkvR',
132 | redirect_url:'https://www.localhoster.com/pay/callback',
133 | custom_fields: ['phone_number', 'age']
134 | })
135 |
136 | promise5.then(function (response){
137 | console.log(response.body);
138 | }).catch(function (error){
139 | // deal with error
140 | })
141 |
142 | // initializeTransaction
143 |
144 | const promise6 = paystack.initializeTransaction({
145 | reference: "7PVGX8MEk85tgeEpVDtD",
146 | amount: 500000, // 5,000 Naira (remember you have to pass amount in kobo)
147 | email: "seun045olayinka@gmail.com",
148 | subaccount: "ACCT_8f4s1eq7ml6rlzj"
149 | })
150 |
151 | promise6.then(function (response){
152 | console.log(response.body);
153 | }).catch(function (error){
154 | // deal with error
155 | })
156 |
157 | // verifyTransaction
158 |
159 | const promise7 = paystack.verifyTransaction({
160 | reference: "7PVGX8MEk85tgeEpVDtD"
161 | })
162 |
163 | promise7.then(function (response){
164 | console.log(response.body);
165 | }).catch(function (error){
166 | // deal with error
167 | })
168 |
169 | // listInvoices
170 |
171 | const promise8 = paystack.listInvoices({
172 | customer: "CUS_je02lbimlqixzax",
173 | status: "pending",
174 | paid: false,
175 | currency: "NGN"
176 | })
177 |
178 | promise8.then(function (response){
179 | console.log(response.body);
180 | }).catch(function (error){
181 | // deal with error
182 | })
183 |
184 | app.use(async function verifications(req, res, next){
185 | let responseBVN = await paystack.resolveBVN({
186 | bvn:req.body.bvn //'22283643840404'
187 | })
188 |
189 | let responseAcctNum = await paystack.resolveAccountNumber({
190 | account_number:req.body.acc_num, // '0004644649'
191 | bank_code:req.body.bank_code // '075'
192 | })
193 |
194 | next()
195 | })
196 |
197 | ```
198 |
199 | ### Mocking the Instance (for Unit/Integration Tests)
200 | >Setting up mocks for testing with the paystack instance is now as easy as fliping a switch like so:
201 |
202 | ```js
203 |
204 | let PayStack = require('paystack-node')
205 |
206 | let APIKEY = 'sk_live_2hWyQ6HW73jS8p1IkXmSWOlE4y9Inhgyd6g5f2R7'
207 | const environment = process.env.NODE_ENV
208 |
209 | const paystack = new PayStack(APIKEY, environment)
210 |
211 | // call the real API methods
212 | const { body } = paystack.chargeCard({
213 | card:{
214 | number: '5399837841116788', // mastercard
215 | cvv: '324',
216 | expiry_year: '2024',
217 | expiry_month: '08'
218 | },
219 | email: 'me.biodunch@xyz.ng',
220 | amount: 15600000 // 156,000 Naira in kobo
221 | })
222 |
223 | // mocking call made on the constructor
224 | // start mocking
225 | PayStack.engageMock()
226 |
227 | // call the mock API methods
228 | const { body } = await paystack.chargeBank({
229 | bank: {
230 | code: "050", // Eco Bank
231 | account_number: "0000000000"
232 | },
233 | email: 'me.biodunch@xyz.ng',
234 | amount: 1850000 // 18,500 Naira in kobo
235 | })
236 |
237 | // replace mocked methods (! don't use arrow functions !)
238 | PayStack.mockMacro(
239 | 'getCustomers',
240 | async function getCustomers (reqPayload = {}) {
241 | // validation for (reqPayload) is already taken care of!
242 |
243 | // @TODO: optionally, connect to a in-memory db (redis) for mocking purposes
244 |
245 | // return mocked response object
246 | return { status: 200, body: { status: "success", data: reqPayload } };
247 | })
248 |
249 | const { body } = await paystack.getCustomers({
250 | customer_id:'CUS_e24m6SqA6g3Jk889o21'
251 | })
252 |
253 | // stop mocking
254 | // mocking call made on the constructor
255 | PayStack.disengageMock()
256 | ```
257 |
258 | ## API Resources
259 |
260 | >Each method expects an object literal with both **route parameters** and **request parameters (query / body)**. Please, go through the _src/endpoints_ folder to see the specific items that should make up the object literal for each method
261 |
262 | - customers
263 | - paystack.createCustomer()
264 | - paystack.getCustomer()
265 | - paystack.listCustomer()
266 | - paystack.updateCustomer()
267 | - paystack.deactivateAuthOnCustomer()
268 | - paystack.setRiskActionOnCustomer()
269 | - disputes
270 | - paystack.listDisputes()
271 | - dedicated nuban
272 | - paystack.createDedicatedNuban()
273 | - paystack.listDedicatedNubans()
274 | - paystack.fetchDedicatedNuban()
275 | - paystack.deactivateDedicatedNuban()
276 | - invoices
277 | - paystack.createInvoice()
278 | - paystack.getMetricsForInvoices()
279 | - paystack.sendInvoiceNotification()
280 | - paystack.listInvoice()
281 | - paystack.updateInvoice()
282 | - paystack.verifyInvoice()
283 | - paystack.finalizeInvoiceDraft()
284 | - paystack.archiveInvoice()
285 | - paystack.markInvoiceAsPaid()
286 | - settlements
287 | - paystack.getSettlements()
288 | - payment sessions {control panel}
289 | - paystack.getPaymentSessionTimeout()
290 | - paystack.updatePaymentSessionTimeout()
291 | - pages
292 | - paystack.createPage()
293 | - paystack.listPages()
294 | - paystack.getPage()
295 | - paystack.updatePage()
296 | - paystack.checkSlugAvailability()
297 | - paystack.addPageProduct()
298 | - products
299 | - paystack.createProduct()
300 | - paystack.listProduct()
301 | - paystack.getProduct()
302 | - paystack.updateProduct()
303 | - transactions
304 | - paystack.initializeTransaction()
305 | - paystack.chargeAuthorization()
306 | - paystack.getTransaction()
307 | - paystack.listTransaction()
308 | - paystack.viewTransactionTimeline()
309 | - paystack.transactionTotals()
310 | - paystack.exportTransaction()
311 | - paystack.requestReauthorization()
312 | - paystack.checkAuthorization()
313 | - paystack.verifyTransaction()
314 | - paystack.partialDebit()
315 | - plans
316 | - paystack.createPlan()
317 | - paystack.getPlan()
318 | - paystack.listPlan()
319 | - paystack.updatePlan()
320 | - refunds
321 | - paystack.createRefund()
322 | - paystack.getRefund()
323 | - paystack.listRefund()
324 | - subscriptions
325 | - paystack.createSubscription()
326 | - paystack.disableSubscription()
327 | - paystack.enableSubscription()
328 | - paystack.getSubscription()
329 | - paystack.listSubscription()
330 | - subaccounts
331 | - paystack.createSubaccount()
332 | - paystack.getSubaccount()
333 | - paystack.listSubaccount()
334 | - paystack.updateSubaccount()
335 | - verifications
336 | - paystack.resolveBVN()
337 | - paystack.resolveBVNPremium()
338 | - paystack.matchBVN()
339 | - paystack.resolveAccountNumber()
340 | - paystack.resolveCardBin()
341 | - paystack.resolvePhoneNumber()
342 | - transfers
343 | - paystack.initiateTransfer()
344 | - paystack.listTransfers()
345 | - paystack.fetchTransfer()
346 | - paystack.finalizeTransfer()
347 | - paystack.initiateBulkTransfer()
348 | - transfer_recipients
349 | - paystack.createTransferRecipient()
350 | - paystack.listTransferRecipients()
351 | - paystack.updateTransferRecipient()
352 | - paystack.deleteTransferRecipient()
353 | - charges
354 | - paystack.chargeCard()
355 | - paystack.chargeBank()
356 | - paystack.chargeUssd()
357 | - paystack.chargeMobileMoney()
358 | - paystack.submitPIN()
359 | - paystack.submitOTP()
360 | - paystack.submitPhone()
361 | - paystack.submitBirthday()
362 | - paystack.submitAddress()
363 | - paystack.checkPendingCharge()
364 | - miscellanous
365 | - paystack.listBanks()
366 | - paystack.listProviders()
367 | - paystack.listCountries()
368 |
369 | # License
370 |
371 | MIT
372 |
373 | # Credits
374 |
375 | - [Ifeora Okechukwu](https://twitter.com/isocroft)
376 | - [Ahmad Abdul-Aziz](https://twitter.com/dev_amaz)
377 |
378 | # Contributing
379 |
380 | See the [CONTRIBUTING.md](https://github.com/stitchng/paystack/blob/master/CONTRIBUTING.md) file for info
381 |
382 | [npm-image]: https://img.shields.io/npm/v/paystack-node.svg?style=flat-square
383 | [npm-url]: https://npmjs.org/package/paystack-node
384 |
385 | [travis-image]: https://img.shields.io/travis/stitchng/paystack/master.svg?style=flat-square
386 | [travis-url]: https://travis-ci.org/stitchng/paystack
387 |
388 | ## Support
389 |
390 | **Coolcodes** is a non-profit software foundation (collective) created by **Oparand** - parent company of StitchNG, Synergixe based in Abuja, Nigeria. You'll find an overview of all our work and supported open source projects on our [Facebook Page](https://www.facebook.com/coolcodes/).
391 |
392 | >Follow us on facebook if you can to get the latest open source software/freeware news and infomation.
393 |
394 | Does your business depend on our open projects? Reach out and support us on [Patreon](https://www.patreon.com/coolcodes/). All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff.
395 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const PayStack = require('./src/PayStack/index.js')
4 |
5 | PayStack.prototype.version = '0.3.0'
6 |
7 | const Fees = function (cap, additionalCharge, percentage, threshold) {
8 | this.additionalCharge = additionalCharge || Fees.INIT_ADDITIONAL_CHARGE
9 | this.percentage = percentage || Fees.INIT_PERCENT
10 | this.threshold = threshold || Fees.INIT_THRESHOLD
11 | this.cap = cap || Fees.INIT_CAP
12 |
13 | this.compute()
14 | }
15 |
16 | Fees.INIT_THRESHOLD = 250000
17 | Fees.INIT_CAP = 200000
18 | Fees.INIT_PERCENT = 0.015
19 | Fees.INIT_ADDITIONAL_CHARGE = 10000
20 |
21 | Fees.prototype.resetDefaults = function () {
22 |
23 | }
24 |
25 | Fees.prototype.withAdditionalCharge = function (additionalCharge) {
26 | this.additionalCharge = additionalCharge
27 | return this
28 | }
29 |
30 | Fees.prototype.withThreshold = function (threshold) {
31 | this.threshold = threshold
32 | return this
33 | }
34 |
35 | Fees.prototype.withCap = function (cap) {
36 | this.cap = cap
37 | return this
38 | }
39 |
40 | Fees.prototype.compute = function () {
41 | this.chargeDivider = this.__chargeDivider()
42 | this.crossover = this.__crossover()
43 | this.flatlinePlusCharge = this.__flatlinePlusCharge()
44 | this.flatline = this.__flatline()
45 | }
46 |
47 | Fees.prototype.__chargeDivider = function () {
48 | return 1 - this.percentage
49 | }
50 |
51 | Fees.prototype.__crossover = function () {
52 | return (this.threshold * this.chargeDivider) - this.additionalCharge
53 | }
54 |
55 | Fees.prototype.__flatlinePlusCharge = function () {
56 | return (this.cap - this.additionalCharge) / this.percentage
57 | }
58 |
59 | Fees.prototype.__flatline = function () {
60 | return this.flatlinePlusCharge - this.cap
61 | }
62 |
63 | Fees.prototype.addFor = function (amountinkobo) {
64 | this.compute()
65 |
66 | if (amountinkobo > this.flatline) {
67 | return parseInt(Math.ceil(amountinkobo + this.cap))
68 | } else if (amountinkobo > this.crossover) {
69 | return parseInt(Math.ceil((amountinkobo + this.additionalCharge) / this.chargeDivider))
70 | } else {
71 | return parseInt(Math.ceil(amountinkobo / this.chargeDivider))
72 | }
73 | }
74 |
75 | Fees.prototype.calculateFor = function (amountinkobo) {
76 | this.compute()
77 |
78 | let fee = this.percentage * amountinkobo
79 | if (amountinkobo >= this.threshold) {
80 | fee += this.additionalCharge
81 | }
82 |
83 | if (fee > this.cap) {
84 | fee = this.cap
85 | }
86 |
87 | return parseInt(Math.ceil(fee))
88 | }
89 |
90 | PayStack.Fees = Fees
91 |
92 | module.exports = PayStack
93 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "paystack-node",
3 | "version": "0.3.0",
4 | "description": "A NodeJS wrapper for the Paystack API",
5 | "main": "index.js",
6 | "files": [
7 | "src",
8 | "index.js"
9 | ],
10 | "directories": {
11 | "test": "test"
12 | },
13 | "scripts": {
14 | "lint": "standard --fix",
15 | "test": "mocha \"test/*.test.js\""
16 | },
17 | "keywords": [
18 | "paystack",
19 | "payments",
20 | "nodejs",
21 | "api-endpoints",
22 | "rest-api"
23 | ],
24 | "author": "StitchNG (https://twitter.com/stitchappn)",
25 | "license": "MIT",
26 | "standard": {
27 | "globals": [
28 | "describe",
29 | "it"
30 | ]
31 | },
32 | "dependencies": {
33 | "got": "^9.3.2",
34 | "lodash": "^4.17.21"
35 | },
36 | "devDependencies": {
37 | "chai": "^4.2.0",
38 | "mocha": "^5.2.0",
39 | "standard": "^12.0.1"
40 | },
41 | "repository": {
42 | "type": "git",
43 | "url": "https://github.com/stitchng/paystack.git"
44 | },
45 | "bugs": {
46 | "url": "https://github.com/stitchng/paystack/issues"
47 | },
48 | "homepage": "https://github.com/stitchng/paystack#readme"
49 | }
50 |
--------------------------------------------------------------------------------
/src/PayStack/extension/Mockable.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const payStackMockFactory = require('./facade/MockFactory.js')
4 |
5 | class Mockable {
6 | static engageMock () {
7 | this.prototype._mock = payStackMockFactory.make(
8 | this.prototype.httpClientBaseOptions.hooks,
9 | Object.keys(this.prototype).filter(
10 | prop => (((this.excludeOnMock || []).concat([
11 | '_mock'
12 | ])).indexOf(prop) === -1)
13 | )
14 | )
15 | }
16 |
17 | static respondWithError () {
18 | if (this.prototype._mock !== null) {
19 | this.prototype._mock['_respondWithError'] = true
20 | }
21 | }
22 |
23 | static respondWithoutError () {
24 | if (this.prototype._mock !== null) {
25 | this.prototype._mock['_respondWithError'] = false
26 | }
27 | }
28 |
29 | static disengageMock () {
30 | this.prototype._mock = null
31 | }
32 |
33 | static mockMacro (methodName = '', methodRoutine) {
34 | if (typeof methodName !== 'string') {
35 | return new TypeError('mock method name is not a string')
36 | }
37 |
38 | if (typeof this.prototype[methodName] !== 'function') {
39 | throw new Error('Cannot monkey-patch non-existing methods on mock object')
40 | }
41 |
42 | if (typeof methodRoutine !== 'function') {
43 | throw new TypeError('mock method for mock object is not a function')
44 | }
45 |
46 | if (this.prototype._mock === null) {
47 | throw new Error('call engageMock() first')
48 | }
49 | return (this.prototype._mock[methodName] = methodRoutine)
50 | }
51 | }
52 |
53 | Mockable.prototype._mock = null
54 |
55 | module.exports = Mockable
56 |
--------------------------------------------------------------------------------
/src/PayStack/extension/facade/MockFactory.js:
--------------------------------------------------------------------------------
1 | 'use strcit'
2 |
3 | module.exports = {
4 | make: function (hooks = {}, methods = []) {
5 | const mockedMethods = methods.map(function (method) {
6 | let mock = {}
7 | mock[method] = function () {
8 | const that = this
9 | return new Promise(function resolver (resolve, reject) {
10 | const args = Array.prototype.slice.call(arguments)
11 | setTimeout(function timeoutCallback (data) {
12 | if (that._respondWithError) {
13 | const err = new Error('[Paystack] : something unexpected happened')
14 | err.response = {
15 | status: 401,
16 | body: {
17 | status: false,
18 | message: 'error on request to paystack'
19 | }
20 | }
21 | return reject(hooks.onError(
22 | err
23 | ))
24 | }
25 | return resolve({
26 | status: 200,
27 | statusText: 'OK',
28 | body: {
29 | status: true,
30 | message: 'successfull paystack request {' + method + '}',
31 | data: data || {
32 | status: 'success'
33 | }
34 | }
35 | })
36 | }, 750, args[0])
37 | })
38 | }
39 | return mock
40 | })
41 |
42 | mockedMethods.unshift({})
43 | return Object.assign.apply(Object, mockedMethods)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/PayStack/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const got = require('got')
4 | const querystring = require('querystring')
5 | const _ = require('lodash')
6 |
7 | const customers = require('../endpoints/customers.js')
8 | const disputes = require('../endpoints/disputes.js')
9 | const transactions = require('../endpoints/transactions.js')
10 | const subaccounts = require('../endpoints/subaccounts.js')
11 | const plans = require('../endpoints/plans.js')
12 | const pages = require('../endpoints/pages.js')
13 | const products = require('../endpoints/products.js')
14 | const balanceHistory = require('../endpoints/balance_history.js')
15 | const transferRecipients = require('../endpoints/transfers_recipients.js')
16 | const refunds = require('../endpoints/refunds.js')
17 | const charges = require('../endpoints/charges.js')
18 | const invoices = require('../endpoints/invoices.js')
19 | const transfers = require('../endpoints/transfers.js')
20 | const verifications = require('../endpoints/verifications.js')
21 | const dedicatedNuban = require('../endpoints/dedicated_nuban.js')
22 | const miscellaneous = require('../endpoints/miscellaneous.js')
23 | const settlements = require('../endpoints/settlements.js')
24 | const subscriptions = require('../endpoints/subscriptions.js')
25 | const controlPanelForSessions = require('../endpoints/control_panel_for_sessions.js')
26 |
27 | const Mockable = require('./extension/Mockable.js')
28 |
29 | /* Any param with '$' at the end is a REQUIRED param both for request body param(s) and request route params */
30 | const apiEndpoints = Object.assign(
31 | {},
32 | customers,
33 | disputes,
34 | transactions,
35 | subaccounts,
36 | plans,
37 | pages,
38 | products,
39 | balanceHistory,
40 | refunds,
41 | charges,
42 | invoices,
43 | transfers,
44 | verifications,
45 | miscellaneous,
46 | dedicatedNuban,
47 | settlements,
48 | subscriptions,
49 | transferRecipients,
50 | controlPanelForSessions
51 | )
52 |
53 | /*!
54 | *
55 | * Provides a convenience extension to _.isEmpty which allows for
56 | * determining an object as being empty based on either the default
57 | * implementation or by evaluating each property to undefined, in
58 | * which case the object is considered empty.
59 | */
60 | _.mixin(function () {
61 | // reference the original implementation
62 | var _isEmpty = _.isEmpty
63 | return {
64 | // If defined is true, and value is an object, object is considered
65 | // to be empty if all properties are undefined, otherwise the default
66 | // implementation is invoked.
67 | isEmpty: function (value, defined) {
68 | if (defined && _.isObject(value)) {
69 | return !_.some(value, function (value, key) {
70 | return value !== undefined
71 | })
72 | }
73 | return _isEmpty(value)
74 | }
75 | }
76 | }())
77 |
78 | /**
79 | * check if a variable literal (or not) is falsy or not
80 | */
81 | const isLiteralFalsey = (variable) => {
82 | return variable === '' || variable === false || variable === 0
83 | }
84 |
85 | /**
86 | * check if a variable is a reference type (not a literal) or not
87 | */
88 |
89 | const isPrimitive = (arg) => {
90 | return typeof arg === 'object' || (Boolean(arg) && typeof arg === 'function')
91 | }
92 |
93 | /**
94 | * retrieve the type name either from a reference variables' constructor name or up its' prototype chain
95 | */
96 | const getNameByChain = (_type, depth) => {
97 | let $depth = depth;
98 |
99 | if(typeof $depth !== 'number'){
100 | $depth = 1;
101 | }
102 |
103 | let name = _type.name || _type.displayName
104 | const chainList = [(_type.__proto__ && _type.__proto__.prototype)];
105 |
106 | while(Boolean(chainList[0]) && $depth !== 0){
107 | const variable = chainList[0].constructor;
108 | if(typeof variable !== 'function'){
109 | break;
110 | }
111 | name = variable.name || variable.displayName
112 | --depth
113 | chainList.unshift((variable.__proto__ && variable.__proto__.prototype));
114 | }
115 | chainList.length = 0
116 |
117 | return name
118 | }
119 |
120 | /**
121 | * provide the name of primitive and/or reference types
122 | */
123 | const checkTypeName = (target, type) => {
124 | let typeName = ''
125 | let match = false
126 | let depth = 0
127 | const MAX_DEPTH = 3
128 |
129 | if(typeof type === 'function'){
130 | type = (getNameByChain(type, depth)).toLowerCase()
131 | }
132 |
133 | if (isLiteralFalsey(target) || !isPrimitive(target)) {
134 | typeName = typeof target
135 | } else {
136 | typeName = (Object.prototype.toString.call(target)).replace(/^\[object (.+)\]$/, '$1')
137 | }
138 |
139 | match = Boolean(typeName.toLowerCase().indexOf(type) + 1)
140 |
141 | while(!match){
142 | ++depth;
143 | if(depth === MAX_DEPTH){
144 | break;
145 | }
146 |
147 | typeName = '' + (target && getNameByChain(target.constructor, depth))
148 | match = Boolean(typeName.toLowerCase().indexOf(type) + 1)
149 | }
150 |
151 | return match;
152 | }
153 |
154 | /**
155 | * get the actual type of a variable
156 | */
157 |
158 | /*!
159 | * @EXAMPLES:
160 | *
161 | * strictTypeOf([], 'Array'); // true
162 | * strictTypeOf({}, 'object'); // true
163 | * strictTypeOf(null, 'null'); // true
164 | * strictTypeOf(window.localStorage, Storage); // true
165 | * strictTypeOf('hello!', 'Boolean'); // false
166 | * strictTypeOf(new URL('/', window.location.origin), 'url'); // true
167 | * strictTypeOf(0x35, ['number', 'string']); // true
168 | * strictTypeOf("200,000", ['number', 'string']); // true
169 | */
170 | const strictTypeOf = (value, type) => {
171 | let result = false
172 |
173 | type = type || []
174 |
175 | if (typeof type === 'object') {
176 | if (typeof type.length !== 'number') {
177 | return result
178 | }
179 |
180 | let bitPiece = 0
181 |
182 | type = [].slice.call(type)
183 |
184 | type.forEach(_type => {
185 | var localResult = false;
186 | if (typeof _type === 'function') {
187 | localResult = value instanceof _type
188 | }
189 | bitPiece |= Number(localResult || checkTypeName(value, _type.toLowerCase()))
190 | })
191 | result = Boolean(bitPiece)
192 | } else {
193 | if (typeof type === 'function') {
194 | result = value instanceof type
195 | }
196 | result = result || checkTypeName(value, type.toLowerCase())
197 | }
198 | return result
199 | }
200 |
201 | const matcherValid = (value, matcher = () => true) => {
202 | return matcher(value);
203 | }
204 |
205 | const isNullOrUndefined = (value) => {
206 | return strictTypeOf(value, ['undefined', 'null'])
207 | }
208 |
209 | const isNumeric = (value) => {
210 | if (strictTypeOf(value, ['string', 'number'])) {
211 | return strictTypeOf(Math.abs(-value), 'number')
212 | }
213 | return false
214 | }
215 |
216 | const objectToJSONString = (value) => {
217 | const isValidObject = value instanceof Object;
218 | if (!value || !isValidObject) {
219 | throw new TypeError(
220 | "objectToJSONString(...): argument 1 is not a valid object"
221 | )
222 | }
223 |
224 | const isDateObject = value instanceof Date;
225 |
226 | if (!isDateObject && ('toJSON' in value)) {
227 | return value.toJSON();
228 | }
229 |
230 | return isDateObject
231 | ? value.toISOString().replace(/Z$/, '')
232 | : JSON.stringify(value);
233 | };
234 |
235 |
236 | const setPathName = (config, values) => {
237 | return config.path.replace(/\{:([\w]+)\}/g, function (
238 | match,
239 | string,
240 | offset) {
241 | let _value = values[string] || (
242 | strictTypeOf(config.alternate_route_params_keymap, 'object')
243 | ? values[config.alternate_route_params_keymap[string]]
244 | : false
245 | );
246 |
247 | if (config.route_params_numeric === true) {
248 | if (!isNumeric(_value)) {
249 | return null
250 | }
251 | }
252 | return strictTypeOf(
253 | _value,
254 | (config.route_params[string] || String)
255 | )
256 | ? _value
257 | : null
258 | })
259 | }
260 |
261 | const _jsonify = (data) => {
262 | if (isNullOrUndefined(data)) {
263 | return 'null';
264 | }
265 |
266 | return typeof data === 'object'
267 | ? objectToJSONString(data)
268 | : String(data)
269 | }
270 |
271 | const setInputValues = (config, inputs) => {
272 | let httpReqOptions = {}
273 | let inputValues = {}
274 | let label = ''
275 |
276 | switch (config.method) {
277 | case 'GET':
278 | case 'HEAD':
279 | case 'DELETE':
280 | label = 'query'
281 | break
282 |
283 | case 'POST':
284 | case 'PUT':
285 | case 'PATCH':
286 | label = 'body'
287 | break
288 | }
289 |
290 | httpReqOptions[label] = {}
291 |
292 | if (config.param_defaults) {
293 | inputs = Object.assign({}, config.param_defaults, inputs)
294 | }
295 |
296 | for (var input in config.params) {
297 | if (config.params.hasOwnProperty(input)) {
298 | let param = input.replace('$', '')
299 | let _input = inputs[param]
300 | let _type = config.params[input]
301 | let _required = false
302 |
303 | if ((input.indexOf('$') + 1) === (input.length)) {
304 | _required = true
305 | }
306 |
307 | if (isNullOrUndefined(_input) || _input === '') {
308 | if (_required) { throw new Error(`param: "${param}" is required but not provided; please provide as needed`) }
309 | } else {
310 | if (!strictTypeOf(_input, _type)) {
311 | throw new Error(
312 | `param: "${param}" is not of the correct type "${_type.toLowerCase()}"; please provide as needed`
313 | );
314 | }
315 |
316 | httpReqOptions[label][param] = matcherValid(
317 | _input,
318 | 'param_matchers' in config ? config.param_matchers[param] : (() => true)
319 | )
320 | ? (label === 'query'
321 | ? querystring.escape(_jsonify(_input))
322 | : _jsonify(_input))
323 | : null
324 |
325 | if (httpReqOptions[label][param] === null) {
326 | throw new TypeError(`param: "${param}" value: '${_input}' did not pass matcher; please provide as needed`)
327 | }
328 | }
329 | }
330 | }
331 |
332 | inputValues[label] = (label === 'body'
333 | ? (config.send_form
334 | ? httpReqOptions[label]
335 | : JSON.stringify(httpReqOptions[label])
336 | )
337 | : querystring.stringify(httpReqOptions[label]))
338 |
339 | return inputValues
340 | }
341 |
342 | const makeMethod = function (config, methodName) {
343 | let httpConfig = {
344 | headers: {
345 | 'Cache-Control': 'no-cache',
346 | 'Accept': 'application/json'
347 | },
348 | json: true
349 | }
350 |
351 | if (config.send_json) {
352 | httpConfig.headers['Content-Type'] = 'application/json'
353 | httpConfig.form = false
354 | } else if (config.send_form) {
355 | httpConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded'
356 | httpConfig.form = true
357 | }
358 |
359 | return function (requestParams = {}) {
360 | let pathname = config.path
361 | let payload = false
362 |
363 | if (!(requestParams instanceof Object)) {
364 | throw new TypeError(
365 | 'Argument: [ requestParam(s) ] Should Be An Object Literal'
366 | )
367 | }
368 |
369 | if (!_.isEmpty(requestParams, true)) {
370 | if (config.params !== null) {
371 | payload = setInputValues(config, requestParams)
372 | }
373 |
374 | if (config.route_params !== null) {
375 | pathname = setPathName(config, requestParams)
376 | }
377 | } else {
378 | if (config.params !== null || config.route_params !== null) {
379 | throw new TypeError(
380 | 'Argument: [ requestParam(s) ] Should Not Be Empty!'
381 | )
382 | }
383 | }
384 |
385 | if (payload === false) {
386 | payload = {}
387 | }
388 |
389 | let reqBody = {}
390 |
391 | for (let type in payload) {
392 | if (payload.hasOwnProperty(type)) {
393 | reqBody = httpConfig[type] = (type === 'query')
394 | ? payload[type]
395 | : JSON.parse(payload[type])
396 | break
397 | }
398 | }
399 |
400 | const reqVerb = config.method.toLowerCase()
401 |
402 | const canInvokeTestingMock = (
403 | this._mock !== null &&
404 | typeof this._mock[methodName] === 'function'
405 | )
406 |
407 | if (canInvokeTestingMock) {
408 | if (methodName !== 'chargeBank' &&
409 | methodName !== 'chargeCard') {
410 | return this._mock[methodName](
411 | Object.assign(
412 | httpConfig,
413 | { 'method': config.method }
414 | ))
415 | } else if (isTypeOf(reqBody.card, Object) ||
416 | isTypeOf(reqBody.bank, Object)) {
417 | /* eslint-disable camelcase */
418 | const { cvv, expiry_month, expiry_year } = reqBody.card
419 | const { code, account_number } = reqBody.bank
420 | /* eslint-enable camelcase */
421 |
422 | // Visa OR Verve
423 | const isTestCardPan = /^408408(4084084081|0000000409|0000005408)$/.test(reqBody.card.number)
424 | const isTestCardCVV = String(cvv) === '408'
425 | const isTestCardExpiry = String(expiry_month) === '02' && String(expiry_year) === '22'
426 | const isTestCard = (isTestCardPan && isTestCardCVV && isTestCardExpiry)
427 |
428 | // Zenith Bank OR First Bank
429 | const isTestBankCode = /^(?:057|011)$/.test(String(code))
430 | /* eslint-disable-next-line camelcase */
431 | const isTestBankAccount = account_number === '0000000000'
432 | const isTestBank = (isTestBankCode && isTestBankAccount)
433 |
434 | if (!isTestCard || !isTestBank) {
435 | return this._mock[methodName](
436 | Object.assign(
437 | httpConfig,
438 | { 'method': config.method }
439 | )
440 | )
441 | }
442 | }
443 | }
444 | return this.httpBaseClient[reqVerb](pathname, httpConfig)
445 | }
446 | }
447 |
448 | class PayStack extends Mockable {
449 | get httpClientBaseOptions () {
450 | return {
451 | headers: { },
452 | hooks: {
453 | beforeResponse: [
454 | async options => {
455 | // console.log(options)
456 | }
457 | ],
458 | onError: [
459 | error => {
460 | const { response } = error
461 | if (response && response.body) {
462 | error.name = 'PayStackError'
463 | error.message = `${response.body.message} (${error.statusCode})`
464 | }
465 |
466 | return error
467 | }
468 | ],
469 | afterResponse: [
470 | (response) => {
471 | let errorMessage = ''
472 | switch (response.statusCode) {
473 | case 400: // Bad Request
474 | errorMessage = 'Request was badly formed | Bad Request (400)'
475 | break
476 | case 401: // Unauthorized
477 | errorMessage = 'Bearer Authorization header may not have been set | Unauthorized (401)'
478 | break
479 | case 404: // Not Found
480 | errorMessage = 'Request endpoint does not exist | Not Found (404)'
481 | break
482 | case 403: // Forbidden
483 | errorMessage = 'Request endpoint requires further priviledges to be accessed | Forbidden (403)'
484 | break
485 | }
486 |
487 | if (response.body && response.body.status === false) {
488 | errorMessage += '; {' + response.body.message + '}'
489 | }
490 |
491 | if (errorMessage !== '') {
492 | const error = new Error(errorMessage)
493 | if (response._isMocked) {
494 | error.response = response
495 | }
496 | error.name = 'PayStackAPIError'
497 | throw error
498 | }
499 |
500 | return response
501 | }
502 | ]
503 | },
504 | mutableDefaults: false
505 | }
506 | }
507 |
508 | constructor (apiKey, appEnv = 'development') {
509 | super()
510 | const environment = /^(?:development|local|dev)$/
511 |
512 | const apiBase = {
513 | sandbox: 'https://api.paystack.co',
514 | live: 'https://api.paystack.co'
515 | }
516 |
517 | const clientOptions = this.httpClientBaseOptions
518 |
519 | clientOptions.baseUrl = environment.test(appEnv) ? apiBase.sandbox : apiBase.live
520 | clientOptions.headers['Authorization'] = `Bearer ${apiKey}`
521 |
522 | this.httpBaseClient = got.extend(clientOptions)
523 | }
524 |
525 | mergeNewOptions (newOptions) {
526 | this.httpBaseClient = this.httpBaseClient.extend(
527 | newOptions
528 | )
529 | }
530 | }
531 |
532 | for (let methodName in apiEndpoints) {
533 | if (apiEndpoints.hasOwnProperty(methodName)) {
534 | PayStack.prototype[methodName] = makeMethod(apiEndpoints[methodName], methodName)
535 | }
536 | }
537 |
538 | PayStack.excludeOnMock = [
539 | 'httpClientBaseOptions',
540 | 'version'
541 | ]
542 |
543 | module.exports = PayStack
544 |
--------------------------------------------------------------------------------
/src/endpoints/balance_history.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Get Balance History
6 | @params: from, to, type
7 | */
8 | getBalanceHistory: {
9 | method: 'GET',
10 | path: '/balance/ledger',
11 | send_json: false,
12 | params: { from: Date, to: Date, type: String },
13 | param_defaults: { type: 'Transaction' },
14 | route_params: null
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/endpoints/bulk_charges.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* Charging banks & cards are split into two separate method APIs */
4 |
5 | module.exports = {
6 | /*
7 | Bulk Charge
8 | @param: (array of objects)
9 | */
10 | bulkCharge: {
11 | method: 'POST',
12 | path: '/bulkcharge',
13 | send_json: true,
14 | params: { },
15 | param_defaults: null,
16 | route_params: null
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/endpoints/charges.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* Charging banks & cards are split into two separate method APIs */
4 |
5 | module.exports = {
6 | /*
7 | Charge Card
8 | @param: card, metadata, reference, amount, email
9 | */
10 | chargeCard: {
11 | method: 'POST',
12 | path: '/charge',
13 | send_json: true,
14 | params: { card$: Object, metadata: Object, reference: String, pin: String, authorization_code: String, device_id: String, amount$: String, email$: String },
15 | param_defaults: null,
16 | route_params: null
17 | },
18 |
19 | /*
20 | Charge USSD
21 | @param: ussd, metadata, reference, amount, email
22 | */
23 | chargeUssd: {
24 | method: 'POST',
25 | path: '/charge',
26 | send_json: true,
27 | params: { ussd$: Object, metadata: Object, reference: String, pin: String, authorization_code: String, device_id: String, amount$: String, email$: String },
28 | param_defaults: { ussd: { type: '737' } },
29 | route_params: null
30 | },
31 |
32 | /*
33 | Charge Mobile Money
34 | @param: mobile_money, currency, metadata, reference, amount, email
35 | */
36 | chargeMobileMoney: {
37 | method: 'POST',
38 | path: '/charge',
39 | send_json: true,
40 | params: { mobile_money$: Object, currency: String, metadata: Object, reference: String, pin: String, authorization_code: String, device_id: String, amount$: String, email$: String },
41 | param_defaults: null,
42 | route_params: null
43 | },
44 |
45 | /*
46 | Charge Bank
47 | @param: bank, metadata, reference, amount, email
48 | */
49 | chargeBank: {
50 | method: 'POST',
51 | path: '/charge',
52 | send_json: true,
53 | params: { bank$: Object, metadata: Object, reference: String, pin: String, authorization_code: String, mobile_money: Object, device_id: String, amount$: String, email$: String },
54 | route_params: null
55 | },
56 |
57 | /*
58 | Submit PIN
59 | @param: pin, reference
60 | */
61 | submitPIN: {
62 | method: 'POST',
63 | path: '/charge/submit_pin',
64 | send_json: true,
65 | params: { pin$: String, reference$: String },
66 | route_params: null
67 | },
68 |
69 | /*
70 | Submit OTP
71 | @param: otp, reference
72 | */
73 | submitOTP: {
74 | method: 'POST',
75 | path: '/charge/submit_otp',
76 | send_json: true,
77 | params: { otp$: String, reference$: String },
78 | route_params: null
79 | },
80 |
81 | /*
82 | Submit Phone
83 | @param: phone, reference
84 | */
85 | submitPhone: {
86 | method: 'POST',
87 | path: '/charge/submit_phone',
88 | send_json: true,
89 | params: { phone$: String, reference$: String },
90 | route_params: null
91 | },
92 |
93 | /*
94 | Submit Birthday
95 | @param: birthday, reference
96 | */
97 | submitBirthday: {
98 | method: 'POST',
99 | path: '/charge/submit_birthday',
100 | send_json: true,
101 | params: { birthday$: Date, reference$: String },
102 | route_params: null
103 | },
104 |
105 | /*
106 | Submit Address
107 | @param: address, reference, city, state, zipcode
108 | */
109 | submitAddress: {
110 | method: 'POST',
111 | path: '/charge/submit_address',
112 | send_json: true,
113 | params: { address$: String, reference$: String, city$: String, state$: String, zipcode: String },
114 | route_params: null
115 | },
116 |
117 | /*
118 | Check Pending Charge
119 | @param: reference
120 | */
121 | checkPendingCharge: {
122 | method: 'GET',
123 | path: '/charge/{:reference}',
124 | send_json: false,
125 | params: null,
126 | param_defaults: null,
127 | route_params: { reference: String }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/endpoints/control_panel_for_sessions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Get Payment session timeout
6 | @params:
7 | */
8 | getPaymentSessionTimeout: {
9 | method: 'GET',
10 | path: '/integration/payment_session_timeout',
11 | send_json: false,
12 | params: null,
13 | route_params: null
14 | },
15 |
16 | /*
17 | Update Payment session timeout
18 | @params: timeout
19 | */
20 | updatePaymentSessionTimeout: {
21 | method: 'PUT',
22 | path: '/integration/payment_session_timeout',
23 | send_json: true,
24 | params: { timeout: Number },
25 | param_defaults: { timeout: 0 },
26 | route_params: null
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/endpoints/customers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create customer
6 | @params: first_name, last_name, email(required), phone
7 | */
8 | createCustomer: {
9 | method: 'POST',
10 | path: '/customer',
11 | send_json: true,
12 | params: { first_name: String, last_name: String, email$: String, phone: String, metadata: Object },
13 | route_params: null
14 | },
15 |
16 | /*
17 | Get customer
18 | @params: customer_id
19 | */
20 | getCustomer: {
21 | method: 'GET',
22 | alternate_route_params_keymap: { email_or_id_or_customer_code: 'customer_id' },
23 | path: '/customer/{:email_or_id_or_customer_code}',
24 | send_json: false,
25 | params: null,
26 | route_params: { email_or_id_or_customer_code: String }
27 | },
28 |
29 | /*
30 | List customers
31 | @params: perPage, page
32 | */
33 | listCustomers: {
34 | method: 'GET',
35 | path: '/customer',
36 | params: { perPage: Number, page: Number },
37 | param_defaults: { perPage: 50, page: 1 },
38 | route_params: null
39 | },
40 |
41 | /*
42 | Update customer
43 | @params: id_or_customer_code, first_name, last_name, email (required), phone
44 | */
45 | updateCustomer: {
46 | method: 'PUT',
47 | alternate_route_params_keymap: { id_or_customer_code: 'customer_id' },
48 | path: '/customer/{:id_or_customer_code}',
49 | send_json: true,
50 | params: { first_name: String, last_name: String, email$: String, phone: String, metadata: Object },
51 | route_params: { id_or_customer_code: String }
52 | },
53 |
54 | /*
55 | Deactivate Customer Authoorization
56 | @params: authorization_code
57 | */
58 | deactivateAuthOnCustomer: {
59 | method: 'POST',
60 | path: '/customer/deactivate_authorization',
61 | send_json: true,
62 | params: { authorization_code: String },
63 | route_params: null
64 | },
65 |
66 | /*
67 | White/Blacklist Customer
68 | @params: customer (required), risk_action
69 |
70 | @info: [ 'allow' to whitelist or 'deny' to blacklist ]
71 | */
72 | setRiskActionOnCustomer: {
73 | method: 'POST',
74 | path: '/customer/set_risk_action',
75 | send_json: true,
76 | params: { customer$: String, risk_action: String },
77 | param_defaults: { risk_action: 'allow' },
78 | route_params: null
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/endpoints/dedicated_nuban.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Dedicated Nuban
6 | @params: customer, prefered_bank
7 | */
8 | createDedicatedNuban: {
9 | method: 'POST',
10 | path: '/dedicated_account',
11 | send_json: true,
12 | params: { $customer: String, preferred_bank: String },
13 | param_defaults: { preferred_bank: 'wema-bank' },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Dedicated Nubans
19 | @params: customer, provider_slug, active, currency, bank_id
20 | */
21 | listDedicatedNubans: {
22 | method: 'GET',
23 | path: '/dedicated_account',
24 | send_json: false,
25 | params: { customer: String, provider_slug: String, $active: Boolean, $currency: String, bank_id: String },
26 | param_defaults: { provider_slug: 'wema-bank' },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch Dedicated Nuban
32 | @params: dedicated_account_id
33 | */
34 | fetchDedicatedNuban: {
35 | method: 'GET',
36 | path: '/dedicated_account/{:dedicated_account_id}',
37 | send_json: false,
38 | params: null,
39 | route_params: { dedicated_account_id: String }
40 | },
41 |
42 | /*
43 | Deactivate Dedicated Nuban
44 | @params: dedicated_account_id
45 | */
46 | deactivateDedicatedNuban: {
47 | method: 'DELETE',
48 | path: '/dedicated_account/{:dedicated_account_id}',
49 | send_json: false,
50 | params: null,
51 | route_params: { dedicated_account_id: String }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/endpoints/disputes.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | List Disputes
6 | @params: from, to, page, perPage, transaction
7 | */
8 | listDisputes: {
9 | method: 'GET',
10 | path: '/dispute',
11 | send_json: false,
12 | params: { from: Date, to: Date, page: Number, perPage: Number, transaction: Number },
13 | param_defaults: { page: 1, perPage: 10 },
14 | route_params: null
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/endpoints/invoices.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create an Invoice
6 | @params: description, line_items, tax, customer, amount, due_date, draft, has_invoice, metadata
7 | */
8 | createInvoice: {
9 | method: 'POST',
10 | path: '/paymentrequest',
11 | send_json: true,
12 | params: { description: String, line_items: Array, tax: Array, customer$: String, amount$: Number, due_date$: String, draft: Boolean, has_invoice: Boolean, metadata: Object, send_notification: Boolean },
13 | param_defaults: { draft: false, has_invoice: false, metadata: {} },
14 | route_params: null
15 | },
16 | /*
17 | View an Invoice
18 | @params: invoice_id_or_code
19 | */
20 | viewInvoice: {
21 | method: 'GET',
22 | path: '/paymentrequest/{:invoice_id_or_code}',
23 | send_json: false,
24 | params: null,
25 | route_params: { invoice_id_or_code: String }
26 | },
27 | /*
28 | Update an Invoice
29 | @params: description, line_items, customer , due_date, metadata, send_notification
30 | */
31 | updateInvoice: {
32 | method: 'PUT',
33 | path: '/paymentrequest/{:invoice_id_or_code}',
34 | send_json: true,
35 | params: { description: String, line_items: Array, tax: Array, customer: String, due_date: String, metadata: Object, send_notification: Boolean },
36 | param_defaults: { send_notification: false },
37 | route_params: { invoice_id_or_code: String }
38 | },
39 |
40 | /*
41 | List All Invoices
42 | @params: customer , status, currency, paid, include_archive
43 | */
44 | listInvoices: {
45 | method: 'GET',
46 | path: '/paymentrequest',
47 | send_json: false,
48 | params: { customer: String, status: String, currency: String, paid: String, include_archive: String },
49 | param_defaults: { currency: 'NGN' },
50 | route_params: null
51 | },
52 |
53 | /*
54 | Verify Invoice
55 | @params: invoice_code
56 | */
57 | verifyInvoice: {
58 | method: 'GET',
59 | path: '/paymentrequest/verify/{:invoice_code}',
60 | send_json: false,
61 | params: null,
62 | param_defaults: null,
63 | route_params: { invoice_code: String }
64 | },
65 |
66 | /*
67 | Finalize [ Invoice ] Draft
68 | @params: id_or_code, send_notification
69 | */
70 | finalizeInvoiceDraft: {
71 | method: 'POST',
72 | path: '/paymentrequest/finalize/{:id_or_code}',
73 | send_json: true,
74 | params: { send_notification: Boolean },
75 | param_defaults: { send_notification: true },
76 | route_params: { id_or_code: String }
77 | },
78 |
79 | /*
80 | Get [ Invoice ] Metrics
81 | @params:
82 | */
83 |
84 | getMetricsForInvoices: {
85 | method: 'GET',
86 | path: '/paymentrequest/totals',
87 | send_form: true,
88 | params: null,
89 | param_defaults: null,
90 | route_params: null
91 | },
92 |
93 | /*
94 | Send [ Invoice ] Notification
95 | @params: id_or_code
96 | */
97 | sendInvoiceNotification: {
98 | method: 'POST',
99 | path: '/paymentrequest/notify/{:id_or_code}',
100 | send_json: true,
101 | params: null,
102 | route_params: { id_or_code: String }
103 | },
104 |
105 | /*
106 | Archive Invoice
107 | @params: invoice_id_or_code
108 | */
109 | archiveInvoice: {
110 | method: 'POST',
111 | path: '/invoice/archive/{:invoice_id_or_code}',
112 | send_form: true,
113 | params: null,
114 | route_params: { invoice_id_or_code: String }
115 | },
116 |
117 | /*
118 | Mark [ Invoice ] As Piad
119 | @params: id, amount_paid, paid_by, payment_date, payment_method, note
120 | */
121 | markInvoiceAsPaid: {
122 | method: 'POST',
123 | path: '/paymentrequest/mark_as_paid/{:id}',
124 | send_json: true,
125 | params: { amount_paid$: Number, paid_by$: String, payment_date$: String, payment_method$: String, note: String },
126 | param_defaults: { payment_method: 'Cash' },
127 | route_params: { id: String }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/endpoints/miscellaneous.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | List Banks
6 | @param: perPage, page, use_cursor(required), next, previous, gateway, type, country(required)
7 | */
8 | listBanks: {
9 | method: 'GET',
10 | path: '/bank',
11 | send_json: false,
12 | params: { perPage$: Number, page: Number, use_cursor: Boolean, next: String, previous: String, type: String, currency: String, country$: String },
13 | param_defaults: { perPage: 50, page: 1, currency: 'NGN', country: 'Nigeria' },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Providers
19 | @param: pay_with_bank_transfer
20 | */
21 | listProviders: {
22 | method: 'GET',
23 | path: '/bank',
24 | send_json: false,
25 | params: { pay_with_bank_transfer$: Boolean },
26 | param_defaults: { pay_with_bank_transfer: true },
27 | route_params: null
28 | },
29 |
30 | /*
31 | List Countries
32 | @param: -
33 | */
34 | listCountries: {
35 | method: 'GET',
36 | path: '/country',
37 | send_json: false,
38 | params: null,
39 | route_params: null
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/endpoints/pages.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Pages
6 | @params: name(required), description, amount, slug, redirect_url, custom_fields
7 | */
8 | createPage: {
9 | method: 'POST',
10 | path: '/page',
11 | send_json: true,
12 | params: { name$: String, description: String, amount: Number, slug: String, redirect_url: String, custom_fields: Array },
13 | route_params: null
14 | },
15 | /*
16 | List Pages
17 | @params: perPage, page
18 | */
19 | listPages: {
20 | method: 'GET',
21 | path: '/page',
22 | send_json: false,
23 | params: { perPage: Number, page: Number },
24 | param_defaults: { perPage: 50, page: 1 },
25 | route_params: null
26 | },
27 | /*
28 | Fetch Page
29 | @params: id_or_slug
30 | */
31 | getPage: {
32 | method: 'GET',
33 | path: '/page/{:id_or_slug}',
34 | send_json: false,
35 | params: null,
36 | route_params: { id_or_slug: String }
37 | },
38 | /*
39 | Update Pages
40 | @params: id_or_slug, name, description, amount, active
41 | */
42 | updatePage: {
43 | method: 'PUT',
44 | path: '/page/{:id_or_slug}',
45 | send_json: true,
46 | params: { name$: String, description: String, amount: Number, active: Boolean },
47 | param_defaults: { active: true },
48 | route_params: { id_or_slug: String }
49 | },
50 |
51 | /*
52 | Check Availability Of Slug
53 | @params: slug
54 | */
55 | checkSlugAvailability: {
56 | method: 'GET',
57 | path: '/page/check_slug_availability/{:slug}',
58 | send_json: false,
59 | params: null,
60 | route_params: { slug: String }
61 | },
62 |
63 | /*
64 | Add Page Product
65 | @params: id, products
66 | */
67 | addPageProduct: {
68 | method: 'POST',
69 | path: '/page/{:id}/product',
70 | send_json: true,
71 | params: { products$: Array },
72 | route_params_numeric: true,
73 | route_params: { id: String }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/endpoints/plans.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Plan
6 | @param: name, interval, currency, amount, send_invoices, send_sms, invoice_limit, description
7 | */
8 | createPlan: {
9 | method: 'POST',
10 | path: '/plan',
11 | send_json: true,
12 | params: { name$: String, interval$: String, currency: String, amount$: Number, send_invoices: Boolean, send_sms: Boolean, invoice_limit: Number, description: String },
13 | param_defaults: { interval: 'monthly', currency: 'NGN', send_invoices: false, send_sms: false, invoice_limit: 0 },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Plans
19 | @param: perPage, page
20 | */
21 | listPlans: {
22 | method: 'GET',
23 | path: '/plan',
24 | params: { perPage: Number, page: Number },
25 | send_json: false,
26 | param_defaults: { perPage: 0, page: 0 },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch Plan
32 | @param: id_or_plan_code
33 | */
34 | getPlan: {
35 | method: 'GET',
36 | path: '/plan/{:id_or_plan_code}',
37 | params: null,
38 | send_json: false,
39 | route_params: { id_or_plan_code: String }
40 | },
41 |
42 | /*
43 | Update Plan
44 | @param: name, interval, currency, amount, send_invoices, send_sms, invoice_limit, description
45 | */
46 | updatePlan: {
47 | method: 'PUT',
48 | path: '/plan/{:id_or_plan_code}',
49 | send_json: true,
50 | params: { name$: String, currency: String, amount$: Number, send_invoices: Boolean, send_sms: Boolean, invoice_limit: Number, description: String },
51 | param_defaults: { currency: 'NGN', send_invoices: false, send_sms: false, invoice_limit: 0 },
52 | route_params: { id_or_plan_code: String }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/endpoints/products.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Product
6 | @param: name, description, price, currency, limited, quantity
7 | */
8 | createProduct: {
9 | method: 'POST',
10 | path: '/product',
11 | send_json: true,
12 | params: { name$: String, description: String, price$: Number, currency$: String, limited: Boolean, quantity: Number },
13 | param_defaults: { currency: 'NGN', limited: false },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Products
19 | @param:
20 | */
21 | listProduct: {
22 | method: 'GET',
23 | path: '/product',
24 | send_json: false,
25 | params: null,
26 | param_defaults: null,
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch Product
32 | @param: id
33 | */
34 | getProduct: {
35 | method: 'GET',
36 | path: '/product/{:id}',
37 | send_json: false,
38 | params: null,
39 | param_defaults: null,
40 | route_params: { id: String }
41 | },
42 |
43 | /*
44 | Update Product
45 | @param: id, name, description, price, currency, limited, quantity
46 | */
47 | updateProduct: {
48 | method: 'PUT',
49 | path: '/product/{:id}',
50 | send_json: true,
51 | params: { name$: String, description: String, price$: Number, currency$: String, limited: Boolean, quantity: Number },
52 | param_defaults: { currency: 'NGN', limited: false },
53 | route_params: { id: String }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/endpoints/refunds.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Refund
6 | @param: transaction(reference), amount, currency, customer_note, merchant_note
7 | */
8 | createRefund: {
9 | method: 'POST',
10 | path: '/refund',
11 | send_json: true,
12 | params: { transaction$: String, amount: Number, currency: String, customer_note: String, merchant_note: String },
13 | param_defaults: { currency: 'NGN' },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Refund
19 | @param: reference(required), currency
20 | */
21 | listRefund: {
22 | method: 'GET',
23 | path: '/refund',
24 | send_json: false,
25 | params: { reference$: String, currency: String },
26 | param_defaults: { currency: 'NGN' },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch Refund
32 | @param: reference, currency
33 | */
34 | getRefund: {
35 | method: 'GET',
36 | path: '/refund/{:reference}',
37 | send_json: false,
38 | params: null,
39 | route_params: { reference: String }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/endpoints/settlements.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Get Settlements
6 | @params: from, to, subaccount
7 | */
8 | getSettlements: {
9 | method: 'GET',
10 | path: '/settlement',
11 | send_json: false,
12 | params: { from: Date, to: Date, subaccount: String },
13 | param_defaults: { subaccount: 'none' },
14 | route_params: null
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/endpoints/subaccounts.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Sub Account
6 | @param: business_name, settlement_bank, account_number, percentage_charge, primary_contact_email, primary_contact_name, primary_contact_phone, metadata, settlement_schedule
7 | */
8 | createSubaccount: {
9 | method: 'POST',
10 | path: '/subaccount',
11 | send_json: true,
12 | params: { business_name$: String, settlement_bank: String, account_number$: String, percentage_charge$: Number, primary_contact_email: String, primary_contact_name: String, primary_contact_phone: String, metadata: String, settlement_schedule: String },
13 | param_defaults: { settlement_schedule: 'auto', percentage_charge: 0 },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Sub Accounts
19 | @param: perPage(50 per page), page
20 | */
21 | listSubaccount: {
22 | method: 'GET',
23 | path: '/subaccount',
24 | send_json: false,
25 | params: { perPage: Number, page: Number },
26 | param_defaults: { perPage: 50, page: 1 },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch Sub Account
32 | @param: id_or_slug
33 | */
34 | getSubaccount: {
35 | method: 'GET',
36 | path: '/subaccount/{:id_or_slug}',
37 | send_json: false,
38 | params: null,
39 | param_defaults: null,
40 | route_params: { id_or_slug: String }
41 | },
42 |
43 | /*
44 | Update Sub Account
45 | @param: id_or_slug, business_name, settlement_bank, account_number, percentage_charge, primary_contact_email, primary_contact_name, primary_contact_phone, metadata, settlement_schedule
46 | */
47 | updateSubaccount: {
48 | method: 'PUT',
49 | path: '/subaccount/{:id_or_slug}',
50 | send_json: true,
51 | params: { business_name: String, settlement_bank: String, account_number: String, percentage_charge: Number, primary_contact_email: String, primary_contact_phone: String, metadata: String, settlement_schedule: String },
52 | param_defaults: { settlement_schedule: 'auto', percentage_charge: 0 },
53 | route_params: { id_or_slug: String }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/endpoints/subscriptions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Subscription
6 | @param: customer, plan, authorization, start_date
7 | */
8 | createSubscription: {
9 | method: 'POST',
10 | path: '/subscription',
11 | send_json: true,
12 | params: { customer$: String, plan$: String, authorization$: String, start_date: Date },
13 | param_defaults: null,
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Subscription
19 | @param: perPage, page, customer, plan
20 | */
21 | listSubscription: {
22 | method: 'GET',
23 | path: '/subscription',
24 | send_json: false,
25 | params: { perPage: Number, page: Number, customer: Number, plan: Number },
26 | param_defaults: { perPage: 50, page: 1, customer: 0, plan: 0 },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Disable Subscription
32 | @param: code, token
33 | */
34 | disableSubscription: {
35 | method: 'POST',
36 | path: '/subscription/disable',
37 | send_json: true,
38 | params: { code: String, token: String },
39 | param_defaults: null,
40 | route_params: null
41 | },
42 |
43 | /*
44 | Enable Subscription
45 | @param: code, token
46 | */
47 | enableSubscription: {
48 | method: 'POST',
49 | path: '/subscription/enable',
50 | send_json: true,
51 | params: { code: String, token: String },
52 | param_defaults: null,
53 | route_params: null
54 | },
55 |
56 | /*
57 | Fetch Subscription
58 | @param: id_or_subscription_code
59 | */
60 | getSubscription: {
61 | method: 'GET',
62 | path: '/subscription/{:id_or_subscription_code}',
63 | params: null,
64 | param_defaults: null,
65 | route_params: { id_or_subscription_code: String }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/endpoints/transactions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Initialize Transaction
6 | @params: reference, callback_url, amount, email, plan, subaccount, transaction_charge, bearer, channels, invoice_limit, metadata
7 | */
8 | initializeTransaction: {
9 | method: 'POST',
10 | path: '/transaction/initialize',
11 | send_json: true,
12 | params: { reference: String, callback_url: String, amount$: Number, email$: String, plan: String, subaccount: String, transaction_charge: Number, bearer: String, channels: Array, invoice_limit: Number, metadata: String },
13 | param_defaults: { invoice_limit: 0, bearer: 'account' },
14 | route_params: null
15 | },
16 |
17 | /*
18 | Verify Transaction
19 | @params: reference
20 | */
21 | verifyTransaction: {
22 | method: 'GET',
23 | path: '/transaction/verify/{:reference}',
24 | send_json: false,
25 | params: null,
26 | param_defaults: null,
27 | route_params: { reference: String }
28 | },
29 |
30 | /*
31 | List Transaction
32 | @params: perPage, page, customer, status, from, to, amount
33 | */
34 | listTransaction: {
35 | method: 'GET',
36 | path: '/transaction',
37 | send_json: false,
38 | params: { perPage: Number, page: Number, customer: Number, status: String, from: Date, to: Date, amount: Number },
39 | param_defaults: { perPage: 50, page: 1 },
40 | route_params: null
41 | },
42 |
43 | /*
44 | Fetch Transaction
45 | @params: id
46 | */
47 | getTransaction: {
48 | method: 'GET',
49 | path: '/transaction/{:id}',
50 | send_json: false,
51 | param_defaults: null,
52 | params: null,
53 | route_params: { id: String }
54 | },
55 |
56 | /*
57 | Charge Authorization
58 | @params: reference, authorization_code, amount, plan, currency, email, metadata, subaccount, transaction_charge, bearer, invoice_limit
59 | */
60 | chargeAuthorization: {
61 | method: 'POST',
62 | path: '/transaction/charge_authorization',
63 | send_json: true,
64 | params: { reference: String, authorization_code$: String, amount$: Number, plan: String, currency: String, email$: String, metadata: Object, subaccount: String, transaction_charge: Number, bearer: String, invoice_limit: Number },
65 | param_defaults: { amount: 0, currency: 'NGN', bearer: 'account', invoice_limit: 0 },
66 | route_params: null
67 | },
68 |
69 | /*
70 | View Transaction Timeline
71 | @params: id
72 | */
73 | viewTransactionTimeline: {
74 | method: 'GET',
75 | path: '/transaction/timeline/{:id}',
76 | send_json: false,
77 | param_defaults: null,
78 | params: null,
79 | route_params: { id: String }
80 | },
81 |
82 | /*
83 | Transaction Totals
84 | @params: from, to
85 | */
86 | transactionTotals: {
87 | method: 'GET',
88 | path: '/transaction/totals',
89 | send_json: false,
90 | params: { from: Date, to: Date },
91 | param_defaults: null,
92 | route_params: null
93 | },
94 |
95 | /*
96 | Export Transaction
97 | @params: from, to, settled, payment_page, customer, currency, settlement, amount, status
98 | */
99 | exportTransaction: {
100 | method: 'GET',
101 | path: '/transaction/export',
102 | send_json: false,
103 | params: { from: Date, to: Date, settled: Boolean, payment_page: Number, customer: Number, currency: String, settlement: Number, amount: Number, status: String },
104 | param_defaults: { status: 'success' },
105 | route_params: null
106 | },
107 |
108 | /*
109 | Request Reauthorization
110 | @params: reference, authorization_code, amount, currency, email, metadata
111 | */
112 | requestReauthorization: {
113 | method: 'POST',
114 | path: '/transaction/request_reauthorization',
115 | send_json: true,
116 | params: { reference: String, authorization_code$: String, amount$: Number, currency: String, email$: String, metadata: Object },
117 | param_defaults: { amount: 0, currency: 'NGN' },
118 | route_params: null
119 | },
120 |
121 | /*
122 | Check Authorization
123 | @params: authorization_code, currency, email, amount
124 | */
125 | checkAuthorization: {
126 | method: 'POST',
127 | path: '/transaction/check_authorization',
128 | send_json: true,
129 | params: { authorization_code$: String, amount$: Number, email$: String, currency: String },
130 | param_defaults: { currency: 'NGN' },
131 | route_params: null
132 | },
133 |
134 | /*
135 | Partial Debit
136 | @params: authorization_code, currency, email, amount, at_least, reference
137 | */
138 | partialDebit: {
139 | method: 'POST',
140 | path: '/transaction/partial_debit',
141 | send_json: true,
142 | params: { authorization_code$: String, amount$: Number, email$: String, currency$: String, at_least: Number, reference: String },
143 | param_defaults: { currency: 'NGN' },
144 | route_params: null
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/endpoints/transfers.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Initiate Transfer
6 | @param: source(required), reason, amount(required), recipient(required), currency, reference
7 | */
8 | initiateTransfer: {
9 | method: 'POST',
10 | path: '/transfer',
11 | params: { source$: String, reason: String, amount$: Number, recipient$: String, currency: String, reference: String },
12 | send_json: true,
13 | param_defaults: { source: 'balance', currency: 'NGN' },
14 | route_params: null
15 | },
16 |
17 | /*
18 | List Transfers
19 | @param: perPage, page
20 | */
21 | listTransfers: {
22 | method: 'GET',
23 | path: '/transfer',
24 | send_json: false,
25 | params: { perPage: Number, page: Number },
26 | param_defaults: { perPage: 0, page: 0 },
27 | route_params: null
28 | },
29 |
30 | /*
31 | Fetch transfer
32 | @param: id_or_code
33 | */
34 | fetchTransfer: {
35 | method: 'GET',
36 | path: '/transfer/{:id_or_code}',
37 | send_json: false,
38 | params: null,
39 | param_defaults: null,
40 | route_params: { id_or_code: String }
41 | },
42 |
43 | /*
44 | Finalize Transfer
45 | @param: transfer_code, otp
46 | */
47 | finalizeTransfer: {
48 | method: 'POST',
49 | path: '/transfer/finalize_transfer',
50 | send_json: true,
51 | params: { transfer_code$: String, otp$: String },
52 | param_defaults: null,
53 | route_params: null
54 | },
55 |
56 | /*
57 | Initiate Bulk Transfer
58 | @param: source, currency, transfers
59 | */
60 | initiateBulkTransfer: {
61 | method: 'POST',
62 | path: '/transfer/bulk',
63 | send_json: true,
64 | params: { source$: String, transfers$: String, currency: String },
65 | param_defaults: { source: 'balance', currency: 'NGN', transfers: [] },
66 | route_params: null
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/endpoints/transfers_recipients.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Create Transfer Recipient
6 | @params: type, name, account_number, bank_code, currency, description, authorization_code
7 | */
8 | createTransferRecipient: {
9 | method: 'POST',
10 | path: '/transferrecipient',
11 | send_json: true,
12 | params: { type$: String, name$: String, account_number$: String, bank_code$: String, currency: String, description: String, authorization_code: String },
13 | param_defaults: { type: 'nuban', currency: 'NGN' },
14 | route_params: null
15 | },
16 | /*
17 | List Transfer Recipients
18 | @params: perPage, page
19 | */
20 | listTransferRecipients: {
21 | method: 'GET',
22 | path: '/transferrecipient',
23 | send_json: false,
24 | params: { perPage: Number, page: Number },
25 | param_defaults: { perPage: 0, page: 0 },
26 | route_params: null
27 | },
28 |
29 | /*
30 | Update a transfer recipient
31 | @params: name, email, recipient_code_or_id
32 | */
33 | updateTransferRecipient: {
34 | method: 'PUT',
35 | path: '/transferrecipient/{:recipient_code_or_id}',
36 | send_json: true,
37 | params: { name: String, email: String },
38 | param_defaults: null,
39 | route_params: { recipient_code_or_id: String }
40 | },
41 | /*
42 | Delete Transfer Recipient
43 | @params: recipient_code_or_id
44 | */
45 | deleteTransferRecipient: {
46 | method: 'DELETE',
47 | path: '/transferrecipient/{:recipient_code_or_id}',
48 | send_json: false,
49 | params: null,
50 | param_defaults: null,
51 | route_params: { recipient_code_or_id: String }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/endpoints/verifications.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | /*
5 | Resolve Bank Verification Number (Standard)
6 | @param: bvn
7 | */
8 | resolveBVN: {
9 | method: 'GET',
10 | path: '/bank/resolve_bvn/{:bvn}',
11 | params: null,
12 | param_defaults: null,
13 | route_params: { bvn: String }
14 | },
15 |
16 | /*
17 | Resolve Bank Verification Number (Premium)
18 | @param: bvn
19 | */
20 | resolveBVNPremium: {
21 | method: 'GET',
22 | path: 'identity/bvn/resolve/{:bvn}',
23 | params: null,
24 | param_defaults: null,
25 | route_params: { bvn: String }
26 | },
27 |
28 | /*
29 | Match Bank Verification Number
30 | @param: bvn (required), account_number(required), bank_code (required), first_name, middle_name, last_name
31 | */
32 | matchBVN: {
33 | method: 'POST',
34 | path: '/bvn/match',
35 | send_json: true,
36 | params: { bvn$: String, bank_code$: String, account_number$: String, first_name: String, middle_name: String, last_name: String },
37 | param_defaults: null,
38 | route_params: null
39 | },
40 |
41 | /*
42 | Resolve Account Number
43 | @param: account_number(required), bank_code (required)
44 | */
45 | resolveAccountNumber: {
46 | method: 'GET',
47 | path: '/bank/resolve',
48 | params: { account_number$: String, bank_code$: String },
49 | param_defaults: null,
50 | route_params: null
51 | },
52 |
53 | /*
54 | Resolve Card Bin
55 | @param: bin
56 | */
57 | resolveCardBin: {
58 | method: 'GET',
59 | path: '/decision/bin/{:bin}',
60 | params: null,
61 | param_defaults: null,
62 | route_params: { bin: String }
63 | },
64 |
65 | /*
66 | Resolve Phone Number
67 | @param: verification_type, phone, callback_url
68 | */
69 | resolvePhoneNumber: {
70 | method: 'POST',
71 | path: '/verifications',
72 | send_json: true,
73 | params: { verification_type$: String, phone$: String, callback_url$: String },
74 | param_defaults: { verification_type: 'truecaller' },
75 | route_params: null
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/test/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/paystack-object.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var chai = require('chai')
4 | var expect = chai.expect
5 | var should = chai.should()
6 |
7 | describe('PayStack Instance Test(s)', function () {
8 | // Created Instance
9 | var PayStack = require('../index.js')
10 | var instance = new PayStack('sk_test_2hWyQ6HW73jS8p1IkXmSWOlE4y9Inhgyd6g5f2R7')
11 |
12 | it('should have a function [mergeNewOptions]', function () {
13 | // mock not enabled
14 | expect(Object.prototype.toString.call(instance._mock)).to.be.equal('[object Null]')
15 |
16 | PayStack.engageMock()
17 | // mock now enabled
18 | expect(Object.prototype.toString.call(instance._mock)).to.be.equal('[object Object]')
19 | /* eslint-disable no-unused-expressions */
20 | expect((typeof instance.mergeNewOptions === 'function')).to.be.true
21 | expect((typeof instance.createCustomer === 'function')).to.be.true
22 | expect((typeof instance.listDisputes === 'function')).to.be.true
23 | expect((typeof instance.addPageProduct === 'function')).to.be.true
24 | expect((typeof instance.listBanks === 'function')).to.be.true
25 | expect((typeof instance.listCountries === 'function')).to.be.true
26 | expect((typeof instance.getBalanceHistory === 'function')).to.be.true
27 |
28 | PayStack.disengageMock()
29 | expect(Object.prototype.toString.call(instance._mock)).to.be.equal('[object Null]')
30 | expect((typeof instance.createInvoice === 'function')).to.be.true
31 | expect((typeof instance.createPage === 'function')).to.be.true
32 | expect((typeof instance.chargeBank === 'function')).to.be.true
33 | expect((typeof instance.chargeCard === 'function')).to.be.true
34 | expect((typeof instance.createDedicatedNuban === 'function')).to.be.true
35 | expect((typeof instance.createProduct === 'function')).to.be.true
36 | expect((typeof PayStack.Fees === 'function')).to.be.true
37 | /* eslint-enable no-unused-expressions */
38 | })
39 |
40 | it('should throw an error if method is called without required arguments', function () {
41 | try {
42 | instance.createCustomer()
43 | } catch (err) {
44 | should.exist(err)
45 | }
46 | })
47 |
48 | it('should throw an error if method is called with any arguments other than an object', function () {
49 | try {
50 | instance.createInvoice([])
51 | } catch (err) {
52 | should.exist(err)
53 | }
54 | })
55 | })
56 |
--------------------------------------------------------------------------------