A Go package for integrating with the IntouchPay Payments Gateway API. This package provides a simple and easy-to-use interface for processing mobile money payments, deposits, balance inquiries, and transaction status checks.
- ✅ Request Payment - Initiate payment requests to subscribers
- ✅ Request Deposit - Send deposits to subscribers
- ✅ Get Balance - Query account balance
- ✅ Get Transaction Status - Check transaction status
- ✅ Full API v1.2 compliance
- ✅ Automatic password generation using SHA256
- ✅ UTC timestamp handling
- ✅ Testable architecture - Mockable authentication and HTTP layers
- ✅ Option pattern - Flexible client configuration
- ✅ Typed errors - APIError and ValidationError types
This package includes special documentation for AI coding assistants (Claude, GPT-4, GitHub Copilot, Cursor, etc.).
If you are an AI coding agent tasked with integrating this package, please read the detailed integration guide:
This guide contains:
- Complete integration checklist
- Required credentials and how to request them
- Code examples for all API operations
- Callback/webhook implementation
- Error handling patterns
- Testing with mocks
- Complete working example
Copy and paste this prompt to your AI coding assistant to integrate go-intouchpay into your project:
I need you to integrate the go-intouchpay payment gateway package into my Go project.
Please follow the instructions in this document:
https://raw.githubusercontent.com/samueltuyizere/go-intouchpay/refs/heads/master/docs/AI_AGENT_INTEGRATION.md
The package documentation is at:
https://github.com/samueltuyizere/go-intouchpay
Before you start, I'll provide my IntouchPay credentials:
- Username: [YOUR_USERNAME]
- Account Number: [YOUR_ACCOUNT_NUMBER]
- Partner Password: [YOUR_PARTNER_PASSWORD]
- Callback URL: [YOUR_CALLBACK_URL]
- Service ID (SID): [0 or 1]
Please:
1. Install the package
2. Create a client with my credentials
3. Implement [payment request / deposit / balance / all operations]
4. Set up the callback webhook handler
5. Add proper error handling
6. Use environment variables for credentials (not hardcoded values)
go get github.com/samueltuyizere/go-intouchpayBefore using this package, you need to have:
- An IntouchPay account with:
- Username
- Account Number
- Partner Password
- A callback URL (webhook endpoint) for receiving transaction status updates (optional but recommended)
- Service ID (SID) - Set to
1for Bulk Payments,0otherwise
package main
import (
"fmt"
"log"
Intouchpay "github.com/samueltuyizere/go-intouchpay"
)
func main() {
// Initialize the IntouchPay client
client := Intouchpay.NewClient(
"your_username", // Username assigned to your account
"your_account_number", // Account number
"your_partner_password", // Partner password
"https://yourdomain.com/callback", // Callback URL (optional)
0, // Service ID (0 or 1)
)
// Use the client to make API calls...
}For more flexible configuration, use the options pattern:
client := Intouchpay.NewClientWithOptions(
"your_username",
"your_account_number",
"your_partner_password",
Intouchpay.WithCallbackURL("https://yourdomain.com/callback"),
Intouchpay.WithSid(0),
Intouchpay.WithTimeout(30 * time.Second),
)Available options:
WithTimeout(duration)- Set HTTP client timeoutWithHTTPClient(*http.Client)- Use a custom HTTP clientWithCallbackURL(url)- Set callback URLWithSid(sid)- Set service IDWithAuthenticator(auth)- Use a custom authenticator (for testing)
Request a payment from a subscriber. The transaction will be pending until the subscriber confirms it.
// Prepare payment parameters
params := &Intouchpay.RequestPaymentParams{
Amount: 1000, // Amount to be paid (positive integer, no decimals)
MobilePhone: "250788888888", // Mobile phone number making the payment
RequestTransactionId: "unique_txn_id_12345", // Unique transaction ID from your system
}
// Make the payment request
response, err := client.RequestPayment(params)
if err != nil {
log.Fatal("Payment request failed:", err)
}
// Check the response
if response.Success {
fmt.Printf("Transaction Status: %s\n", response.Status)
fmt.Printf("Transaction ID: %s\n", response.TransactionId)
fmt.Printf("Response Code: %s\n", response.ResponseCode)
fmt.Printf("Message: %s\n", response.Message)
} else {
fmt.Printf("Payment request failed: %s\n", response.Message)
}Response Example:
{
"status": "Pending",
"requesttransactionid": "unique_txn_id_12345",
"success": true,
"responsecode": "1000",
"transactionid": "1425",
"message": "Transaction Pending"
}Note: After the subscriber confirms the transaction, IntouchPay will send a POST request to your callback URL with the final transaction status.
Send a deposit to a subscriber. This is processed immediately.
// Prepare deposit parameters
params := &Intouchpay.RequestDepositParams{
Amount: 5000, // Amount to deposit (positive integer, no decimals)
WithdrawCharge: 0, // Set to 1 to include withdraw charges in amount
Reason: "Payment for services", // Reason for deposit
MobilePhone: "250788888888", // Mobile phone number receiving the deposit
RequestTransactionId: "unique_deposit_id_67890", // Unique transaction ID from your system
}
// Make the deposit request
response, err := client.RequestDeposit(params)
if err != nil {
log.Fatal("Deposit request failed:", err)
}
// Check the response
if response.Success {
fmt.Printf("Deposit successful!\n")
fmt.Printf("Reference ID: %s\n", response.ReferenceId)
fmt.Printf("Response Code: %s\n", response.ResponseCode)
} else {
fmt.Printf("Deposit failed: %s\n", response.ResponseCode)
}Response Example (Success):
{
"requesttransactionid": "unique_deposit_id_67890",
"referenceid": "312333883",
"responsecode": "2001",
"success": true
}Query your account balance.
// Get account balance
balance, err := client.GetBalance()
if err != nil {
log.Fatal("Balance inquiry failed:", err)
}
if balance.Success {
fmt.Printf("Account Balance: %s\n", balance.Balance)
} else {
fmt.Printf("Failed to get balance: %s\n", balance.Message)
}Response Example:
{
"balance": "100000.0",
"success": true
}Check the status of a previously initiated transaction.
// Prepare status check parameters
params := &Intouchpay.GetTransactionStatusParams{
RequestTransactionId: "unique_txn_id_12345", // Your transaction ID
TransactionId: "1425", // IntouchPay transaction ID
}
// Get transaction status
status, err := client.GetTransactionStatus(params)
if err != nil {
log.Fatal("Status check failed:", err)
}
if status.Success {
fmt.Printf("Transaction Status: %s\n", status.Status)
fmt.Printf("Message: %s\n", status.Message)
fmt.Printf("Response Code: %s\n", status.ResponseCode)
} else {
fmt.Printf("Status check failed: %s\n", status.Message)
}Response Example:
{
"success": true,
"responsecode": "01",
"status": "Successfully",
"message": "Transaction Successful"
}For testing without real credentials, use a mock authenticator:
// Create a mock authenticator
type MockAuthenticator struct {
Creds Intouchpay.Credentials
}
func (m *MockAuthenticator) Authenticate() Intouchpay.Credentials {
return m.Creds
}
// Use it in tests
mockAuth := &MockAuthenticator{
Creds: Intouchpay.Credentials{
Username: "test_user",
Timestamp: "20260320120000",
Password: "test_hash",
},
}
client := Intouchpay.NewClientWithAuth(mockAuth)For testing without network calls, use a mock HTTP client:
// Create a mock HTTP client
type MockHTTPClient struct {
Response *map[string]interface{}
Error error
}
func (m *MockHTTPClient) Do(endpoint string, body interface{}) (*map[string]interface{}, error) {
return m.Response, m.Error
}
// Use it in tests
mockHTTP := &MockHTTPClient{
Response: &map[string]interface{}{
"success": true,
"balance": 10000.50,
},
}
client := Intouchpay.NewClientWithHTTPClient(mockAuth, mockHTTP)go test ./...Run with coverage:
go test -cover ./...The package provides typed errors for better error handling:
response, err := client.RequestPayment(params)
if err != nil {
// Check for API errors
if Intouchpay.IsAPIError(err) {
apiErr := err.(*Intouchpay.APIError)
log.Printf("API Error: %d - %s", apiErr.StatusCode, apiErr.Status)
}
// Check for validation errors
if Intouchpay.IsValidationError(err) {
valErr := err.(*Intouchpay.ValidationError)
log.Printf("Validation Error: %s - %s", valErr.Field, valErr.Message)
}
return
}
if !response.Success {
// Handle API-level errors
switch response.ResponseCode {
case "2400":
log.Println("Duplicate transaction ID")
case "1005":
log.Println("Insufficient funds")
case "1002":
log.Println("Mobile number not registered")
default:
log.Printf("Payment failed: %s", response.Message)
}
return
}
// Success case
log.Println("Payment request successful")-
APIError - Returned when the API returns a non-200 status code
StatusCode- HTTP status codeStatus- HTTP status textResponse- Parsed API responseMessage- Error message
-
ValidationError - Returned for client-side validation failures
Field- The field that failed validationMessage- Error message
This package follows deep module design principles for testability:
┌─────────────────────────────────────────────────────┐
│ Client │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Authenticator│ │ APIRequester │ │
│ │ (interface) │ │ (interface) │ │
│ └──────────────┘ └──────────────┘ │
│ ↓ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ sha256Auth │ │ defaultHTTPClient │ │
│ │ (production) │ │ (production) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
-
Authenticator - Generates authentication credentials
Authenticate() Credentials
-
APIRequester - Makes HTTP requests to the API
Do(endpoint string, body interface{}) (*map[string]interface{}, error)
// With mock authenticator
client := Intouchpay.NewClientWithAuth(mockAuth)
// With mock HTTP client
client := Intouchpay.NewClientWithHTTPClient(mockAuth, mockHTTP)
// With options
client := Intouchpay.NewClientWithOptions(
"username", "account", "password",
Intouchpay.WithTimeout(30*time.Second),
Intouchpay.WithAuthenticator(mockAuth),
Intouchpay.WithHTTPClientInterface(mockHTTP),
)When you initiate a payment request, IntouchPay will send a POST request to your callback URL with the transaction status. You need to implement an endpoint to receive these callbacks.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type CallbackPayload struct {
JSONPayload struct {
RequestTransactionId string `json:"requesttransactionid"`
TransactionId string `json:"transactionid"`
ResponseCode string `json:"responsecode"`
Status string `json:"status"`
StatusDesc string `json:"statusdesc"`
ReferenceNo string `json:"referenceno"`
} `json:"jsonpayload"`
}
func callbackHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var payload CallbackPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Process the callback
fmt.Printf("Transaction ID: %s\n", payload.JSONPayload.TransactionId)
fmt.Printf("Status: %s\n", payload.JSONPayload.Status)
fmt.Printf("Response Code: %s\n", payload.JSONPayload.ResponseCode)
// Respond to IntouchPay
response := map[string]interface{}{
"message": "success",
"success": true,
"request_id": payload.JSONPayload.RequestTransactionId,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/callback", callbackHandler)
http.ListenAndServe(":8080", nil)
}| Code | Description |
|---|---|
| 1000 | Pending |
| 01 | Successfully |
| 0002 | Missing Username Information |
| 0003 | Missing Password Information |
| 0004 | Missing Date Information |
| 0005 | Invalid Password |
| 0006 | User Does not have an intouchPay Account |
| 0007 | No such user |
| 0008 | Failed to Authenticate |
| 2100 | Amount should be greater than 0 |
| 2200 | Amount below minimum |
| 2300 | Amount above maximum |
| 2400 | Duplicate Transaction ID |
| 2500 | Route Not Found |
| 2600 | Operation Not Allowed |
| 2700 | Failed to Complete Transaction |
| 1005 | Failed Due to Insufficient Funds |
| 1002 | Mobile number not registered on mobile money |
| 1008 | General Failure |
| 1200 | Invalid Number |
| 1100 | Number not supported on this Mobile money network |
| 1300 | Failed to Complete Transaction, Unknown Exception |
| Code | Description |
|---|---|
| 2001 | Request Successful |
| 1100 | Error in Request |
| 1101 | Service ID not Recognized |
| 1102 | Invalid Mobile Phone Number |
| 1103 | Payment Above Allowed Maximum |
| 1104 | Payment Below Allowed Minimum |
| 1105 | Network Not Supported |
| 1106 | Operation Not Permitted |
| 1107 | Payment Account Not Configured |
| 1108 | Insufficient Account Balance |
| 1110 | Duplicate Remit ID |
| 2102 | Subscriber Could not be Identified |
| 2105 | Non Existent Mobile Account |
| 2106 | Own Mobile Account Provided |
| 2107 | Invalid Amount Format |
| 2108 | Insufficient Funds on Source Account |
| 2109 | Daily Limit Exceeded |
| 2110 | Source Account Not Active |
| 2111 | Mobile Account Not Active |
| Code | Description |
|---|---|
| 1000 | Transaction Pending |
| 01 | Transaction Successful for Payment Transaction |
| 2001 | Transaction Successful for Deposit Transaction |
| 3000 | Missing Transaction ID Information |
| 3100 | Transaction Doesn't Exist |
| 3200 | Missing Request Transaction ID Information |
The Service ID parameter determines the type of payment:
0- Standard payments1- Bulk payments
When making a deposit, the WithdrawCharge parameter:
0- Withdraw charges are NOT included in the amount sent1- Withdraw charges are included in the amount sent to the subscriber
The package automatically generates timestamps in UTC format: yyyymmddhhmmss (e.g., 20161231115242)
The package automatically generates passwords using SHA256 encryption:
password = SHA256(username + accountno + partnerpassword + timestamp)
The default HTTP client timeout is 60 seconds. You can customize this with:
client := Intouchpay.NewClientWithOptions(
"username", "account", "password",
Intouchpay.WithTimeout(30 * time.Second),
)package main
import (
"fmt"
"log"
Intouchpay "github.com/samueltuyizere/go-intouchpay"
)
func main() {
// Initialize client
client := Intouchpay.NewClient(
"your_username",
"your_account_number",
"your_partner_password",
"https://yourdomain.com/callback",
0,
)
// Example 1: Request Payment
paymentParams := &Intouchpay.RequestPaymentParams{
Amount: 1000,
MobilePhone: "250788888888",
RequestTransactionId: "txn_001",
}
paymentResp, err := client.RequestPayment(paymentParams)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Payment Response: %+v\n", paymentResp)
// Example 2: Get Balance
balance, err := client.GetBalance()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Balance: %s\n", balance.Balance)
// Example 3: Request Deposit
depositParams := &Intouchpay.RequestDepositParams{
Amount: 5000,
WithdrawCharge: 0,
Reason: "Payment for services",
MobilePhone: "250788888888",
RequestTransactionId: "deposit_001",
}
depositResp, err := client.RequestDeposit(depositParams)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deposit Response: %+v\n", depositResp)
// Example 4: Get Transaction Status
statusParams := &Intouchpay.GetTransactionStatusParams{
RequestTransactionId: "txn_001",
TransactionId: paymentResp.TransactionId,
}
statusResp, err := client.GetTransactionStatus(statusParams)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %+v\n", statusResp)
}This package implements the IntouchPay API v1.2 For more details, refer to the official IntouchPay API documentation.
See the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions:
- Open an issue on GitHub
- Contact IntouchPay support for API-related questions
// This continues to work exactly as before
client := Intouchpay.NewClient(
"your_username",
"your_account_number",
"your_partner_password",
"https://yourdomain.com/callback",
0,
)1. Flexible Configuration with Options
// NEW: Use options for more control
client := Intouchpay.NewClientWithOptions(
"your_username",
"your_account_number",
"your_partner_password",
Intouchpay.WithCallbackURL("https://yourdomain.com/callback"),
Intouchpay.WithSid(0),
Intouchpay.WithTimeout(30 * time.Second),
)2. Better Error Handling
// NEW: Typed errors for clearer error handling
response, err := client.RequestPayment(params)
if err != nil {
if Intouchpay.IsAPIError(err) {
apiErr := err.(*Intouchpay.APIError)
log.Printf("API returned status %d", apiErr.StatusCode)
}
if Intouchpay.IsValidationError(err) {
valErr := err.(*Intouchpay.ValidationError)
log.Printf("Invalid field: %s", valErr.Field)
}
return
}3. Testing with Mocks
// NEW: Mock authentication and HTTP for tests
mockAuth := &MockAuthenticator{Creds: Intouchpay.Credentials{
Username: "test", Timestamp: "20260320120000", Password: "hash",
}}
mockHTTP := &MockHTTPClient{Response: &map[string]interface{}{"success": true}}
client := Intouchpay.NewClientWithHTTPClient(mockAuth, mockHTTP)The default HTTP timeout changed from undefined to 60 seconds. This should not affect most users, but if you need a different timeout:
client := Intouchpay.NewClientWithOptions(
"username", "account", "password",
Intouchpay.WithTimeout(30 * time.Second),
)Architecture Improvements:
- Extracted authentication into
Authenticatorinterface for testability - Extracted HTTP layer into
APIRequesterinterface for mocking - Added typed errors:
APIErrorandValidationError - Added
PhoneValidatorstruct for phone number validation - Added option pattern for flexible client configuration
- Fixed timeout inconsistency (now 60 seconds default)
New Constructors:
NewClientWithOptions()- Flexible configuration with optionsNewClientWithAuth()- Custom authenticator for testingNewClientWithHTTPClient()- Custom HTTP client for testing
New Options:
WithTimeout()- Set HTTP timeoutWithHTTPClient()- Use custom HTTP clientWithCallbackURL()- Set callback URLWithSid()- Set service IDWithAuthenticator()- Use custom authenticatorWithHTTPClientInterface()- Use mock HTTP client
Testing:
- Added comprehensive test suite (41 tests)
- Full coverage of authentication, HTTP layer, phone validation, errors
Initial release with basic API functionality.