WIP: Subscription page shows available plans for product, not to figure out how to wire it up to make a payment and create a tennnnnnant with a subscription

This commit is contained in:
Colin Henry 2019-10-01 11:15:37 -07:00
parent bf7dd04b69
commit 477395572d
11 changed files with 312 additions and 3 deletions

22
cmd/sub-demo/home.html Normal file
View File

@ -0,0 +1,22 @@
<html>
<head>
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- font awesome from BootstrapCDN -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="/public/app.css" rel="stylesheet">
</head>
<body class="home">
<div class="container">
<div class="login-page clearfix">
<div class="login-box auth0-box before">
<a id="qsLoginBtn" class="btn btn-primary btn-lg btn-block" href="/login">SignIn</a>
</div>
</div>
</div>
</body>
</html>

37
cmd/sub-demo/main.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"net/http"
"os"
"github.com/codegangsta/negroni"
"github.com/jchenry/jchenry/auth"
jch_http "github.com/jchenry/jchenry/http"
"github.com/jchenry/jchenry/payments"
)
func main() {
auth.Init()
StartServer()
}
func StartServer() {
auth.PrintConfig()
payments.PrintConfig()
s := jch_http.NewServer(negroni.New()).
Static("/public/*filepath", http.Dir("public/")).
Service("", auth.Service(auth.FromEnv())).
Service("", payments.Service(payments.FromEnv())).
GET("/", "", http.HandlerFunc(HomeHandler))
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
s.Run(":" + port)
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
jch_http.RenderTemplate(w, "home", nil)
}

View File

@ -0,0 +1,95 @@
body {
font-family: "proxima-nova", sans-serif;
text-align: center;
font-size: 300%;
font-weight: 100;
}
input[type=checkbox],
input[type=radio] {
position: absolute;
opacity: 0;
}
input[type=checkbox] + label,
input[type=radio] + label {
display: inline-block;
}
input[type=checkbox] + label:before,
input[type=radio] + label:before {
content: "";
display: inline-block;
vertical-align: -0.2em;
width: 1em;
height: 1em;
border: 0.15em solid #0074d9;
border-radius: 0.2em;
margin-right: 0.3em;
background-color: white;
}
input[type=radio] + label:before {
border-radius: 50%;
}
input[type=radio]:checked + label:before,
input[type=checkbox]:checked + label:before {
background-color: #0074d9;
box-shadow: inset 0 0 0 0.15em white;
}
input[type=radio]:focus + label:before,
input[type=checkbox]:focus + label:before {
outline: 0;
}
.btn {
font-size: 140%;
text-transform: uppercase;
letter-spacing: 1px;
border: 0;
background-color: #16214D;
color: white;
}
.btn:hover {
background-color: #44C7F4;
}
.btn:focus {
outline: none !important;
}
.btn.btn-lg {
padding: 20px 30px;
}
.btn:disabled {
background-color: #333;
color: #666;
}
h1,
h2,
h3 {
font-weight: 100;
}
#logo img {
width: 300px;
margin-bottom: 60px;
}
.home-description {
font-weight: 100;
margin: 100px 0;
}
h2 {
margin-top: 30px;
margin-bottom: 40px;
font-size: 200%;
}
label {
font-size: 100%;
font-weight: 300;
}
.btn-next {
margin-top: 30px;
}
.answer {
width: 70%;
margin: auto;
text-align: left;
padding-left: 10%;
margin-bottom: 20px;
}
.login-page .login-box {
padding: 100px 0;
}

8
cmd/sub-demo/run.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
AUTH_DOMAIN="https://dev-pb4s8m55.auth0.com/" \
AUTH_CLIENT_ID="ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9" \
AUTH_CLIENT_SECRET="NFC5KYeM9GA2z0ptvzKPo9jmkQDRjx_WcsWyK0hzOJmr1CykS9cEmTcNh0-hKiMd" \
AUTH_CALLBACK_URL="http://localhost:3000/callback" \
STRIPE_KEY="sk_test_3yIcJl5ev3WfFZ4HNbTMqWv800B26e0c4V" \
STRIPE_PRODUCT_ID="prod_FtzugcD89mNVhp" \
./sub-demo

View File

