Fix Chapa verify JSON parsing when amount is numeric.

Accept string or number for amount in verify and webhook payloads so GET /payments/verify can complete successfully.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-06-02 02:27:13 -07:00
parent 632371c3d0
commit a83745fd93
2 changed files with 88 additions and 8 deletions

View File

@ -1,5 +1,41 @@
package domain package domain
import (
"encoding/json"
"fmt"
)
// ChapaFlexibleString unmarshals JSON string or number (Chapa verify/webhook payloads vary).
type ChapaFlexibleString string
func (s *ChapaFlexibleString) UnmarshalJSON(data []byte) error {
if len(data) == 0 || string(data) == "null" {
*s = ""
return nil
}
switch data[0] {
case '"':
var v string
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*s = ChapaFlexibleString(v)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
var n json.Number
if err := json.Unmarshal(data, &n); err != nil {
return err
}
*s = ChapaFlexibleString(n.String())
default:
return fmt.Errorf("chapa flexible string: unsupported json type %q", data[0])
}
return nil
}
func (s ChapaFlexibleString) String() string {
return string(s)
}
// ChapaInitializeRequest is sent to POST /transaction/initialize. // ChapaInitializeRequest is sent to POST /transaction/initialize.
type ChapaInitializeRequest struct { type ChapaInitializeRequest struct {
Amount string `json:"amount"` Amount string `json:"amount"`
@ -32,13 +68,13 @@ type ChapaVerifyResponse struct {
} }
type ChapaTransactionData struct { type ChapaTransactionData struct {
TxRef string `json:"tx_ref"` TxRef string `json:"tx_ref"`
Reference string `json:"reference"` Reference string `json:"reference"`
Amount string `json:"amount"` Amount ChapaFlexibleString `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`
Status string `json:"status"` Status string `json:"status"`
PaymentMethod string `json:"payment_method"` PaymentMethod string `json:"payment_method"`
Mode string `json:"mode"` Mode string `json:"mode"`
} }
// ChapaWebhookPayload is the body POSTed to the webhook URL. // ChapaWebhookPayload is the body POSTed to the webhook URL.
@ -48,7 +84,7 @@ type ChapaWebhookPayload struct {
TxRef string `json:"tx_ref"` TxRef string `json:"tx_ref"`
Reference string `json:"reference"` Reference string `json:"reference"`
Status string `json:"status"` Status string `json:"status"`
Amount string `json:"amount"` Amount ChapaFlexibleString `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`
PaymentMethod string `json:"payment_method"` PaymentMethod string `json:"payment_method"`
Mode string `json:"mode"` Mode string `json:"mode"`

View File

@ -0,0 +1,44 @@
package domain
import (
"encoding/json"
"testing"
)
func TestChapaFlexibleString_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
raw string
want string
wantErr bool
}{
{name: "string amount", raw: `"500.00"`, want: "500.00"},
{name: "number amount", raw: `500`, want: "500"},
{name: "float amount", raw: `499.99`, want: "499.99"},
{name: "null", raw: `null`, want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var s ChapaFlexibleString
err := json.Unmarshal([]byte(tt.raw), &s)
if (err != nil) != tt.wantErr {
t.Fatalf("err=%v wantErr=%v", err, tt.wantErr)
}
if s.String() != tt.want {
t.Fatalf("got %q want %q", s.String(), tt.want)
}
})
}
}
func TestChapaVerifyResponse_UnmarshalNumberAmount(t *testing.T) {
raw := `{"status":"success","message":"ok","data":{"tx_ref":"tx-1","reference":"ref-1","amount":500,"currency":"ETB","status":"success","payment_method":"telebirr"}}`
var resp ChapaVerifyResponse
if err := json.Unmarshal([]byte(raw), &resp); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if resp.Data.Amount.String() != "500" {
t.Fatalf("amount=%q want 500", resp.Data.Amount.String())
}
}