payments: call CoinPay REST API directly (drop coinpay CLI dependency)

CreatePremiumCharge/VerifyPremium now POST /payments/create and GET
/payments/:id against the CoinPay API (Bearer COINPAY_API_KEY, business_id =
AGENTBBS_COINPAY_MERCHANT_ID), so the droplet needs no coinpay CLI installed.
The created payment id is stored (store: User.PremiumPayID + premium_pay_id col
+ SetPremiumPayment); ensurePremium verifies that id on a later connect and
grants premium on confirmed/forwarded status. Removed the CLI command-template
env knobs; added AGENTBBS_COINPAY_API_URL. Tests: httptest-backed payments_test
+ store SetPremiumPayment test. Build/vet/gofmt/test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-14 08:27:33 +00:00
parent 5c0feda8d4
commit 7fd10c3436
6 changed files with 289 additions and 167 deletions

View file

@ -0,0 +1,78 @@
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)
_, _ = 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")
}
}