|
|
@@ -35,6 +35,7 @@ import ( |
|
|
|
"image" |
|
|
|
_ "image/jpeg" |
|
|
|
"image/png" |
|
|
|
"embed" |
|
|
|
) |
|
|
|
|
|
|
|
type Config struct { |
|
|
@@ -71,6 +72,7 @@ type Subscription struct { |
|
|
|
PriceId string `json:"priceId"` |
|
|
|
Start int `json:"start"` |
|
|
|
End int `json:"end"` |
|
|
|
CancelAtEnd bool `json:"cancelAtEnd"` |
|
|
|
ClientSecret string `json:"clientSecret,omitempty"` |
|
|
|
PaymentStatus string `json:"paymentStatus"` |
|
|
|
Status string `json:"status"` |
|
|
@@ -302,6 +304,15 @@ var hookKeys = HookKeys{ |
|
|
|
SubDeleted: "", |
|
|
|
} |
|
|
|
|
|
|
|
//go:embed assets |
|
|
|
var assets embed.FS |
|
|
|
|
|
|
|
//go:embed migrations |
|
|
|
var migrations embed.FS |
|
|
|
|
|
|
|
//go:embed views |
|
|
|
var views embed.FS |
|
|
|
|
|
|
|
var standardPriceId = "price_1OZLK9BPMoXn2pf9kuTAf8rs" |
|
|
|
|
|
|
|
// Used to validate claim in JWT token body. Checks if user id is greater than |
|
|
@@ -366,7 +377,7 @@ func cachePdf(name string) Page { |
|
|
|
"views/report/summary.tpl", |
|
|
|
"views/report/comparison.tpl"} |
|
|
|
|
|
|
|
tpl := template.Must(template.New("master.tpl").Funcs(fm).ParseFiles(p...)) |
|
|
|
tpl := template.Must(template.New("master.tpl").Funcs(fm).ParseFS(views, p...)) |
|
|
|
return Page{tpl: tpl, Title: "", Name: name} |
|
|
|
} |
|
|
|
|
|
|
@@ -1395,8 +1406,10 @@ func querySub(db *sql.DB, id int) (Subscription, error) { |
|
|
|
customer_id, |
|
|
|
current_period_end, |
|
|
|
current_period_start, |
|
|
|
cancel_at_end, |
|
|
|
client_secret, |
|
|
|
payment_status |
|
|
|
payment_status, |
|
|
|
status |
|
|
|
FROM subscription WHERE id = ? |
|
|
|
` |
|
|
|
row := db.QueryRow(query, id) |
|
|
@@ -1408,8 +1421,10 @@ func querySub(db *sql.DB, id int) (Subscription, error) { |
|
|
|
&s.CustomerId, |
|
|
|
&s.End, |
|
|
|
&s.Start, |
|
|
|
&s.CancelAtEnd, |
|
|
|
&s.ClientSecret, |
|
|
|
&s.PaymentStatus, |
|
|
|
&s.Status, |
|
|
|
) |
|
|
|
|
|
|
|
return s, err |
|
|
@@ -1520,7 +1535,7 @@ func insertUser(db *sql.DB, user User) (int, error) { |
|
|
|
) |
|
|
|
VALUES (?, ?, ?, sha2(?, 256), ?, ?, ?, ?, ?, ?, |
|
|
|
CASE @b := ? WHEN 0 THEN NULL ELSE @b END, |
|
|
|
?, NOW(), NOW()) |
|
|
|
?, ?, NOW(), NOW()) |
|
|
|
RETURNING id |
|
|
|
` |
|
|
|
|
|
|
@@ -1564,11 +1579,12 @@ func (sub *Subscription) insertSub(db *sql.DB) (error) { |
|
|
|
price_id, |
|
|
|
current_period_end, |
|
|
|
current_period_start, |
|
|
|
cancel_at_end, |
|
|
|
client_secret, |
|
|
|
payment_status, |
|
|
|
status |
|
|
|
) |
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) |
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
|
|
|
RETURNING id |
|
|
|
` |
|
|
|
|
|
|
@@ -1579,6 +1595,7 @@ func (sub *Subscription) insertSub(db *sql.DB) (error) { |
|
|
|
sub.PriceId, |
|
|
|
sub.End, |
|
|
|
sub.Start, |
|
|
|
sub.CancelAtEnd, |
|
|
|
sub.ClientSecret, |
|
|
|
sub.PaymentStatus, |
|
|
|
sub.Status, |
|
|
@@ -1598,8 +1615,9 @@ func (sub *Subscription) updateSub(db *sql.DB) error { |
|
|
|
@b := ? WHEN 0 THEN current_period_end ELSE @b END, |
|
|
|
current_period_start = CASE |
|
|
|
@c := ? WHEN 0 THEN current_period_start ELSE @c END, |
|
|
|
payment_status = CASE @d := ? WHEN '' THEN client_secret ELSE @d END, |
|
|
|
status = CASE @e := ? WHEN '' THEN client_secret ELSE @e END |
|
|
|
payment_status = CASE @d := ? WHEN '' THEN payment_status ELSE @d END, |
|
|
|
status = CASE @e := ? WHEN '' THEN status ELSE @e END, |
|
|
|
cancel_at_end = ? |
|
|
|
WHERE id = ? |
|
|
|
` |
|
|
|
|
|
|
@@ -1609,8 +1627,10 @@ func (sub *Subscription) updateSub(db *sql.DB) error { |
|
|
|
sub.Start, |
|
|
|
sub.PaymentStatus, |
|
|
|
sub.Status, |
|
|
|
sub.CancelAtEnd, |
|
|
|
sub.Id, |
|
|
|
) |
|
|
|
|
|
|
|
if err != nil { return err } |
|
|
|
|
|
|
|
return err |
|
|
@@ -3162,6 +3182,17 @@ func createTrialSubscription(cid string) (*stripe.Subscription, error) { |
|
|
|
return s, err |
|
|
|
} |
|
|
|
|
|
|
|
// Cancel or uncancel subscription at the end of the current billing cycle. |
|
|
|
func (sub *Subscription) CancelSubscription(cancel bool) (*stripe.Subscription, error) { |
|
|
|
|
|
|
|
subscriptionParams := &stripe.SubscriptionParams{ |
|
|
|
CancelAtPeriodEnd: stripe.Bool(cancel), |
|
|
|
} |
|
|
|
s, err := subscription.Update(sub.StripeId, subscriptionParams) |
|
|
|
|
|
|
|
return s, err |
|
|
|
} |
|
|
|
|
|
|
|
func ( user *User ) SyncSub( sub *stripe.Subscription, db *sql.DB ) error { |
|
|
|
var err error |
|
|
|
|
|
|
@@ -3174,6 +3205,7 @@ func ( user *User ) SyncSub( sub *stripe.Subscription, db *sql.DB ) error { |
|
|
|
user.Sub.ClientSecret = sub.LatestInvoice.PaymentIntent.ClientSecret |
|
|
|
user.Sub.PaymentStatus = string(sub.LatestInvoice.PaymentIntent.Status) |
|
|
|
user.Sub.Status = string(sub.Status) |
|
|
|
user.Sub.CancelAtEnd = sub.CancelAtPeriodEnd |
|
|
|
|
|
|
|
if user.Sub.Id != 0 { |
|
|
|
err = user.Sub.insertSub(db) |
|
|
@@ -3312,6 +3344,36 @@ func trialSubscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) { |
|
|
|
json.NewEncoder(w).Encode(user.Sub) |
|
|
|
} |
|
|
|
|
|
|
|
// Sets Stripe subscription to cancel automatically at end of current period, |
|
|
|
// and updates subscription's 'CancelAtEnd' to true. If user's subscription is |
|
|
|
// already canceled, it will uncancel the subscription. |
|
|
|
func unsubscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) { |
|
|
|
stripe.Key = os.Getenv("STRIPE_SECRET_KEY") |
|
|
|
claims, err := getClaims(r) |
|
|
|
user, err := queryUser(db, claims.Id) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
w.WriteHeader(422) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
user.querySub(db) |
|
|
|
_, err = user.Sub.CancelSubscription(!user.Sub.CancelAtEnd) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
http.Error(w, err.Error(), 500) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
user.Sub.CancelAtEnd = !user.Sub.CancelAtEnd |
|
|
|
err = user.Sub.updateSub(db) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
http.Error(w, err.Error(), 500) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// A successful subscription payment should be confirmed by Stripe and |
|
|
|
// Updated through this hook. |
|
|
|
func invoicePaid(w http.ResponseWriter, db *sql.DB, r *http.Request) { |
|
|
@@ -3698,6 +3760,10 @@ func api(w http.ResponseWriter, r *http.Request) { |
|
|
|
r.Method == http.MethodPost && |
|
|
|
guard(r, 1): |
|
|
|
subscribe(w, db, r) |
|
|
|
case match(p, "/api/user/unsubscribe", &args) && |
|
|
|
r.Method == http.MethodGet && |
|
|
|
guard(r, 1): |
|
|
|
unsubscribe(w, db, r) |
|
|
|
case match(p, "/api/user/trial", &args) && |
|
|
|
r.Method == http.MethodPost && |
|
|
|
guard(r, 1): |
|
|
@@ -3813,7 +3879,7 @@ func serve() { |
|
|
|
} |
|
|
|
|
|
|
|
func dbReset(db *sql.DB) { |
|
|
|
b, err := os.ReadFile("migrations/reset.sql") |
|
|
|
b, err := migrations.ReadFile("migrations/reset.sql") |
|
|
|
if err != nil { |
|
|
|
log.Fatal(err) |
|
|
|
} |
|
|
@@ -3823,7 +3889,7 @@ func dbReset(db *sql.DB) { |
|
|
|
log.Fatal(err) |
|
|
|
} |
|
|
|
|
|
|
|
b, err = os.ReadFile("migrations/0_29092022_setup_tables.sql") |
|
|
|
b, err = migrations.ReadFile("migrations/0_29092022_setup_tables.sql") |
|
|
|
if err != nil { |
|
|
|
log.Fatal(err) |
|
|
|
} |
|
|
@@ -4135,7 +4201,7 @@ func dev(args []string) { |
|
|
|
} |
|
|
|
|
|
|
|
func check(args []string) { |
|
|
|
files := http.FileServer(http.Dir("")) |
|
|
|
files := http.FileServerFS(assets) |
|
|
|
|
|
|
|
http.Handle("/assets/", files) |
|
|
|
http.HandleFunc("/", checkPdf) |
|
|
|