Compare commits
5 Commits
01690d0236
...
48098ba817
Author | SHA1 | Date | |
---|---|---|---|
48098ba817 | |||
5ab6ad85f7 | |||
c3897d47bc | |||
fd1d1808f0 | |||
0b35136d3f |
@ -3,15 +3,27 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"myapp/internal/cards"
|
"myapp/internal/cards"
|
||||||
|
"myapp/internal/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/stripe/stripe-go/v79"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stripePayload struct {
|
type stripePayload struct {
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
CardBrand string `json:"card_brand"`
|
||||||
|
ExpiryMonth int `json:"expiry_month"`
|
||||||
|
ExpiryYear int `json:"expiry_year"`
|
||||||
|
LastFour string `json:"last_four"`
|
||||||
|
Plan string `json:"plan"`
|
||||||
|
ProductID string `json:"product_id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonResponse struct {
|
type jsonResponse struct {
|
||||||
@ -83,3 +95,138 @@ func (app *application) GetWidgetByID(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) CreateCustomerAndSubscribeToPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data stripePayload
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.infoLog.Println(data.Email, data.LastFour, data.PaymentMethod, data.Plan)
|
||||||
|
|
||||||
|
card := cards.Card{
|
||||||
|
Secret: app.config.stripe.secret,
|
||||||
|
Key: app.config.stripe.key,
|
||||||
|
Currency: data.Currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
okay := true
|
||||||
|
var subscription *stripe.Subscription
|
||||||
|
txnMsg := "Transaction successful"
|
||||||
|
|
||||||
|
stripeCustomer, msg, err := card.CreateCustomer(data.PaymentMethod, data.Email)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
okay = false
|
||||||
|
txnMsg = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
if okay {
|
||||||
|
subscription, err = card.SubscribeToPlan(
|
||||||
|
stripeCustomer,
|
||||||
|
data.Plan,
|
||||||
|
data.Email,
|
||||||
|
data.LastFour,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
okay = false
|
||||||
|
txnMsg = "Error subscribing customer"
|
||||||
|
}
|
||||||
|
app.infoLog.Println("subscription id is", subscription.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if okay {
|
||||||
|
productID, _ := strconv.Atoi(data.ProductID)
|
||||||
|
customerID, err := app.SaveCustomer(data.FirstName, data.LastName, data.Email)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new txn
|
||||||
|
amount, _ := strconv.Atoi(data.Amount)
|
||||||
|
txn := models.Transaction{
|
||||||
|
Amount: amount,
|
||||||
|
Currency: "eur",
|
||||||
|
LastFour: data.LastFour,
|
||||||
|
ExpiryMonth: data.ExpiryMonth,
|
||||||
|
ExpiryYear: data.ExpiryYear,
|
||||||
|
TransactionStatusID: 2,
|
||||||
|
}
|
||||||
|
txnID, err := app.SaveTransaction(txn)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create order
|
||||||
|
order := models.Order{
|
||||||
|
WidgetID: productID,
|
||||||
|
TransactionID: txnID,
|
||||||
|
CustomerID: customerID,
|
||||||
|
StatusID: 1,
|
||||||
|
Quantity: 1,
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = app.SaveOrder(order)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := jsonResponse{
|
||||||
|
OK: okay,
|
||||||
|
Message: txnMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := json.MarshalIndent(resp, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) SaveCustomer(firstName, lastName, email string) (int, error) {
|
||||||
|
customer := models.Customer{
|
||||||
|
FirstName: firstName,
|
||||||
|
LastName: lastName,
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := app.DB.InsertCustomer(customer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) SaveTransaction(txn models.Transaction) (int, error) {
|
||||||
|
txnID, err := app.DB.InsertTransaction(txn)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return txnID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) SaveOrder(order models.Order) (int, error) {
|
||||||
|
id, err := app.DB.InsertOrder(order)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
@ -20,5 +20,6 @@ func (app *application) routes() http.Handler {
|
|||||||
|
|
||||||
mux.Post("/api/payment-intent", app.GetPaymentIntent)
|
mux.Post("/api/payment-intent", app.GetPaymentIntent)
|
||||||
mux.Get("/api/widget/{id}", app.GetWidgetByID)
|
mux.Get("/api/widget/{id}", app.GetWidgetByID)
|
||||||
|
mux.Post("/api/create-customer-and-subscribe-to-plan", app.CreateCustomerAndSubscribeToPlan)
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
@ -277,3 +277,9 @@ func (app *application) BronzePlan(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.errorLog.Println(err)
|
app.errorLog.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) BronzePlanReceipt(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := app.renderTemplate(w, r, "receipt-plan", &templateData{}); err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ func (app *application) routes() http.Handler {
|
|||||||
mux.Post("/payment-succeeded", app.PaymentSucceeded)
|
mux.Post("/payment-succeeded", app.PaymentSucceeded)
|
||||||
|
|
||||||
mux.Get("/plans/bronze", app.BronzePlan)
|
mux.Get("/plans/bronze", app.BronzePlan)
|
||||||
|
mux.Get("/receipt/bronze", app.BronzePlanReceipt)
|
||||||
|
|
||||||
fileServer := http.FileServer(http.Dir("./static"))
|
fileServer := http.FileServer(http.Dir("./static"))
|
||||||
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
@ -4,7 +4,7 @@ Bronze Plan
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{$widget := index .Data "widget"}}
|
{{$widget := index .Data "widget"}}
|
||||||
<h2 class="mt-3 text-center">Bronze Plan: {{formatCurrency $widget.Price}}</h2>
|
<h2 class="mt-3 text-center">Bronze Plan: {{ formatCurrency $widget.Price }}</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="alert alert-danger text-center d-none" id="card-messages"></div>
|
<div class="alert alert-danger text-center d-none" id="card-messages"></div>
|
||||||
<form action="/payment-succeeded-temp"
|
<form action="/payment-succeeded-temp"
|
||||||
@ -14,7 +14,7 @@ Bronze Plan
|
|||||||
class="d-blick needs-validation charge-form"
|
class="d-blick needs-validation charge-form"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
novalidate="">
|
novalidate="">
|
||||||
<input type="hidden" name="product_id" value="{{$widget.ID}}">
|
<input type="hidden" id="product_id" name="product_id" value="{{$widget.ID}}">
|
||||||
<input type="hidden" id="amount" name="amount" value="{{$widget.Price}}">
|
<input type="hidden" id="amount" name="amount" value="{{$widget.Price}}">
|
||||||
<p class="mt-2 mb-3 text-center">{{$widget.Description}}</p>
|
<p class="mt-2 mb-3 text-center">{{$widget.Description}}</p>
|
||||||
<hr>
|
<hr>
|
||||||
@ -62,10 +62,7 @@ Bronze Plan
|
|||||||
<div class="alert-success text-center" id="card-success" role="alert"></div>
|
<div class="alert-success text-center" id="card-success" role="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<a href="javascript:void(0)"
|
<a href="javascript:void(0)" id="pay-button" class="btn btn-primary">Pay {{ formatCurrency $widget.Price }}/month</a>
|
||||||
id="pay-button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
onclick="val({{.API}})">Pay {{formatCurrency $widget.Price}}/month</a>
|
|
||||||
<div class="text-center d-none" id="processing-payment">
|
<div class="text-center d-none" id="processing-payment">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
@ -90,13 +87,14 @@ Bronze Plan
|
|||||||
</form>
|
</form>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "js" }}
|
{{ define "js" }}
|
||||||
|
{{$widget := index .Data "widget"}}
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {stripeInit} from "/static/js/common.js";
|
import {stripeInit} from "/static/js/common.js";
|
||||||
import {val} from "/static/js/stripe-plan.js"
|
import {val} from "/static/js/stripe-plan.js"
|
||||||
stripeInit('{{.StripePubKey}}');
|
stripeInit('{{.StripePubKey}}');
|
||||||
document.getElementById("pay-button").addEventListener("click", () => {
|
document.getElementById("pay-button").addEventListener("click", () => {
|
||||||
val({{.API}});
|
val({{$widget.PlanID}}, {{.API}});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
6
go.mod
6
go.mod
@ -3,13 +3,11 @@ module myapp
|
|||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alexedwards/scs/v2 v2.8.0
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/stripe/stripe-go/v79 v79.6.0
|
github.com/stripe/stripe-go/v79 v79.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
|
||||||
github.com/alexedwards/scs/v2 v2.8.0 // indirect
|
|
||||||
)
|
|
||||||
|
@ -2,8 +2,10 @@ package cards
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stripe/stripe-go/v79"
|
"github.com/stripe/stripe-go/v79"
|
||||||
|
"github.com/stripe/stripe-go/v79/customer"
|
||||||
"github.com/stripe/stripe-go/v79/paymentintent"
|
"github.com/stripe/stripe-go/v79/paymentintent"
|
||||||
"github.com/stripe/stripe-go/v79/paymentmethod"
|
"github.com/stripe/stripe-go/v79/paymentmethod"
|
||||||
|
"github.com/stripe/stripe-go/v79/subscription"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Card struct {
|
type Card struct {
|
||||||
@ -71,6 +73,51 @@ func (c *Card) RetrievePaymentIntent(id string) (*stripe.PaymentIntent, error) {
|
|||||||
return pi, nil
|
return pi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Card) SubscribeToPlan(
|
||||||
|
cust *stripe.Customer,
|
||||||
|
plan, email, last4, cardType string,
|
||||||
|
) (*stripe.Subscription, error) {
|
||||||
|
stripeCustomerID := cust.ID
|
||||||
|
items := []*stripe.SubscriptionItemsParams{
|
||||||
|
{Plan: stripe.String(plan)},
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &stripe.SubscriptionParams{
|
||||||
|
Customer: stripe.String(stripeCustomerID),
|
||||||
|
Items: items,
|
||||||
|
}
|
||||||
|
|
||||||
|
params.AddMetadata("last_four", last4)
|
||||||
|
params.AddMetadata("card_type", cardType)
|
||||||
|
params.AddExpand("latest_invoice.payment_intent")
|
||||||
|
subscription, err := subscription.New(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return subscription, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Card) CreateCustomer(pm, email string) (*stripe.Customer, string, error) {
|
||||||
|
stripe.Key = c.Secret
|
||||||
|
customerParams := &stripe.CustomerParams{
|
||||||
|
PaymentMethod: stripe.String(pm),
|
||||||
|
Email: stripe.String(email),
|
||||||
|
InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{
|
||||||
|
DefaultPaymentMethod: stripe.String(pm),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cust, err := customer.New(customerParams)
|
||||||
|
if err != nil {
|
||||||
|
msg := ""
|
||||||
|
if stripeErr, ok := err.(*stripe.Error); ok {
|
||||||
|
msg = cardErrorMessage(stripeErr.Code)
|
||||||
|
}
|
||||||
|
return nil, msg, err
|
||||||
|
}
|
||||||
|
return cust, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func cardErrorMessage(code stripe.ErrorCode) string {
|
func cardErrorMessage(code stripe.ErrorCode) string {
|
||||||
msg := ""
|
msg := ""
|
||||||
|
|
||||||
|
@ -39,10 +39,17 @@ function stripePaymentMethodHandler(result, plan_id, api) {
|
|||||||
} else {
|
} else {
|
||||||
// create a customer and subscribe to plan
|
// create a customer and subscribe to plan
|
||||||
let payload = {
|
let payload = {
|
||||||
|
product_id: document.getElementById("product_id").value,
|
||||||
plan: plan_id,
|
plan: plan_id,
|
||||||
payment_method: result.paymentMethod.id,
|
payment_method: result.paymentMethod.id,
|
||||||
email: document.getElementById("cardholder-email").value,
|
email: document.getElementById("cardholder-email").value,
|
||||||
last_four: result.paymentMethod.card.last4,
|
last_four: result.paymentMethod.card.last4,
|
||||||
|
card_brand: result.paymentMethod.card.brand,
|
||||||
|
expiry_month: result.paymentMethod.card.exp_month,
|
||||||
|
expiry_year: result.paymentMethod.card.exp_year,
|
||||||
|
first_name: document.getElementById("first-name").value,
|
||||||
|
last_name: document.getElementById("last-name").value,
|
||||||
|
amount: document.getElementById("amount").value,
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
@ -59,8 +66,12 @@ function stripePaymentMethodHandler(result, plan_id, api) {
|
|||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
processing.classList.add("d-none");
|
processing.classList.add("d-none");
|
||||||
// set hidden vars
|
showCardSuccess();
|
||||||
// submit the form
|
sessionStorage.first_name = document.getElementById("first-name").value;
|
||||||
|
sessionStorage.last_name = document.getElementById("last-name").value;
|
||||||
|
sessionStorage.amount = document.getElementById("amount").value;
|
||||||
|
sessionStorage.last_four = result.paymentMethod.card.last4;
|
||||||
|
location.href = "/receipt/bronze";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user