ソースを参照

Add endpoints trial subscription and verification

Generate verification tokens and sendout SMTP relay from endpoint.
Authentication values rely on environment variables. The new trial
subscription endpoint copies the existing one but adds the trial days
parameter.
master
Immanuel Onyeka 7ヶ月前
コミット
18e5086e89
1個のファイルの変更151行の追加9行の削除
  1. +151
    -9
      skouter.go

+ 151
- 9
skouter.go ファイルの表示

@@ -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):


読み込み中…
キャンセル
保存