Coder Social home page Coder Social logo

mpesa-rust's Introduction

mpesa-rust

Rust

Discord

About

An unofficial Rust wrapper around the Safaricom API for accessing M-Pesa services.

Install

Cargo.toml

[dependencies]
mpesa = { version = "1" }

Optionally, you can disable default-features, which is basically the entire suite of MPESA APIs to conditionally select individual features. (See Services table for the full list of Cargo features)

Example:

[dependencies]
mpesa = { version = "1", default_features = false, features = ["b2b", "express_request"] }

In your lib or binary crate:

use mpesa::Mpesa;

Usage

Creating a Mpesa client

You will first need to create an instance of the Mpesa instance (the client). You are required to provide a CONSUMER_KEY and CONSUMER_SECRET. Here is how you can get these credentials for the Safaricom sandbox environment. It's worth noting that these credentials are only valid in the sandbox environment. To go live and get production keys read the docs here.

These are the following ways you can instantiate Mpesa:

use mpesa::{Mpesa, Environment};

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let client = Mpesa::new(
        dotenvy::var("CONSUMER_KEY").unwrap(),
        dotenvy::var("CONSUMER_SECRET").unwrap(),
        Environment::Sandbox,
    );

    assert!(client.is_connected().await);
}

Since the Environment enum implements FromStr and TryFrom for String and &str types, you can call Environment::from_str or Environment::try_from to create an Environment type. This is ideal if the environment values are stored in a .env or any other configuration file:

use mpesa::{Mpesa, Environment};
use std::convert::TryFrom;
use std::error::Error;
use std::str::FromStr;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    dotenvy::dotenv().ok();

    let client = Mpesa::new(
        dotenvy::var("CONSUMER_KEY").unwrap(),
        dotenvy::var("CONSUMER_SECRET").unwrap(),
        Environment::from_str("sandbox")?, // or
        // Environment::try_from("sandbox")?,
    );

    assert!(client.is_connected().await);
    Ok(())
}

The Mpesa struct's environment parameter is generic over any type that implements the ApiEnvironment trait. This trait expects the following methods to be implemented for a given type:

pub trait ApiEnvironment {
    fn base_url(&self) -> &str;
    fn get_certificate(&self) -> &str;
}

This trait allows you to create your own type to pass to the environment parameter. With this in place, you are able to mock http requests (for testing purposes) from the MPESA api by returning a mock server uri from the base_url method as well as using your own certificates, required to sign select requests to the MPESA api, by providing your own get_certificate implementation.

See the example below (and here so see how the trait is implemented for the Environment enum):

use mpesa::{Mpesa, ApiEnvironment};

#[derive(Clone)]
pub struct CustomEnvironment;

impl ApiEnvironment for CustomEnvironment {
    fn base_url(&self) -> &str {
        // your base url here
        "https://your_base_url.com"
    }

    fn get_certificate(&self) -> &str {
        // your certificate here
        r#"..."#
    }
}

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let client = Mpesa::new(
        dotenvy::var("CONSUMER_KEY").unwrap(),
        dotenvy::var("CONSUMER_SECRET").unwrap(),
        CustomEnvironment,
    );
}

If you intend to use in production, you will need to call a the set_initiator_password method from Mpesa after initially creating the client. Here you provide your initiator password, which overrides the default password used in sandbox "Safcom496!":

use mpesa::{Mpesa, Environment};

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let client = Mpesa::new(
        dotenvy::var("CONSUMER_KEY").unwrap(),
        dotenvy::var("CONSUMER_SECRET").unwrap(),
        Environment::Sandbox,
    );

    client.set_initiator_password("new_password");
    assert!(client.is_connected().await)
}

Services

The table below shows all the MPESA APIs from Safaricom and those supported by the crate along with their cargo features and usage examples

API Cargo Feature Status Example
Account Balance account_balance Stable ✅ account balance example
B2B Express Checkout N/A Unimplemented N/A
Bill Manager bill_manager Unstable ⚠️ bill manager examples
Business Buy Goods b2b Stable ✅ business buy goods example
Business Pay Bill N/A Unimplemented N/A
Business To Customer (B2C) b2c Stable ✅️ b2c example
Customer To Business (Register URL) c2b_register Stable ✅️ c2b register example
Customer To Business (Simulate) c2b_simulate Stable ✅️ c2b simulate example
Dynamic QR dynamic_qr Stable ✅️ dynamic qr example
M-PESA Express (Query) express Stable ✅️ ️ express query example
M-PESA Express (Simulate)/ STK push express Stable ✅️ express request example
Transaction Status transaction_status Stable ✅️ transaction status example
Transaction Reversal transaction_reversal Stable ✅️ transaction reversal example
Tax Remittance N/A Unimplemented N/A

Author

Collins Muriuki

  • Twitter: @c12i_
  • Not affiliated with Safaricom.

Contributing

Contributions, issues and feature requests are welcome!
Feel free to check issues page. You can also take a look at the contributing guide.

Made with contrib.rocks.


Copyright © 2023 Collins Muriuki.
This project is MIT licensed.

mpesa-rust's People

Contributors

