diff --git a/.babelrc b/.babelrc
index 2f01e1d..1320b9a 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,3 @@
{
- "presets": ["env"]
-}
\ No newline at end of file
+ "presets": ["@babel/preset-env"]
+}
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index b735373..9e67458 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,35 +1,43 @@
---
-name: Bug report
-about: Create a report to help us improve
-
+name: Bug Report
+about: Help us improve by reporting an issue
+labels: [bug, needs-triage]
---
-**Describe the bug**
-A clear and concise description of what the bug is.
+## Bug Description
+
+A clear and concise description of the issue.
+
+## Steps to Reproduce
-**To Reproduce**
-Steps to reproduce the behavior:
1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
+2. Click on '...'
+3. Scroll down to '...'
+4. Observe the issue
+
+## Expected Behavior
+
+A clear and concise description of what should happen instead.
+
+## Screenshots (if applicable)
+
+Attach screenshots or screen recordings to illustrate the issue.
+
+## System Information
+
+### Desktop:
-**Expected behavior**
-A clear and concise description of what you expected to happen.
+- OS: [e.g. Windows 11, macOS Ventura]
+- Browser: [e.g. Chrome, Safari]
+- Version: [e.g. 120.0.1]
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
+### Mobile:
-**Desktop (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
- - Version [e.g. 22]
+- Device: [e.g. iPhone 13, Samsung Galaxy S22]
+- OS: [e.g. iOS 17, Android 13]
+- Browser: [e.g. Chrome, Safari]
+- Version: [e.g. 120.0.1]
-**Smartphone (please complete the following information):**
- - Device: [e.g. iPhone6]
- - OS: [e.g. iOS8.1]
- - Browser [e.g. stock browser, safari]
- - Version [e.g. 22]
+## Additional Context
-**Additional context**
-Add any other context about the problem here.
+Add any other relevant details, logs, or error messages.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 066b2d9..64b4b83 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,17 +1,25 @@
---
-name: Feature request
-about: Suggest an idea for this project
-
+name: Feature Request
+about: Suggest an improvement or a new idea for this project
+labels: [enhancement, needs-triage]
---
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+## Feature Description
+
+Provide a clear and concise description of the feature or improvement you are suggesting.
+
+## Problem Statement
+
+Is your feature request addressing a specific problem? Clearly describe the issue or limitation. Example: "It is frustrating when [...]"
+
+## Proposed Solution
+
+Describe the solution you would like to see implemented. Be as detailed as possible.
+
+## Alternative Solutions
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
+List any alternative approaches or workarounds you have considered.
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
+## Additional Context
-**Additional context**
-Add any other context or screenshots about the feature request here.
+Include any other relevant details, references, or screenshots that can help clarify the request.
diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml
new file mode 100644
index 0000000..673952a
--- /dev/null
+++ b/.github/workflows/qodana_code_quality.yml
@@ -0,0 +1,25 @@
+name: Qodana
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - master
+ - npm-standard-lib
+
+jobs:
+ qodana:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ checks: write
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ - name: "Qodana Scan"
+ uses: JetBrains/qodana-action@v2024.3
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 19375eb..d993cbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,9 +26,10 @@ bower_components
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
-# Dependency directories
+# Dependency directories and lock files
node_modules/
jspm_packages/
+package-lock.json
# Typescript v1 declaration files
typings/
@@ -49,10 +50,11 @@ typings/
.yarn-integrity
# dotenv environment variables file
-.env
+lib/tests/.env.local
# yarn error-log
yarn-error.log
# idea
.idea/
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..a99bb25
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,396 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://json.schemastore.org/prettierrc.json",
+ "definitions": {
+ "optionsDefinition": {
+ "type": "object",
+ "properties": {
+ "arrowParens": {
+ "description": "Include parentheses around a sole arrow function parameter.",
+ "default": "always",
+ "oneOf": [
+ {
+ "enum": ["always"],
+ "description": "Always include parens. Example: `(x) => x`"
+ },
+ {
+ "enum": ["avoid"],
+ "description": "Omit parens when possible. Example: `x => x`"
+ }
+ ]
+ },
+ "bracketSameLine": {
+ "description": "Put > of opening tags on the last line instead of on a new line.",
+ "default": false,
+ "type": "boolean"
+ },
+ "bracketSpacing": {
+ "description": "Print spaces between brackets.",
+ "default": true,
+ "type": "boolean"
+ },
+ "cursorOffset": {
+ "description": "Print (to stderr) where a cursor at the given position would move to after formatting.",
+ "default": -1,
+ "type": "integer"
+ },
+ "embeddedLanguageFormatting": {
+ "description": "Control how Prettier formats quoted code embedded in the file.",
+ "default": "auto",
+ "oneOf": [
+ {
+ "enum": ["auto"],
+ "description": "Format embedded code if Prettier can automatically identify it."
+ },
+ {
+ "enum": ["off"],
+ "description": "Never automatically format embedded code."
+ }
+ ]
+ },
+ "endOfLine": {
+ "description": "Which end of line characters to apply.",
+ "default": "lf",
+ "oneOf": [
+ {
+ "enum": ["lf"],
+ "description": "Line Feed only (\\n), common on Linux and macOS as well as inside git repos"
+ },
+ {
+ "enum": ["crlf"],
+ "description": "Carriage Return + Line Feed characters (\\r\\n), common on Windows"
+ },
+ {
+ "enum": ["cr"],
+ "description": "Carriage Return character only (\\r), used very rarely"
+ },
+ {
+ "enum": ["auto"],
+ "description": "Maintain existing\n(mixed values within one file are normalised by looking at what's used after the first line)"
+ }
+ ]
+ },
+ "experimentalTernaries": {
+ "description": "Use curious ternaries, with the question mark after the condition.",
+ "default": false,
+ "type": "boolean"
+ },
+ "filepath": {
+ "description": "Specify the input filepath. This will be used to do parser inference.",
+ "type": "string"
+ },
+ "htmlWhitespaceSensitivity": {
+ "description": "How to handle whitespaces in HTML.",
+ "default": "css",
+ "oneOf": [
+ {
+ "enum": ["css"],
+ "description": "Respect the default value of CSS display property."
+ },
+ {
+ "enum": ["strict"],
+ "description": "Whitespaces are considered sensitive."
+ },
+ {
+ "enum": ["ignore"],
+ "description": "Whitespaces are considered insensitive."
+ }
+ ]
+ },
+ "insertPragma": {
+ "description": "Insert @format pragma into file's first docblock comment.",
+ "default": false,
+ "type": "boolean"
+ },
+ "jsxSingleQuote": {
+ "description": "Use single quotes in JSX.",
+ "default": false,
+ "type": "boolean"
+ },
+ "parser": {
+ "description": "Which parser to use.",
+ "anyOf": [
+ {
+ "enum": ["flow"],
+ "description": "Flow"
+ },
+ {
+ "enum": ["babel"],
+ "description": "JavaScript"
+ },
+ {
+ "enum": ["babel-flow"],
+ "description": "Flow"
+ },
+ {
+ "enum": ["babel-ts"],
+ "description": "TypeScript"
+ },
+ {
+ "enum": ["typescript"],
+ "description": "TypeScript"
+ },
+ {
+ "enum": ["acorn"],
+ "description": "JavaScript"
+ },
+ {
+ "enum": ["espree"],
+ "description": "JavaScript"
+ },
+ {
+ "enum": ["meriyah"],
+ "description": "JavaScript"
+ },
+ {
+ "enum": ["css"],
+ "description": "CSS"
+ },
+ {
+ "enum": ["less"],
+ "description": "Less"
+ },
+ {
+ "enum": ["scss"],
+ "description": "SCSS"
+ },
+ {
+ "enum": ["json"],
+ "description": "JSON"
+ },
+ {
+ "enum": ["json5"],
+ "description": "JSON5"
+ },
+ {
+ "enum": ["jsonc"],
+ "description": "JSON with Comments"
+ },
+ {
+ "enum": ["json-stringify"],
+ "description": "JSON.stringify"
+ },
+ {
+ "enum": ["graphql"],
+ "description": "GraphQL"
+ },
+ {
+ "enum": ["markdown"],
+ "description": "Markdown"
+ },
+ {
+ "enum": ["mdx"],
+ "description": "MDX"
+ },
+ {
+ "enum": ["vue"],
+ "description": "Vue"
+ },
+ {
+ "enum": ["yaml"],
+ "description": "YAML"
+ },
+ {
+ "enum": ["glimmer"],
+ "description": "Ember / Handlebars"
+ },
+ {
+ "enum": ["html"],
+ "description": "HTML"
+ },
+ {
+ "enum": ["angular"],
+ "description": "Angular"
+ },
+ {
+ "enum": ["lwc"],
+ "description": "Lightning Web Components"
+ },
+ {
+ "type": "string",
+ "description": "Custom parser"
+ }
+ ]
+ },
+ "plugins": {
+ "description": "Add a plugin. Multiple plugins can be passed as separate `--plugin`s.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "printWidth": {
+ "description": "The line length where Prettier will try wrap.",
+ "default": 80,
+ "type": "integer"
+ },
+ "proseWrap": {
+ "description": "How to wrap prose.",
+ "default": "preserve",
+ "oneOf": [
+ {
+ "enum": ["always"],
+ "description": "Wrap prose if it exceeds the print width."
+ },
+ {
+ "enum": ["never"],
+ "description": "Do not wrap prose."
+ },
+ {
+ "enum": ["preserve"],
+ "description": "Wrap prose as-is."
+ }
+ ]
+ },
+ "quoteProps": {
+ "description": "Change when properties in objects are quoted.",
+ "default": "as-needed",
+ "oneOf": [
+ {
+ "enum": ["as-needed"],
+ "description": "Only add quotes around object properties where required."
+ },
+ {
+ "enum": ["consistent"],
+ "description": "If at least one property in an object requires quotes, quote all properties."
+ },
+ {
+ "enum": ["preserve"],
+ "description": "Respect the input use of quotes in object properties."
+ }
+ ]
+ },
+ "rangeEnd": {
+ "description": "Format code ending at a given character offset (exclusive).\nThe range will extend forwards to the end of the selected statement.",
+ "default": null,
+ "type": "integer"
+ },
+ "rangeStart": {
+ "description": "Format code starting at a given character offset.\nThe range will extend backwards to the start of the first line containing the selected statement.",
+ "default": 0,
+ "type": "integer"
+ },
+ "requirePragma": {
+ "description": "Require either '@prettier' or '@format' to be present in the file's first docblock comment\nin order for it to be formatted.",
+ "default": false,
+ "type": "boolean"
+ },
+ "semi": {
+ "description": "Print semicolons.",
+ "default": true,
+ "type": "boolean"
+ },
+ "singleAttributePerLine": {
+ "description": "Enforce single attribute per line in HTML, Vue and JSX.",
+ "default": false,
+ "type": "boolean"
+ },
+ "singleQuote": {
+ "description": "Use single quotes instead of double quotes.",
+ "default": false,
+ "type": "boolean"
+ },
+ "tabWidth": {
+ "description": "Number of spaces per indentation level.",
+ "default": 2,
+ "type": "integer"
+ },
+ "trailingComma": {
+ "description": "Print trailing commas wherever possible when multi-line.",
+ "default": "all",
+ "oneOf": [
+ {
+ "enum": ["all"],
+ "description": "Trailing commas wherever possible (including function arguments)."
+ },
+ {
+ "enum": ["es5"],
+ "description": "Trailing commas where valid in ES5 (objects, arrays, etc.)"
+ },
+ {
+ "enum": ["none"],
+ "description": "No trailing commas."
+ }
+ ]
+ },
+ "useTabs": {
+ "description": "Indent with tabs instead of spaces.",
+ "default": false,
+ "type": "boolean"
+ },
+ "vueIndentScriptAndStyle": {
+ "description": "Indent script and style tags in Vue files.",
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ },
+ "overridesDefinition": {
+ "type": "object",
+ "properties": {
+ "overrides": {
+ "type": "array",
+ "description": "Provide a list of patterns to override prettier configuration.",
+ "items": {
+ "type": "object",
+ "required": ["files"],
+ "properties": {
+ "files": {
+ "description": "Include these files in this override.",
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "excludeFiles": {
+ "description": "Exclude these files from this override.",
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "options": {
+ "$ref": "#/definitions/optionsDefinition",
+ "type": "object",
+ "description": "The options to apply for this override."
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+ },
+ "oneOf": [
+ {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/optionsDefinition"
+ },
+ {
+ "$ref": "#/definitions/overridesDefinition"
+ }
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "title": "Schema for .prettierrc"
+}
diff --git a/.travis.yml b/.travis.yml
index a3fd835..bd70782 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,13 @@
language: node_js
-sudo: false
node_js:
- - "8.6"
+ - "20" # Specify the Node.js version or use latest version if you want
+
+# Install dependencies
install:
- npm install
+
+# Run the build step if needed
script:
- - npm test
+ - npm run format # Format code
+ - npm run test # Run dist
+ - npm run test-integration # Run dist with NYC covergae
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 65126d0..a97f6c1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,45 +2,131 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
## Our Standards
-Examples of behavior that contributes to creating a positive environment include:
+Examples of behavior that contributes to a positive environment for our
+community include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall
+ community
-Examples of unacceptable behavior by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a professional setting
+- The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
-## Our Responsibilities
+## Enforcement Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
## Scope
-This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official email address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
## Enforcement
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at apifeedback@safaricom.co.ke. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+[apifeedback@safaricom.co.ke](mailto:apifeedback@safaricom.co.ke).
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
-Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/LICENSE b/LICENSE
index 261eeb9..3e341ff 100644
--- a/LICENSE
+++ b/LICENSE
@@ -180,13 +180,13 @@
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
+ the brackets!) The text Should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright [2025] [Safaricom]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 8e31e5a..4774983 100644
--- a/README.md
+++ b/README.md
@@ -1,254 +1,491 @@
# Node.js M-Pesa API
+
**M-Pesa Library for Node.js using REST API**
-
+
+
+
+
+
-
-[](https://travis-ci.org/safaricom/mpesa-node-library)
[](https://github.com/collections/made-in-africa)
[](https://snyk.io/test/github/safaricom/mpesa-node-library?targetFile=package.json)
+[](https://www.npmjs.com/package/mpesa-node)
+[](https://www.apache.org/licenses/LICENSE-2.0)
## Prerequisites
-1. Node v6+, 8+ recommended.
-2. Yarn* (optional) You can still use npm
-3. ES6 knowledge
+
+- **Node.js v20+** – Ensure you have Node.js version 20 or later installed for improved performance, security, and
+ compatibility.
+- **Ngrok CLI** – Install the [**Ngrok CLI**](https://download.ngrok.com/) to expose your local server for testing M-Pesa
+ callbacks. Ensure you have followed the official guide on how to setup Ngrok
## Installation
-Use npm/yarn:
-```
-npm i mpesa-node
-```
-### Pre-Usage
+Based on the **package manager** you prefer, run the commands below, to install all necessary dependencies
+
+**npm**: `npm install`
+
+**Yarn**: `yarn install`
+
+## Pre-Usage
+
+**Please make sure you have read the documentation on [Daraja](https://developer.safaricom.co.ke/home) before
+continuing.**
-**Please make sure you have read the documentation on [Daraja](https://developer.safaricom.co.ke/home) before continuing.**
+You need to sign up for a Safaricom developer [**account**](https://developer.safaricom.co.ke/home) to obtain your **Consumer Key** and **Consumer Secret**. In addition, you'll need to download the **sandbox encryption certificate** to
+test the APIs in your project. For production or **going live** you'll be issued with a **production encryption
+certificate**
-You need the following before getting to use this library:
-1. Consumer Key and Consume Secret
-2. Test Credentials *(Optional only for sandbox)*
+For convenience, the sandbox certificate required for testing the library is already provided in the `libs/cert`
+directory _**(For testing the library itself)**_. In your own project, I recommend one to specify the certificate path
+in the `.env` for either **production** or **sandbox - development mode**.
+
+For example, your .env file might look like this:
+
+```dotenv
+# For sandbox/development
+MPESA_CERT_PATH_DEV=./path/to/your/sandbox-cert.pem
+# For production
+MPESA_CERT_PATH_PROD=./path/to/your/production-cert.pem
+```
## Getting Started
-This library is extremely modular, meaning you can create more than one Mpesa instance
-````js
-const Mpesa = require('mpesa-node')
-const mpesaApi = new Mpesa({ consumerKey: '', consumerSecret: '' })
-// another instance
-// const instance = new Mpesa({ consumerKey: 'test', consumerSecret: 'test', environment: 'production' })
-mpesaApi
- .c2bSimulate(
- 254708374149,
- 500,
- 'h6dk0Ue2'
- )
- .then((result) => {
- //do something
- })
- .catch((err) => {
- // retry?
- })
-````
-
-While working with the Mpesa Class, you only need two key-value items, ie: consumerKey and consumerSecret.
-Nonetheless, prefilling some things means you dont have to re-enter them again. A complete config object looks like this
-````js
-new Mpesa({
- consumerKey: '',
- consumerSecret: '',
- environment: 'sandbox',
- shortCode: '600111',
- initiatorName: 'Test Initiator',
- lipaNaMpesaShortCode: 123456,
- lipaNaMpesaShortPass: '',
- securityCredential: '',
- certPath: path.resolve('keys/myKey.cert')
-})
-````
-## API
-Please note that this library is in active development, use in production with caution.
-
-Current API:
-````js
-const mpesaApi = new Mpesa({ consumerKey: '', consumerSecret: '' })
-const {
- accountBalance,
- b2b,
- b2c,
- c2bRegister,
- c2bSimulate,
- lipaNaMpesaOnline,
- lipaNaMpesaQuery,
- reversal,
- transactionStatus
-} = mpesaApi
-````
-Ofcourse you dont need to import all methods, you can import the only ones you need.
-
-All methods return a ``, hence you can use `.then` or `await`.
-All calls are done by Axios, so for the response structure check Axios documentation.
-
-### Methods
-• [B2C Request](https://developer.safaricom.co.ke/b2c/apis/post/paymentrequest)
-
-This initiates a business to customer transactions from a company (shortcode) to end users (mobile numbers) of their services.
-````js
-/*
- * b2c(senderParty, receiverParty, amount, queueUrl, resultUrl, commandId = 'BusinessPayment', initiatorName = null, remarks = 'B2C Payment', occasion = null)
- * Example:
-*/
-const { shortCode } = mpesaApi.configs
-const testMSISDN = 254708374149
-await mpesaApi.b2c(shortCode, testMSISDN, 100, URL + '/b2c/timeout', URL + '/b2c/success')
-````
-
-• [B2B Request](https://developer.safaricom.co.ke/b2b/apis/post/paymentrequest)
-
-This initiates a business to business transaction between one company to another.
-````js
-/*
- * b2c(senderParty, receiverParty, amount, queueUrl, resultUrl, senderType = 4, receiverType = 4, initiator = null, commandId = 'BusinessToBusinessTransfer', accountRef = null, remarks = 'B2B Request')
- * Example:
-*/
-const { shortCode } = mpesaApi.configs
-const testShortcode2 = 600000
-await mpesaApi.b2b(shortCode, testShortcode2, 100, URL + '/b2b/timeout', URL + '/b2b/success')
-````
-• [C2B Register](https://developer.safaricom.co.ke/c2b/apis/post/registerurl)
-
-This initiates a C2B confirmation and validation registration for a company's URLs
-
-````js
-/*
- * c2bRegister(confirmationUrl, validationUrl, shortCode = null, responseType = 'Completed')
- * Example:
- */
-await mpesaApi.c2bRegister(URL + '/c2b/validation', URL + '/c2b/success')
+**Note:** This library follows a **modular approach**, allowing you to import only the **specific functions or endpoints** you need. Before getting started, make sure the following steps are **properly set up** ✔.
-````
+### Setting up environmental credentials
-• [C2B Simulate](https://developer.safaricom.co.ke/c2b/apis/post/simulate)
+A `.env` file in your project's root directory is required to configure the **M-Pesa API** credentials. This file **should**
+contain the following environment variables
-This initiates a C2B transaction between an end-user and a company (paybill or till number)
+```dotenv
+MPESA_CONSUMER_KEY=your_consumer_key
+MPESA_CONSUMER_SECRET=your_consumer_secret
+MPESA_SECURITY_CREDENTIAL=your_encrypted_credential
+MPESA_PASS_KEY=your_pass_key
+MPESA_CERT_PATH_DEV=./certs/dev-cert.pem
+MPESA_CERT_PATH_PROD=./certs/prod-cert.pem
+ENVIRONMENT=sandbox
+```
-````js
-/*
- * c2bSimulate(msisdn, amount, billRefNumber, commandId = 'CustomerPayBillOnline', shortCode = null)
- * Example:
- */
-const testMSISDN = 254708374149
-await mPesa.c2bSimulate(testMSISDN, 100, Math.random().toString(35).substr(2, 7))
+The `MPESA_PASS_KEY` Can be found specifically here [**Daraja**](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate) - _(my Apis - MpesaExpressSimulate)_. Click on the **console**, select an app, scroll down you'll see the field labeled `passKey`
-````
-• [M-Pesa Express Request - Lipa Na M-Pesa Online Payment API](https://developer.safaricom.co.ke/lipa-na-m-pesa-online/apis/post/stkpush/v1/processrequest)
+**Note for Library Developers**: If you're contributing to or working on the **M-Pesa** library itself, place a
+`.env.local` or `.env` file in the `lib/tests` directory to run the included tests. This is not required for simply using the APIs
+in your own projects.
-This initiates a Lipa Na M-Pesa Online Payment transaction using STK Push.
+## Simulating an account balance check:
-````js
-/*
- * lipaNaMpesaOnline(senderMsisdn, amount, callbackUrl, accountRef, transactionDesc = 'Lipa na mpesa online', transactionType = 'CustomerPayBillOnline', shortCode = null, passKey = null)
- * Example:
+You can simulate an account balance check by importing and calling the `balanceQuery` function, together with its
+callback handler (optional) `handleBalanceQueryCallbacks`
+
+**TypeScript Support:** This library has a `.d.ts` for each **API endpoint**, providing seamless integration and type
+checking for TypeScript projects.
+
+Below is an example of how to set up the account balance api endpoint `balanceQuery`:
+
+```js
+import { mpesa } from "mpesa-node";
+
+// Account Balance Query Example
+/**
+ * @name balanceQuery
+ * @description Fetches the account balance from M-Pesa.
+ * @see {@link https://developer.safaricom.co.ke/APIs/AccountBalance} - Daraja API Documentation
*/
- const testMSISDN = 254708374149
- const amount = 100
- const accountRef = Math.random().toString(35).substr(2, 7)
-await mpesaApi.lipaNaMpesaOnline(testMSISDN, amount, URL + '/lipanampesa/success', accountRef)
-````
-• [M-Pesa Express Query Request - Lipa Na M-Pesa Query Request API](https://developer.safaricom.co.ke/lipa-na-m-pesa-online/apis/post/stkpushquery/v1/query)
+const { balanceQuery } = mpesa;
+
+// Ensure you replace these placeholders with valid values
+// For the url, check on how to handle callbacks, paste the url provided below
+const VALID_HTTPS_URL = "paste here";
+const INITIATOR_NAME = "yourInitiatorUsername";
+
+async function checkAccountBalance() {
+ try {
+ const response = await balanceQuery({
+ idType: 2, // Example: 2 (Till Number)
+ shortCode: 600977,
+ initiator: INITIATOR_NAME,
+ queueURL: `${VALID_HTTPS_URL}/accountbalance/queuetimeouturl`,
+ resultURL: `${VALID_HTTPS_URL}/accountbalance/result`,
+ });
+ //do something ...
+ console.log("Account Balance Response:", JSON.stringify(response, null, 2));
+ } catch (error) {
+ //do something...
+ console.error("Error fetching account balance:", error);
+ }
+}
+// Execute the function
+checkAccountBalance();
+```
-This API checks the status of a Lipa Na M-Pesa Online Payment transaction
+### Handling callbacks
-````js
-/*
- * lipaNaMpesaQuery(checkoutRequestId, shortCode = null, passKey = null)
- * Example:
+After setting up the `balanceQuery` endpoint, lets configure the callback handler associated with it; `handleBalanceQueryCallbacks`. A **server instance** is required to use callback handlers
+. For this to work, ensure you have **ngrok CLI**
+installed on your machine, see [**getting started with Ngrok**](https://ngrok.com/docs/getting-started/)
+
+Using the library's default callback handlers is **completely optional**—you're free to handle them `manually` if
+preferred.
+
+```js
+import express from "express";
+import { callbacks } from "mpesa-node";
+
+const app = express();
+const { handleBalanceQueryCallbacks } = callbacks;
+
+// Middleware for parsing JSON requests
+app.use(express.json());
+
+// Register M-Pesa balance query callback handler
+handleBalanceQueryCallbacks(app);
+
+/**
+ * STARTING THE SERVER
+ * -------------------
+ * - The server listens on port 3000 (or an environment-defined port)
+ * - Developers can use tools like `ngrok` to expose the server publicly for testing callbacks
*/
-const checkoutRequestId ='ws_co_123456789'
-await mpesaApi.lipaNaMpesaQuery(checkoutRequestId)
-````
-• [Reversal Request](https://developer.safaricom.co.ke/reversal/apis/post/request)
+const PORT = process.env.PORT || 3000;
+app.listen(PORT, () => {
+ console.log(`Server running on port ${PORT}`);
+ console.log(`To expose this locally, run: ngrok http ${PORT}`);
+ console.log(
+ `Ensure the public URL provided by ngrok is set as the 'resultURL' and 'queueURL' in your M-Pesa request`,
+ );
+});
+```
-This initiates an M-Pesa transaction reversal on B2B, B2C or C2B API
-````js
+When using the default **callback handlers**, the response includes the main API `balanceQuery` response body, along with either a **result** or **queue** callback body.
+
+```js
+const response = await balanceQuery({
+ idType: 2, // Example: 2 (Till Number)
+ shortCode: 600977,
+ initiator: INITIATOR_NAME,
+ queueURL: `${VALID_HTTPS_URL}/accountbalance/queuetimeouturl`,
+ resultURL: `${VALID_HTTPS_URL}/accountbalance/result`,
+});
+//do something ...
+console.log("Account Balance Response:", JSON.stringify(response, null, 2));
/*
- * reversal(transactionId, amount, queueUrl, resultUrl, shortCode = null, remarks = 'Reversal', occasion = 'Reversal', initiator = null, receiverIdType = '11', commandId = 'TransactionReversal')
- * Example:
- */
-await mpesaApi.reversal('LKXXXX1234', 100, URL + '/reversal/timeout', URL + '/reversal/success')
-````
-• [Transaction Status Request](https://developer.safaricom.co.ke/transaction-status/apis/post/query)
+ if callback handlers are used expect such a json response...
+ "Account balance response": {
+ "balanceResponse": {
+ ...some data
+ },
+ "conditionalCallbackData": {
+ "type": "result or queue",
+ "data": {
+ ...some data
+ }
+ }
+ }
+ if callback handlers aren't used simply expect
+ "Account balance response": {
+ "balanceResponse": {
+ ...some data
+ },
+ "conditionalCallbackData": {}
+ */
+```
-This API is used to check the status of B2B, B2C and C2B transactions
+**NOTE**: At all costs avoid using URLs offered by **Ngrok** for **production** or **going live**
+
+## Supported API endpoints:
+
+#### Caution!
+
+This library is still in development. We recommend **thorough testing** before using it in a **production environment**.
+
+Here is a comprehensive list of all supported API endpoints with their respective documentation links:
+
+- **balanceQuery**: [**Daraja**](https://developer.safaricom.co.ke/APIs/AccountBalance)
+ or [**JsDocs**](./docs/balance-Query.js.html)
+- **b2cRequest**: [**Daraja**](https://developer.safaricom.co.ke/APIs/BusinessToCustomer)
+ or [**JsDocs**](./docs/b2c-Request.js.html)
+- **c2bRegister**: [**Daraja**](https://developer.safaricom.co.ke/APIs/CustomerToBusinessRegisterURL)
+ or [**JsDocs**](./docs/c2b-Register.js.html)
+- **c2bSimulate**: [**Daraja**](https://developer.safaricom.co.ke/c2b/apis/post/simulate)
+ or [**JsDocs**](./docs/c2b-Simulate.js.html)
+- **mpesaSimulate**: [**Daraja**](https://developer.safaricom.co.ke/c2b/apis/post/simulate)
+ or [**JsDocs**](./docs/c2b-Simulate.js.html)
+- **mpesaQuery**: [**Daraja**](https://developer.safaricom.co.ke/c2b/apis/post/simulate)
+ or [**JsDocs**](./docs/c2b-Simulate.js.html)
+- **reversals**: [**Daraja**](https://developer.safaricom.co.ke/APIs/MpesaExpressQuery)
+ or [**JsDocs**](./docs/reversals.js.html)
+- **generateQrCode**: [**Daraja**](https://developer.safaricom.co.ke/APIs/DynamicQRCode)
+ or [**JsDocs**](./docs/qr-Generate.js.html)
+- **transactionStatus**: [**Daraja**](https://developer.safaricom.co.ke/transaction-status/apis/post/query)
+ or [**JsDocs**](./docs/transaction-Status.js.html)
+- **b2cTopUp**: [**Daraja**](https://developer.safaricom.co.ke/transaction-status/apis/post/query)
+ or [**JsDocs**](./docs/b2c-Topup.js.html)
+- **businessPaybill**: [**Daraja**](https://developer.safaricom.co.ke/APIs/BusinessPayBill)
+ or [**JsDocs**](./docs/business-Paybill.js.html)
+- **taxRemittance**: [**Daraja**](https://developer.safaricom.co.ke/APIs/TaxRemittance)
+ or [**JsDocs**](https://developer.safaricom.co.ke/APIs/TaxRemittance)
+
+Developers are strongly encouraged to consult the [**JsDocs**](./docs/global.html) (_which comes bundled with the library_) for detailed information on how the required fields are mapped. This documentation clearly outlines the necessary configurations for successfully initiating any endpoint, ensuring a smooth integration process.
+
+### Options for each API
+
+Here is a comprehensive list of all supported APIs along with their respective **options**. Use this as a reference when configuring the parameters for your chosen API.
+
+```ts
+export interface balanceQueryOptions {
+ partyA: number;
+ identifierType: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ initiator: string;
+ remarks: string;
+}
+
+export interface b2cRequestOptions {
+ partyA: number;
+ partyB: string;
+ amount: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ commandId: string;
+ initiatorName: string;
+ remarks: string;
+ occasion: string;
+}
+
+export interface c2bRegisterOptions {
+ confirmationURL: string;
+ validationURL: string;
+ shortCode: number;
+ responseType: string;
+}
+
+export interface c2bSimulateOptions {
+ msisdn: string;
+ amount: number;
+ billRefNumber: string;
+ shortCode: number;
+}
+
+export interface mpesaSimulateOptions {
+ partyA: string;
+ phoneNumber: string;
+ amount: number;
+ callbackURL: string;
+ accountRef: string;
+ transactionType: string;
+ partyB: number;
+ transactionDesc: string;
+}
+
+export interface mpesaQueryOptions {
+ checkoutRequestId: string;
+ businessShortCode: number;
+}
+
+export interface reversalsOptions {
+ transactionId: string;
+ amount: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ receiverParty: string;
+ initiator: string;
+ receiverIdType: string;
+ remarks: string;
+ occasion: string;
+}
+
+export interface transactionStatusOptions {
+ transactionId: string;
+ partyA: number;
+ identifierType: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ initiator: string;
+ OriginatorConversationID: string;
+ remarks: string;
+ occasion: string;
+}
+
+export interface generateQrCodeOptions {
+ merchantName: string;
+ refNo: string;
+ amount: number;
+ trxCode: "BG" | "WA" | "PB" | "SM" | "SB";
+ cpi: string;
+ size: string;
+}
+
+export interface b2cTopUpOptions {
+ initiator: string;
+ amount: number;
+ partyA: number;
+ partyB: number;
+ accountReference: number;
+ requester?: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ remarks: string;
+}
+
+export interface businessPaybillOptions {
+ initiator: string;
+ amount: number;
+ partyA: number;
+ partyB: number;
+ accountReference: number;
+ requester?: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ remarks: string;
+}
+
+export interface taxRemittanceOptions {
+ initiator: string;
+ amount: number;
+ partyA: number;
+ partyB: number;
+ accountReference: number;
+ QueueTimeOutURL: string;
+ resultURL: string;
+ remarks: string;
+}
+```
-````js
-/*
- * transactionStatus(transactionId, receiverParty, idType, queueUrl, resultUrl, remarks = 'TransactionReversal', occasion = 'TransactionReversal', initiator = null, commandId = 'TransactionStatusQuery')
- * Example:
- */
-await mpesaApi.transactionStatus('LKXXXX1234', shortCode, 4, URL + '/transactionstatus/timeout', URL + '/transactionstatus/success')
-````
-• [Account Balance Request](https://developer.safaricom.co.ke/account-balance/apis/post/query)
+### MSISDN formatting
-This initiates a request for the account balance of a shortcode
+When working with APIs that require an `msisdn` (a **phone number**), always provide it as a `string` in the format: `0708374149`. The library automatically processes the number into the required format, so no additional configuration is needed.
+
+### External configurations
+
+Certain endpoints require external configurations to function correctly, particularly when working in a production environment. For seamless integration and optimal performance, it is crucial to review the API documentation thoroughly. Some APIs, such as **taxRemittance**, **b2cRequest** and **c2bRegister**, may depend on additional setup or external parameters that are necessary for proper functionality.
+
+In a **production development** setting, these configurations are especially critical to ensure that all aspects of the API perform as expected. It is highly recommended that developers pay close attention to the specific requirements outlined in the [**official documentation**](https://developer.safaricom.co.ke/APIs) for each API. Relying on the most up-to-date and detailed guidelines from the official sources will help mitigate potential issues and ensure smooth integration.
-````js
-/*
- * accountBalance(shortCode, idType, queueUrl, resultUrl, remarks = 'Checking account balance', initiator = null, commandId = 'AccountBalance')
- * Example:
- */
-const { shortCode } = mpesaApi.configs
-await mpesaApi.accountBalance(shortCode, 4, URL + '/accountbalance/timeout', URL + '/accountbalance/success')
-````
## Testing
-Testing needs you to clone this repo.
-The command below runs both integration and unit test.
+This library is built around **integration tests**, following a **Behavior-Driven Development (BDD)** approach.
+
+This approach is ideal for a wide range of audiences because **BDD focuses** on clear, human-readable test scenarios
+that describe expected behaviors. Additionally, integration tests validate real-world interactions, ensuring the library
+works reliably in actual usage scenarios.
+
+**BDD approach** + **on integration tests**, can help:
+
+- Catch authentication issues (**OAuth failures**)
+- Verify actual API responses (**instead of mocked ones**)
+- Check if callbacks are received & handled properly
+- Detect network timeouts or incorrect response formats
+
+To run tests, first, **clone this repository**.
+
+The command below (_based on your package manager_) executes **integration tests**, which **require an active internet
+connection** to accurately simulate **real API interactions** over **HTTPS**.
+
+**npm**: `npm test`
+
+**Yarn**: `yarn test`
+
+### Activating callback handlers in tests
-Integration tests launch a ngrok instance and await callbacks (you will need an active internet connection for this).
+Callback handlers in **testing** are automatically configured but **disabled** by default.
-To run each separately, check `package.json` for the commands.
-````
-npm test
-````
-## Going Live/Production
+Below is an example of a `c2bSimulate` **mocha** test. To activate callback handling swap `true` to `false`.
-You will need to first click on "Going Live" on [Daraja](https://developer.safaricom.co.ke/user/me/apps)
+```js
+import { expect } from "chai";
+import { mpesa } from "../../../../index.js";
+import { setupNgrokServer } from "../utils/server.js";
+import { createOptionsForC2bSimulate } from "../utils/options.js";
-The only thing you need to tweek in this Libs config is `environment`:
-````js
-new Mpesa({
- consumerKey: '',
- consumerSecret: '',
- environment: 'production', //<------
- .....
- })
-````
+describe("C2B Simulate API with OAuth", function () {
+ this.timeout(28000);
+ let NGROK_URL, teardown;
+ const { c2bSimulate } = mpesa;
+
+ // To enable callback handling swap true to false
+ before(async function () {
+ // ({ NGROK_URL, teardown } = await setupNgrokServer("c2bSimulate", true));
+ ({ NGROK_URL, teardown } = await setupNgrokServer("c2bSimulate", false));
+ });
+
+ after(async function () {
+ await teardown();
+ });
+
+ it("Should simulate a C2B transaction", function (done) {
+ c2bSimulate(
+ createOptionsForC2bSimulate(
+ NGROK_URL === "" ? "https://mock.url" : NGROK_URL,
+ ),
+ )
+ .then((responseBody) => {
+ expect(responseBody).to.be.an("object");
+ console.log("RESPONSE BODY:", JSON.stringify(responseBody, null, 2));
+ done();
+ })
+ .catch(done);
+ });
+});
+```
+
+### Temporary port exposure
+
+Once the callback handler is enabled, the boolean option (`false` allows the test to temporarily:
+
+- Spawn a local server
+- Expose it via Ngrok
+- Fetch responses from API endpoint servers
+
+This setup ensures that callbacks are properly handled during testing.
+
+## Production environment (Going live)
+
+Before **deploying** to **production**, successful **sandbox testing** is essential. We **expect** the library to behave
+**consistently** in both environments, with the key difference being that production callbacks will contain real
+transaction data, whereas sandbox callbacks return simulated responses.
+
+To **go live**, log in to [**Daraja**](https://developer.safaricom.co.ke/) and click on
+the "[**Going Live**](https://developer.safaricom.co.ke/GoLive)" option.
+
+For this to work properly, you need to tweak the `ENVIRONMENT` option to `"production"` and `MPESA_CERT_PATH_PROD` to a
+valid `"/production.cer"` path, in the `.env` file:
+
+```dotenv
+ENVIRONMENT=production
+MPESA_CERT_PATH_PROD=./path/to/your/production-cert.pem
+```
## Pending Stuff
-- [x] E2E Integration Tests
-- [x] Deploy to Npm
-- [x] Reduce number of args
-- [x] Detailed Documentation
-- [ ] Enumify
-- [ ] Validators for MSISDN and other expected inputs
-- [x] More detailed Unit tests
-- [ ] Handle all Promises
+- [x] **Integration Tests**
+- [x] **Deploy to Npm**
+- [x] **Detailed Documentation**
+- [x] **Typescript Definitions**
+- [x] **Validators for MSISDN and URLs**
+- [ ] **Production testing**
## Contributing
-1. Create your feature branch: `git checkout -b my-new-feature`
-2. Commit your changes: `git commit -m 'Add some feature'`
-3. Push to the branch: `git push origin my-new-feature`
-4. Submit a pull request :D
+
+We welcome **contributions**! Follow these steps to get started:
+
+1. **Create** your feature branch: `git checkout -b my-new-feature`
+2. **Commit** your changes: `git commit -m 'Add some feature'`
+3. **Push** to the branch: `git push origin my-new-feature`
+4. Open a **pull request** and share your updates
## Credits
-| **Contributor** |
-
-| [DGatere](https://github.com/DGatere) |
-| [geofmureithi](https://github.com/geofmureithi) |
+**Contributors**
+- [DGatere](https://github.com/DGatere)
+- [geofmureithi](https://github.com/geofmureithi)
+- [Waturu Samm](https://github.com/tu-ru/)
## License
-MIT
+**APACHE 2.0**
diff --git a/docs/b2c-Request.js.html b/docs/b2c-Request.js.html
new file mode 100644
index 0000000..bcba7de
--- /dev/null
+++ b/docs/b2c-Request.js.html
@@ -0,0 +1,261 @@
+
+
+
+
+
+ b2c-Request.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
b2c-Request.js
+
+
+
+
import axios from "axios";
+import { CommandIDs } from "../utils/constants.js";
+import {
+ callbackTrigger,
+ encryptSecurityCredential,
+ generateOAuthToken,
+ handleCallbacks,
+ logErrorDetails,
+ originatorID,
+ throwErrorMessages, validateFormatPhone,
+ validateUrl
+} from "../utils/helpers.js";
+
+// Globals
+let callbackHandlerInitialized = false;
+let conditionalCallbackData = {};
+
+/**
+ * @name b2cRequest
+ * @description B2C API can be used in several scenarios by businesses that require to either make Salary Payments, Cashback payments, Promotional Payments(e.g. betting winning payouts), winnings, financial institutions withdrawal of funds, loan disbursements, etc.
+ * @summary B2C payments involve a business sending money to an individual. This is a direct transaction from a business shortcode to a consumer's mobile number (MSISDN).
+ * @see {@link https://developer.safaricom.co.ke/APIs/BusinessToCustomer open external link}
+ * @param {Object} options Options for the B2C payment request.
+ * @param {number} options.partyA The B2C organization shortcode sending the money.
+ * @param {string} options.partyB Customer mobile number (with country code, e.g., 254).
+ * @param {number} options.amount The amount being transacted.
+ * @param {string} options.remarks Information to be associated with the transaction.
+ * @param {string} options.occasion Information to be associated with the transaction.
+ * @param {string} options.QueueTimeOutURL URL for timeout notifications.
+ * @param {string} options.resultURL URL for M-PESA to send payment processing notifications.
+ * @param {string} options.commandId Unique command specifying B2C transaction type (e.g., BusinessPayment).
+ * @param {string} options.initiatorName API user created by Business Administrator for B2C transactions.
+ * @param {boolean} [options.proErrorLogging=false] Logs out advanced error details - good for debugging.
+ * @return {Promise<object>} b2cRequestResponse and (optional) conditionalCallbackData. */
+async function b2cRequest({
+ partyA,
+ partyB,
+ amount,
+ QueueTimeOutURL,
+ remarks,
+ occasion,
+ resultURL,
+ commandId,
+ initiatorName,
+ proErrorLogging = false,
+}) {
+ if (!callbackHandlerInitialized) {
+ console.info(
+ "\x1b[42m\x1b[30m\x1b[1m The default balance query callback handler ('handleB2cRequestCallbacks') was not called. Ignore if handling manually.\x1b[0m",
+ );
+ }
+ /**
+ * @param {string} validCommandIds - loops through object values with strings "SalaryPayment", "BusinessPayment" PromotionPayment"
+ */
+ const validCommandIds = Object.values(CommandIDs);
+ if (!validCommandIds.includes(commandId)) {
+ throw new Error(
+ `Invalid commandId provided. Must be one of: ${validCommandIds.join(", ")}`,
+ );
+ }
+ const _msisdn = validateFormatPhone(partyB)
+ try {
+ validateUrl(resultURL, "resultURL");
+ validateUrl(QueueTimeOutURL, "QueueTimeOutURL");
+ const OriginatorConversationID = originatorID();
+ const { accessToken, baseURL } = await generateOAuthToken();
+
+ const req = axios.create({
+ baseURL,
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ const responseBody = await req.post("/mpesa/b2c/v3/paymentrequest", {
+ OriginatorConversationID,
+ InitiatorName: initiatorName,
+ SecurityCredential: encryptSecurityCredential(),
+ CommandID: commandId,
+ Amount: amount,
+ PartyA: partyA,
+ PartyB: _msisdn,
+ Remarks: remarks,
+ QueueTimeOutURL: QueueTimeOutURL,
+ ResultURL: resultURL,
+ Occasion: occasion
+ });
+
+ conditionalCallbackData = await callbackTrigger(callbackHandlerInitialized);
+
+ return { b2cRequestResponse: responseBody.data, conditionalCallbackData};
+ } catch (error) {
+ if (proErrorLogging) {
+ console.info(
+ "\x1b[35m%s\x1b[0m",
+ "Advanced error logging for b2cRequest has been initialized",
+ );
+ logErrorDetails(
+ error,
+ {
+ apiEndpoint: "/mpesa/b2c/v3/paymentrequest",
+ method: "POST",
+ payload: {
+ partyA,
+ _msisdn,
+ amount,
+ QueueTimeOutURL,
+ resultURL,
+ commandId,
+ initiatorName,
+ occasion,
+ remarks
+ },
+ },
+ "B2C transaction error details:",
+ );
+ }
+ throw throwErrorMessages(error);
+ }
+}
+const handleB2cRequestCallbacks = async (app) => {
+ callbackHandlerInitialized = true;
+ await handleCallbacks(app, "b2cRequest");
+};
+export { b2cRequest, handleB2cRequestCallbacks };
+
Registers callback validation and confirmation URLs on M-Pesa to receive payment notifications for your paybill/till numbers.
+
+
+
+
+
Register URL API works hand in hand with Customer to Business (C2B) APIs and allows receiving payment notifications to your paybill. This API enables you to register the callback URLs via which you shall receive notifications for payments to your pay bill/till numbers
B2C payments involve a business sending money to an individual. This is a direct transaction from a business shortcode to a consumer's mobile number (MSISDN).
+
+
+
+
+
B2C API can be used in several scenarios by businesses that require to either make Salary Payments, Cashback payments, Promotional Payments(e.g. betting winning payouts), winnings, financial institutions withdrawal of funds, loan disbursements, etc.
Transfers funds from your MMF/Working account to the recipient's utility account for disbursement to a B2C shortcode.
+
+
+
+
+
This API enables you to load funds to a B2C shortcode directly for disbursement. The transaction moves money from your MMF/Working account to the recipient’s utility account.
Facilitates bill payments from a business account to a paybill number, transferring funds to the recipient’s utility account.
+
+
+
+
+
This API enables you to pay bills directly from your business account to a pay bill number, or a paybill store. You can use this API to pay on behalf of a consumer/requester. The transaction moves money from your MMF/Working account to the recipient’s utility account.
Generates QR codes for customers using My Safaricom app
+
+
+
+
+
This generates a Dynamic QR which enables Safaricom M-PESA customers who have My Safaricom App or M-PESA app, to scan a QR (Quick Response) code, to capture till number and amount
Sends a payment prompt to the customer's M-PESA registered phone number, requesting them to enter their M-PESA pin to authorize and complete payment.
+
+
+
+
+
Lipa na M-PESA online API also known as M-PESA express (STK Push/NI push) is a Merchant/Business initiated C2B (Customer to Business) Payment. it enables you to send a payment prompt on the customer's phone (Popularly known as STK Push Prompt) to your customer's M-PESA registered phone number requesting them to enter their M-PESA pin to authorize and complete payment.
Reverses a C2B M-Pesa transaction. Once a customer pays and there is a need to reverse the transaction, the organization will use this API to reverse the amount.
Enables one to remit tax to Kenya Revenue Authority (KRA)
+
+
+
+
+
This API enables businesses to remit tax to Kenya Revenue Authority (KRA). To use this API, prior integration is required with KRA for tax declaration, payment registration number (PRN) generation, and exchange of other tax-related information.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..ab53111
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+ Home - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
MPESA-NODE LIBRARY
+
JSDocs guide
+
About
+
+ This documentation is generated using JSDoc and is designed to help
+ developers accurately map fields to API endpoints. It provides a
+ clear understanding of which fields are preconfigured by default and
+ which can be modified based on your requirements.
+
+
+ By referring to this guide, you can ensure that you're using the API
+ correctly and efficiently. Whether you're integrating the library or
+ extending its functionality, this documentation will serve as a
+ valuable reference.
+
+
Keeping Documentation Up to Date
+
+ If you're contributing to the project, make sure to regenerate the
+ documentation whenever changes are made to the API. Use the
+ following commands:
+
+
Generate Documentation
+
+ After updating the library, you may
+ optionally run the commands below to regenerate the
+ documentation. However, if you prefer, you can leave this task to
+ the repository maintainer.
+
+
npm:npm docs
+
yarn:yarn docs
+
Takeaways
+
+ Keeping the documentation updated ensures that all contributors and
+ users have access to accurate and up-to-date information.
+
import axios from "axios";
+import {
+ callbackTrigger,
+ encryptSecurityCredential,
+ generateOAuthToken,
+ handleCallbacks,
+ logErrorDetails,
+ throwErrorMessages,
+ validateUrl,
+} from "../utils/helpers.js";
+
+// Globals
+let callbackHandlerInitialized = false;
+let conditionalCallbackData = {};
+
+/**
+ * @name reversals
+ * @description Reverses a C2B M-Pesa transaction. Once a customer pays and there is a need to reverse the transaction, the organization will use this API to reverse the amount.
+ * @summary Reverses an M-pesa transaction
+ * @see {@link https://developer.safaricom.co.ke/APIs/Reversal open external link}
+ * @param {Object} options Options for the reversal API.
+ * @param {string} options.transactionId Transaction ID for reversal (e.g., LKXXXX1234).
+ * @param {number} options.amount Amount to be reversed.
+ * @param {string} options.QueueTimeOutURL URL for timeout transaction details.
+ * @param {string} options.resultURL URL for transaction details.
+ * @param {string} options.remarks Information to be associated with the transaction.
+ * @param {string} options.occasion Information to be associated with the transaction.
+ * @param {number} options.receiverParty Organization receiving the transaction.
+ * @param {string} options.initiator Name of the initiator of the request.
+ * @param {boolean} [options.proErrorLogging] Logs out advanced error details - good for debugging
+ * @returns {Promise<Object>} reversalsResponse and (optional) conditionalCallbackData.
+ */
+async function reversals({
+ transactionId,
+ amount,
+ QueueTimeOutURL,
+ resultURL,
+ receiverParty,
+ initiator,
+ remarks,
+ occasion,
+ proErrorLogging = false,
+}) {
+ if (!callbackHandlerInitialized) {
+ console.info("\x1b[42m\x1b[30m\x1b[1m The default callback handler ('handleReversalCallbacks') was not called. Ignore if handling manually.\x1b[0m",
+ );
+ }
+ try {
+ validateUrl(resultURL, "resultURL");
+ validateUrl(QueueTimeOutURL, "timeoutUrl");
+ const { accessToken, baseURL } = await generateOAuthToken();
+ const req = axios.create({
+ baseURL,
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+ // default configurations
+ const config = {
+ receiverIdType: "11",
+ commandId: "TransactionReversal",
+ };
+ const responseBody = await req.post("/mpesa/reversal/v1/request", {
+ Initiator: initiator,
+ SecurityCredential: encryptSecurityCredential(),
+ CommandID: config.commandId,
+ TransactionID: transactionId,
+ Amount: amount,
+ ReceiverParty: receiverParty,
+ RecieverIdentifierType: config.receiverIdType,
+ ResultURL: resultURL,
+ QueueTimeOutURL: QueueTimeOutURL,
+ Remarks: remarks,
+ Occasion: occasion,
+ });
+
+ conditionalCallbackData = await callbackTrigger(callbackHandlerInitialized);
+
+ return { reversalsResponse: responseBody.data, conditionalCallbackData };
+ } catch (error) {
+ if (proErrorLogging) {
+ console.info(
+ "\x1b[35m%s\x1b[0m",
+ "Advanced error logging for reversals has been initialized",
+ );
+ logErrorDetails(
+ error,
+ {
+ apiEndpoint: "/mpesa/reversal/v1/request",
+ method: "POST",
+ payload: {
+ transactionId,
+ amount,
+ QueueTimeOutURL,
+ resultURL,
+ receiverParty,
+ initiator,
+ remarks,
+ occasion
+ },
+ },
+ "Transaction reversal error details:",
+ );
+ }
+ throw throwErrorMessages(error);
+ }
+}
+const handleReversalCallbacks = async (app) => {
+ callbackHandlerInitialized = true;
+ await handleCallbacks(app, "reversals");
+};
+
+export { reversals, handleReversalCallbacks };
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/scripts/linenumber.js b/docs/scripts/linenumber.js
new file mode 100644
index 0000000..20b6b09
--- /dev/null
+++ b/docs/scripts/linenumber.js
@@ -0,0 +1,25 @@
+/*global document */
+(function () {
+ var source = document.getElementsByClassName("prettyprint source linenums");
+ var i = 0;
+ var lineNumber = 0;
+ var lineId;
+ var lines;
+ var totalLines;
+ var anchorHash;
+
+ if (source && source[0]) {
+ anchorHash = document.location.hash.substring(1);
+ lines = source[0].getElementsByTagName("li");
+ totalLines = lines.length;
+
+ for (; i < totalLines; i++) {
+ lineNumber++;
+ lineId = "line" + lineNumber;
+ lines[i].id = lineId;
+ if (lineId === anchorHash) {
+ lines[i].className += " selected";
+ }
+ }
+ }
+})();
diff --git a/docs/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/docs/scripts/prettify/Apache-License-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/docs/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js
new file mode 100644
index 0000000..1918eab
--- /dev/null
+++ b/docs/scripts/prettify/lang-css.js
@@ -0,0 +1,36 @@
+PR.registerLangHandler(
+ PR.createSimpleLexer(
+ [["pln", /^[\t\n\f\r ]+/, null, " \t\r\n"]],
+ [
+ ["str", /^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/, null],
+ ["str", /^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/, null],
+ ["lang-css-str", /^url\(([^"')]*)\)/i],
+ [
+ "kwd",
+ /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,
+ null,
+ ],
+ [
+ "lang-css-kw",
+ /^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i,
+ ],
+ ["com", /^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],
+ ["com", /^(?:<\!--|--\>)/],
+ ["lit", /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],
+ ["lit", /^#[\da-f]{3,6}/i],
+ ["pln", /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],
+ ["pun", /^[^\s\w"']+/],
+ ],
+ ),
+ ["css"],
+);
+PR.registerLangHandler(
+ PR.createSimpleLexer(
+ [],
+ [["kwd", /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]],
+ ),
+ ["css-kw"],
+);
+PR.registerLangHandler(PR.createSimpleLexer([], [["str", /^[^"')]+/]]), [
+ "css-str",
+]);
diff --git a/docs/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js
new file mode 100644
index 0000000..d08f9d8
--- /dev/null
+++ b/docs/scripts/prettify/prettify.js
@@ -0,0 +1,740 @@
+var q = null;
+window.PR_SHOULD_USE_CONTINUATION = !0;
+(function () {
+ function L(a) {
+ function m(a) {
+ var f = a.charCodeAt(0);
+ if (f !== 92) return f;
+ var b = a.charAt(1);
+ return (f = r[b])
+ ? f
+ : "0" <= b && b <= "7"
+ ? parseInt(a.substring(1), 8)
+ : b === "u" || b === "x"
+ ? parseInt(a.substring(2), 16)
+ : a.charCodeAt(1);
+ }
+ function e(a) {
+ if (a < 32) return (a < 16 ? "\\x0" : "\\x") + a.toString(16);
+ a = String.fromCharCode(a);
+ if (a === "\\" || a === "-" || a === "[" || a === "]") a = "\\" + a;
+ return a;
+ }
+ function h(a) {
+ for (
+ var f = a
+ .substring(1, a.length - 1)
+ .match(
+ /\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g,
+ ),
+ a = [],
+ b = [],
+ o = f[0] === "^",
+ c = o ? 1 : 0,
+ i = f.length;
+ c < i;
+ ++c
+ ) {
+ var j = f[c];
+ if (/\\[bdsw]/i.test(j)) a.push(j);
+ else {
+ var j = m(j),
+ d;
+ c + 2 < i && "-" === f[c + 1]
+ ? ((d = m(f[c + 2])), (c += 2))
+ : (d = j);
+ b.push([j, d]);
+ d < 65 ||
+ j > 122 ||
+ (d < 65 ||
+ j > 90 ||
+ b.push([Math.max(65, j) | 32, Math.min(d, 90) | 32]),
+ d < 97 ||
+ j > 122 ||
+ b.push([Math.max(97, j) & -33, Math.min(d, 122) & -33]));
+ }
+ }
+ b.sort(function (a, f) {
+ return a[0] - f[0] || f[1] - a[1];
+ });
+ f = [];
+ j = [NaN, NaN];
+ for (c = 0; c < b.length; ++c)
+ (i = b[c]),
+ i[0] <= j[1] + 1 ? (j[1] = Math.max(j[1], i[1])) : f.push((j = i));
+ b = ["["];
+ o && b.push("^");
+ b.push.apply(b, a);
+ for (c = 0; c < f.length; ++c)
+ (i = f[c]),
+ b.push(e(i[0])),
+ i[1] > i[0] && (i[1] + 1 > i[0] && b.push("-"), b.push(e(i[1])));
+ b.push("]");
+ return b.join("");
+ }
+ function y(a) {
+ for (
+ var f = a.source.match(
+ /\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g,
+ ),
+ b = f.length,
+ d = [],
+ c = 0,
+ i = 0;
+ c < b;
+ ++c
+ ) {
+ var j = f[c];
+ j === "("
+ ? ++i
+ : "\\" === j.charAt(0) &&
+ (j = +j.substring(1)) &&
+ j <= i &&
+ (d[j] = -1);
+ }
+ for (c = 1; c < d.length; ++c) -1 === d[c] && (d[c] = ++t);
+ for (i = c = 0; c < b; ++c)
+ (j = f[c]),
+ j === "("
+ ? (++i, d[i] === void 0 && (f[c] = "(?:"))
+ : "\\" === j.charAt(0) &&
+ (j = +j.substring(1)) &&
+ j <= i &&
+ (f[c] = "\\" + d[i]);
+ for (i = c = 0; c < b; ++c)
+ "^" === f[c] && "^" !== f[c + 1] && (f[c] = "");
+ if (a.ignoreCase && s)
+ for (c = 0; c < b; ++c)
+ (j = f[c]),
+ (a = j.charAt(0)),
+ j.length >= 2 && a === "["
+ ? (f[c] = h(j))
+ : a !== "\\" &&
+ (f[c] = j.replace(/[A-Za-z]/g, function (a) {
+ a = a.charCodeAt(0);
+ return "[" + String.fromCharCode(a & -33, a | 32) + "]";
+ }));
+ return f.join("");
+ }
+ for (var t = 0, s = !1, l = !1, p = 0, d = a.length; p < d; ++p) {
+ var g = a[p];
+ if (g.ignoreCase) l = !0;
+ else if (
+ /[a-z]/i.test(
+ g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi, ""),
+ )
+ ) {
+ s = !0;
+ l = !1;
+ break;
+ }
+ }
+ for (
+ var r = { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 },
+ n = [],
+ p = 0,
+ d = a.length;
+ p < d;
+ ++p
+ ) {
+ g = a[p];
+ if (g.global || g.multiline) throw Error("" + g);
+ n.push("(?:" + y(g) + ")");
+ }
+ return RegExp(n.join("|"), l ? "gi" : "g");
+ }
+ function M(a) {
+ function m(a) {
+ switch (a.nodeType) {
+ case 1:
+ if (e.test(a.className)) break;
+ for (var g = a.firstChild; g; g = g.nextSibling) m(g);
+ g = a.nodeName;
+ if ("BR" === g || "LI" === g)
+ (h[s] = "\n"), (t[s << 1] = y++), (t[(s++ << 1) | 1] = a);
+ break;
+ case 3:
+ case 4:
+ (g = a.nodeValue),
+ g.length &&
+ ((g = p
+ ? g.replace(/\r\n?/g, "\n")
+ : g.replace(/[\t\n\r ]+/g, " ")),
+ (h[s] = g),
+ (t[s << 1] = y),
+ (y += g.length),
+ (t[(s++ << 1) | 1] = a));
+ }
+ }
+ var e = /(?:^|\s)nocode(?:\s|$)/,
+ h = [],
+ y = 0,
+ t = [],
+ s = 0,
+ l;
+ a.currentStyle
+ ? (l = a.currentStyle.whiteSpace)
+ : window.getComputedStyle &&
+ (l = document.defaultView
+ .getComputedStyle(a, q)
+ .getPropertyValue("white-space"));
+ var p = l && "pre" === l.substring(0, 3);
+ m(a);
+ return { a: h.join("").replace(/\n$/, ""), c: t };
+ }
+ function B(a, m, e, h) {
+ m && ((a = { a: m, d: a }), e(a), h.push.apply(h, a.e));
+ }
+ function x(a, m) {
+ function e(a) {
+ for (
+ var l = a.d,
+ p = [l, "pln"],
+ d = 0,
+ g = a.a.match(y) || [],
+ r = {},
+ n = 0,
+ z = g.length;
+ n < z;
+ ++n
+ ) {
+ var f = g[n],
+ b = r[f],
+ o = void 0,
+ c;
+ if (typeof b === "string") c = !1;
+ else {
+ var i = h[f.charAt(0)];
+ if (i) (o = f.match(i[1])), (b = i[0]);
+ else {
+ for (c = 0; c < t; ++c)
+ if (((i = m[c]), (o = f.match(i[1])))) {
+ b = i[0];
+ break;
+ }
+ o || (b = "pln");
+ }
+ if (
+ (c = b.length >= 5 && "lang-" === b.substring(0, 5)) &&
+ !(o && typeof o[1] === "string")
+ )
+ (c = !1), (b = "src");
+ c || (r[f] = b);
+ }
+ i = d;
+ d += f.length;
+ if (c) {
+ c = o[1];
+ var j = f.indexOf(c),
+ k = j + c.length;
+ o[2] && ((k = f.length - o[2].length), (j = k - c.length));
+ b = b.substring(5);
+ B(l + i, f.substring(0, j), e, p);
+ B(l + i + j, c, C(b, c), p);
+ B(l + i + k, f.substring(k), e, p);
+ } else p.push(l + i, b);
+ }
+ a.e = p;
+ }
+ var h = {},
+ y;
+ (function () {
+ for (
+ var e = a.concat(m), l = [], p = {}, d = 0, g = e.length;
+ d < g;
+ ++d
+ ) {
+ var r = e[d],
+ n = r[3];
+ if (n) for (var k = n.length; --k >= 0; ) h[n.charAt(k)] = r;
+ r = r[1];
+ n = "" + r;
+ p.hasOwnProperty(n) || (l.push(r), (p[n] = q));
+ }
+ l.push(/[\S\s]/);
+ y = L(l);
+ })();
+ var t = m.length;
+ return e;
+ }
+ function u(a) {
+ var m = [],
+ e = [];
+ a.tripleQuotedStrings
+ ? m.push([
+ "str",
+ /^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,
+ q,
+ "'\"",
+ ])
+ : a.multiLineStrings
+ ? m.push([
+ "str",
+ /^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+ q,
+ "'\"`",
+ ])
+ : m.push([
+ "str",
+ /^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,
+ q,
+ "\"'",
+ ]);
+ a.verbatimStrings && e.push(["str", /^@"(?:[^"]|"")*(?:"|$)/, q]);
+ var h = a.hashComments;
+ h &&
+ (a.cStyleComments
+ ? (h > 1
+ ? m.push(["com", /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, q, "#"])
+ : m.push([
+ "com",
+ /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,
+ q,
+ "#",
+ ]),
+ e.push([
+ "str",
+ /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
+ q,
+ ]))
+ : m.push(["com", /^#[^\n\r]*/, q, "#"]));
+ a.cStyleComments &&
+ (e.push(["com", /^\/\/[^\n\r]*/, q]),
+ e.push(["com", /^\/\*[\S\s]*?(?:\*\/|$)/, q]));
+ a.regexLiterals &&
+ e.push([
+ "lang-regex",
+ /^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/,
+ ]);
+ (h = a.types) && e.push(["typ", h]);
+ a = ("" + a.keywords).replace(/^ | $/g, "");
+ a.length &&
+ e.push(["kwd", RegExp("^(?:" + a.replace(/[\s,]+/g, "|") + ")\\b"), q]);
+ m.push(["pln", /^\s+/, q, " \r\n\t\xa0"]);
+ e.push(
+ ["lit", /^@[$_a-z][\w$@]*/i, q],
+ ["typ", /^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/, q],
+ ["pln", /^[$_a-z][\w$@]*/i, q],
+ [
+ "lit",
+ /^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,
+ q,
+ "0123456789",
+ ],
+ ["pln", /^\\[\S\s]?/, q],
+ ["pun", /^.[^\s\w"-$'./@\\`]*/, q],
+ );
+ return x(m, e);
+ }
+ function D(a, m) {
+ function e(a) {
+ switch (a.nodeType) {
+ case 1:
+ if (k.test(a.className)) break;
+ if ("BR" === a.nodeName)
+ h(a), a.parentNode && a.parentNode.removeChild(a);
+ else for (a = a.firstChild; a; a = a.nextSibling) e(a);
+ break;
+ case 3:
+ case 4:
+ if (p) {
+ var b = a.nodeValue,
+ d = b.match(t);
+ if (d) {
+ var c = b.substring(0, d.index);
+ a.nodeValue = c;
+ (b = b.substring(d.index + d[0].length)) &&
+ a.parentNode.insertBefore(s.createTextNode(b), a.nextSibling);
+ h(a);
+ c || a.parentNode.removeChild(a);
+ }
+ }
+ }
+ }
+ function h(a) {
+ function b(a, d) {
+ var e = d ? a.cloneNode(!1) : a,
+ f = a.parentNode;
+ if (f) {
+ var f = b(f, 1),
+ g = a.nextSibling;
+ f.appendChild(e);
+ for (var h = g; h; h = g) (g = h.nextSibling), f.appendChild(h);
+ }
+ return e;
+ }
+ for (; !a.nextSibling; ) if (((a = a.parentNode), !a)) return;
+ for (
+ var a = b(a.nextSibling, 0), e;
+ (e = a.parentNode) && e.nodeType === 1;
+
+ )
+ a = e;
+ d.push(a);
+ }
+ var k = /(?:^|\s)nocode(?:\s|$)/,
+ t = /\r\n?|\n/,
+ s = a.ownerDocument,
+ l;
+ a.currentStyle
+ ? (l = a.currentStyle.whiteSpace)
+ : window.getComputedStyle &&
+ (l = s.defaultView
+ .getComputedStyle(a, q)
+ .getPropertyValue("white-space"));
+ var p = l && "pre" === l.substring(0, 3);
+ for (l = s.createElement("LI"); a.firstChild; ) l.appendChild(a.firstChild);
+ for (var d = [l], g = 0; g < d.length; ++g) e(d[g]);
+ m === (m | 0) && d[0].setAttribute("value", m);
+ var r = s.createElement("OL");
+ r.className = "linenums";
+ for (var n = Math.max(0, (m - 1) | 0) || 0, g = 0, z = d.length; g < z; ++g)
+ (l = d[g]),
+ (l.className = "L" + ((g + n) % 10)),
+ l.firstChild || l.appendChild(s.createTextNode("\xa0")),
+ r.appendChild(l);
+ a.appendChild(r);
+ }
+ function k(a, m) {
+ for (var e = m.length; --e >= 0; ) {
+ var h = m[e];
+ A.hasOwnProperty(h)
+ ? window.console &&
+ console.warn("cannot override language handler %s", h)
+ : (A[h] = a);
+ }
+ }
+ function C(a, m) {
+ if (!a || !A.hasOwnProperty(a))
+ a = /^\s*= o && (h += 2);
+ e >= c && (a += 2);
+ }
+ } catch (w) {
+ "console" in window && console.log(w && w.stack ? w.stack : w);
+ }
+ }
+ var v = ["break,continue,do,else,for,if,return,while"],
+ w = [
+ [
+ v,
+ "auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile",
+ ],
+ "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof",
+ ],
+ F = [
+ w,
+ "alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where",
+ ],
+ G = [
+ w,
+ "abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient",
+ ],
+ H = [
+ G,
+ "as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var",
+ ],
+ w = [
+ w,
+ "debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN",
+ ],
+ I = [
+ v,
+ "and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None",
+ ],
+ J = [
+ v,
+ "alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END",
+ ],
+ v = [v, "case,done,elif,esac,eval,fi,function,in,local,set,then,until"],
+ K =
+ /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,
+ N = /\S/,
+ O = u({
+ keywords: [
+ F,
+ H,
+ w,
+ "caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END" +
+ I,
+ J,
+ v,
+ ],
+ hashComments: !0,
+ cStyleComments: !0,
+ multiLineStrings: !0,
+ regexLiterals: !0,
+ }),
+ A = {};
+ k(O, ["default-code"]);
+ k(
+ x(
+ [],
+ [
+ ["pln", /^[^]+/],
+ ["dec", /^]*(?:>|$)/],
+ ["com", /^<\!--[\S\s]*?(?:--\>|$)/],
+ ["lang-", /^<\?([\S\s]+?)(?:\?>|$)/],
+ ["lang-", /^<%([\S\s]+?)(?:%>|$)/],
+ ["pun", /^(?:<[%?]|[%?]>)/],
+ ["lang-", /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],
+ ["lang-js", /^
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
tax-Remittance.js
+
+
+
+
import axios from "axios";
+import {
+ generateOAuthToken,
+ encryptSecurityCredential,
+ throwErrorMessages,
+ logErrorDetails,
+ callbackTrigger,
+ validateUrl,
+ handleCallbacks,
+} from "../utils/helpers.js";
+
+// Globals
+let callbackHandlerInitialized = false;
+let conditionalCallbackData = {};
+
+/**
+ * @name taxRemittance
+ * @description This API enables businesses to remit tax to Kenya Revenue Authority (KRA). To use this API, prior integration is required with KRA for tax declaration, payment registration number (PRN) generation, and exchange of other tax-related information.
+ * @summary Enables one to remit tax to Kenya Revenue Authority (KRA)
+ * @see {@link https://developer.safaricom.co.ke/APIs/TaxRemittance open external link}
+ * @param {Object} options Options for the tax remittance API.
+ * @param {string} options.initiator The M-Pesa API operator username with the tax remittance API initiator role.
+ * @param {number} options.amount The transaction amount.
+ * @param {string} options.remarks Information to be associated with the transaction.
+ * @param {number} options.partyA This is your own shortcode from which the money will be deducted.
+ * @param {number} options.partyB The account to which money will be credited.
+ * @param {number} options.accountReference The payment registration number (PRN) issued by KRA
+ * @param {string} options.QueueTimeOutURL URL to notify in case of a request timeout before processing.
+ * @param {string} options.resultURL URL to send transaction results after processing.
+ * @param {boolean} [options.proErrorLogging] Logs out advanced error details - good for debugging
+ * @returns {Promise<Object>} remittanceResponse and (optional) conditionalCallbackData.
+ */
+async function taxRemittance({
+ initiator,
+ amount,
+ partyA,
+ partyB,
+ accountReference,
+ QueueTimeOutURL,
+ remarks,
+ resultURL,
+ proErrorLogging = false,
+}) {
+ if (!callbackHandlerInitialized) {
+ console.info(
+ "\x1b[42m\x1b[30m\x1b[1m The default balance query callback handler ('handleTaxRemittanceCallbacks') was not called. Ignore if handling manually.\x1b[0m",
+ );
+ }
+ try {
+ validateUrl(resultURL, "resultURL");
+ validateUrl(QueueTimeOutURL, "timeoutUrl");
+ const { accessToken, baseURL } = await generateOAuthToken();
+ const req = axios.create({
+ baseURL,
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ // Default configurations
+ const config = {
+ commandId: "PayTaxToKRA",
+ senderIdentifierType: "4",
+ receiverIdentifierType: "4",
+ };
+ const responseBody = await req.post("/mpesa/b2b/v1/remittax", {
+ Initiator: initiator,
+ SecurityCredential: encryptSecurityCredential(),
+ CommandID: config.commandId,
+ SenderIdentifierType: config.senderIdentifierType,
+ RecieverIdentifierType: config.receiverIdentifierType,
+ Amount: amount,
+ PartyA: partyA,
+ PartyB: partyB,
+ AccountReference: accountReference,
+ Remarks: remarks,
+ QueueTimeOutURL: QueueTimeOutURL,
+ ResultURL: resultURL,
+ });
+ conditionalCallbackData = await callbackTrigger(callbackHandlerInitialized);
+ return { remittanceResponse: responseBody.data, conditionalCallbackData };
+ } catch (error) {
+ if (proErrorLogging) {
+ console.info(
+ "\x1b[35m%s\x1b[0m",
+ "Advanced error logging for taxRemittance has been initialized",
+ );
+ logErrorDetails(
+ error,
+ {
+ apiEndpoint: "/mpesa/b2b/v1/remittax",
+ method: "POST",
+ payload: {
+ initiator,
+ amount,
+ partyA,
+ partyB,
+ accountReference,
+ QueueTimeOutURL,
+ resultURL,
+ remarks
+ },
+ },
+ "Tax remittance error details:",
+ );
+ }
+ throw throwErrorMessages(error);
+ }
+}
+
+const handleTaxRemittanceCallbacks = async (app) => {
+ callbackHandlerInitialized = true;
+ await handleCallbacks(app, "taxRemittance");
+};
+
+export { taxRemittance, handleTaxRemittanceCallbacks };
+