Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.

Commit e4b8d14

Browse files
authored
Merge pull request #26 from silaselisha/develop
dynamic qrcode gen
2 parents c82a3fa + b317da8 commit e4b8d14

31 files changed

Lines changed: 486 additions & 444 deletions

README.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ Go-daraja is an open-source project facilitating seamless integration of Safaric
2626
cd duka-letu
2727
go mod init github.com/user-github-name/project-name
2828
```
29-
29+
```cmd
30+
cp example/.env .env
31+
```
3032
3. Install go-daraja package
3133
3234
```go
3335
go get github.com/silaselisha/go-daraja
3436
```
3537
36-
3738
``` go
3839
import (
3940
"log"
@@ -46,19 +47,15 @@ Go-daraja is an open-source project facilitating seamless integration of Safaric
4647
if err != nil {
4748
log.Panic(err)
4849
}
49-
// generate an access token
50-
auth, err := client.ClientAuth()
51-
if err != nil {
52-
log.Panic(err)
53-
}
50+
5451
// invoke STK/NI Push
55-
buff, err := client.NIPush("test STK push", "0708374149", 1, auth.AccessToken)
52+
res, err := client.NIPush("test STK push", "0708374149", 1)
5653
if err != nil {
5754
log.Panic(err)
5855
}
59-
log.Print(string(buff))
56+
log.Printf("%+v\n", res)
6057
}
6158
```
6259
6360
**Note**:
64-
Majority of the services provided by daraja API require the client to be authenticated before invoking them. With go-daraja a simplified interface `Daraja` shall provide the user with all necessary services such as `ClientAuth`, primarily used to generate an `access-token`.
61+
Majority of the services provided by daraja API require the client to be authenticated before invoking them. With go-daraja a simplified interface `Daraja` shall provide the user with all necessary services.

example/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ DARAJA_ACCOUNT_REF=CompanyXLTD
1111
DARAJA_INITIATOR_NAME=testapi
1212
DARAJA_INITIATOR_PASSWORD=BSafaricom999!*!
1313
DARAJA_BUSINESS_CONSUMR_PARTY_A=600984
14+
DARAJA_CREDIT_PARTY_IDENTIFIER=373132
1415
DARAJA_BUSINESS_EXPRESS_CHECKOUT_SHORT_CODE=7318002

example/example.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,21 @@ func dukaHandler(w http.ResponseWriter, r *http.Request) {
4949
return
5050
}
5151

52-
auth, err := darajaClient.ClientAuth()
52+
res, err := darajaClient.NIPush(duka.Description, duka.PhoneNumber, amount)
5353
if err != nil {
5454
w.WriteHeader(http.StatusInternalServerError)
5555
w.Write([]byte(err.Error()))
5656
return
5757
}
5858

59-
res, err := darajaClient.NIPush(duka.Description, duka.PhoneNumber, amount, auth.AccessToken)
59+
buff, err := json.Marshal(res)
6060
if err != nil {
6161
w.WriteHeader(http.StatusInternalServerError)
6262
w.Write([]byte(err.Error()))
6363
return
6464
}
65-
_, err = w.Write(res)
65+
66+
_, err = w.Write(buff)
6667
if err != nil {
6768
w.WriteHeader(http.StatusInternalServerError)
6869
w.Write([]byte(err.Error()))

internal/auth/auth.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package auth
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
)
7+
8+
func GenAuthorizationToken(consumerKey, consumerSecret string) string {
9+
// combine both consumer key & secret
10+
// base64 encode it to come up with a Authorization token
11+
key := []byte(fmt.Sprintf("%s:%s", consumerKey, consumerSecret))
12+
token := base64.URLEncoding.EncodeToString(key)
13+
14+
return token
15+
}

internal/auth/auth_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package auth
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestAuthGen(t *testing.T) {
11+
consumerKey := "AinW3mPD40202e78JRxqacXGU0YF3laj"
12+
consumerSecret := "gMsGecybWqGDpCDL"
13+
14+
token := GenAuthorizationToken(consumerKey, consumerSecret)
15+
require.NotEmpty(t, token)
16+
fmt.Print(token)
17+
}

internal/builder/builder.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package builder
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"regexp"
7+
"time"
8+
)
9+
10+
func BaseUrlBuilder(environment string) string {
11+
// build the base URL based on the code running environment
12+
// for non existing environments use the default base URL
13+
var baseURL string
14+
15+
switch environment {
16+
case "sandbox":
17+
baseURL = "https://sandbox.safaricom.co.ke"
18+
case "production":
19+
baseURL = "https://sandbox.safaricom.co.ke"
20+
default:
21+
baseURL = "https://sandbox.safaricom.co.ke"
22+
}
23+
return baseURL
24+
}
25+
26+
func PhoneNumberFormatter(phoneNumber string) (string, error) {
27+
// format the phoneNumber to 2547XXXXXXXX
28+
re := regexp.MustCompile(`^(07|01)\d{8}$`)
29+
if !re.MatchString(phoneNumber) {
30+
err := errors.New("invalid phone number")
31+
return "", err
32+
}
33+
34+
return fmt.Sprintf("%s%s", "254", phoneNumber[1:]), nil
35+
}
36+
37+
func GenTimestamp() string {
38+
// YYYYMMDDHHmmss
39+
timestamp := time.Now()
40+
return fmt.Sprintf("%d%02d%02d%02d%02d%02d", timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), timestamp.Second())
41+
}
Lines changed: 30 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,57 @@
1-
package util
1+
package builder
22

33
import (
4-
"fmt"
54
"testing"
65

76
"github.com/stretchr/testify/require"
87
)
98

10-
func TestAuthGenUtil(t *testing.T) {
11-
consumerKey := "AinW3mPD40202e78JRxqacXGU0YF3laj"
12-
consumerSecret := "gMsGecybWqGDpCDL"
13-
14-
token := GenAuthorizationToken(consumerKey, consumerSecret)
15-
require.NotEmpty(t, token)
16-
fmt.Print(token)
17-
}
18-
19-
func TestGenTimestamp(t *testing.T) {
20-
timestamp := GenTimestamp()
21-
require.NotNil(t, timestamp)
22-
require.Equal(t, 14, len(timestamp))
23-
}
24-
25-
func TestGenSecurityCred(t *testing.T) {
26-
cred, err := GenSecurityCred(&Configs{DarajaEnvironment: "sandbox"}, "./../pkg")
27-
require.NoError(t, err)
28-
require.NotNil(t, cred)
29-
fmt.Println(cred)
30-
}
31-
32-
func TestLoadConfigs(t *testing.T) {
9+
func TestPhoneNumberFormat(t *testing.T) {
3310
testCases := []struct {
34-
name string
35-
path string
36-
check func(t *testing.T, envs *Configs, err error)
11+
name string
12+
phoneNumber string
13+
check func(t *testing.T, phoneNumber string, err error)
3714
}{
3815
{
39-
name: "invalid env path",
40-
path: "200",
41-
check: func(t *testing.T, envs *Configs, err error) {
42-
require.Error(t, err)
43-
require.Empty(t, envs)
16+
name: "07 valid phone number",
17+
phoneNumber: "0721024268",
18+
check: func(t *testing.T, phoneNumber string, err error) {
19+
require.NoError(t, err)
20+
require.Equal(t, "254721024268", phoneNumber)
4421
},
4522
},
4623
{
47-
name: "valid env path",
48-
path: "./../example",
49-
check: func(t *testing.T, envs *Configs, err error) {
50-
fmt.Println(envs)
24+
name: "01 valid phone number",
25+
phoneNumber: "0121024268",
26+
check: func(t *testing.T, phoneNumber string, err error) {
5127
require.NoError(t, err)
52-
require.NotEmpty(t, envs)
28+
require.Equal(t, "254121024268", phoneNumber)
29+
},
30+
},
31+
{
32+
name: "invalid phone number",
33+
phoneNumber: "072102426",
34+
check: func(t *testing.T, phoneNumber string, err error) {
35+
require.Error(t, err)
36+
require.Empty(t, phoneNumber)
5337
},
5438
},
5539
}
5640

5741
for _, test := range testCases {
5842
t.Run(test.name, func(t *testing.T) {
59-
envs, err := LoadConfigs(test.path)
60-
test.check(t, envs, err)
43+
phoneNumber, err := PhoneNumberFormatter(test.phoneNumber)
44+
test.check(t, phoneNumber, err)
6145
})
6246
}
6347
}
6448

49+
func TestGenTimestamp(t *testing.T) {
50+
timestamp := GenTimestamp()
51+
require.NotNil(t, timestamp)
52+
require.Equal(t, 14, len(timestamp))
53+
}
54+
6555
func TestBaseURL(t *testing.T) {
6656
testCases := []struct {
6757
name string
@@ -101,43 +91,3 @@ func TestBaseURL(t *testing.T) {
10191
})
10292
}
10393
}
104-
105-
func TestPhoneNumberFormat(t *testing.T) {
106-
testCases := []struct {
107-
name string
108-
phoneNumber string
109-
check func(t *testing.T, phoneNumber string, err error)
110-
}{
111-
{
112-
name: "07 valid phone number",
113-
phoneNumber: "0721024268",
114-
check: func(t *testing.T, phoneNumber string, err error) {
115-
require.NoError(t, err)
116-
require.Equal(t, "254721024268", phoneNumber)
117-
},
118-
},
119-
{
120-
name: "01 valid phone number",
121-
phoneNumber: "0121024268",
122-
check: func(t *testing.T, phoneNumber string, err error) {
123-
require.NoError(t, err)
124-
require.Equal(t, "254121024268", phoneNumber)
125-
},
126-
},
127-
{
128-
name: "invalid phone number",
129-
phoneNumber: "072102426",
130-
check: func(t *testing.T, phoneNumber string, err error) {
131-
require.Error(t, err)
132-
require.Empty(t, phoneNumber)
133-
},
134-
},
135-
}
136-
137-
for _, test := range testCases {
138-
t.Run(test.name, func(t *testing.T) {
139-
phoneNumber, err := PhoneNumberFormatter(test.phoneNumber)
140-
test.check(t, phoneNumber, err)
141-
})
142-
}
143-
}

internal/config/config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package config
2+
3+
import (
4+
"github.com/spf13/viper"
5+
)
6+
7+
type Configs struct {
8+
DarajaEnvironment string `mapstructure:"DARAJA_ENVIRONMENT"`
9+
DarajaConsumerKey string `mapstructure:"DARAJA_CONSUMER_KEY"`
10+
DarajaConsumerSecret string `mapstructure:"DARAJA_CONSUMER_SECRET"`
11+
DarajaBusinessShortCode string `mapstructure:"DARAJA_BUSINESS_SHORT_CODE"`
12+
DarajaPassKey string `mapstructure:"DARAJA_PASS_KEY"`
13+
DarajaTransactionType string `mapstructure:"DARAJA_TRANSACTION_TYPE"`
14+
DarajaCallBackURL string `mapstructure:"DARAJA_CALL_BACK_URL"`
15+
DarajaPartyA string `mapstructure:"DARAJA_PARTY_A"`
16+
DarajaPartyB string `mapstructure:"DARAJA_PARTY_B"`
17+
DarajaAccountRef string `mapstructure:"DARAJA_ACCOUNT_REF"`
18+
DarajaInitiatorName string `mapstructure:"DARAJA_INITIATOR_NAME"`
19+
DarajaInitiatorPassword string `mapstructure:"DARAJA_INITIATOR_PASSWORD"`
20+
DarajaBusinessConsumerPartyA string `mapstructure:"DARAJA_BUSINESS_CONSUMR_PARTY_A"`
21+
DarajaCPI string `mapstructure:"DARAJA_CREDIT_PARTY_IDENTIFIER"`
22+
DarajaBusinessExpressCheckoutShortCode string `mapstructure:"DARAJA_BUSINESS_EXPRESS_CHECKOUT_SHORT_CODE"`
23+
}
24+
25+
func LoadConfigs(path string) (envs *Configs, err error) {
26+
viper.SetConfigType("env")
27+
viper.AddConfigPath(path)
28+
viper.SetConfigName(".env")
29+
30+
viper.AutomaticEnv()
31+
err = viper.ReadInConfig()
32+
if err != nil {
33+
return
34+
}
35+
36+
err = viper.Unmarshal(&envs)
37+
if err != nil {
38+
return
39+
}
40+
return
41+
}

internal/config/config_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestLoadConfigs(t *testing.T) {
11+
testCases := []struct {
12+
name string
13+
path string
14+
check func(t *testing.T, envs *Configs, err error)
15+
}{
16+
{
17+
name: "invalid env path",
18+
path: "200",
19+
check: func(t *testing.T, envs *Configs, err error) {
20+
require.Error(t, err)
21+
require.Empty(t, envs)
22+
},
23+
},
24+
{
25+
name: "valid env path",
26+
path: "./../../example",
27+
check: func(t *testing.T, envs *Configs, err error) {
28+
fmt.Println(envs)
29+
require.NoError(t, err)
30+
require.NotEmpty(t, envs)
31+
},
32+
},
33+
}
34+
35+
for _, test := range testCases {
36+
t.Run(test.name, func(t *testing.T) {
37+
envs, err := LoadConfigs(test.path)
38+
test.check(t, envs, err)
39+
})
40+
}
41+
}

0 commit comments

Comments
 (0)