|
|
@@ -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", |
|
|
|