diff --git a/.idea/GitLink.xml b/.idea/GitLink.xml
new file mode 100644
index 0000000..009597c
--- /dev/null
+++ b/.idea/GitLink.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/send_sms_example.go b/examples/send_sms_example.go
index 6621073..e520b9c 100644
--- a/examples/send_sms_example.go
+++ b/examples/send_sms_example.go
@@ -1,24 +1,34 @@
package sms_sender
import (
- "fmt"
+ "context"
"github.com/MikeMwita/africastalking-go/pkg/sms"
"log"
+ "net/http"
+ "os"
+ "time"
)
func main() {
- // Example usage
- sender := sms.SmsSender{
- ApiKey: "your_api_key",
- ApiUser: "your_api_user",
+ apiKey := os.Getenv("API_KEY")
+ apiUser := os.Getenv("API_USER")
+
+ client := sms.NewClient(&http.Client{}, apiKey, apiUser)
+ sender := &sms.SmsSender{
+ Client: client,
Recipients: []string{"+1234567890"},
- Message: "Hello, world!",
+ Message: "Test message",
+ Sender: "YourSenderID",
+ SmsKey: "unique_sms_key",
}
- response, err := sender.SendSMS()
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ resp, err := sender.RetrySendSMS(ctx, 3)
if err != nil {
- log.Fatal(err)
+ log.Fatalf("Failed to send SMS: %v", err)
}
- fmt.Printf("Response: %+v\n", response)
+ log.Printf("SMS Response: %+v", resp)
}
diff --git a/pkg/sms/client.go b/pkg/sms/client.go
new file mode 100644
index 0000000..d210b89
--- /dev/null
+++ b/pkg/sms/client.go
@@ -0,0 +1,28 @@
+package sms
+
+import "net/http"
+
+const (
+ DefaultAPIURL = "https://api.africastalking.com/version1/messaging"
+ sandboxAPIURL = "https://api.sandbox.africastalking.com/version1/messaging"
+)
+
+type Doer interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+type Client struct {
+ apiURL string
+ apiKey string
+ apiUser string
+ client Doer
+}
+
+func NewClient(client Doer, apiKey, apiUser string) *Client {
+ return &Client{
+ apiURL: DefaultAPIURL,
+ apiKey: apiKey,
+ apiUser: apiUser,
+ client: client,
+ }
+}
diff --git a/pkg/sms/sms_sender.go b/pkg/sms/sms_sender.go
index 1b6626e..579282e 100644
--- a/pkg/sms/sms_sender.go
+++ b/pkg/sms/sms_sender.go
@@ -1,8 +1,10 @@
package sms
import (
+ "context"
"encoding/json"
"fmt"
+ "math/rand"
"net/http"
"net/url"
"strings"
@@ -11,149 +13,94 @@ import (
"github.com/google/uuid"
)
-type SmsSender struct {
- ApiKey string `json:"api_key"`
- ApiUser string `json:"api_user"`
- Recipients []string `json:"recipients"`
- Message string `json:"message"`
- Sender string `json:"sender"`
- SmsKey string `json:"sms_key"`
-}
-
-type Recipient struct {
- Key string `json:"key"`
- Cost string `json:"cost"`
- SmsKey string `json:"sms_key"`
- MessageId string `json:"message_id"`
- MessagePart int `json:"message_part"`
- Number string `json:"number"`
- Status string `json:"status"`
- StatusCode string `json:"status_code"`
-}
-
-type SmsMessageData struct {
- Message string `json:"message"`
- Cost string `json:"cost"`
- Recipients []Recipient `json:"recipients"`
-}
-
-type ErrorResponse struct {
- HasError bool `json:"has_error"`
- Message string `json:"message"`
-}
-
-type SmsSenderResponse struct {
- ErrorResponse ErrorResponse `json:"error_response"`
- SmsMessageData SmsMessageData `json:"sms_message_data"`
-}
-
-// SendSMS sends an SMS using the Africa's Talking API
-func (s *SmsSender) SendSMS() (SmsSenderResponse, error) {
- endpoint := "https://api.africastalking.com/version1/messaging"
- parsedURL, err := url.Parse(endpoint)
- if err != nil {
- return SmsSenderResponse{}, err
- }
-
- body := map[string][]string{
- "username": {s.ApiUser},
- "to": s.Recipients,
- "message": {s.Message},
- "from": {s.Sender},
- }
-
+func (s *SmsSender) SendSMS(ctx context.Context) (SmsSenderResponse, error) {
form := url.Values{}
- for key, values := range body {
- for _, value := range values {
- form.Add(key, value)
- }
- }
+ form.Add("username", s.Client.apiUser)
+ form.Add("to", strings.Join(s.Recipients, ","))
+ form.Add("message", s.Message)
+ form.Add("from", s.Sender)
- req, err := http.NewRequest(http.MethodPost, parsedURL.String(), strings.NewReader(form.Encode()))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.Client.apiURL, strings.NewReader(form.Encode()))
if err != nil {
return SmsSenderResponse{}, err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("apiKey", s.ApiKey)
+ req.Header.Set("apiKey", s.Client.apiKey)
- client := &http.Client{}
- res, err := client.Do(req)
+ res, err := s.Client.client.Do(req)
if err != nil {
return SmsSenderResponse{}, err
}
defer res.Body.Close()
- if res.StatusCode == http.StatusCreated {
- recipients := make([]Recipient, 0)
-
- var data map[string]interface{}
- err = json.NewDecoder(res.Body).Decode(&data)
- if err != nil {
- return SmsSenderResponse{}, err
- }
+ if res.StatusCode != http.StatusCreated {
+ return SmsSenderResponse{
+ ErrorResponse: ErrorResponse{
+ HasError: true,
+ Message: "Message not sent",
+ },
+ }, fmt.Errorf("status code: %d", res.StatusCode)
+ }
- smsMessageData := data["SMSMessageData"].(map[string]interface{})
- message := smsMessageData["Message"].(string)
+ var data map[string]interface{}
+ err = json.NewDecoder(res.Body).Decode(&data)
+ if err != nil {
+ return SmsSenderResponse{}, err
+ }
- cost := ""
- for _, word := range strings.Split(message, " ") {
- cost = word
- }
- recipientsData := smsMessageData["Recipients"].([]interface{})
-
- for _, recipient := range recipientsData {
- recipientData := recipient.(map[string]interface{})
-
- rct := Recipient{
- Key: uuid.New().String(),
- Cost: recipientData["cost"].(string),
- SmsKey: s.SmsKey,
- MessageId: recipientData["messageId"].(string),
- MessagePart: int(recipientData["messageParts"].(float64)),
- Number: recipientData["number"].(string),
- Status: recipientData["status"].(string),
- StatusCode: fmt.Sprintf("%v", recipientData["statusCode"]),
- }
-
- recipients = append(recipients, rct)
- }
+ smsMessageData := data["SMSMessageData"].(map[string]interface{})
+ message := smsMessageData["Message"].(string)
+ cost := ""
+ for _, word := range strings.Split(message, " ") {
+ cost = word
+ }
- smsSenderResponse := SmsSenderResponse{
- ErrorResponse: ErrorResponse{
- HasError: false,
- },
- SmsMessageData: SmsMessageData{
- Message: message,
- Cost: cost,
- Recipients: recipients,
- },
+ recipientsData := smsMessageData["Recipients"].([]interface{})
+ recipients := make([]Recipient, 0)
+
+ for _, recipient := range recipientsData {
+ recipientData := recipient.(map[string]interface{})
+
+ rct := Recipient{
+ Key: uuid.New().String(),
+ Cost: recipientData["cost"].(string),
+ SmsKey: s.SmsKey,
+ MessageId: recipientData["messageId"].(string),
+ MessagePart: int(recipientData["messageParts"].(float64)),
+ Number: recipientData["number"].(string),
+ Status: recipientData["status"].(string),
+ StatusCode: fmt.Sprintf("%v", recipientData["statusCode"]),
}
- return smsSenderResponse, nil
+ recipients = append(recipients, rct)
}
- smsSenderResponse := SmsSenderResponse{
+ return SmsSenderResponse{
ErrorResponse: ErrorResponse{
- HasError: true,
- Message: "Message not sent",
+ HasError: false,
},
- }
-
- return smsSenderResponse, fmt.Errorf("status code: %d", res.StatusCode)
+ SmsMessageData: SmsMessageData{
+ Message: message,
+ Cost: cost,
+ Recipients: recipients,
+ },
+ }, nil
}
-// Retry sends an SMS with exponential backoff
-func (s *SmsSender) RetrySendSMS(maxRetries int) (SmsSenderResponse, error) {
+
+func (s *SmsSender) RetrySendSMS(ctx context.Context, maxRetries int) (SmsSenderResponse, error) {
for retry := 0; retry < maxRetries; retry++ {
- response, err := s.SendSMS()
- if err == nil {
- return response, nil
- }
+ response, err := s.SendSMS(ctx)
+ if err == nil {
+ return response, nil
+ }
- delay := time.Duration(1<