mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans, usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0. Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
139 lines
3.2 KiB
Go
139 lines
3.2 KiB
Go
package subscription
|
|
|
|
import (
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/datastore"
|
|
"github.com/hanzoai/commerce/log"
|
|
"github.com/hanzoai/commerce/models/organization"
|
|
"github.com/hanzoai/commerce/models/plan"
|
|
"github.com/hanzoai/commerce/models/subscription"
|
|
"github.com/hanzoai/commerce/models/user"
|
|
"github.com/hanzoai/commerce/util/json"
|
|
)
|
|
|
|
func subscriptionRequest(c *zip.Ctx, org *organization.Organization) (*SubscriptionReq, error) {
|
|
// Create AuthReq properly by calling order.New
|
|
sr := new(SubscriptionReq)
|
|
sr.Db = datastore.New(org.Namespaced(c.Context()))
|
|
|
|
// Try decode request body
|
|
if err := json.DecodeBytes(c.Body(), &sr); err != nil {
|
|
log.Error("Failed to decode request body: %v\n%v", c.Body(), err, c)
|
|
return nil, FailedToDecodeRequestBody
|
|
}
|
|
|
|
return sr, nil
|
|
}
|
|
|
|
func subscribe(c *zip.Ctx, org *organization.Organization) (*subscription.Subscription, *user.User, error) {
|
|
ctx := org.Datastore().Context
|
|
nsCtx := org.Namespaced(ctx)
|
|
db := datastore.New(nsCtx)
|
|
|
|
// Parse request
|
|
sr, err := subscriptionRequest(c, org)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
log.Debug("AuthorizationReq.User_: %#v", sr.User_, c)
|
|
log.Debug("AuthorizationReq.Subscription_: %#v", sr.Subscription_, c)
|
|
|
|
sub, err := sr.Subscription()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
log.Debug("Subscription: %#v", sub, c)
|
|
|
|
// Subscription w/o quantity defaults to 1
|
|
if sub.Quantity < 1 {
|
|
sub.Quantity = 1
|
|
}
|
|
|
|
// Try to find plan
|
|
pln := plan.New(db)
|
|
err = pln.GetById(sub.PlanId)
|
|
if err != nil {
|
|
return nil, nil, PlanDoesNotExist
|
|
}
|
|
log.Debug("Plan: %#v", pln, c)
|
|
|
|
// Get user
|
|
usr, err := sr.User()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
log.Debug("User: %#v", usr, c)
|
|
|
|
// Payment information
|
|
sub.Buyer = usr.Buyer()
|
|
log.Debug("Buyer: %#v", sub.Buyer, c)
|
|
|
|
// The mode is the server's: the org's, or a test buyer's. Never the body's.
|
|
sub.Test = org.TestMode() || org.IsTestEmail(sub.Buyer.Email)
|
|
|
|
// Parent subscription to user
|
|
sub.Parent = usr.Key()
|
|
sub.UserId = usr.Id()
|
|
|
|
// Set plan on subscription
|
|
sub.PlanId = pln.Id()
|
|
sub.Plan = *pln
|
|
|
|
// Save user and subscription
|
|
usr.MustPut()
|
|
sub.MustPut()
|
|
|
|
return sub, usr, nil
|
|
}
|
|
|
|
func updateSubscribe(c *zip.Ctx, org *organization.Organization, sub *subscription.Subscription) (*subscription.Subscription, error) {
|
|
ctx := org.Datastore().Context
|
|
nsCtx := org.Namespaced(ctx)
|
|
db := datastore.New(nsCtx)
|
|
|
|
userId := sub.UserId
|
|
test := sub.Test
|
|
|
|
// Try decode request body
|
|
if err := json.DecodeBytes(c.Body(), &sub); err != nil {
|
|
log.Error("Failed to decode request body: %v\n%v", c.Body(), err, c)
|
|
return nil, FailedToDecodeRequestBody
|
|
}
|
|
sub.Test = test
|
|
|
|
if userId != sub.UserId {
|
|
return nil, CannotChangeUser
|
|
}
|
|
|
|
log.Warn("Quantity %v", sub.Quantity)
|
|
|
|
// Delete Case
|
|
if sub.Quantity < 1 {
|
|
return unsubscribe(c, org, sub)
|
|
}
|
|
|
|
pln := plan.New(db)
|
|
err := pln.GetById(sub.PlanId)
|
|
if err != nil {
|
|
return nil, PlanDoesNotExist
|
|
}
|
|
log.Debug("Plan: %#v", pln, c)
|
|
|
|
sub.Plan = *pln
|
|
|
|
sub.MustPut()
|
|
|
|
return sub, nil
|
|
}
|
|
|
|
func unsubscribe(c *zip.Ctx, org *organization.Organization, sub *subscription.Subscription) (*subscription.Subscription, error) {
|
|
_ = c
|
|
_ = org
|
|
|
|
sub.MustPut()
|
|
|
|
return sub, nil
|
|
}
|