This repository has been archived by the owner on Apr 19, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 548
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* page loads * inventory and creation * shipping update * add the fetching * version * webhooks * readme * integrate PR feedback
- Loading branch information
1 parent
37caff0
commit 7dfaa0c
Showing
10 changed files
with
751 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Stripe Payments Demo - Go Server | ||
|
||
This demo uses a simple [Echo](https://echo.labstack.com/) application as the server. | ||
|
||
## Payments Integration | ||
|
||
- [`app.go`](app.go) contains the routes that interface with Stripe to create charges and receive webhook events. | ||
|
||
## Requirements | ||
|
||
You’ll need the following: | ||
|
||
- [Go 1.11 or later](https://golang.org/doc/install) (for module support) | ||
- Modern browser that supports ES6 (Chrome to see the Payment Request, and Safari to see Apple Pay). | ||
- Stripe account to accept payments ([sign up](https://dashboard.stripe.com/register) for free!) | ||
|
||
## Getting Started | ||
|
||
Copy the example environment variables file `.env.example` from the root of the repo into your own environment file called `.env`: | ||
|
||
``` | ||
cp .env.example .env | ||
``` | ||
|
||
Run the application from this directory (after running `cd server/go`): | ||
|
||
``` | ||
go run app.go -root-directory=$(realpath ../..) | ||
``` | ||
|
||
You should now see it running on [`http://localhost:4567/`](http://localhost:4567/) | ||
|
||
### Testing Webhooks | ||
|
||
If you want to test [receiving webhooks](https://stripe.com/docs/webhooks), we recommend using ngrok to expose your local server. | ||
|
||
First [download ngrok](https://ngrok.com) and start your Echo application. | ||
|
||
[Run ngrok](https://ngrok.com/docs). Assuming your Echo application is running on the default port 4567, you can simply run ngrok in your Terminal in the directory where you downloaded ngrok: | ||
|
||
``` | ||
ngrok http 4567 | ||
``` | ||
|
||
ngrok will display a UI in your terminal telling you the new forwarding address for your Echo app. Use this URL as the URL to be called in your developer [webhooks panel.](https://dashboard.stripe.com/account/webhooks) | ||
|
||
Don't forget to append `/webhook` when you set up your Stripe webhook URL in the Dashboard. Example URL to be called: `https://75795038.ngrok.io/webhook`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
"path" | ||
|
||
"github.com/joho/godotenv" | ||
"github.com/labstack/echo" | ||
"github.com/labstack/echo/middleware" | ||
"github.com/labstack/gommon/log" | ||
"github.com/stripe/stripe-go" | ||
"github.com/stripe/stripe-go/webhook" | ||
|
||
"github.com/stripe/stripe-payments-demo/config" | ||
"github.com/stripe/stripe-payments-demo/inventory" | ||
"github.com/stripe/stripe-payments-demo/payments" | ||
"github.com/stripe/stripe-payments-demo/setup" | ||
"github.com/stripe/stripe-payments-demo/webhooks" | ||
) | ||
|
||
func main() { | ||
rootDirectory := flag.String("root-directory", "", "Root directory of the stripe-payments-demo repository") | ||
flag.Parse() | ||
|
||
if *rootDirectory == "" { | ||
panic("-root-directory is a required argument") | ||
} | ||
|
||
err := godotenv.Load(path.Join(*rootDirectory, ".env")) | ||
if err != nil { | ||
panic(fmt.Sprintf("error loading .env: %v", err)) | ||
} | ||
|
||
stripe.Key = os.Getenv("STRIPE_SECRET_KEY") | ||
if stripe.Key == "" { | ||
panic("STRIPE_SECRET_KEY must be in environment") | ||
} | ||
|
||
publicDirectory := path.Join(*rootDirectory, "public") | ||
e := buildEcho(publicDirectory) | ||
e.Logger.Fatal(e.Start(":4567")) | ||
} | ||
|
||
type listing struct { | ||
Data interface{} `json:"data"` | ||
} | ||
|
||
func buildEcho(publicDirectory string) *echo.Echo { | ||
e := echo.New() | ||
e.Use(middleware.Logger()) | ||
e.Logger.SetLevel(log.DEBUG) | ||
|
||
e.File("/", path.Join(publicDirectory, "index.html")) | ||
e.File("/.well-known/apple-developer-merchantid-domain-association", path.Join(publicDirectory, ".well-known/apple-developer-merchantid-domain-association")) | ||
e.Static("/javascripts", path.Join(publicDirectory, "javascripts")) | ||
e.Static("/stylesheets", path.Join(publicDirectory, "stylesheets")) | ||
e.Static("/images", path.Join(publicDirectory, "images")) | ||
|
||
e.GET("/config", func(c echo.Context) error { | ||
return c.JSON(http.StatusOK, config.Default()) | ||
}) | ||
|
||
e.GET("/products", func(c echo.Context) error { | ||
products, err := inventory.ListProducts() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !setup.ExpectedProductsExist(products) { | ||
err := setup.CreateData() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
products, err = inventory.ListProducts() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return c.JSON(http.StatusOK, listing{products}) | ||
}) | ||
|
||
e.GET("/product/:product_id/skus", func(c echo.Context) error { | ||
skus, err := inventory.ListSKUs(c.Param("product_id")) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.JSON(http.StatusOK, listing{skus}) | ||
}) | ||
|
||
e.GET("/products/:product_id", func(c echo.Context) error { | ||
product, err := inventory.RetrieveProduct(c.Param("product_id")) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.JSON(http.StatusOK, product) | ||
}) | ||
|
||
e.POST("/payment_intents", func(c echo.Context) error { | ||
r := new(payments.IntentCreationRequest) | ||
err := c.Bind(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
pi, err := payments.CreateIntent(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.JSON(http.StatusOK, map[string]*stripe.PaymentIntent{ | ||
"paymentIntent": pi, | ||
}) | ||
}) | ||
|
||
e.POST("/payment_intents/:id/shipping_change", func(c echo.Context) error { | ||
r := new(payments.IntentShippingChangeRequest) | ||
err := c.Bind(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
pi, err := payments.UpdateShipping(c.Param("id"), r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.JSON(http.StatusOK, map[string]*stripe.PaymentIntent{ | ||
"paymentIntent": pi, | ||
}) | ||
}) | ||
|
||
e.GET("/payment_intents/:id/status", func(c echo.Context) error { | ||
pi, err := payments.RetrieveIntent(c.Param("id")) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.JSON(http.StatusOK, map[string]map[string]string{ | ||
"paymentIntent": { | ||
"status": string(pi.Status), | ||
}, | ||
}) | ||
}) | ||
|
||
e.POST("/webhook", func(c echo.Context) error { | ||
request := c.Request() | ||
payload, err := ioutil.ReadAll(request.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var event stripe.Event | ||
|
||
webhookSecret := os.Getenv("STRIPE_WEBHOOK_SECRET") | ||
if webhookSecret != "" { | ||
event, err = webhook.ConstructEvent(payload, request.Header.Get("Stripe-Signature"), webhookSecret) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
err := json.Unmarshal(payload, &event) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
objectType := event.Data.Object["object"].(string) | ||
|
||
var handled bool | ||
switch objectType { | ||
case "payment_intent": | ||
var pi *stripe.PaymentIntent | ||
err = json.Unmarshal(event.Data.Raw, &pi) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
handled, err = webhooks.HandlePaymentIntent(event, pi) | ||
case "source": | ||
var source *stripe.Source | ||
err := json.Unmarshal(event.Data.Raw, &source) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
handled, err = webhooks.HandleSource(event, source) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if !handled { | ||
fmt.Printf("🔔 Webhook received and not handled! %s\n", event.Type) | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
return e | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package config | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/stripe/stripe-payments-demo/inventory" | ||
) | ||
|
||
type Config struct { | ||
StripePublishableKey string `json:"stripePublishableKey"` | ||
StripeCountry string `json:"stripeCountry"` | ||
Country string `json:"country"` | ||
Currency string `json:"currency"` | ||
PaymentMethods []string `json:"paymentMethods"` | ||
|
||
ShippingOptions []inventory.ShippingOption `json:"shippingOptions"` | ||
} | ||
|
||
func PaymentMethods() []string { | ||
paymentMethodsString := os.Getenv("PAYMENT_METHODS") | ||
if paymentMethodsString == "" { | ||
return []string{"card"} | ||
} else { | ||
return strings.Split(paymentMethodsString, ", ") | ||
} | ||
} | ||
|
||
func Default() Config { | ||
stripeCountry := os.Getenv("STRIPE_ACCOUNT_COUNTRY") | ||
if stripeCountry == "" { | ||
stripeCountry = "US" | ||
} | ||
|
||
return Config{ | ||
StripePublishableKey: os.Getenv("STRIPE_PUBLISHABLE_KEY"), | ||
StripeCountry: stripeCountry, | ||
Country: "US", | ||
Currency: "eur", | ||
PaymentMethods: PaymentMethods(), | ||
ShippingOptions: inventory.ShippingOptions(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module github.com/stripe/stripe-payments-demo | ||
|
||
require ( | ||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | ||
github.com/joho/godotenv v1.3.0 // indirect | ||
github.com/labstack/echo v3.3.10+incompatible // indirect | ||
github.com/labstack/gommon v0.2.8 // indirect | ||
github.com/mattn/go-colorable v0.1.1 // indirect | ||
github.com/mattn/go-isatty v0.0.7 // indirect | ||
github.com/stripe/stripe-go v60.5.0+incompatible | ||
github.com/valyala/fasttemplate v1.0.1 // indirect | ||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= | ||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | ||
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= | ||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= | ||
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= | ||
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= | ||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= | ||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= | ||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= | ||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||
github.com/stripe/stripe-go v60.5.0+incompatible h1:nxxftMdmgWA4sbtxXPCk+/Fl+20McZZZl3nPr8KOfW0= | ||
github.com/stripe/stripe-go v60.5.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= | ||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= | ||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g= | ||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= | ||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
Oops, something went wrong.