瀏覽代碼

Create separate functions for subscription hooks

Creating separate endpoints for each webhook simplifies value checking for
event.Type and HookKeys while being easy to test for errors.
master
Immanuel Onyeka 7 月之前
父節點
當前提交
6a412e5a47
共有 1 個檔案被更改,包括 104 行新增59 行删除
  1. +104
    -59
      skouter.go

+ 104
- 59
skouter.go 查看文件

@@ -571,6 +571,23 @@ func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
return fees, nil
}

func constructEvent(r *http.Request, key string) (*stripe.Event, error) {
b, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("io.ReadAll: %v", err)
return nil, err
}
event, err :=
webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), key)
if err != nil {
log.Printf("webhook.ConstructEvent: %v", err)
return nil, err
}
return &event, nil
}

// Fetch fees from the database
func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var fees []FeeTemplate
@@ -3037,6 +3054,28 @@ func createSubscription(cid string) (*stripe.Subscription, error) {
return s, err
}

func ( user *User ) SyncSub( sub *stripe.Subscription, db *sql.DB ) error {
var err error
user.Sub.UserId = user.Id
user.Sub.StripeId = sub.ID
user.Sub.CustomerId = user.CustomerId
user.Sub.PriceId = standardPriceId
user.Sub.End = int(sub.CurrentPeriodEnd)
user.Sub.Start = int(sub.CurrentPeriodStart)
user.Sub.ClientSecret = sub.LatestInvoice.PaymentIntent.ClientSecret
user.Sub.PaymentStatus = string(sub.LatestInvoice.PaymentIntent.Status)
user.Sub.Status = string(sub.Status)
if user.Sub.Id != 0 {
err = user.Sub.insertSub(db)
} else {
user.Sub.updateSub(db)
}
return err
}

// Creates a new subscription instance for a new user or retrieves the
// existing instance if possible. It's main purpose is to supply a
// client secret used for sending billing information to stripe.
@@ -3164,25 +3203,14 @@ func invoiceFailed(w http.ResponseWriter, db *sql.DB, r *http.Request) {
}

// Important for catching subscription creation through Stripe dashboard
// although it already happens at subscribe(). It checks if the user already
// has a subscription and replaces those fields if necessary so a seperate
// subCreated() is not necessary.
func subUpdated(w http.ResponseWriter, db *sql.DB, r *http.Request) {

// although it already happens at subscribe().
func subCreated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var sub stripe.Subscription
b, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("io.ReadAll: %v", err)
return
}
event, err := webhook.ConstructEvent(b,
r.Header.Get("Stripe-Signature"),
hookKeys.SubUpdated)
var err error

event, err := constructEvent(r, hookKeys.SubCreated)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("webhook.ConstructEvent: %v", err)
return
}
@@ -3191,7 +3219,7 @@ func subUpdated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
w.WriteHeader(http.StatusOK)
if event.Type != "customer.subscription.created" {
log.Println(
"Invalid event type sent to customer.subscription.created.")
"Invalid event type. Expecting customer.subscription.created.")
return
}
@@ -3212,21 +3240,7 @@ func subUpdated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
user.update(db)
}
user.Sub.UserId = user.Id
user.Sub.StripeId = sub.ID
user.Sub.CustomerId = user.CustomerId
user.Sub.PriceId = standardPriceId
user.Sub.End = int(sub.CurrentPeriodEnd)
user.Sub.Start = int(sub.CurrentPeriodStart)
user.Sub.ClientSecret = sub.LatestInvoice.PaymentIntent.ClientSecret
user.Sub.PaymentStatus = string(sub.LatestInvoice.PaymentIntent.Status)
user.Sub.Status = string(sub.Status)
if user.Sub.Id != 0 {
err = user.Sub.insertSub(db)
} else {
user.Sub.updateSub(db)
}
err = user.SyncSub(&sub, db)
if err != nil {
http.Error(w, err.Error(), 500)
@@ -3236,31 +3250,71 @@ func subUpdated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
log.Println("User subscription created:", user.Id, sub.ID)
}

