Skip to content
This repository has been archived by the owner on Apr 19, 2022. It is now read-only.

Commit

Permalink
Add Go Echo server (#63)
Browse files Browse the repository at this point in the history
* page loads

* inventory and creation

* shipping update

* add the fetching

* version

* webhooks

* readme

* integrate PR feedback
  • Loading branch information
sobel-stripe authored and thorsten-stripe committed Apr 17, 2019
1 parent 37caff0 commit 7dfaa0c
Show file tree
Hide file tree
Showing 10 changed files with 751 additions and 0 deletions.
47 changes: 47 additions & 0 deletions server/go/README.md
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`.
210 changes: 210 additions & 0 deletions server/go/app.go
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
}
43 changes: 43 additions & 0 deletions server/go/config/config.go
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(),
}
}
13 changes: 13 additions & 0 deletions server/go/go.mod
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
)
23 changes: 23 additions & 0 deletions server/go/go.sum
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=
Loading

0 comments on commit 7dfaa0c

Please sign in to comment.