got basic flow of subscription working. next to tie it together

This commit is contained in:
Colin Henry 2019-10-09 08:56:57 -07:00
parent 477395572d
commit e1863709df
18 changed files with 268 additions and 49 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
cmd/sub-demo/sub-demo
cmd/auth-demo/auth-demo
.vscode/

View File

@ -11,7 +11,7 @@ import (
func NewCallbackHandler(c Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := Store.Get(r, "auth-session")
session, err := Store.Get(r, SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -9,6 +9,9 @@ type Config struct {
Domain string
ClientID string
ClientSecret string
ManagementClientID string
ManagementClientSecret string
CallbackURL string
RedirectURL string
}
@ -18,6 +21,9 @@ func FromEnv() Config {
Domain: os.Getenv("AUTH_DOMAIN"),
ClientID: os.Getenv("AUTH_CLIENT_ID"),
ClientSecret: os.Getenv("AUTH_CLIENT_SECRET"),
ManagementClientID: os.Getenv("AUTH_MGMT_CLIENT_ID"),
ManagementClientSecret: os.Getenv("AUTH_MGMT_CLIENT_SECRET"),
CallbackURL: os.Getenv("AUTH_CALLBACK_URL"),
RedirectURL: "/user",
}

View File

@ -19,7 +19,7 @@ func NewLoginHandler(c Config) http.HandlerFunc {
}
state := base64.StdEncoding.EncodeToString(b)
session, err := Store.Get(r, "auth-session")
session, err := Store.Get(r, SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -7,20 +7,37 @@ import (
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
if cook, err := r.Cookie(SessionName); err == nil {
cook.MaxAge = -1
http.SetCookie(w, cook)
}
domain := "dev-pb4s8m55.auth0.com"
var Url *url.URL
Url, err := url.Parse("https://" + domain)
// var Url *url.URL
URL, err := url.Parse("https://" + domain)
if err != nil {
panic(err.Error())
}
Url.Path += "/v2/logout"
session, err := Store.Get(r, SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Options.MaxAge = -1
err = session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
URL.Path += "/v2/logout"
parameters := url.Values{}
parameters.Add("returnTo", "http://localhost:3000")
parameters.Add("client_id", "ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9")
Url.RawQuery = parameters.Encode()
URL.RawQuery = parameters.Encode()
http.Redirect(w, r, Url.String(), http.StatusTemporaryRedirect)
http.Redirect(w, r, URL.String(), http.StatusTemporaryRedirect)
}

View File

@ -4,7 +4,7 @@ import "net/http"
func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
session, err := Store.Get(r, "auth-session")
session, err := Store.Get(r, SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -5,6 +5,7 @@ import (
"github.com/codegangsta/negroni"
jch_http "github.com/jchenry/jchenry/http"
"gopkg.in/auth0.v1/management"
)
func Service(c Config) ServiceInstance {
@ -25,3 +26,15 @@ func (si ServiceInstance) Register(uriBase string, s *jch_http.Server) {
negroni.Wrap(http.HandlerFunc(UserHandler)),
))
}
func (si ServiceInstance) UpdateUser(u User) error {
m, err := management.New(si.c.Domain, si.c.ManagementClientID, si.c.ManagementClientSecret)
if err != nil {
return err
}
um := management.NewUserManager(m)
return um.Update(u.ID, &management.User{AppMetadata: u.Apps})
}

View File

@ -6,14 +6,14 @@ import (
"github.com/gorilla/sessions"
)
const SessionName = "auth-session"
var (
Store *sessions.FilesystemStore
)
func Init() error {
Store = sessions.NewFilesystemStore("", []byte("something-very-secret"))
gob.Register(map[string]interface{}{})
gob.Register(User{})
return nil
}

View File

@ -8,7 +8,7 @@ import (
func UserHandler(w http.ResponseWriter, r *http.Request) {
session, err := Store.Get(r, "auth-session")
session, err := Store.Get(r, SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -18,23 +18,16 @@ func UserHandler(w http.ResponseWriter, r *http.Request) {
}
type User struct {
ID string `json:"sub"`
Email string `json:"email"`
FirstName string `json:"given_name"`
LastName string `json:"family_name"`
Picture string `json:"picture"`
Nickname string `json:"nickname"`
AppMetadata AppMetadata `json:"app_metadata"`
Apps map[string]interface{} `json:"app_metadata,omitempty"`
//UserMetadata UserMetadata `json:"user_metadata"`
}
type AppMetadata struct {
Apps map[string]string // an association between the unique applicationID and the tenantID that the user is associated with
// Apps []struct {
// ApplicationID string
// TenantID string
// }
}
// type UserMetadata struct {
// }

View File

@ -18,10 +18,12 @@ func main() {
func StartServer() {
auth.PrintConfig()
payments.PrintConfig()
auth_service := auth.Service(auth.FromEnv())
s := jch_http.NewServer(negroni.New()).
Static("/public/*filepath", http.Dir("public/")).
Service("", auth.Service(auth.FromEnv())).
Service("", payments.Service(payments.FromEnv())).
Service("", auth_service).
Service("", payments.Service(payments.FromEnv(), &auth_service)).
GET("/", "", http.HandlerFunc(HomeHandler))
port := os.Getenv("PORT")

View File

@ -3,6 +3,8 @@ AUTH_DOMAIN="https://dev-pb4s8m55.auth0.com/" \
AUTH_CLIENT_ID="ae1e02bTwXA35O3r3Xxk4kbRf31j5ge9" \
AUTH_CLIENT_SECRET="NFC5KYeM9GA2z0ptvzKPo9jmkQDRjx_WcsWyK0hzOJmr1CykS9cEmTcNh0-hKiMd" \
AUTH_CALLBACK_URL="http://localhost:3000/callback" \
AUTH_MGMT_CLIENT_ID="0SpgXDo7HleFZ6NH9ds2t2vkntEzgYqy" \
AUTH_MGMT_CLIENT_SECRET="DhOE1JqO7A2uosadjuyCluK5P3NlxOf4V9mPkEDy4gDO_lYnMffzYfVpgcINvzfr" \
STRIPE_KEY="sk_test_3yIcJl5ev3WfFZ4HNbTMqWv800B26e0c4V" \
STRIPE_PRODUCT_ID="prod_FtzugcD89mNVhp" \
./sub-demo

View File

@ -10,26 +10,114 @@
<script src="/public/js.cookie.js"></script>
<link href="/public/app.css" rel="stylesheet">
<script src="https://js.stripe.com/v3/"></script>
</head>
<body class="home">
<div class="container">
{{.Product.Name}}
<form action="" method="POST">
<form method="POST" id="payment-form">
<div class="form-group">
{{range .Plans}}
<div class="form-check">
<input type="radio" name="plan" class="form-control"
id="{{.ID}}" value="{{.ID}}">
<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 class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
<!-- Used to display form errors. -->
<div id="card-errors" role="alert"></div>
</div>
<button>Submit Payment</button>
</div>
</form>
</div>
<script>
// Create a Stripe client.
var stripe = Stripe('pk_test_ghSfaAtzKpBNxyrRlgGPuVBu00v5d1ptjL');
// Create an instance of Elements.
var elements = stripe.elements();
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
// Create an instance of the card Element.
var card = elements.create('card', { style: style });
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function (event) {
event.preventDefault();
stripe.createToken(card).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
</script>
</body>
</html>

View File

@ -1,4 +1,5 @@
<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>
@ -11,6 +12,7 @@
<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">
@ -21,5 +23,14 @@
</div>
</div>
</div>
<script>
// $(document).ready(function () {
// $('.btn-logout').click(function (e) {
// Cookies.remove('auth-session');
// });
// });
</script>
</body>
</html>

3
go.mod
View File

@ -5,13 +5,14 @@ go 1.13
require (
github.com/codegangsta/negroni v1.0.0
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/gorilla/mux v1.7.3
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/stretchr/testify v1.4.0 // 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/auth0.v1 v1.2.7
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
rsc.io/dbstore v0.1.1
)

20
go.sum
View File

@ -1,20 +1,27 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9 h1:VE0eMvNSQI72dADsq4gm5KpNPmt97WgqneTfaS5MWrs=
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9/go.mod h1:ItsOiHl4XeMOV3rzbZqQRjLc3QQxbE6391/9iNG7rE8=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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=
@ -25,16 +32,25 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/auth0.v1 v1.2.7 h1:9UCE5rKFL60rqQENmmJaGdNu7/aby8r8wVcJ83Vj5oU=
gopkg.in/auth0.v1 v1.2.7/go.mod h1:1FRtMXwYDgygZcO7Of7kj/I4mf9UjHGhMHUOqNT0d0M=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
rsc.io/dbstore v0.1.1 h1:LI4gBJUwbejn0wHJWe0KTwgCM33zUVP3BsNz5y2fkEE=
rsc.io/dbstore v0.1.1/go.mod h1:zI7k1PCSLg9r/T2rBM4E/SctbGmqdtt3kjQSemVh1Rs=
rsc.io/sqlite v0.5.0 h1:HG63YxeP0eALjqorwnJ9ENxUUOUR6NYJ4FHEKFJ7aVk=

View File

@ -8,12 +8,15 @@ import (
type Config struct {
StripeKey string
StripeProductID string
RedirectURL string
TenantSetup func(subscriptionID, customerID string) (tenantID string)
}
func FromEnv() Config {
return Config{
StripeKey: os.Getenv("STRIPE_KEY"),
StripeProductID: os.Getenv("STRIPE_PRODUCT_ID"),
RedirectURL: "/",
}
}

View File

@ -9,7 +9,7 @@ import (
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")
session, err := auth.Store.Get(r, auth.SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -17,7 +17,7 @@ func HasTenantAndSubscription(productID string) func(w http.ResponseWriter, r *h
if u, ok := session.Values["profile"]; ok {
user := u.(auth.User)
if _, exist := user.AppMetadata.Apps[productID]; !exist {
if _, exist := user.Apps[productID]; exist {
next(w, r)
} else {
http.Redirect(w, r, "/subscription", http.StatusSeeOther)

View File

@ -1,6 +1,7 @@
package payments
import (
"fmt"
"net/http"
"github.com/codegangsta/negroni"
@ -8,30 +9,38 @@ import (
jch_http "github.com/jchenry/jchenry/http"
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/client"
"github.com/stripe/stripe-go/customer"
"github.com/stripe/stripe-go/plan"
"github.com/stripe/stripe-go/product"
"github.com/stripe/stripe-go/sub"
)
func Service(c Config) ServiceInstance {
func Service(c Config, auth *auth.ServiceInstance) ServiceInstance {
stripe.Key = c.StripeKey
sc := &client.API{}
sc.Init(c.StripeKey, nil)
return ServiceInstance{
c: c,
stripe: sc,
auth: auth,
}
}
type ServiceInstance struct {
c Config
stripe *client.API
auth *auth.ServiceInstance
}
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)),
)).POST(uriBase+"/subscription", "subscription payment endpoint", negroni.New(
negroni.HandlerFunc(auth.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(si.paymentHandler)),
))
}
func (si ServiceInstance) subscriptionHandler(w http.ResponseWriter, r *http.Request) {
@ -54,3 +63,59 @@ type offering struct {
Product stripe.Product
Plans []stripe.Plan
}
func (si ServiceInstance) paymentHandler(w http.ResponseWriter, r *http.Request) {
session, err := auth.Store.Get(r, auth.SessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if u, ok := session.Values["profile"]; ok {
user := u.(auth.User)
r.ParseForm()
params := &stripe.CustomerParams{
Email: stripe.String(user.Email),
Name: stripe.String(fmt.Sprintf("%s, %s", user.LastName, user.FirstName)),
}
params.SetSource(r.PostFormValue("stripeToken"))
cus, err := customer.New(params)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
p := &stripe.SubscriptionParams{
Customer: stripe.String(cus.ID),
Items: []*stripe.SubscriptionItemsParams{
{
Plan: stripe.String(r.PostFormValue("plan")),
},
},
}
s, err := sub.New(p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if si.c.TenantSetup == nil {
panic("need code to setup the tenant")
}
if user.Apps == nil {
user.Apps = map[string]interface{}{}
}
user.Apps[si.c.StripeProductID] = si.c.TenantSetup(s.ID, user.ID)
err = si.auth.UpdateUser(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, si.c.RedirectURL, http.StatusSeeOther)
}
}