From f2dd73def023ca67d2c36019c2e39be56db7c158 Mon Sep 17 00:00:00 2001 From: Immanuel Onyeka Date: Tue, 23 Jan 2024 13:40:19 -0500 Subject: [PATCH] Return payment intent for new or existing subs --- .../user/js/registration/registration.vue | 8 +- migrations/0_29092022_setup_tables.sql | 2 + skouter.go | 180 +++++++++++++----- 3 files changed, 137 insertions(+), 53 deletions(-) diff --git a/grav-admin/user/js/registration/registration.vue b/grav-admin/user/js/registration/registration.vue index 4a12fca..764de78 100644 --- a/grav-admin/user/js/registration/registration.vue +++ b/grav-admin/user/js/registration/registration.vue @@ -58,7 +58,6 @@ function create(user) { } function intent(user) { - console.log(user) return fetch(`/api/user/subscribe`, {method: 'POST', @@ -69,7 +68,10 @@ function intent(user) { }, }).then(resp => { if (resp.ok) { - return resp.json().then(u => { err.value = "" }) + return resp.json().then(u => { + err.value = "" + console.log(u) + }) } else { resp.text().then( e => err.value = e) } @@ -78,7 +80,7 @@ function intent(user) { onMounted(() => { getUser().then( u => { - intent(u) + if (u) intent(u) }) }) diff --git a/migrations/0_29092022_setup_tables.sql b/migrations/0_29092022_setup_tables.sql index 7252f7f..08b8ab7 100644 --- a/migrations/0_29092022_setup_tables.sql +++ b/migrations/0_29092022_setup_tables.sql @@ -33,6 +33,7 @@ CREATE TABLE user ( address INT NOT NULL, password CHAR(64) NOT NULL, verified BOOLEAN, + newsletter BOOLEAN DEFAULT 0, branch_id INT DEFAULT NULL, avatar BLOB(102400) NOT NULL DEFAULT 0, letterhead BLOB(102400) NOT NULL DEFAULT 0, @@ -62,6 +63,7 @@ CREATE TABLE subscription ( stripe_id VARCHAR(255) DEFAULT '', user_id INT, customer_id VARCHAR(255) NOT NULL, + client_secret VARCHAR(255) NOT NULL, current_period_end INT DEFAULT 0, current_period_start INT DEFAULT 0, PRIMARY KEY (`id`), diff --git a/skouter.go b/skouter.go index 4cef825..7cc27c6 100644 --- a/skouter.go +++ b/skouter.go @@ -31,7 +31,8 @@ import ( "github.com/stripe/stripe-go/v76" "github.com/stripe/stripe-go/v76/customer" "github.com/stripe/stripe-go/v76/subscription" - // "github.com/stripe/stripe-go/v76/paymentintent" + "github.com/stripe/stripe-go/v76/invoice" + "github.com/stripe/stripe-go/v76/paymentintent" "image" _ "image/jpeg" "image/png" @@ -355,6 +356,25 @@ func match(path, pattern string, args *[]string) bool { return true } +func createToken(id int, role string) (*http.Cookie, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, + UserClaims{Id: id, Role: role, + Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)}) + + tokenStr, err := token.SignedString([]byte(os.Getenv("JWT_SECRET"))) + if err != nil { + log.Println("Token could not be signed: ", err, tokenStr) + return nil, errors.New("Token generation error.") + } + + cookie := http.Cookie{Name: "skouter", + Value: tokenStr, + Path: "/", + Expires: time.Now().Add(time.Hour * 24)} + + return &cookie, nil +} + func (estimate *Estimate) makeResults() []Result { var results []Result amortize := func(principle float64, rate float64, periods float64) int { @@ -866,27 +886,16 @@ func login(w http.ResponseWriter, db *sql.DB, r *http.Request) { return } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, - UserClaims{Id: id, Role: role, - Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)}) - - tokenStr, err := token.SignedString([]byte(os.Getenv("JWT_SECRET"))) + cookie, err := createToken(id, role) if err != nil { - log.Println("Token could not be signed: ", err, tokenStr) - http.Error(w, "Token generation error.", http.StatusInternalServerError) - return + http.Error(w, err.Error(), 500) } - - cookie := http.Cookie{Name: "skouter", - Value: tokenStr, - Path: "/", - Expires: time.Now().Add(time.Hour * 24)} - http.SetCookie(w, &cookie) - _, err = w.Write([]byte(tokenStr)) + + http.SetCookie(w, cookie) + _, err = w.Write([]byte(cookie.Value)) if err != nil { - http.Error(w, - "Could not complete token write.", - http.StatusInternalServerError) + http.Error(w, "Could not complete token write.", + http.StatusInternalServerError) } } @@ -950,6 +959,7 @@ func guard(r *http.Request, required int) bool { if roles[claims.Role] < required { return false } + return true } @@ -1251,7 +1261,8 @@ func querySub(db *sql.DB, id int) (Subscription, error) { user_id, customer_id, current_period_end, - current_period_start + current_period_start, + client_secret FROM subscription WHERE id = ? ` row := db.QueryRow(query, id) @@ -1262,6 +1273,7 @@ func querySub(db *sql.DB, id int) (Subscription, error) { &s.CustomerId, &s.End, &s.Start, + &s.ClientSecret, ) return s, err @@ -1277,7 +1289,8 @@ func (user *User) querySub(db *sql.DB) error { user_id, customer_id, current_period_end, - current_period_start + current_period_start, + client_secret FROM subscription WHERE user_id = ? ` row := db.QueryRow(query, user.Id) @@ -1289,6 +1302,7 @@ func (user *User) querySub(db *sql.DB) error { &user.Sub.CustomerId, &user.Sub.End, &user.Sub.Start, + &user.Sub.ClientSecret, ) return err @@ -1397,6 +1411,7 @@ func insertUser(db *sql.DB, user User) (int, error) { // Insert user returning it's ID or any error func (sub *Subscription) insertSub(db *sql.DB) (error) { var query string + var row *sql.Row var err error query = `INSERT INTO subscription @@ -1405,17 +1420,49 @@ func (sub *Subscription) insertSub(db *sql.DB) (error) { user_id, customer_id, current_period_end, - current_period_start + current_period_start, + client_secret ) - VALUES (?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?) + RETURNING id ` - - _, err = db.Exec(query, + + row = db.QueryRow(query, sub.StripeId, sub.UserId, sub.CustomerId, sub.End, sub.Start, + sub.ClientSecret, + ) + + err = row.Scan(&sub.Id) + return err +} + +func (sub *Subscription) updateSubSecret(db *sql.DB) error { + var query string + var err error + + query = `UPDATE subscription SET + client_secret = ? + WHERE id = ? + ` + + s, err := subscription.Get(sub.StripeId, &stripe.SubscriptionParams{}) + if err != nil { return err } + + i, err := invoice.Get(s.LatestInvoice.ID, &stripe.InvoiceParams{}) + if err != nil { return err } + + p, err := paymentintent.Get(i.PaymentIntent.ID, + &stripe.PaymentIntentParams{}) + if err != nil { return err } + + + _, err = db.Exec(query, + p.ClientSecret, + sub.Id, ) return err @@ -1435,8 +1482,11 @@ func (user *User) updateCustomerId(db *sql.DB, cid string) (error) { cid, user.Id, ) + if err != nil { return err } + + user.CustomerId = cid - return err + return nil } func updateAddress(address Address, db *sql.DB) error { @@ -2869,25 +2919,9 @@ stripe.Customer, error) { return *result, err } -func createSubscription(w http.ResponseWriter, db *sql.DB, r *http.Request) { - claims, err := getClaims(r) - user, err := queryUser(db, claims.Id) - if err != nil { - w.WriteHeader(422) - return - } - - var name string = user.FirstName + " " + user.LastName - - c, err := createCustomer(name, user.Email, user.Address) - - if err != nil { - http.Error(w, err.Error(), 422) - return - } - - err = user.updateCustomerId(db, c.ID) - +// Initiates a new standard subscription using a given customer ID +func createSubscription(cid string) (*stripe.Subscription, error) { + // Automatically save the payment method to the subscription // when the first payment is successful. paymentSettings := &stripe.SubscriptionPaymentSettingsParams{ @@ -2898,7 +2932,7 @@ func createSubscription(w http.ResponseWriter, db *sql.DB, r *http.Request) { // latest invoice and that invoice's payment_intent // so we can pass it to the front end to confirm the payment subscriptionParams := &stripe.SubscriptionParams{ - Customer: stripe.String(c.ID), + Customer: stripe.String(cid), Items: []*stripe.SubscriptionItemsParams{ { Price: stripe.String("price_1OZLK9BPMoXn2pf9kuTAf8rs"), @@ -2910,12 +2944,58 @@ func createSubscription(w http.ResponseWriter, db *sql.DB, r *http.Request) { subscriptionParams.AddExpand("latest_invoice.payment_intent") s, err := subscription.New(subscriptionParams) - if err != nil { - http.Error(w, err.Error(), 500) + return s, err +} + +func subscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) { + claims, err := getClaims(r) + user, err := queryUser(db, claims.Id) + if err != nil { + w.WriteHeader(422) return } - json.NewEncoder(w).Encode(s) + user.querySub(db) + + var name string = user.FirstName + " " + user.LastName + + if user.CustomerId == "" { + c, err := createCustomer(name, user.Email, user.Address) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + err = user.updateCustomerId(db, c.ID) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + } + + if user.Sub.Id == 0 { + s, err := createSubscription(user.CustomerId) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + user.Sub.UserId = user.Id + user.Sub.StripeId = s.ID + user.Sub.CustomerId = user.CustomerId + user.Sub.End = int(s.CurrentPeriodEnd) + user.Sub.Start = int(s.CurrentPeriodStart) + user.Sub.ClientSecret = s.LatestInvoice.PaymentIntent.ClientSecret + + user.Sub.insertSub(db) + json.NewEncoder(w).Encode(user.Sub) + } else { + json.NewEncoder(w).Encode(user.Sub) + } + + // Should check that subscription is still valid and has payment intent + // here + } func api(w http.ResponseWriter, r *http.Request) { @@ -2991,7 +3071,7 @@ func api(w http.ResponseWriter, r *http.Request) { case match(p, "/api/user/subscribe", &args) && r.Method == http.MethodPost && guard(r, 1): - createSubscription(w, db, r) + subscribe(w, db, r) case match(p, "/api/fees", &args) && r.Method == http.MethodGet && guard(r, 1):