mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 14:27:27 +00:00
The live CoinPay API returns crypto_amount as a bare JSON number (e.g. 0.0031), but the struct decoded it as a string, so /payments/create failed with "cannot unmarshal number into ... crypto_amount of type string" and join@ showed "Payment is temporarily unavailable". The unit test had hidden the bug by sending the value quoted. Add a flexStr type that unmarshals from either a JSON number or string and use it for crypto_amount; update the test to send a number and add a direct flexStr decode test for both forms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3 KiB
Go
94 lines
3 KiB
Go
package payments
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestCreateAndVerifyPremium(t *testing.T) {
|
|
var gotAuth, gotBlockchain, gotBusiness string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth = r.Header.Get("Authorization")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch {
|
|
case r.Method == http.MethodPost && r.URL.Path == "/payments/create":
|
|
var body map[string]any
|
|
b, _ := io.ReadAll(r.Body)
|
|
_ = json.Unmarshal(b, &body)
|
|
gotBlockchain, _ = body["blockchain"].(string)
|
|
gotBusiness, _ = body["business_id"].(string)
|
|
// crypto_amount comes back as a bare JSON number from the live API.
|
|
_, _ = w.Write([]byte(`{"payment":{"id":"pay_1","payment_address":"0xABC","crypto_amount":0.0031,"crypto_currency":"ETH","status":"pending"}}`))
|
|
case r.Method == http.MethodGet && r.URL.Path == "/payments/pay_1":
|
|
_, _ = w.Write([]byte(`{"payment":{"id":"pay_1","status":"confirmed"}}`))
|
|
default:
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
t.Setenv("AGENTBBS_COINPAY_API_URL", srv.URL)
|
|
t.Setenv("COINPAY_API_KEY", "cp_test_key")
|
|
t.Setenv("AGENTBBS_COINPAY_MERCHANT_ID", "biz_42")
|
|
|
|
if !APIConfigured() {
|
|
t.Fatal("APIConfigured should be true with key + merchant id")
|
|
}
|
|
|
|
c, ok, err := CreatePremiumCharge("abbs-premium-deadbeef")
|
|
if err != nil || !ok {
|
|
t.Fatalf("create: ok=%v err=%v", ok, err)
|
|
}
|
|
if c.Address != "0xABC" || c.ID != "pay_1" || c.CryptoAmount != "0.0031" {
|
|
t.Fatalf("charge fields wrong: %+v", c)
|
|
}
|
|
if gotAuth != "Bearer cp_test_key" {
|
|
t.Fatalf("auth header = %q", gotAuth)
|
|
}
|
|
if gotBlockchain != "ETH" {
|
|
t.Fatalf("blockchain not upper-cased: %q", gotBlockchain)
|
|
}
|
|
if gotBusiness != "biz_42" {
|
|
t.Fatalf("business_id = %q", gotBusiness)
|
|
}
|
|
|
|
if paid, checked := VerifyPremium("pay_1"); !checked || !paid {
|
|
t.Fatalf("verify confirmed: paid=%v checked=%v", paid, checked)
|
|
}
|
|
// Empty id is a clean "not checked".
|
|
if _, checked := VerifyPremium(""); checked {
|
|
t.Fatal("empty payID must not be checked")
|
|
}
|
|
}
|
|
|
|
func TestNotConfigured(t *testing.T) {
|
|
t.Setenv("COINPAY_API_KEY", "")
|
|
t.Setenv("AGENTBBS_COINPAY_MERCHANT_ID", "")
|
|
if APIConfigured() {
|
|
t.Fatal("must not be configured without key/merchant")
|
|
}
|
|
if _, ok, err := CreatePremiumCharge("ref"); ok || err != nil {
|
|
t.Fatalf("unconfigured create: ok=%v err=%v", ok, err)
|
|
}
|
|
if _, checked := VerifyPremium("pay_1"); checked {
|
|
t.Fatal("unconfigured verify must not be checked")
|
|
}
|
|
}
|
|
|
|
func TestFlexStrDecodesNumberOrString(t *testing.T) {
|
|
for _, raw := range []string{
|
|
`{"payment":{"crypto_amount":0.0031}}`, // live API: bare number
|
|
`{"payment":{"crypto_amount":"0.0031"}}`, // legacy: quoted string
|
|
} {
|
|
var env paymentEnvelope
|
|
if err := json.Unmarshal([]byte(raw), &env); err != nil {
|
|
t.Fatalf("unmarshal %s: %v", raw, err)
|
|
}
|
|
if got := string(env.Payment.CryptoAmount); got != "0.0031" {
|
|
t.Fatalf("crypto_amount from %s = %q, want 0.0031", raw, got)
|
|
}
|
|
}
|
|
}
|