// Handles changes to customer subscriptions sent by Stripe
func subDeleted(w http.ResponseWriter, db *sql.DB, r *http.Request) {
// Checks if the user already has a subscription and replaces those fields if
// necessary.
func subUpdated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var sub stripe.Subscription
b, err := io.ReadAll(r.Body)
var err error

event, err := constructEvent(r, hookKeys.SubUpdated)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("io.ReadAll: %v", err)
return
}
event, err := webhook.ConstructEvent(b,
r.Header.Get("Stripe-Signature"),
hookKeys.SubUpdated)
// OK should be sent before any processing to confirm with Stripe that
// the hook was received
w.WriteHeader(http.StatusOK)
if event.Type != "customer.subscription.updated" {
log.Println(
"Invalid event type sent. Expecting customer.subscription.updated.")
return
}
json.Unmarshal(event.Data.Raw, &sub)
log.Println(event.Type, sub.ID, sub.Customer.ID)
user, err := queryCustomer(db, sub.Customer.ID)
if err != nil {
log.Printf("Could not query customer: %v", err)
return
}
if statuses[user.Status] < 5 && sub.Status == "trialing" {
user.Status = "Trial"
user.update(db)
} else if sub.Status != "active" {
user.Status = "Unsubscribed"
user.update(db)
}
err = user.SyncSub(&sub, db)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
log.Println("User subscription created:", user.Id, sub.ID)
}

// Handles deleted subscriptions hooks sent by Stripe
func subDeleted(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var sub stripe.Subscription
var err error

event, err := constructEvent(r, hookKeys.SubDeleted)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("webhook.ConstructEvent: %v", err)
return
}
// OK should be sent before any processing to confirm with Stripe that
// the hook was received
w.WriteHeader(http.StatusOK)
if event.Type != "customer.subscription.updated" {
if event.Type != "customer.subscription.deleted" {
log.Println(
"Invalid event type sent to customer.subscription.updated.")
"Invalid event type sent. Expecting customer.subscription.deleted.")
return
}
@@ -3281,21 +3335,9 @@ func subDeleted(w http.ResponseWriter, db *sql.DB, r *http.Request) {
user.update(db)
}
user.Sub.UserId = user.Id
user.Sub.StripeId = sub.ID
user.Sub.CustomerId = user.CustomerId
user.Sub.PriceId = standardPriceId
user.Sub.End = int(sub.CurrentPeriodEnd)
user.Sub.Start = int(sub.CurrentPeriodStart)
user.Sub.ClientSecret = sub.LatestInvoice.PaymentIntent.ClientSecret
user.Sub.PaymentStatus = string(sub.LatestInvoice.PaymentIntent.Status)
user.Sub.Status = string(sub.Status)
user.Sub.Status = "canceled"
if user.Sub.Id != 0 {
err = user.Sub.insertSub(db)
} else {
user.Sub.updateSub(db)
}
err = user.SyncSub(&sub, db)
if err != nil {
http.Error(w, err.Error(), 500)
@@ -3435,13 +3477,13 @@ func api(w http.ResponseWriter, r *http.Request) {
invoiceFailed(w, db, r)
case match(p, "/api/stripe/sub-created", &args) &&
r.Method == http.MethodPost:
subUpdated(w, db, r)
subCreated(w, db, r)
case match(p, "/api/stripe/sub-updated", &args) &&
r.Method == http.MethodPost:
subUpdated(w, db, r)
case match(p, "/api/stripe/sub-updated", &args) &&
case match(p, "/api/stripe/sub-deleted", &args) &&
r.Method == http.MethodPost:
subUpdated(w, db, r)
subDeleted(w, db, r)
default:
http.Error(w, "Invalid route or token", 404)
}
@@ -3774,6 +3816,9 @@ func dev(args []string) {
hookKeys = HookKeys{
InvoicePaid: os.Getenv("DEV_WEBHOOK_KEY"),
InvoiceFailed: os.Getenv("DEV_WEBHOOK_KEY"),
SubCreated: os.Getenv("DEV_WEBHOOK_KEY"),
SubUpdated: os.Getenv("DEV_WEBHOOK_KEY"),
SubDeleted: os.Getenv("DEV_WEBHOOK_KEY"),
}

db, err := sql.Open("mysql",


Loading…
取消
儲存