borwe avatar c12i avatar crispinkoech avatar dxphilo avatar itsyaasir avatar tevinthuku avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mpesa-rust's Issues

Create a standardized http response error struct

At the moment, failed requests return a MpesaError with the variant respective of the service enclosing a serde_json::Value.

Rather than storing the error details as serde_json::Value, we should create an error struct that would store the information returned by the api error response, which usually takes the shape:

{
    "requestId": "119407-259475624-1",
    "errorCode": "401.003.01",
    "errorMessage": "Error Occurred"
}

This will greatly improve DX and error reporting since the end user will not need to deserialize the serde_json::Value to a custom struct for example in order to get the failure details.

Cache `access_token` and re-use for subsequent request

Currently, we are fetching new key for any request we make, while I don't think there's any problems with it, we can make it more efficient and save on more calls.

My proposal is we can cache the access token which is valid for one hour, and reuse that for the subsequent calls, until the key expires (which is normally one hour). I think this should be pretty easy to make it.

What do you think @collinsmuriuki ?

RUSTSEC-2020-0071: Potential segfault in the time crate

Potential segfault in the time crate

Details
Package time
Version 0.1.43
URL time-rs/time#293
Date 2020-11-18
Patched versions >=0.2.23
Unaffected versions =0.2.0,=0.2.1,=0.2.2,=0.2.3,=0.2.4,=0.2.5,=0.2.6

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

The affected functions from time 0.2.7 through 0.2.22 are:

  • time::UtcOffset::local_offset_at
  • time::UtcOffset::try_local_offset_at
  • time::UtcOffset::current_local_offset
  • time::UtcOffset::try_current_local_offset
  • time::OffsetDateTime::now_local
  • time::OffsetDateTime::try_now_local

The affected functions in time 0.1 (all versions) are:

  • at
  • at_utc

Non-Unix targets (including Windows and wasm) are unaffected.

Patches

Pending a proper fix, the internal method that determines the local offset has been modified to always return None on the affected operating systems. This has the effect of returning an Err on the try_* methods and UTC on the non-try_* methods.

Users and library authors with time in their dependency tree should perform cargo update, which will pull in the updated, unaffected code.

Users of time 0.1 do not have a patch and should upgrade to an unaffected version: time 0.2.23 or greater or the 0.3. series.

Workarounds

No workarounds are known.

References

time-rs/time#293

See advisory page for additional details.

RUSTSEC-2020-0159: Potential segfault in `localtime_r` invocations

Potential segfault in localtime_r invocations

Details
Package chrono
Version 0.4.19
URL chronotope/chrono#499
Date 2020-11-10

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

Workarounds

No workarounds are known.

References

See advisory page for additional details.

RUSTSEC-2021-0141: dotenv is Unmaintained

dotenv is Unmaintained

Details
Status unmaintained
Package dotenv
Version 0.15.0
URL dotenv-rs/dotenv#74
Date 2021-12-24

dotenv by description is meant to be used in development or testing only.

Using this in production may or may not be advisable.

Alternatives

The below may or may not be feasible alternative(s):

See advisory page for additional details.

Employ builder pattern

Will prevent passing of many function parameters to Client methods.

For each service, we will need a builder struct. The builder struct will have adaptor methods that basically accept mut self as a parameter, and return self on transforming the data.

The builder consumer method will build the main struct and perform the necessary api call

Fix & Refactor `bill_manager` API

  • Current bill manager implementation is inconsistent with the current bill manager API
  • Once these inconsistencies are addressed, the doc tests in /docs/bill_manager/ can be enabled to tests against the sandbox API

Clean up error enum

Currrent enum is pretty much a ball of mud enum that exposes unnecessary internal implementations that the end user doesn't really need to know about and continues to grow bigger and bigger with every new feature that gets added to the SDK.

Suggestion would be to separate internal and user facing errors from the enum, ultimately we would have the following variants (pseudocode)

HttpError(ApiError)
InternalError(InternalErrors)
ValidationError(&'static str)

Early `Error` return if a required param is missing

Currently we don’t have any protection against sending an empty required field. We have to return early error.
Take a look at transaction_reversal.rs file and see how it’s implemented. We need to add a new Error type.

MIgrate to `dotenvy`

dotenv crate is no longer maintained, best alternative suggested is dotenvy which is fork of the original crate dotenv.
See this issue

RUSTSEC-2020-0036: failure is officially deprecated/unmaintained

failure is officially deprecated/unmaintained

Details
Status unmaintained
Package failure
Version 0.1.8
URL rust-lang-deprecated/failure#347
Date 2020-05-02

The failure crate is officially end-of-life: it has been marked as deprecated
by the former maintainer, who has announced that there will be no updates or
maintenance work on it going forward.

The following are some suggested actively developed alternatives to switch to:

See advisory page for additional details.

`CommandID` fields are not needed in the Builders

Each API has CommandID field which are filled with that certain API request you're doing. While it's optional to fill it, I just don't see the need to have it since it Errors out if the client inputs any other CommandID other than required one.
I suggest we get rid of CommandID setter and fill with the respective CommandID's in the respective API.

Optimize CI

Current build time is almost 10 minutes +, needs fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.