mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 14:27:27 +00:00
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:
parent
5c0feda8d4
commit
7fd10c3436
6 changed files with 289 additions and 167 deletions
|
|
@ -18,20 +18,21 @@ type User struct {
|
|||
PubKeyFP string
|
||||
Email string
|
||||
EmailVerified bool
|
||||
Premium bool // paid the one-time lifetime membership
|
||||
Premium bool // paid the one-time lifetime membership
|
||||
PremiumPayID string // CoinPay payment id of the pending/settled premium charge
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// userCols is the column list (in struct order) for every user SELECT, kept in
|
||||
// sync with scanUser.
|
||||
const userCols = `id, name, kind, pubkey_fp, email, email_verified, premium, created_at`
|
||||
const userCols = `id, name, kind, pubkey_fp, email, email_verified, premium, premium_pay_id, created_at`
|
||||
|
||||
// scanUser reads one user row selected with userCols.
|
||||
func scanUser(sc interface{ Scan(...any) error }) (User, error) {
|
||||
var u User
|
||||
var verified, premium int
|
||||
var created string
|
||||
if err := sc.Scan(&u.ID, &u.Name, &u.Kind, &u.PubKeyFP, &u.Email, &verified, &premium, &created); err != nil {
|
||||
if err := sc.Scan(&u.ID, &u.Name, &u.Kind, &u.PubKeyFP, &u.Email, &verified, &premium, &u.PremiumPayID, &created); err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
u.EmailVerified = verified != 0
|
||||
|
|
@ -74,6 +75,9 @@ type Store interface {
|
|||
// and clears the code. Returns ok=false on a wrong/empty code.
|
||||
ConfirmEmailCode(userID int64, code string) (User, bool, error)
|
||||
|
||||
// SetPremiumPayment records the CoinPay payment id of a pending premium
|
||||
// charge so a later visit can verify whether it settled.
|
||||
SetPremiumPayment(userID int64, payID string) error
|
||||
// GrantPremium marks the account as a lifetime premium member (the $10
|
||||
// one-time membership), recording the CoinPay payment reference. Idempotent.
|
||||
GrantPremium(userID int64, paymentRef string) error
|
||||
|
|
@ -154,6 +158,7 @@ func migrate(db *sql.DB) error {
|
|||
{"verify_token", "verify_token TEXT NOT NULL DEFAULT ''"},
|
||||
{"premium", "premium INTEGER NOT NULL DEFAULT 0"},
|
||||
{"premium_ref", "premium_ref TEXT NOT NULL DEFAULT ''"},
|
||||
{"premium_pay_id", "premium_pay_id TEXT NOT NULL DEFAULT ''"},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -312,6 +317,11 @@ func (s *sqliteStore) ConfirmEmailCode(userID int64, code string) (User, bool, e
|
|||
return u, true, nil
|
||||
}
|
||||
|
||||
func (s *sqliteStore) SetPremiumPayment(userID int64, payID string) error {
|
||||
_, err := s.db.Exec(`UPDATE users SET premium_pay_id = ? WHERE id = ?`, payID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sqliteStore) GrantPremium(userID int64, paymentRef string) error {
|
||||
_, err := s.db.Exec(`UPDATE users SET premium = 1, premium_ref = ? WHERE id = ?`, paymentRef, userID)
|
||||
return err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue