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:
parent
bf7dd04b69
commit
477395572d
22
cmd/sub-demo/home.html
Normal file
22
cmd/sub-demo/home.html
Normal 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
37
cmd/sub-demo/main.go
Normal 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)
|
||||
}
|
95
cmd/sub-demo/public/app.css
Normal file
95
cmd/sub-demo/public/app.css
Normal 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
8
cmd/sub-demo/run.sh
Executable 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
|
35
cmd/sub-demo/subscription.html
Normal file
35
cmd/sub-demo/subscription.html
Normal 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
25
cmd/sub-demo/user.html
Normal 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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -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
27
payments/middleware.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user