@ -0,0 +1,35 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
<!-- font awesome from BootstrapCDN -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="/public/js.cookie.js"></script>
<link href="/public/app.css" rel="stylesheet">
</head>
<body class="home">
<div class="container">
{{.Product.Name}}
<form action="" method="POST">
<div class="form-group">
{{range .Plans}}
<div class="form-check">
<input type="radio" name="plan" class="form-control"
id="{{.ID}}" value="{{.ID}}">
<label class="form-check-label" for="{{.ID}}">
{{.Nickname}}({{.AmountDecimal}})
</label>
</div>
{{end}}
</div>
</form>
</div>
</body>
</html>

25
cmd/sub-demo/user.html Normal file
View File

@ -0,0 +1,25 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
<!-- font awesome from BootstrapCDN -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="/public/js.cookie.js"></script>
<script src="/public/user.js"> </script>
<link href="/public/app.css" rel="stylesheet">
</head>
<body class="home">
<div class="container">
<div class="login-page clearfix">
<div class="logged-in-box auth0-box logged-in">
<img class="avatar" src="{{.Picture}}"/>
<h2>Welcome {{.Nickname}}</h2>
<a id="qsLogoutBtn" class="btn btn-primary btn-lg btn-logout btn-block" href="/logout">Logout</a>
</div>
</div>
</div>
</body>
</html>

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/gorilla/sessions v1.2.0
github.com/julienschmidt/httprouter v1.2.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/stripe/stripe-go v63.4.0+incompatible
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
gopkg.in/square/go-jose.v2 v2.3.1 // indirect

2
go.sum
View File

@ -15,6 +15,8 @@ github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/stripe/stripe-go v63.4.0+incompatible h1:zzZR004GZ/si7nyckn4NBhoQOViUu5VJ/sA7NT7oTSs=
github.com/stripe/stripe-go v63.4.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -1,4 +1,22 @@
package payments
import (
"fmt"
"os"
)
type Config struct {
StripeKey string
StripeProductID string
}
func FromEnv() Config {
return Config{
StripeKey: os.Getenv("STRIPE_KEY"),
StripeProductID: os.Getenv("STRIPE_PRODUCT_ID"),
}
}
func PrintConfig() {
fmt.Printf("%#v\n", FromEnv())
}

27
payments/middleware.go Normal file
View File

@ -0,0 +1,27 @@
package payments
import (
"net/http"
"github.com/jchenry/jchenry/auth"
)
func HasTenantAndSubscription(productID string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
session, err := auth.Store.Get(r, "auth-session")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if u, ok := session.Values["profile"]; ok {
user := u.(auth.User)
if _, exist := user.AppMetadata.Apps[productID]; !exist {
next(w, r)
} else {
http.Redirect(w, r, "/subscription", http.StatusSeeOther)
}
}
}
}

View File

@ -1,17 +1,56 @@
package payments
import (
"net/http"
"github.com/codegangsta/negroni"
"github.com/jchenry/jchenry/auth"
jch_http "github.com/jchenry/jchenry/http"
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/client"
"github.com/stripe/stripe-go/plan"
"github.com/stripe/stripe-go/product"
)
func Service(c Config) ServiceInstance {
return ServiceInstance{c: c}
stripe.Key = c.StripeKey
sc := &client.API{}
sc.Init(c.StripeKey, nil)
return ServiceInstance{
c: c,
stripe: sc,
}
}
type ServiceInstance struct {
c Config
c Config
stripe *client.API
}
func (si ServiceInstance) Register(uriBase string, s *jch_http.Server) {
s.GET(uriBase+"/subscription", "subscription info endpoint", negroni.New(
negroni.HandlerFunc(auth.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(si.subscriptionHandler)),
))
}
func (si ServiceInstance) subscriptionHandler(w http.ResponseWriter, r *http.Request) {
prod, _ := product.Get(si.c.StripeProductID, nil)
params := &stripe.PlanListParams{
Product: &si.c.StripeProductID,
}
it := plan.List(params)
var plans []stripe.Plan
for it.Next() {
plans = append(plans, *it.Plan())
}
jch_http.RenderTemplate(w, "subscription", offering{Product: *prod, Plans: plans})
}
type offering struct {
Product stripe.Product
Plans []stripe.Plan
}