Skip to content

Commit

Permalink
Update docs and extract constant types
Browse files Browse the repository at this point in the history
  • Loading branch information
jwambugu committed Feb 1, 2024
1 parent 9c51574 commit cb21800
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 103 deletions.
136 changes: 60 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

Mpesa Golang SDK facilitates in integrating M-pesa APIS into your go project. The following APIs are currently supported:

| API | Description |
|-------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| [Authorization](https://developer.safaricom.co.ke/APIs/Authorization) | Generates an access token for authenticating APIs |
| [Lipa Na M-Pesa Online API](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate) | Initiates online payment on behalf of a customer. |
| [Business To Customer (B2C) ](https://developer.safaricom.co.ke/APIs/BusinessToCustomer) | Transact between an M-Pesa short code to a phone number registered on M-Pesa. |
| [M-Pesa Express Query](https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query) | Check the status of a Lipa Na M-Pesa Online Payment. |
| API | Description |
|-------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Authorization](https://developer.safaricom.co.ke/APIs/Authorization) | Generates an access token for authenticating APIs |
| [Lipa Na M-Pesa Online API](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate) | Initiates online payment on behalf of a customer. |
| [Business To Customer (B2C) ](https://developer.safaricom.co.ke/APIs/BusinessToCustomer) | Transact between an M-Pesa short code to a phone number registered on M-Pesa. |
| [M-Pesa Express Query](https://developer.safaricom.co.ke/APIs/MpesaExpressQuery) | Check the status of a Lipa Na M-Pesa Online Payment. |
| [Dynamic QR](https://developer.safaricom.co.ke/APIs/DynamicQRCode) | Generates a dynamic M-PESA QR Code 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 then authorize to pay for goods and services at select LIPA NA M-PESA (LNM) merchant outlets. |
| [Transaction Status](https://developer.safaricom.co.ke/APIs/TransactionStatus) | Check the status of a transaction. |
| [Account Balance](https://developer.safaricom.co.ke/APIs/AccountBalance) | Enquire the balance on an M-Pesa BuyGoods (Till Number). |
| [Business Pay Bill](https://developer.safaricom.co.ke/APIs/BusinessPayBill) | This API enables you to pay bills directly from your business account to a pay bill number, or a paybill store. |

## Getting Started

Expand All @@ -34,6 +38,8 @@ The SDK supports the following environments:

### Examples

More examples can be found [here](https://github.com/jwambugu/mpesa-golang-sdk/examples)

```go
package main

Expand All @@ -46,75 +52,51 @@ import (
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

mpesaApp := mpesa.NewApp(http.DefaultClient, "CONSUMER_KEY_GOES_HERE", "CONSUMER_SECRET_GOES_HERE", mpesa.Sandbox)

stkPushRes, err := mpesaApp.STKPush(ctx, "PASSKEY_GOES_HERE", mpesa.STKPushRequest{
BusinessShortCode: 174379,
TransactionType: "CustomerPayBillOnline",
Amount: 10,
PartyA: 254708374149,
PartyB: 174379,
PhoneNumber: 254708374149,
CallBackURL: "https://example.com",
AccountReference: "Test",
TransactionDesc: "Test Request",
})

if err != nil {
log.Fatalln(err)
}

log.Printf("%+v", stkPushRes)

b2cRes, err := mpesaApp.B2C(ctx, "INITIATOR_PASSWORD_GOES_HERE", mpesa.B2CRequest{
InitiatorName: "TestG2Init",
CommandID: "BusinessPayment",
Amount: 10,
PartyA: 600123,
PartyB: 254728762287,
Remarks: "This is a remark",
QueueTimeOutURL: "https://example.com",
ResultURL: "https://example.com",
Occasion: "Test Occasion",
})

if err != nil {
log.Fatalln(err)
}

log.Printf("%+v", b2cRes)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

mpesaApp := mpesa.NewApp(http.DefaultClient, "CONSUMER_KEY", "CONSUMER_SECRET", mpesa.EnvironmentSandbox)

stkResp, err := mpesaApp.STKPush(ctx, "YOUR_PASSKEY", mpesa.STKPushRequest{
BusinessShortCode: 174379,
TransactionType: mpesa.CustomerPayBillOnlineTransactionType,
Amount: 10,
PartyA: 254708374149,
PartyB: 174379,
PhoneNumber: 254708374149,
CallBackURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
AccountReference: "Test reference",
TransactionDesc: "Test description",
})

if err != nil {
log.Fatalf("stk: %v\n", err)
}

log.Printf("stk: %+v\n", stkResp)

stkQueryRes, err := mpesaApp.STKQuery(ctx, "YOUR_PASSKEY", mpesa.STKQueryRequest{
BusinessShortCode: 174379,
CheckoutRequestID: "ws_CO_260520211133524545",
})

if err != nil {
log.Fatalf("stk query: %v\n", err)
}

log.Printf("stk query %+v\n", stkQueryRes)

stkQueryRes, err := mpesaApp.STKQuery(ctx, "PASSKEY_GOES_HERE", mpesa.STKQueryRequest{
BusinessShortCode: 174379,
CheckoutRequestID: "ws_CO_03082022131319635708374149", // STK PUSH CheckoutRequestID,
})

if err != nil {
log.Fatalln(err)
}

log.Printf("%+v", stkQueryRes)
}
```

### Processing Callbacks
The SDK adds a helper functions to decode callbacks. These are:
1. `mpesa.UnmarshalSTKPushCallback(v)`
2. `mpesa.UnmarshalB2CCallback(v)`

The following types are supported, any other type will be decoded using `json.Unmarshal`

| Supported Types |
|-----------------|
| string |
| *http.Request |
2. `mpesa.UnmarshalCallback(v)` for all other callbacks received

```go
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
callback, err := mpesa.UnmarshalB2CCallback(r)
callback, err := mpesa.UnmarshalB2CCallback(r.Body)
if err != nil {
log.Fatalln(err)
}
Expand All @@ -123,17 +105,19 @@ mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
})


callback, err := mpesa.UnmarshalSTKPushCallback(`
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": 1032,
"ResultDesc": "Request cancelled by user."
}
}
}`)
data := strings.NewReader(`
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": 1032,
"ResultDesc": "Request cancelled by user."
}
}
}`)

callback, err := mpesa.UnmarshalSTKPushCallback(data)ack()

if err != nil {
log.Fatalln(err)
Expand Down
51 changes: 31 additions & 20 deletions api-schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,42 @@ const (
type CommandID string

const (
// AccountBalance command ID is applied when getting the account balance of a shortcode
AccountBalance CommandID = "AccountBalance"
// AccountBalanceCommandID is applied when getting the account balance of a shortcode
AccountBalanceCommandID CommandID = "AccountBalance"

// BusinessPayBill coomand ID is applied for BusinessPayBillRequest
BusinessPayBill CommandID = "BusinessPayBill"
// BusinessPayBillCommandID is applied for BusinessPayBillRequest
BusinessPayBillCommandID CommandID = "BusinessPayBill"

// BusinessPayment is a normal business to customer payment, supports only M-PESA registered customers.
BusinessPayment CommandID = "BusinessPayment"
// BusinessPaymentCommandID is a normal business to customer payment, supports only M-PESA registered customers.
BusinessPaymentCommandID CommandID = "BusinessPayment"

// PromotionPayment is a promotional payment to customers. The M-PESA notification message is a congratulatory
// PromotionPaymentCommandID is a promotional payment to customers. The M-PESA notification message is a congratulatory
// message. Supports only M-PESA registered customers.
PromotionPayment CommandID = "PromotionPayment"
PromotionPaymentCommandID CommandID = "PromotionPayment"

// SalaryPayment command is used for sending money to both registered and unregistered M-Pesa customers.
SalaryPayment CommandID = "SalaryPayment"
// SalaryPaymentCommandID is used for sending money to both registered and unregistered M-Pesa customers.
SalaryPaymentCommandID CommandID = "SalaryPayment"

// TransactionStatusQuery command ID is applied when getting the status of a transaction.
TransactionStatusQuery CommandID = "TransactionStatusQuery"
// TransactionStatusQueryCommandID is applied when getting the status of a transaction.
TransactionStatusQueryCommandID CommandID = "TransactionStatusQuery"
)

// IdentifierType is the type of organization receiving the transaction
type IdentifierType uint8

const ShortcodeIdentifierType IdentifierType = 4

// TransactionType is used ti identify the type of the transaction being made.
type TransactionType string

const (
// CustomerBuyGoodsOnlineTransactionType us used to STK push requests for till numbers.
CustomerBuyGoodsOnlineTransactionType = "CustomerBuyGoodsOnline"

// CustomerPayBillOnlineTransactionType us used to STK push requests for paybill numbers.
CustomerPayBillOnlineTransactionType = "CustomerPayBillOnline"
)

type (
// AuthorizationResponse is returned when trying to authenticate the app using provided credentials
AuthorizationResponse struct {
Expand Down Expand Up @@ -69,7 +80,7 @@ type (

// TransactionType identifies the transaction when sending the request to M-Pesa. Expects CustomerPayBillOnline
// or CustomerBuyGoodsOnline
TransactionType string `json:"TransactionType"`
TransactionType TransactionType `json:"TransactionType"`

// Amount to be transacted which will be deducted from the customer.
Amount uint `json:"Amount,omitempty"`
Expand Down Expand Up @@ -227,9 +238,9 @@ type (

/*
CommandID is a unique command that specifies B2C transaction type.
- SalaryPayment: This supports sending money to both registered and unregistered M-Pesa customers.
- BusinessPayment: This is a normal business to customer payment,supports only M-Pesa registered customers.
- PromotionPayment: This is a promotional payment to customers. The M-Pesa notification message is a
- SalaryPaymentCommandID: This supports sending money to both registered and unregistered M-Pesa customers.
- BusinessPaymentCommandID: This is a normal business to customer payment,supports only M-Pesa registered customers.
- PromotionPaymentCommandID: This is a promotional payment to customers. The M-Pesa notification message is a
congratulatory message and supports only M-Pesa registered customers.
*/
CommandID CommandID `json:"CommandID"`
Expand Down Expand Up @@ -406,7 +417,7 @@ type (
}

TransactionStatusRequest struct {
// The CommandID for the request - TransactionStatusQuery
// The CommandID for the request - TransactionStatusQueryCommandID
CommandID CommandID `json:"CommandID"`

// IdentifierType is the type of organization receiving the transaction
Expand All @@ -423,7 +434,7 @@ type (
OriginatorConversationID string `json:"OriginatorConversationID,omitempty"`

// PartyA is an Organization/MSISDN receiving the transaction. Shortcode (6-9 digits) MSISDN (12 Digits)
PartyA string `json:"PartyA"`
PartyA uint `json:"PartyA"`

// QueueTimeOutURL is the endpoint that will be used by API Proxy to send notification incase the request is timed
// out while awaiting processing in the queue. Must be served via https.
Expand All @@ -444,7 +455,7 @@ type (
}

AccountBalanceRequest struct {
// The CommandID for the request - AccountBalance
// The CommandID for the request - AccountBalanceCommandID
CommandID CommandID `json:"CommandID"`

// IdentifierType is the type of organization fetching the balance - Shortcode (Till Number Organization shortcode)
Expand Down Expand Up @@ -478,7 +489,7 @@ type (
// Amount is the transaction amount.
Amount uint `json:"Amount"`

// The CommandID for the request - BusinessPayBill
// The CommandID for the request - BusinessPayBillCommandID
CommandID CommandID `json:"CommandID"`

// Initiator is the credential/username used to authenticate the request.
Expand Down
95 changes: 95 additions & 0 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"context"
"github.com/jwambugu/mpesa-golang-sdk"
"log"
"net/http"
"time"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

mpesaApp := mpesa.NewApp(http.DefaultClient, "CONSUMER_KEY", "CONSUMER_SECRET", mpesa.EnvironmentSandbox)

stkResp, err := mpesaApp.STKPush(ctx, "YOUR_PASSKEY", mpesa.STKPushRequest{
BusinessShortCode: 174379,
TransactionType: mpesa.CustomerPayBillOnlineTransactionType,
Amount: 10,
PartyA: 254708374149,
PartyB: 174379,
PhoneNumber: 254708374149,
CallBackURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
AccountReference: "Test reference",
TransactionDesc: "Test description",
})

if err != nil {
log.Fatalf("stk: %v\n", err)
}

log.Printf("stk: %+v\n", stkResp)

stkQueryRes, err := mpesaApp.STKQuery(ctx, "YOUR_PASSKEY", mpesa.STKQueryRequest{
BusinessShortCode: 174379,
CheckoutRequestID: "ws_CO_260520211133524545",
})

if err != nil {
log.Fatalf("stk query: %v\n", err)
}

log.Printf("stk query %+v\n", stkQueryRes)

txnStatusRes, err := mpesaApp.GetTransactionStatus(ctx, "Safaricom999!*!", mpesa.TransactionStatusRequest{
IdentifierType: mpesa.ShortcodeIdentifierType,
Initiator: "YOUR_INITIATOR_NAME",
Occasion: "Test occassion",
OriginatorConversationID: "",
PartyA: 600426,
QueueTimeOutURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
Remarks: "Test remarks",
ResultURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
TransactionID: "SB162HIYLY",
})

if err != nil {
log.Fatalf("txn status: %v\n", err)
}

log.Printf("txn status: %+v\n", txnStatusRes)

b2cRes, err := mpesaApp.B2C(ctx, "Safaricom999!*!", mpesa.B2CRequest{
InitiatorName: "YOUR_INITIATOR_NAME",
CommandID: mpesa.BusinessPaymentCommandID,
Amount: 10,
PartyA: 600426,
PartyB: 254708374149,
Remarks: "This is a remark",
QueueTimeOutURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
ResultURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
Occasion: "Test Occasion",
})

if err != nil {
log.Fatalf("b2c: %v\n", err)
}

log.Printf("b2c: %+v\n", b2cRes)

accBal, err := mpesaApp.GetAccountBalance(ctx, "Safaricom999!*!", mpesa.AccountBalanceRequest{
Initiator: "YOUR_INITIATOR_NAME",
PartyA: 600981,
QueueTimeOutURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
Remarks: "Test Local",
ResultURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
})

if err != nil {
log.Fatalf("account balance: %v\n", err)
}

log.Printf("account balance: %+v\n", accBal)
}
Loading

0 comments on commit cb21800

Please sign in to comment.