├── .gitignore ├── .gitattributes ├── index.js ├── .prettierrc ├── rollup.config.js ├── src ├── apis │ ├── inquiry.js │ ├── checkout.js │ └── transfers.js ├── lib │ └── utils.js └── OPay │ └── index.js ├── package.json ├── README.md ├── dist ├── index.esm.js └── index.cjs.js └── stats.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .vscode 4 | 5 | package-lock.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-detectable=false 2 | *.js linguist-detectable=true 3 | *.html linguist-detectable=false -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const OPay = require('./src/OPay'); 2 | OPay.prototype.version = '1.0.3'; 3 | 4 | module.exports = OPay; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import visualizer from 'rollup-plugin-visualizer'; 3 | import pkg from './package.json'; 4 | 5 | export default [ 6 | { 7 | input: './index.js', 8 | plugins: [commonjs(), visualizer()], 9 | output: [ 10 | { file: pkg.main, format: 'cjs' }, 11 | { file: pkg.module, format: 'es' }, 12 | ], 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/apis/inquiry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * Query Balance 6 | */ 7 | queryBalance: { 8 | method: 'post', 9 | path: `balances`, 10 | authorization: 'PUBLIC_KEY', 11 | body: null, 12 | }, 13 | 14 | /** 15 | * Validate OPay User 16 | */ 17 | validateUser: { 18 | method: 'post', 19 | path: `info/user`, 20 | authorization: 'PUBLIC_KEY', 21 | body: { phoneNumber$: String }, 22 | }, 23 | 24 | /** 25 | * Validate OPay Merchant 26 | */ 27 | validateMerchant: { 28 | method: 'post', 29 | path: `info/merchant`, 30 | authorization: 'PUBLIC_KEY', 31 | body: { email$: String }, 32 | }, 33 | 34 | /** 35 | * Validate Bank Account Number 36 | */ 37 | validateBankAccount: { 38 | method: 'post', 39 | path: `verification/accountNumber/resolve`, 40 | authorization: 'PUBLIC_KEY', 41 | body: { bankAccountNo$: String, bankCode$: String, countryCode: String }, 42 | default_body: { countryCode: 'NG' }, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /src/apis/checkout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * Initialize Transaction 6 | */ 7 | initializeTransaction: { 8 | method: 'post', 9 | path: `cashier/initialize`, 10 | authorization: 'PUBLIC_KEY', 11 | body: { 12 | amount$: String, 13 | callbackUrl$: String, 14 | currency: String, 15 | expireAt$: String, 16 | mchShortName$: String, 17 | payMethods: Array, 18 | payTypes: Array, 19 | productDesc$: String, 20 | productName$: String, 21 | reference$: String, 22 | returnUrl$: String, 23 | userPhone$: String, 24 | userRequestIp$: String, 25 | }, 26 | default_body: { 27 | currency: 'NGN', 28 | payMethods: ['account', 'qrcode', 'bankCard', 'bankAccount'], 29 | payTypes: ['BalancePayment', 'BonusPayment', 'OWealth'], 30 | }, 31 | }, 32 | 33 | /** 34 | * Cashier Status 35 | */ 36 | cashierStatus: { 37 | method: 'post', 38 | path: `cashier/status`, 39 | authorization: 'PUBLIC_KEY', 40 | body: { orderNo$: String, reference$: String }, 41 | }, 42 | 43 | /** 44 | * Close Transaction 45 | */ 46 | closeStatus: { 47 | method: 'post', 48 | path: `cashier/close`, 49 | authorization: 'PUBLIC_KEY', 50 | body: { orderNo$: String, reference$: String }, 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opay-node", 3 | "version": "1.0.3", 4 | "main": "dist/index.cjs.js", 5 | "module": "dist/index.esm.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/thisisyinka/opay-nodejs-sdk" 9 | }, 10 | "author": "Layinka Jaji ", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "rollup -c", 14 | "dev": "rollup -c -w" 15 | }, 16 | "files": [ 17 | "dist/*" 18 | ], 19 | "keywords": [ 20 | "opay", 21 | "node", 22 | "nodejs", 23 | "sdk" 24 | ], 25 | "dependencies": { 26 | "alphabetize-object-keys": "^3.0.0", 27 | "got": "^11.8.0", 28 | "lodash": "^4.17.20", 29 | "lodash.foreach": "^4.5.0", 30 | "lodash.isempty": "^4.4.0", 31 | "lodash.isobject": "^3.0.2", 32 | "lodash.keys": "^4.2.0", 33 | "lodash.some": "^4.6.0", 34 | "lodash.sortby": "^4.7.0" 35 | }, 36 | "devDependencies": { 37 | "@rollup/plugin-commonjs": "^16.0.0", 38 | "nodemon": "^2.0.6", 39 | "rollup": "^2.33.3", 40 | "rollup-plugin-visualizer": "^4.2.0" 41 | }, 42 | "description": "This NodeJS SDK makes it easy to integrate your applications & interact with the [OPay](https://opayweb.com) APIs.", 43 | "bugs": { 44 | "url": "https://github.com/thisisyinka/opay-nodejs-sdk/issues" 45 | }, 46 | "homepage": "https://github.com/thisisyinka/opay-nodejs-sdk#readme" 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OPay NodeJS SDK 2 | 3 | This NodeJS SDK makes it easy to integrate your applications & interact with the [OPay](https://opayweb.com) APIs. 4 | 5 | ## Introduction 6 | 7 | To view OPay's API documentation, please [click here](https://documentation.opayweb.com). 8 | 9 | Please note that you will need your public key, private/secret key and your merchantId. Please visit OPay's API documentation for more information on how to get this. 10 | 11 | ## OPay Services exposed 12 | 13 | #### Checkout (Collect payment): 14 | 15 | - Initializing a Transaction 16 | - Checking status of a Transaction 17 | - Closing a transaction 18 | 19 | #### Transfers (Send money): 20 | 21 | - Transfer to User Wallet 22 | - Transfer to Merchant Wallet 23 | - Transfer to Bank 24 | - Wallet Transfer Status 25 | - Get list of Banks 26 | - Get list of Countries 27 | - Bank Transfer Status 28 | 29 | #### Inquiry (Check balance, Validate User/Merchant & Bank Account Number) 30 | 31 | - Validate OPay User 32 | - Validate OPay Merchant 33 | - Query Balance 34 | - Validate Bank Account Number 35 | 36 | ## Getting Started 37 | 38 | ### Installing SDK 39 | 40 | `npm install opay` 41 | 42 | ### Setup & Usage 43 | 44 | ```javascript 45 | const OPay = require('opay-node'); 46 | 47 | const public_key = "OPAYPUB16057006237420.047574601637614955"; 48 | const secret_key = ""; 49 | const environment = process.env.NODE_ENV; 50 | const merchantId = "256620111818015"; 51 | 52 | const opay = new OPay(public_key,"", merchantId, environment); 53 | 54 | //Initialize transaction 55 | const initializeTransaction = () => { 56 | try { 57 | const { body } = await opay.initializeTransaction({ 58 | reference: "test_20191123132233", 59 | mchShortName: "Jerry's shop", 60 | productName: "Apple AirPods Pro", 61 | productDesc: "The best wireless earphone in history", 62 | userPhone: "+2349876543210", 63 | userRequestIp: "123.123.123.123", 64 | amount: "100", 65 | callbackUrl: "https://you.domain.com/callbackUrl", 66 | returnUrl: "https://you.domain.com/returnUrl", 67 | expireAt: "10" 68 | }); 69 | // do something with body 70 | }catch(e){ 71 | // do something with error 72 | } 73 | } 74 | 75 | // List of banks 76 | const bankList = () => { 77 | try{ 78 | const { body } = await opay.getBanks(); 79 | // do something with body 80 | } catch(e) { 81 | // do something with error 82 | } 83 | } 84 | ``` 85 | 86 | # Contributions 87 | 88 | This project is open-sourced, thus we encourage other developers to contribute and help improve it. To get started please: 89 | 90 | 1. Fork this repo 91 | 2. Create your feature branch (`git checkout -b your-feature-branch`) 92 | 3. Off you go! 93 | -------------------------------------------------------------------------------- /src/apis/transfers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * Transfer to User Wallet 6 | */ 7 | transferToUserWallet: { 8 | method: 'post', 9 | path: `transfer/toWallet`, 10 | authorization: 'SIGNATURE', 11 | body: { 12 | amount$: String, 13 | country: String, 14 | currency: String, 15 | reason$: String, 16 | receiver$: Object, 17 | 'receiver.name$': String, 18 | 'receiver.phoneNumber$': String, 19 | 'receiver.type$': String, 20 | reference$: String, 21 | }, 22 | default_body: { country: 'NG', currency: 'NGN' }, 23 | }, 24 | 25 | /** 26 | * Transfer to Merchant Wallet 27 | */ 28 | transferToMerchantWallet: { 29 | method: 'post', 30 | path: `transfer/toWallet`, 31 | authorization: 'SIGNATURE', 32 | body: { 33 | amount$: String, 34 | country: String, 35 | currency: String, 36 | reason$: String, 37 | receiver$: Object, 38 | 'receiver.merchantId$': String, 39 | 'receiver.name$': String, 40 | 'receiver.type$': String, 41 | reference$: String, 42 | }, 43 | default_body: { country: 'NG', currency: 'NGN' }, 44 | }, 45 | 46 | /** 47 | * Wallet Trasfer Status 48 | */ 49 | walletTransferStatus: { 50 | method: 'post', 51 | path: `transfer/status/toWallet`, 52 | authorization: 'SIGNATURE', 53 | body: { orderNo$: String, reference$: String }, 54 | }, 55 | 56 | /** 57 | * Get list of banks 58 | */ 59 | getBanks: { 60 | method: 'post', 61 | path: `banks`, 62 | authorization: 'PUBLIC_KEY', 63 | body: { countryCode: String }, 64 | default_body: { countryCode: 'NG' }, 65 | }, 66 | 67 | /** 68 | * Get list of countries 69 | */ 70 | 71 | getCountries: { 72 | method: 'post', 73 | path: `countries`, 74 | authorization: 'PUBLIC_KEY', 75 | body: null, 76 | }, 77 | 78 | /** 79 | * Transfer to Bank 80 | */ 81 | transferToBank: { 82 | method: 'post', 83 | path: `transfer/toBank`, 84 | authorization: 'SIGNATURE', 85 | body: { 86 | amount$: String, 87 | country$: String, 88 | currency$: String, 89 | reason$: String, 90 | receiver$: Object, 91 | 'receiver.bankAccountNumber$': String, 92 | 'receiver.bankCode$': String, 93 | 'receiver.name$': String, 94 | reference$: String, 95 | }, 96 | default_body: { country: 'NG', currency: 'NGN' }, 97 | }, 98 | 99 | /** 100 | * Bank Transfer status 101 | */ 102 | bankTransferStatus: { 103 | method: 'post', 104 | path: `transfer/status/toBank`, 105 | authorization: 'SIGNATURE', 106 | body: { orderNo$: String, reference$: String }, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | const sortBy = require('lodash.sortby'); 2 | const getKeys = require('lodash.keys'); 3 | const forEach = require('lodash.foreach'); 4 | const crypto = require('crypto'); 5 | 6 | const sortObjectAlphabetically = (map) => { 7 | const keys = sortBy(getKeys(map), (a) => { 8 | return a; 9 | }); 10 | const newmap = {}; 11 | forEach(keys, (k) => { 12 | newmap[k] = map[k]; 13 | }); 14 | return newmap; 15 | }; 16 | 17 | const pluckDeep = (obj, key) => key.split('.').reduce((accum, item) => accum[item], obj); 18 | 19 | const setDeep = (obj, key, value) => { 20 | var i; 21 | key = key.split('.'); 22 | for (i = 0; i < key.length - 1; i++) obj = obj[key[i]]; 23 | obj[key[i]] = value; 24 | }; 25 | 26 | const generatePrivateKey = (key, data) => { 27 | return crypto.createHmac('sha512', key).update(JSON.stringify(data)).digest('hex'); 28 | }; 29 | 30 | const isLiteralFalsey = (variable) => { 31 | return variable === '' || variable === false || variable === 0; 32 | }; 33 | 34 | const checkTypeName = (target, type) => { 35 | let typeName = ''; 36 | if (isLiteralFalsey(target)) { 37 | typeName = typeof target; 38 | } else { 39 | typeName = '' + (target && target.constructor.name); 40 | } 41 | return !!(typeName.toLowerCase().indexOf(type) + 1); 42 | }; 43 | 44 | const isTypeOf = (value, type) => { 45 | let result = false; 46 | 47 | type = type || []; 48 | 49 | if (typeof type === 'object') { 50 | if (typeof type.length !== 'number') { 51 | return result; 52 | } 53 | 54 | let bitPiece = 0; 55 | type = [].slice.call(type); 56 | 57 | type.forEach((_type) => { 58 | if (typeof _type === 'function') { 59 | _type = (_type.name || _type.displayName).toLowerCase(); 60 | } 61 | bitPiece |= 1 * checkTypeName(value, _type); 62 | }); 63 | 64 | result = !!bitPiece; 65 | } else { 66 | if (typeof type === 'function') { 67 | type = (type.name || type.displayName).toLowerCase(); 68 | } 69 | 70 | result = checkTypeName(value, type); 71 | } 72 | 73 | return result; 74 | }; 75 | 76 | const isNullOrUndefined = (value) => { 77 | return isTypeOf(value, ['undefined', 'null']); 78 | }; 79 | 80 | const getClientBody = (config, inputs) => { 81 | let body = {}; 82 | let inputValues = {}; 83 | 84 | for (var input in config.body) { 85 | if (config.body.hasOwnProperty(input)) { 86 | let key = input.replace('$', ''); 87 | let value = pluckDeep(inputs, key); 88 | let type = config.body[input]; 89 | let required = false; 90 | 91 | if (input.indexOf('$') + 1 === input.length) { 92 | required = true; 93 | } 94 | 95 | if ((isNullOrUndefined(value) || value === '') && required) { 96 | throw new Error(`Param: ${key} is required but not provided; please provide as needed`); 97 | } else { 98 | setDeep(body, key, isTypeOf(value, type) ? value : null); 99 | if (body[key] === null) { 100 | throw new Error(`Key: "${key}" is not of type ${type.name || type}; please provided as needed`); 101 | } 102 | } 103 | } 104 | } 105 | 106 | inputValues = JSON.stringify(body); 107 | return inputValues; 108 | }; 109 | 110 | module.exports = { 111 | generatePrivateKey, 112 | getClientBody, 113 | sortObjectAlphabetically, 114 | }; 115 | -------------------------------------------------------------------------------- /src/OPay/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const got = require('got'); 3 | const _isEmpty = require('lodash.isempty'); 4 | const isObject = require('lodash.isobject'); 5 | const some = require('lodash.some'); 6 | const alphabetizeObjectKeys = require('alphabetize-object-keys'); 7 | 8 | const checkout = require('../apis/checkout'); 9 | const inquiry = require('../apis/inquiry'); 10 | const transfers = require('../apis/transfers'); 11 | 12 | const { generatePrivateKey, getClientBody } = require('../lib/utils'); 13 | 14 | const endpoints = Object.assign({}, checkout, inquiry, transfers); 15 | 16 | const isEmpty = (value, defined) => { 17 | if (defined && isObject(value)) { 18 | return !some(value, function (value, key) { 19 | return value !== undefined; 20 | }); 21 | } 22 | return _isEmpty(value); 23 | }; 24 | 25 | const createApiFunction = (config) => { 26 | let customConfig = { headers: {} }; 27 | return function (params = {}) { 28 | let payload = false; 29 | let inputs = {}; 30 | 31 | if (config.body !== null) { 32 | if (config.default_body) { 33 | inputs = Object.assign({}, config.default_body, params); 34 | } else { 35 | inputs = Object.assign({}, params); 36 | } 37 | 38 | if (!(params instanceof Object)) { 39 | throw new TypeError('Argument: [ params(s) ] Should Be An Object Literal'); 40 | } 41 | 42 | if (!isEmpty(inputs, true)) { 43 | const body = getClientBody(config, alphabetizeObjectKeys(inputs)); 44 | payload = alphabetizeObjectKeys(body); 45 | } else { 46 | throw new TypeError('Argument: [ params(s) ] Not Meant To Be Empty!'); 47 | } 48 | } 49 | 50 | if (payload === false) { 51 | payload = '{}'; 52 | } 53 | 54 | if (config.authorization === 'SIGNATURE') { 55 | customConfig.headers.Authorization = `Bearer ${generatePrivateKey(this.privateKey, JSON.parse(payload))}`; 56 | } 57 | 58 | customConfig.body = payload; 59 | return this.httpClient[config.method](config.path, customConfig); 60 | }; 61 | }; 62 | 63 | function OPayError(message) { 64 | this.name = 'OPayError'; 65 | this.message = message || ''; 66 | } 67 | 68 | OPayError.prototype = Error.prototype; 69 | 70 | class OPay { 71 | constructor(publicKey, privateKey, merchantID, appEnv = 'development') { 72 | const environment = /^(?:development|local|dev)$/; 73 | this.privateKey = privateKey; 74 | 75 | this.base_url = { 76 | sandbox: 'http://sandbox-cashierapi.opayweb.com/api/v3/', 77 | production: 'https://cashierapi.opayweb.com/api/v3/', 78 | }; 79 | 80 | this.httpClientOptions = { 81 | prefixUrl: environment.test(appEnv) ? this.base_url.sandbox : this.base_url.production, 82 | headers: { 83 | 'Cache-Control': 'no-cache', 84 | 'Content-Type': 'application/json', 85 | Accept: 'application/json', 86 | Authorization: `Bearer ${publicKey}`, 87 | MerchantID: merchantID, 88 | }, 89 | mutableDefaults: true, 90 | hooks: { 91 | onError: [ 92 | (error) => { 93 | const { response } = error; 94 | if (response && response.body) { 95 | error.name = 'OPayError'; 96 | error.message = `${response.body.code}: ${response.body.message} (${error.statusCode})`; 97 | } 98 | return error; 99 | }, 100 | ], 101 | afterResponse: [ 102 | (response) => { 103 | if (response.body.code !== '00000') { 104 | let message = `${response.body.code}: ${response.body.message}`; 105 | throw new OPayError(message); 106 | } 107 | return response; 108 | }, 109 | ], 110 | }, 111 | responseType: 'json', 112 | }; 113 | 114 | this.httpClient = got.extend(this.httpClientOptions); 115 | } 116 | 117 | mergeClientOptions(options) { 118 | this.httpClient = this.httpClient.extend(options); 119 | } 120 | } 121 | 122 | for (let endpoint in endpoints) { 123 | if (endpoints.hasOwnProperty(endpoint)) { 124 | OPay.prototype[endpoint] = createApiFunction(endpoints[endpoint]); 125 | } 126 | } 127 | 128 | module.exports = OPay; 129 | -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | import got from 'got'; 2 | import _isEmpty from 'lodash.isempty'; 3 | import isObject from 'lodash.isobject'; 4 | import some from 'lodash.some'; 5 | import alphabetizeObjectKeys from 'alphabetize-object-keys'; 6 | import sortBy from 'lodash.sortby'; 7 | import getKeys from 'lodash.keys'; 8 | import forEach from 'lodash.foreach'; 9 | import crypto from 'crypto'; 10 | 11 | var checkout = { 12 | /** 13 | * Initialize Transaction 14 | */ 15 | initializeTransaction: { 16 | method: 'post', 17 | path: `cashier/initialize`, 18 | authorization: 'PUBLIC_KEY', 19 | body: { 20 | amount$: String, 21 | callbackUrl$: String, 22 | currency: String, 23 | expireAt$: String, 24 | mchShortName$: String, 25 | payMethods: Array, 26 | payTypes: Array, 27 | productDesc$: String, 28 | productName$: String, 29 | reference$: String, 30 | returnUrl$: String, 31 | userPhone$: String, 32 | userRequestIp$: String, 33 | }, 34 | default_body: { 35 | currency: 'NGN', 36 | payMethods: ['account', 'qrcode', 'bankCard', 'bankAccount'], 37 | payTypes: ['BalancePayment', 'BonusPayment', 'OWealth'], 38 | }, 39 | }, 40 | 41 | /** 42 | * Cashier Status 43 | */ 44 | cashierStatus: { 45 | method: 'post', 46 | path: `cashier/status`, 47 | authorization: 'PUBLIC_KEY', 48 | body: { orderNo$: String, reference$: String }, 49 | }, 50 | 51 | /** 52 | * Close Transaction 53 | */ 54 | closeStatus: { 55 | method: 'post', 56 | path: `cashier/close`, 57 | authorization: 'PUBLIC_KEY', 58 | body: { orderNo$: String, reference$: String }, 59 | }, 60 | }; 61 | 62 | var inquiry = { 63 | /** 64 | * Query Balance 65 | */ 66 | queryBalance: { 67 | method: 'post', 68 | path: `balances`, 69 | authorization: 'PUBLIC_KEY', 70 | body: null, 71 | }, 72 | 73 | /** 74 | * Validate OPay User 75 | */ 76 | validateUser: { 77 | method: 'post', 78 | path: `info/user`, 79 | authorization: 'PUBLIC_KEY', 80 | body: { phoneNumber$: String }, 81 | }, 82 | 83 | /** 84 | * Validate OPay Merchant 85 | */ 86 | validateMerchant: { 87 | method: 'post', 88 | path: `info/merchant`, 89 | authorization: 'PUBLIC_KEY', 90 | body: { email$: String }, 91 | }, 92 | 93 | /** 94 | * Validate Bank Account Number 95 | */ 96 | validateBankAccount: { 97 | method: 'post', 98 | path: `verification/accountNumber/resolve`, 99 | authorization: 'PUBLIC_KEY', 100 | body: { bankAccountNo$: String, bankCode$: String, countryCode: String }, 101 | default_body: { countryCode: 'NG' }, 102 | }, 103 | }; 104 | 105 | var transfers = { 106 | /** 107 | * Transfer to User Wallet 108 | */ 109 | transferToUserWallet: { 110 | method: 'post', 111 | path: `transfer/toWallet`, 112 | authorization: 'SIGNATURE', 113 | body: { 114 | amount$: String, 115 | country: String, 116 | currency: String, 117 | reason$: String, 118 | receiver$: Object, 119 | 'receiver.name$': String, 120 | 'receiver.phoneNumber$': String, 121 | 'receiver.type$': String, 122 | reference$: String, 123 | }, 124 | default_body: { country: 'NG', currency: 'NGN' }, 125 | }, 126 | 127 | /** 128 | * Transfer to Merchant Wallet 129 | */ 130 | transferToMerchantWallet: { 131 | method: 'post', 132 | path: `transfer/toWallet`, 133 | authorization: 'SIGNATURE', 134 | body: { 135 | amount$: String, 136 | country: String, 137 | currency: String, 138 | reason$: String, 139 | receiver$: Object, 140 | 'receiver.merchantId$': String, 141 | 'receiver.name$': String, 142 | 'receiver.type$': String, 143 | reference$: String, 144 | }, 145 | default_body: { country: 'NG', currency: 'NGN' }, 146 | }, 147 | 148 | /** 149 | * Wallet Trasfer Status 150 | */ 151 | walletTransferStatus: { 152 | method: 'post', 153 | path: `transfer/status/toWallet`, 154 | authorization: 'SIGNATURE', 155 | body: { orderNo$: String, reference$: String }, 156 | }, 157 | 158 | /** 159 | * Get list of banks 160 | */ 161 | getBanks: { 162 | method: 'post', 163 | path: `banks`, 164 | authorization: 'PUBLIC_KEY', 165 | body: { countryCode: String }, 166 | default_body: { countryCode: 'NG' }, 167 | }, 168 | 169 | /** 170 | * Get list of countries 171 | */ 172 | 173 | getCountries: { 174 | method: 'post', 175 | path: `countries`, 176 | authorization: 'PUBLIC_KEY', 177 | body: null, 178 | }, 179 | 180 | /** 181 | * Transfer to Bank 182 | */ 183 | transferToBank: { 184 | method: 'post', 185 | path: `transfer/toBank`, 186 | authorization: 'SIGNATURE', 187 | body: { 188 | amount$: String, 189 | country$: String, 190 | currency$: String, 191 | reason$: String, 192 | receiver$: Object, 193 | 'receiver.bankAccountNumber$': String, 194 | 'receiver.bankCode$': String, 195 | 'receiver.name$': String, 196 | reference$: String, 197 | }, 198 | default_body: { country: 'NG', currency: 'NGN' }, 199 | }, 200 | 201 | /** 202 | * Bank Transfer status 203 | */ 204 | bankTransferStatus: { 205 | method: 'post', 206 | path: `transfer/status/toBank`, 207 | authorization: 'SIGNATURE', 208 | body: { orderNo$: String, reference$: String }, 209 | }, 210 | }; 211 | 212 | const sortObjectAlphabetically = (map) => { 213 | const keys = sortBy(getKeys(map), (a) => { 214 | return a; 215 | }); 216 | const newmap = {}; 217 | forEach(keys, (k) => { 218 | newmap[k] = map[k]; 219 | }); 220 | return newmap; 221 | }; 222 | 223 | const pluckDeep = (obj, key) => key.split('.').reduce((accum, item) => accum[item], obj); 224 | 225 | const setDeep = (obj, key, value) => { 226 | var i; 227 | key = key.split('.'); 228 | for (i = 0; i < key.length - 1; i++) obj = obj[key[i]]; 229 | obj[key[i]] = value; 230 | }; 231 | 232 | const generatePrivateKey = (key, data) => { 233 | return crypto.createHmac('sha512', key).update(JSON.stringify(data)).digest('hex'); 234 | }; 235 | 236 | const isLiteralFalsey = (variable) => { 237 | return variable === '' || variable === false || variable === 0; 238 | }; 239 | 240 | const checkTypeName = (target, type) => { 241 | let typeName = ''; 242 | if (isLiteralFalsey(target)) { 243 | typeName = typeof target; 244 | } else { 245 | typeName = '' + (target && target.constructor.name); 246 | } 247 | return !!(typeName.toLowerCase().indexOf(type) + 1); 248 | }; 249 | 250 | const isTypeOf = (value, type) => { 251 | let result = false; 252 | 253 | type = type || []; 254 | 255 | if (typeof type === 'object') { 256 | if (typeof type.length !== 'number') { 257 | return result; 258 | } 259 | 260 | let bitPiece = 0; 261 | type = [].slice.call(type); 262 | 263 | type.forEach((_type) => { 264 | if (typeof _type === 'function') { 265 | _type = (_type.name || _type.displayName).toLowerCase(); 266 | } 267 | bitPiece |= 1 * checkTypeName(value, _type); 268 | }); 269 | 270 | result = !!bitPiece; 271 | } else { 272 | if (typeof type === 'function') { 273 | type = (type.name || type.displayName).toLowerCase(); 274 | } 275 | 276 | result = checkTypeName(value, type); 277 | } 278 | 279 | return result; 280 | }; 281 | 282 | const isNullOrUndefined = (value) => { 283 | return isTypeOf(value, ['undefined', 'null']); 284 | }; 285 | 286 | const getClientBody = (config, inputs) => { 287 | let body = {}; 288 | let inputValues = {}; 289 | 290 | for (var input in config.body) { 291 | if (config.body.hasOwnProperty(input)) { 292 | let key = input.replace('$', ''); 293 | let value = pluckDeep(inputs, key); 294 | let type = config.body[input]; 295 | let required = false; 296 | 297 | if (input.indexOf('$') + 1 === input.length) { 298 | required = true; 299 | } 300 | 301 | if ((isNullOrUndefined(value) || value === '') && required) { 302 | throw new Error(`Param: ${key} is required but not provided; please provide as needed`); 303 | } else { 304 | setDeep(body, key, isTypeOf(value, type) ? value : null); 305 | if (body[key] === null) { 306 | throw new Error(`Key: "${key}" is not of type ${type.name || type}; please provided as needed`); 307 | } 308 | } 309 | } 310 | } 311 | 312 | inputValues = JSON.stringify(body); 313 | return inputValues; 314 | }; 315 | 316 | var utils = { 317 | generatePrivateKey, 318 | getClientBody, 319 | sortObjectAlphabetically, 320 | }; 321 | 322 | const { generatePrivateKey: generatePrivateKey$1, getClientBody: getClientBody$1 } = utils; 323 | 324 | const endpoints = Object.assign({}, checkout, inquiry, transfers); 325 | 326 | const isEmpty = (value, defined) => { 327 | if (defined && isObject(value)) { 328 | return !some(value, function (value, key) { 329 | return value !== undefined; 330 | }); 331 | } 332 | return _isEmpty(value); 333 | }; 334 | 335 | const createApiFunction = (config) => { 336 | let customConfig = { headers: {} }; 337 | return function (params = {}) { 338 | let payload = false; 339 | let inputs = {}; 340 | 341 | if (config.body !== null) { 342 | if (config.default_body) { 343 | inputs = Object.assign({}, config.default_body, params); 344 | } else { 345 | inputs = Object.assign({}, params); 346 | } 347 | 348 | if (!(params instanceof Object)) { 349 | throw new TypeError('Argument: [ params(s) ] Should Be An Object Literal'); 350 | } 351 | 352 | if (!isEmpty(inputs, true)) { 353 | const body = getClientBody$1(config, alphabetizeObjectKeys(inputs)); 354 | payload = alphabetizeObjectKeys(body); 355 | } else { 356 | throw new TypeError('Argument: [ params(s) ] Not Meant To Be Empty!'); 357 | } 358 | } 359 | 360 | if (payload === false) { 361 | payload = '{}'; 362 | } 363 | 364 | if (config.authorization === 'SIGNATURE') { 365 | customConfig.headers.Authorization = `Bearer ${generatePrivateKey$1(this.privateKey, JSON.parse(payload))}`; 366 | } 367 | 368 | customConfig.body = payload; 369 | return this.httpClient[config.method](config.path, customConfig); 370 | }; 371 | }; 372 | 373 | function OPayError(message) { 374 | this.name = 'OPayError'; 375 | this.message = message || ''; 376 | } 377 | 378 | OPayError.prototype = Error.prototype; 379 | 380 | class OPay { 381 | constructor(publicKey, privateKey, merchantID, appEnv = 'development') { 382 | const environment = /^(?:development|local|dev)$/; 383 | this.privateKey = privateKey; 384 | 385 | this.base_url = { 386 | sandbox: 'http://sandbox-cashierapi.opayweb.com/api/v3/', 387 | production: 'https://cashierapi.opayweb.com/api/v3/', 388 | }; 389 | 390 | this.httpClientOptions = { 391 | prefixUrl: environment.test(appEnv) ? this.base_url.sandbox : this.base_url.production, 392 | headers: { 393 | 'Cache-Control': 'no-cache', 394 | 'Content-Type': 'application/json', 395 | Accept: 'application/json', 396 | Authorization: `Bearer ${publicKey}`, 397 | MerchantID: merchantID, 398 | }, 399 | mutableDefaults: true, 400 | hooks: { 401 | onError: [ 402 | (error) => { 403 | const { response } = error; 404 | if (response && response.body) { 405 | error.name = 'OPayError'; 406 | error.message = `${response.body.code}: ${response.body.message} (${error.statusCode})`; 407 | } 408 | return error; 409 | }, 410 | ], 411 | afterResponse: [ 412 | (response) => { 413 | if (response.body.code !== '00000') { 414 | let message = `${response.body.code}: ${response.body.message}`; 415 | throw new OPayError(message); 416 | } 417 | return response; 418 | }, 419 | ], 420 | }, 421 | responseType: 'json', 422 | }; 423 | 424 | this.httpClient = got.extend(this.httpClientOptions); 425 | } 426 | 427 | mergeClientOptions(options) { 428 | this.httpClient = this.httpClient.extend(options); 429 | } 430 | } 431 | 432 | for (let endpoint in endpoints) { 433 | if (endpoints.hasOwnProperty(endpoint)) { 434 | OPay.prototype[endpoint] = createApiFunction(endpoints[endpoint]); 435 | } 436 | } 437 | 438 | var OPay_1 = OPay; 439 | 440 | OPay_1.prototype.version = '1.0.2'; 441 | 442 | var opayNodejsSdk = OPay_1; 443 | 444 | export default opayNodejsSdk; 445 | -------------------------------------------------------------------------------- /dist/index.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var got = require('got'); 4 | var _isEmpty = require('lodash.isempty'); 5 | var isObject = require('lodash.isobject'); 6 | var some = require('lodash.some'); 7 | var alphabetizeObjectKeys = require('alphabetize-object-keys'); 8 | var sortBy = require('lodash.sortby'); 9 | var getKeys = require('lodash.keys'); 10 | var forEach = require('lodash.foreach'); 11 | var crypto = require('crypto'); 12 | 13 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 14 | 15 | var got__default = /*#__PURE__*/_interopDefaultLegacy(got); 16 | var _isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(_isEmpty); 17 | var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject); 18 | var some__default = /*#__PURE__*/_interopDefaultLegacy(some); 19 | var alphabetizeObjectKeys__default = /*#__PURE__*/_interopDefaultLegacy(alphabetizeObjectKeys); 20 | var sortBy__default = /*#__PURE__*/_interopDefaultLegacy(sortBy); 21 | var getKeys__default = /*#__PURE__*/_interopDefaultLegacy(getKeys); 22 | var forEach__default = /*#__PURE__*/_interopDefaultLegacy(forEach); 23 | var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto); 24 | 25 | var checkout = { 26 | /** 27 | * Initialize Transaction 28 | */ 29 | initializeTransaction: { 30 | method: 'post', 31 | path: `cashier/initialize`, 32 | authorization: 'PUBLIC_KEY', 33 | body: { 34 | amount$: String, 35 | callbackUrl$: String, 36 | currency: String, 37 | expireAt$: String, 38 | mchShortName$: String, 39 | payMethods: Array, 40 | payTypes: Array, 41 | productDesc$: String, 42 | productName$: String, 43 | reference$: String, 44 | returnUrl$: String, 45 | userPhone$: String, 46 | userRequestIp$: String, 47 | }, 48 | default_body: { 49 | currency: 'NGN', 50 | payMethods: ['account', 'qrcode', 'bankCard', 'bankAccount'], 51 | payTypes: ['BalancePayment', 'BonusPayment', 'OWealth'], 52 | }, 53 | }, 54 | 55 | /** 56 | * Cashier Status 57 | */ 58 | cashierStatus: { 59 | method: 'post', 60 | path: `cashier/status`, 61 | authorization: 'PUBLIC_KEY', 62 | body: { orderNo$: String, reference$: String }, 63 | }, 64 | 65 | /** 66 | * Close Transaction 67 | */ 68 | closeStatus: { 69 | method: 'post', 70 | path: `cashier/close`, 71 | authorization: 'PUBLIC_KEY', 72 | body: { orderNo$: String, reference$: String }, 73 | }, 74 | }; 75 | 76 | var inquiry = { 77 | /** 78 | * Query Balance 79 | */ 80 | queryBalance: { 81 | method: 'post', 82 | path: `balances`, 83 | authorization: 'PUBLIC_KEY', 84 | body: null, 85 | }, 86 | 87 | /** 88 | * Validate OPay User 89 | */ 90 | validateUser: { 91 | method: 'post', 92 | path: `info/user`, 93 | authorization: 'PUBLIC_KEY', 94 | body: { phoneNumber$: String }, 95 | }, 96 | 97 | /** 98 | * Validate OPay Merchant 99 | */ 100 | validateMerchant: { 101 | method: 'post', 102 | path: `info/merchant`, 103 | authorization: 'PUBLIC_KEY', 104 | body: { email$: String }, 105 | }, 106 | 107 | /** 108 | * Validate Bank Account Number 109 | */ 110 | validateBankAccount: { 111 | method: 'post', 112 | path: `verification/accountNumber/resolve`, 113 | authorization: 'PUBLIC_KEY', 114 | body: { bankAccountNo$: String, bankCode$: String, countryCode: String }, 115 | default_body: { countryCode: 'NG' }, 116 | }, 117 | }; 118 | 119 | var transfers = { 120 | /** 121 | * Transfer to User Wallet 122 | */ 123 | transferToUserWallet: { 124 | method: 'post', 125 | path: `transfer/toWallet`, 126 | authorization: 'SIGNATURE', 127 | body: { 128 | amount$: String, 129 | country: String, 130 | currency: String, 131 | reason$: String, 132 | receiver$: Object, 133 | 'receiver.name$': String, 134 | 'receiver.phoneNumber$': String, 135 | 'receiver.type$': String, 136 | reference$: String, 137 | }, 138 | default_body: { country: 'NG', currency: 'NGN' }, 139 | }, 140 | 141 | /** 142 | * Transfer to Merchant Wallet 143 | */ 144 | transferToMerchantWallet: { 145 | method: 'post', 146 | path: `transfer/toWallet`, 147 | authorization: 'SIGNATURE', 148 | body: { 149 | amount$: String, 150 | country: String, 151 | currency: String, 152 | reason$: String, 153 | receiver$: Object, 154 | 'receiver.merchantId$': String, 155 | 'receiver.name$': String, 156 | 'receiver.type$': String, 157 | reference$: String, 158 | }, 159 | default_body: { country: 'NG', currency: 'NGN' }, 160 | }, 161 | 162 | /** 163 | * Wallet Trasfer Status 164 | */ 165 | walletTransferStatus: { 166 | method: 'post', 167 | path: `transfer/status/toWallet`, 168 | authorization: 'SIGNATURE', 169 | body: { orderNo$: String, reference$: String }, 170 | }, 171 | 172 | /** 173 | * Get list of banks 174 | */ 175 | getBanks: { 176 | method: 'post', 177 | path: `banks`, 178 | authorization: 'PUBLIC_KEY', 179 | body: { countryCode: String }, 180 | default_body: { countryCode: 'NG' }, 181 | }, 182 | 183 | /** 184 | * Get list of countries 185 | */ 186 | 187 | getCountries: { 188 | method: 'post', 189 | path: `countries`, 190 | authorization: 'PUBLIC_KEY', 191 | body: null, 192 | }, 193 | 194 | /** 195 | * Transfer to Bank 196 | */ 197 | transferToBank: { 198 | method: 'post', 199 | path: `transfer/toBank`, 200 | authorization: 'SIGNATURE', 201 | body: { 202 | amount$: String, 203 | country$: String, 204 | currency$: String, 205 | reason$: String, 206 | receiver$: Object, 207 | 'receiver.bankAccountNumber$': String, 208 | 'receiver.bankCode$': String, 209 | 'receiver.name$': String, 210 | reference$: String, 211 | }, 212 | default_body: { country: 'NG', currency: 'NGN' }, 213 | }, 214 | 215 | /** 216 | * Bank Transfer status 217 | */ 218 | bankTransferStatus: { 219 | method: 'post', 220 | path: `transfer/status/toBank`, 221 | authorization: 'SIGNATURE', 222 | body: { orderNo$: String, reference$: String }, 223 | }, 224 | }; 225 | 226 | const sortObjectAlphabetically = (map) => { 227 | const keys = sortBy__default['default'](getKeys__default['default'](map), (a) => { 228 | return a; 229 | }); 230 | const newmap = {}; 231 | forEach__default['default'](keys, (k) => { 232 | newmap[k] = map[k]; 233 | }); 234 | return newmap; 235 | }; 236 | 237 | const pluckDeep = (obj, key) => key.split('.').reduce((accum, item) => accum[item], obj); 238 | 239 | const setDeep = (obj, key, value) => { 240 | var i; 241 | key = key.split('.'); 242 | for (i = 0; i < key.length - 1; i++) obj = obj[key[i]]; 243 | obj[key[i]] = value; 244 | }; 245 | 246 | const generatePrivateKey = (key, data) => { 247 | return crypto__default['default'].createHmac('sha512', key).update(JSON.stringify(data)).digest('hex'); 248 | }; 249 | 250 | const isLiteralFalsey = (variable) => { 251 | return variable === '' || variable === false || variable === 0; 252 | }; 253 | 254 | const checkTypeName = (target, type) => { 255 | let typeName = ''; 256 | if (isLiteralFalsey(target)) { 257 | typeName = typeof target; 258 | } else { 259 | typeName = '' + (target && target.constructor.name); 260 | } 261 | return !!(typeName.toLowerCase().indexOf(type) + 1); 262 | }; 263 | 264 | const isTypeOf = (value, type) => { 265 | let result = false; 266 | 267 | type = type || []; 268 | 269 | if (typeof type === 'object') { 270 | if (typeof type.length !== 'number') { 271 | return result; 272 | } 273 | 274 | let bitPiece = 0; 275 | type = [].slice.call(type); 276 | 277 | type.forEach((_type) => { 278 | if (typeof _type === 'function') { 279 | _type = (_type.name || _type.displayName).toLowerCase(); 280 | } 281 | bitPiece |= 1 * checkTypeName(value, _type); 282 | }); 283 | 284 | result = !!bitPiece; 285 | } else { 286 | if (typeof type === 'function') { 287 | type = (type.name || type.displayName).toLowerCase(); 288 | } 289 | 290 | result = checkTypeName(value, type); 291 | } 292 | 293 | return result; 294 | }; 295 | 296 | const isNullOrUndefined = (value) => { 297 | return isTypeOf(value, ['undefined', 'null']); 298 | }; 299 | 300 | const getClientBody = (config, inputs) => { 301 | let body = {}; 302 | let inputValues = {}; 303 | 304 | for (var input in config.body) { 305 | if (config.body.hasOwnProperty(input)) { 306 | let key = input.replace('$', ''); 307 | let value = pluckDeep(inputs, key); 308 | let type = config.body[input]; 309 | let required = false; 310 | 311 | if (input.indexOf('$') + 1 === input.length) { 312 | required = true; 313 | } 314 | 315 | if ((isNullOrUndefined(value) || value === '') && required) { 316 | throw new Error(`Param: ${key} is required but not provided; please provide as needed`); 317 | } else { 318 | setDeep(body, key, isTypeOf(value, type) ? value : null); 319 | if (body[key] === null) { 320 | throw new Error(`Key: "${key}" is not of type ${type.name || type}; please provided as needed`); 321 | } 322 | } 323 | } 324 | } 325 | 326 | inputValues = JSON.stringify(body); 327 | return inputValues; 328 | }; 329 | 330 | var utils = { 331 | generatePrivateKey, 332 | getClientBody, 333 | sortObjectAlphabetically, 334 | }; 335 | 336 | const { generatePrivateKey: generatePrivateKey$1, getClientBody: getClientBody$1 } = utils; 337 | 338 | const endpoints = Object.assign({}, checkout, inquiry, transfers); 339 | 340 | const isEmpty = (value, defined) => { 341 | if (defined && isObject__default['default'](value)) { 342 | return !some__default['default'](value, function (value, key) { 343 | return value !== undefined; 344 | }); 345 | } 346 | return _isEmpty__default['default'](value); 347 | }; 348 | 349 | const createApiFunction = (config) => { 350 | let customConfig = { headers: {} }; 351 | return function (params = {}) { 352 | let payload = false; 353 | let inputs = {}; 354 | 355 | if (config.body !== null) { 356 | if (config.default_body) { 357 | inputs = Object.assign({}, config.default_body, params); 358 | } else { 359 | inputs = Object.assign({}, params); 360 | } 361 | 362 | if (!(params instanceof Object)) { 363 | throw new TypeError('Argument: [ params(s) ] Should Be An Object Literal'); 364 | } 365 | 366 | if (!isEmpty(inputs, true)) { 367 | const body = getClientBody$1(config, alphabetizeObjectKeys__default['default'](inputs)); 368 | payload = alphabetizeObjectKeys__default['default'](body); 369 | } else { 370 | throw new TypeError('Argument: [ params(s) ] Not Meant To Be Empty!'); 371 | } 372 | } 373 | 374 | if (payload === false) { 375 | payload = '{}'; 376 | } 377 | 378 | if (config.authorization === 'SIGNATURE') { 379 | customConfig.headers.Authorization = `Bearer ${generatePrivateKey$1(this.privateKey, JSON.parse(payload))}`; 380 | } 381 | 382 | customConfig.body = payload; 383 | return this.httpClient[config.method](config.path, customConfig); 384 | }; 385 | }; 386 | 387 | function OPayError(message) { 388 | this.name = 'OPayError'; 389 | this.message = message || ''; 390 | } 391 | 392 | OPayError.prototype = Error.prototype; 393 | 394 | class OPay { 395 | constructor(publicKey, privateKey, merchantID, appEnv = 'development') { 396 | const environment = /^(?:development|local|dev)$/; 397 | this.privateKey = privateKey; 398 | 399 | this.base_url = { 400 | sandbox: 'http://sandbox-cashierapi.opayweb.com/api/v3/', 401 | production: 'https://cashierapi.opayweb.com/api/v3/', 402 | }; 403 | 404 | this.httpClientOptions = { 405 | prefixUrl: environment.test(appEnv) ? this.base_url.sandbox : this.base_url.production, 406 | headers: { 407 | 'Cache-Control': 'no-cache', 408 | 'Content-Type': 'application/json', 409 | Accept: 'application/json', 410 | Authorization: `Bearer ${publicKey}`, 411 | MerchantID: merchantID, 412 | }, 413 | mutableDefaults: true, 414 | hooks: { 415 | onError: [ 416 | (error) => { 417 | const { response } = error; 418 | if (response && response.body) { 419 | error.name = 'OPayError'; 420 | error.message = `${response.body.code}: ${response.body.message} (${error.statusCode})`; 421 | } 422 | return error; 423 | }, 424 | ], 425 | afterResponse: [ 426 | (response) => { 427 | if (response.body.code !== '00000') { 428 | let message = `${response.body.code}: ${response.body.message}`; 429 | throw new OPayError(message); 430 | } 431 | return response; 432 | }, 433 | ], 434 | }, 435 | responseType: 'json', 436 | }; 437 | 438 | this.httpClient = got__default['default'].extend(this.httpClientOptions); 439 | } 440 | 441 | mergeClientOptions(options) { 442 | this.httpClient = this.httpClient.extend(options); 443 | } 444 | } 445 | 446 | for (let endpoint in endpoints) { 447 | if (endpoints.hasOwnProperty(endpoint)) { 448 | OPay.prototype[endpoint] = createApiFunction(endpoints[endpoint]); 449 | } 450 | } 451 | 452 | var OPay_1 = OPay; 453 | 454 | OPay_1.prototype.version = '1.0.2'; 455 | 456 | var opayNodejsSdk = OPay_1; 457 | 458 | module.exports = opayNodejsSdk; 459 | -------------------------------------------------------------------------------- /stats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | RollUp Visualizer 9 | 141 | 142 | 143 |
144 | 2649 | 2666 | 2667 | 2668 | 2669 | --------------------------------------------------------------------------------