Files
zooqueenandHanzo Dev a265087b13 No route makes or records a refund; a refund or dispute is recorded only from the processor's signed webhook, once, and an inquiry that closes without a chargeback restores its period
Commerce never initiates a refund. POST /v1/billing/refunds, POST /v1/billing/refund,
POST /v1/order/:orderid/refund and POST /v1/payment/:paymentid/refund are gone with
their handlers (engine.CreateRefund, checkout Refund, square.Refund) and the helpers
only they used; GET /v1/billing/refunds stays read-only. A claim is settled by a
replacement order, never a refund.

A refund or dispute of a plan's payment reaches its invoice only from the
processor's webhook, under the processor's own refund id, through one locked
writer that reads the invoice afresh (engine.RecordRefund, engine.RecordDispute).
Every state a dispute reports reaches the invoice; INQUIRY_CLOSED holds the
payment no longer, WON stays final and LOST stays disputed.

No backfill of the payment-to-invoice index is run: the only plan payments made
before v1.50.139 are the team's own card purchases (z24, zk) and one Square
record.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-01 17:04:54 -07:00

245 lines
7.6 KiB
Go

package counter
import (
"context"
"strconv"
"time"
"github.com/hanzoai/commerce/log"
"github.com/hanzoai/commerce/models/lineitem"
"github.com/hanzoai/commerce/models/order"
"github.com/hanzoai/commerce/models/product"
return_ "github.com/hanzoai/commerce/models/return"
)
var incrementSep = "."
func IncrementByAll(ctx context.Context, tag, storeId, geo string, value int, t time.Time) error {
t1 := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
t2 := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
baseKey := tag + incrementSep
if storeId != "" {
storeKey := baseKey + storeId + incrementSep
key := storeKey + string(Hourly) + incrementSep + strconv.FormatInt(t1.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, "", Hourly, value, t); err != nil {
return err
}
key = storeKey + string(Monthly) + incrementSep + strconv.FormatInt(t2.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, "", Monthly, value, t); err != nil {
return err
}
key = storeKey + string(Total)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, "", Total, value, t); err != nil {
return err
}
}
if geo != "" {
geoKey := baseKey + geo + incrementSep
key := geoKey + string(Hourly) + incrementSep + strconv.FormatInt(t1.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", geo, Hourly, value, t); err != nil {
return err
}
key = geoKey + string(Monthly) + incrementSep + strconv.FormatInt(t2.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", geo, Monthly, value, t); err != nil {
return err
}
key = geoKey + string(Total)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", geo, Total, value, t); err != nil {
return err
}
}
if storeId != "" && geo != "" {
storeGeoKey := baseKey + storeId + incrementSep + geo + incrementSep
key := storeGeoKey + string(Hourly) + incrementSep + strconv.FormatInt(t1.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, geo, Hourly, value, t); err != nil {
return err
}
key = storeGeoKey + string(Monthly) + incrementSep + strconv.FormatInt(t2.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, geo, Monthly, value, t); err != nil {
return err
}
key = storeGeoKey + string(Total)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, storeId, geo, Total, value, t); err != nil {
return err
}
}
key := baseKey + string(Hourly) + incrementSep + strconv.FormatInt(t1.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", "", Hourly, value, t); err != nil {
return err
}
key = baseKey + string(Monthly) + incrementSep + strconv.FormatInt(t2.Unix(), 10)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", "", Monthly, value, t); err != nil {
return err
}
key = baseKey + string(Total)
log.Debug("%v incremented by %v", key, value, ctx)
if err := IncrementBy(ctx, key, tag, "", "", Total, value, t); err != nil {
return err
}
return nil
}
func IncrUser(ctx context.Context, t time.Time) error {
return IncrementByAll(ctx, "user.count", "", "", 1, t)
}
func IncrSubscriber(ctx context.Context, t time.Time) error {
return IncrementByAll(ctx, "subscriber.count", "", "", 1, t)
}
func IncrOrder(ctx context.Context, ord *order.Order) error {
if ord.Test {
return nil
}
if err := IncrementByAll(ctx, "order.count", ord.StoreId, ord.ShippingAddress.Country, 1, ord.CreatedAt); err != nil {
return err
}
if err := IncrementByAll(ctx, "order.revenue", ord.StoreId, ord.ShippingAddress.Country, int(ord.Total), ord.CreatedAt); err != nil {
return err
}
projectedPrice := 0
// Calculate Projected
ord.GetItemEntities()
for _, item := range ord.Items {
projectedPrice += item.Quantity * int(item.ProjectedPrice)
}
if err := IncrementByAll(ctx, "order.projected.revenue", ord.StoreId, ord.ShippingAddress.Country, projectedPrice, ord.CreatedAt); err != nil {
return err
}
for _, item := range ord.Items {
prod := product.New(ord.Datastore())
if err := prod.GetById(item.ProductId); err != nil {
return err
}
for i := 0; i < item.Quantity; i++ {
if err := IncrProduct(ctx, prod, ord); err != nil {
log.Error("IncrProduct Error %v", err, ctx)
return err
}
}
}
return nil
}
func IncrProduct(ctx context.Context, prod *product.Product, ord *order.Order) error {
if ord.Test {
return nil
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".sold", ord.StoreId, ord.ShippingAddress.Country, 1, ord.CreatedAt); err != nil {
return err
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".revenue", ord.StoreId, ord.ShippingAddress.Country, int(prod.Price), ord.CreatedAt); err != nil {
return err
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".projected.revenue", ord.StoreId, ord.ShippingAddress.Country, int(prod.ProjectedPrice), ord.CreatedAt); err != nil {
return err
}
if prod.InventoryCost == 0 {
return nil
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".inventory.cost", ord.StoreId, ord.ShippingAddress.Country, int(prod.InventoryCost), ord.CreatedAt); err != nil {
return err
}
return nil
}
func IncrOrderShip(ctx context.Context, ord *order.Order, t time.Time) error {
if ord.Test {
return nil
}
if ord.Fulfillment.Pricing == 0 {
return nil
}
if err := IncrementByAll(ctx, "order.shipped.count", ord.StoreId, ord.ShippingAddress.Country, 1, t); err != nil {
return err
}
if err := IncrementByAll(ctx, "order.shipped.cost", ord.StoreId, ord.ShippingAddress.Country, int(ord.Fulfillment.Pricing), t); err != nil {
return err
}
for _, item := range ord.Items {
prod := product.New(ord.Datastore())
if err := prod.GetById(item.ProductId); err != nil {
return err
}
for i := 0; i < item.Quantity; i++ {
if err := IncrProductShip(ctx, prod, ord); err != nil {
log.Error("IncrProduct Error %v", err, ctx)
return err
}
}
}
return nil
}
func IncrProductShip(ctx context.Context, prod *product.Product, ord *order.Order) error {
if ord.Test {
return nil
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".shipped.count", ord.StoreId, ord.ShippingAddress.Country, 1, ord.CreatedAt); err != nil {
return err
}
return nil
}
func IncrOrderReturn(ctx context.Context, items []lineitem.LineItem, rtn *return_.Return) error {
ord := order.New(rtn.Datastore())
if err := ord.GetById(rtn.OrderId); err != nil {
return err
}
if ord.Test {
return nil
}
if err := IncrementByAll(ctx, "order.returned.count", ord.StoreId, ord.ShippingAddress.Country, 1, rtn.CreatedAt); err != nil {
return err
}
for _, item := range items {
prod := product.New(rtn.Datastore())
if err := prod.GetById(item.ProductId); err != nil {
return err
}
for i := 0; i < item.Quantity; i++ {
if err := IncrProductReturn(ctx, prod, ord, rtn); err != nil {
log.Error("IncrProduct Error %v", err, ctx)
return err
}
}
}
return nil
}
func IncrProductReturn(ctx context.Context, prod *product.Product, ord *order.Order, rtn *return_.Return) error {
if ord.Test {
return nil
}
if err := IncrementByAll(ctx, "product."+prod.Id()+".returned.count", ord.StoreId, ord.ShippingAddress.Country, 1, rtn.CreatedAt); err != nil {
return err
}
return nil
}