@@ -16,6 +16,7 @@ import (
"net/http/httputil"
"net/mail"
"net/url"
"net/smtp"
"os"
"os/exec"
"regexp"
@@ -324,6 +325,20 @@ func (c UserClaims) Valid() error {
return err
}
func (c VerificationClaims) Valid() error {
if c.Id < 1 {
return errors.New("Invalid id")
}
t, err := time.Parse(time.UnixDate, c.Exp)
if err != nil {
return err
}
if t.Before(time.Now()) {
return errors.New("Token expired.")
}
return err
}
func cache(name string, title string) Page {
var p = []string{"views/master.tpl", paths[name]}
tpl := template.Must(template.ParseFiles(p...))
@@ -3059,6 +3074,35 @@ func createSubscription(cid string) (*stripe.Subscription, error) {
return s, err
}
// Initiates a new trial subscription using a given customer ID
func createTrialSubscription(cid string) (*stripe.Subscription, error) {
// Automatically save the payment method to the subscription
// when the first payment is successful.
paymentSettings := &stripe.SubscriptionPaymentSettingsParams{
SaveDefaultPaymentMethod: stripe.String("on_subscription"),
}
// Create the subscription. Note we're expanding the Subscription's
// 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(cid),
Items: []*stripe.SubscriptionItemsParams{
{
Price: stripe.String(standardPriceId),
},
},
TrialPeriodDays: stripe.Int64(30),
PaymentSettings: paymentSettings,
PaymentBehavior: stripe.String("default_incomplete"),
}
subscriptionParams.AddExpand("latest_invoice.payment_intent")
s, err := subscription.New(subscriptionParams)
return s, err
}
func ( user *User ) SyncSub( sub *stripe.Subscription, db *sql.DB ) error {
var err error
@@ -3127,8 +3171,9 @@ func subscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) {
user.Sub.ClientSecret = s.LatestInvoice.PaymentIntent.ClientSecret
user.Sub.PaymentStatus = string(s.LatestInvoice.PaymentIntent.Status)
// Inserting from here is probably unnecessary and confusing because
// new subs are already handled by the stripe hook
// Inserting from here is unnecessary and confusing because
// new subs are already handled by the stripe hook. It remains for
// easier testing of the endpoint.
err = user.Sub.insertSub(db)
if err != nil {
http.Error(w, err.Error(), 500)
@@ -3140,7 +3185,68 @@ func subscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) {
}
json.NewEncoder(w).Encode(user.Sub)
}
// 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.
func trialSubscribe(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
}
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 := createTrialSubscription(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.PriceId = standardPriceId
user.Sub.End = int(s.CurrentPeriodEnd)
user.Sub.Start = int(s.CurrentPeriodStart)
user.Sub.ClientSecret = s.LatestInvoice.PaymentIntent.ClientSecret
user.Sub.PaymentStatus = string(s.LatestInvoice.PaymentIntent.Status)
// Inserting from here is unnecessary and confusing because
// new subs are already handled by the stripe hook. It remains for
// easier testing of the endpoint.
err = user.Sub.insertSub(db)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
} else {
// This should handle creating a new subscription when the old one
// has an incomplete_expired status and cannot be paid
}
json.NewEncoder(w).Encode(user.Sub)
}
// A successful subscription payment should be confirmed by Stripe and
@@ -3255,8 +3361,8 @@ func subCreated(w http.ResponseWriter, db *sql.DB, r *http.Request) {
log.Println("User subscription created:", user.Id, sub.ID)
}
// Checks if the user already has a subscription and replaces those fields if
// necessary.
// 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
var err error
@@ -3352,24 +3458,56 @@ func subDeleted(w http.ResponseWriter, db *sql.DB, r *http.Request) {
log.Println("User subscription created:", user.Id, sub.ID)
}
func verificationToken(id int) string {
func verificationToken(id int) ( string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256,
VerificationClaims{Id: id,
Exp: time.Now().Add(time.Day * 2 ).Format(time.UnixDate)})
Exp: time.Now().Add(time.Hour * 48 ).Format(time.UnixDate)})
tokenStr, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
if err != nil {
log.Println("Verification could not be signed: ", err, tokenStr )
return err
log.Println("Verification could not be signed: ", err)
return tokenStr, err
}
return tokenStr
return tokenStr, nil
}
func verifyUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
}
func (user *User) sendVerification(w http.ResponseWriter,
db *sql.DB,
r *http.Request) {
auth := smtp.PlainAuth("",
os.Getenv("SMTP_USERNAME"),
os.Getenv("SMTP_PASSWORD"),
os.Getenv("SMTP_HOST"))
address := os.Getenv("SMTP_HOST") + ":" + os.Getenv("SMTP_PORT")
message := `Subject: Email Verification
Welcome %s,
Click the link below to verify your email address
%s`
t, err := verificationToken(user.Id)
if err != nil { return }
message = fmt.Sprintf(message, user.FirstName, t)
err = smtp.SendMail(address,
auth,
os.Getenv("SMTP_EMAIL"),
[]string{user.Email},
[]byte(message))
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent Successfully!")
}
func api(w http.ResponseWriter, r *http.Request) {
var args []string
@@ -3447,6 +3585,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/trial", &args) &&
r.Method == http.MethodPost &&
guard(r, 1):
trialSubscribe(w, db, r)
case match(p, "/api/fees", &args) &&
r.Method == http.MethodGet &&
guard(r, 1):