mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
Subscriptions carry Settled: the current period's paid-invoice method (card only when a card paid all of it; mixed beside prepaid money) or external:<processor> for a recorded plan. Every settled payment is indexed by payment id to the org and wallet its receipt lives in (models/paymentorg), and the refund/dispute webhook resolves from that index, never the request's org. CreditLedger gains Restore for a dispute won. Co-authored-by: Hanzo Dev <[email protected]>
70 lines
2.0 KiB
Go
70 lines
2.0 KiB
Go
// Package paymentorg records, for every settled payment, the org whose books its
|
|
// receipt lives in — by the processor's payment id, in the system namespace.
|
|
//
|
|
// A processor's callback names a payment and no org: Square sends no X-Org-Id, and a
|
|
// sessionless request resolves to the default org. So a refund or a dispute of a
|
|
// customer's top-up is traced to its org here, never read off the request.
|
|
package paymentorg
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/commerce/datastore"
|
|
"github.com/hanzoai/commerce/models/mixin"
|
|
"github.com/hanzoai/commerce/util/nscontext"
|
|
"github.com/hanzoai/orm"
|
|
)
|
|
|
|
func init() {
|
|
orm.Register[PaymentOrg]("payment-org", orm.WithStringKey[PaymentOrg]())
|
|
}
|
|
|
|
// PaymentOrg is where one payment's receipt lives: the org, the wallet it credited,
|
|
// and whether the charge was a sandbox one.
|
|
type PaymentOrg struct {
|
|
mixin.Model[PaymentOrg]
|
|
|
|
Org string `json:"org"`
|
|
Subject string `json:"subject"`
|
|
Test bool `json:"test"`
|
|
}
|
|
|
|
func (p *PaymentOrg) Load(ps []datastore.Property) error { return datastore.LoadStruct(p, ps) }
|
|
func (p *PaymentOrg) Save() ([]datastore.Property, error) { return datastore.SaveStruct(p) }
|
|
|
|
func db() *datastore.Datastore {
|
|
return datastore.New(nscontext.WithNamespace(context.Background(), "system"))
|
|
}
|
|
|
|
// Put records that payment's receipt lives in org, for subject. Idempotent: the
|
|
// payment id is the key.
|
|
func Put(payment, org, subject string, test bool) error {
|
|
payment = strings.TrimSpace(payment)
|
|
if payment == "" || org == "" {
|
|
return nil
|
|
}
|
|
d := db()
|
|
p := new(PaymentOrg)
|
|
p.Init(d)
|
|
p.SetId(payment)
|
|
p.Org, p.Subject, p.Test = org, subject, test
|
|
return p.Put()
|
|
}
|
|
|
|
// Get answers where payment's receipt lives, and false when nothing recorded it.
|
|
func Get(payment string) (*PaymentOrg, bool, error) {
|
|
d := db()
|
|
p := new(PaymentOrg)
|
|
p.Init(d)
|
|
err := p.Get(d.NewKey(p.Kind(), strings.TrimSpace(payment), 0, nil))
|
|
switch {
|
|
case err == nil:
|
|
return p, true, nil
|
|
case err == datastore.ErrNoSuchEntity:
|
|
return nil, false, nil
|
|
default:
|
|
return nil, false, err
|
|
}
|
|
}
|