Skip to content

Commit

Permalink
Added stripe checkout webhook and recognizing paid user
Browse files Browse the repository at this point in the history
  • Loading branch information
chew-z committed Feb 19, 2020
1 parent 5dd5b87 commit 527ef4e
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 20 deletions.
17 changes: 17 additions & 0 deletions CloudFunctions/StripeCheckout/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module go-spotify/CloudFunctions/StripeCheckout

go 1.13

require (
cloud.google.com/go/firestore v1.1.1
firebase.google.com/go v3.12.0+incompatible
github.com/chew-z/spotify v0.0.21 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/gin-contrib/sessions v0.0.3 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/stretchr/testify v1.5.0 // indirect
github.com/stripe/stripe-go v68.20.0+incompatible
github.com/zmb3/spotify v0.0.0-20200112163645-71a4c67d18db // indirect
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
)
83 changes: 83 additions & 0 deletions CloudFunctions/StripeCheckout/stripecheckout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package stripecheckout

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"

"cloud.google.com/go/firestore"
firebase "firebase.google.com/go"
"github.com/stripe/stripe-go/webhook"
)

var (
firestoreClient *firestore.Client
projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
)

func main() {
}
func init() {
// Use the application default credentials.
conf := &firebase.Config{ProjectID: projectID}
// Use context.Background() because the app/client should persist across
// invocations.
ctx := context.Background()
app, err := firebase.NewApp(ctx, conf)
if err != nil {
log.Panicf("firebase.NewApp: %v", err)
}
firestoreClient, err = app.Firestore(ctx)
if err != nil {
log.Fatalf("app.Firestore: %v", err)
}
}

/*StripeCheckout - ...
*/
func StripeCheckout(w http.ResponseWriter, r *http.Request) {
defer firestoreClient.Close()

b, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("ioutil.ReadAll: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), os.Getenv("STRIPE_WEBHOOK_SECRET"))
if err != nil {
log.Printf("webhook.ConstructEvent: %s", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if event.Type != "checkout.session.completed" {
response := fmt.Sprintf("Wrong event type. Expected: checkout.session.completed")
log.Println(response)
http.Error(w, response, http.StatusBadRequest)
return
}

user := event.GetObjectValue("client_reference_id")
if user != "" {
ctx := context.Background()
_, err = firestoreClient.Collection("users").Doc(user).Set(ctx, map[string]interface{}{
"premium_user": true,
"subscription_expires": time.Now().AddDate(1, 0, 15), // a year plus two weeks plus a day
}, firestore.MergeAll)
} else {
log.Println("user ID is missing!")
}
response := fmt.Sprintf("Checkout completed for %s user", user)
log.Println(response)
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(&response); err != nil {
log.Panic(err)
}
}
6 changes: 6 additions & 0 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ func user(c *gin.Context) {
User.Country = user.Country
User.Name = user.DisplayName
User.URL = user.ExternalURLs["spotify"]
dsnap, err := firestoreClient.Collection("users").Doc(user.ID).Get(ctx)
if err != nil {
log.Printf("Error retrieving token from Firestore for %s %s.\nPossibly it ain't there..", user.ID, err.Error())
}
tok := dsnap.Data()
User.Premium = tok["premium_user"].(bool)
c.HTML(
http.StatusOK,
"user.html",
Expand Down
5 changes: 2 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,14 @@ func init() {
router.GET("/callback", callback)
router.GET("/login", login)
// Payments - Stripe
// router.Use(static.Serve("/", static.LocalFile(os.Getenv("STATIC_DIR"), false)))
router.GET("/payment", func(c *gin.Context) {
c.HTML(http.StatusOK, "payment.html", gin.H{
"title": "Payment",
"title": "Subscription",
})
})
router.GET("/paymentcancel", func(c *gin.Context) {
c.HTML(http.StatusOK, "paymentcancel.html", gin.H{
"title": "Cancel Payment",
"title": "Canceled Payment",
})
})
router.GET("/paymentsuccess", func(c *gin.Context) {
Expand Down
24 changes: 14 additions & 10 deletions structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ type popularTrack struct {
// retrieve token form firestore and for some
//initialization
type firestoreToken struct {
user string // Spotify user ID
displayname string
email string
country string // The country of the user, as set in the user's account profile
user string `firestore:"userID"` // Spotify user ID
displayname string `firestore:"user_displayname,omitempty"`
email string `firestore:"user_email,omitempty"`
premium bool `firestore:"premium_user,omitempty"`
expiration time.Time `firestore:"subscription_expires,omitempty"`
country string `firestore:"country,omitempty"` // The country of the user, as set in the user's account profile
path string // authorization path (gin routes group)
token *oauth2.Token // Spotify token
}
Expand Down Expand Up @@ -85,10 +87,12 @@ type frontendAlbumPlaylist struct {
}

type userLocation struct {
Name string
URL string
Country string
Lat string
Lon string
City string
Name string
Premium bool
Expiration time.Time
URL string
Country string
Lat string
Lon string
City string
}
10 changes: 5 additions & 5 deletions templates/payment.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
<div class="card-header">
Subscribe
</div>
<img src="/static/music.svg" class="img-fluid" style="object-fit: cover;" alt="music.suka.yoga">
<img src="/static/music.svg" class="img-fluid" style="object-fit: scale-down;" alt="music.suka.yoga">
<div class="card-body text-center">
<h1 class="card-title">€12.90</h1>
<h4 class="card-subtitle">anual subscription</h4>
<button id="submit" class="btn btn-success">Checkout</button>
</div>
<div class="input-group">
<label class="input-group-text">
<input type="checkbox" name="donation" aria-label="Donation checkbox">
&nbsp;Make a donation to suka.yoga (+€10.00)
<div class="form-check">
<label class="form-check-label text-center">
<input type="checkbox" name="donation" class="form-check-input" aria-label="Donation checkbox">
&nbsp;Please make discretionary donation to support our NGO suka.yoga (+€10.00)
</label>
</div>
<div class="card-footer text-muted">
Expand Down
10 changes: 8 additions & 2 deletions templates/user.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js" integrity="sha256-4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.27/moment-timezone-with-data-2012-2022.min.js" integrity="sha256-gfmyBrlUtChLivBA2rRK/bsljMoum6kxbl7oFeCVkmc=" crossorigin="anonymous"></script>
<script>
$( document ).ready(function() {
$( document ).ready(function() {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// const gotime = 1000 * {{ .Location.UnixTime }}
let now = moment().tz(tz).format('HH:mm');
Expand All @@ -18,8 +18,14 @@
{{ with .User }}
<h6 class="display-4">You are logged in as <a href="{{ .URL }}">{{ .Name }}</a>.</h6>
<p class="lead">Your Spotify account is from <strong>{{ .Country }}</strong></p>
{{ end }}
<p>Your timezone is probably <mark id="timezone"></mark> and the time is <strong id="time"></strong>
{{ if .Premium }}
<p>You are premium user. We are grateful for your support. If you have any problems or suggestions please let us know.</p>
{{ else }}
<p>You are infrequent user. It is cool and you are welcome here.</p>
<p>But developing the app and running servers in the cloud costs time and money so please <a class="btn btn-success" role="button" href="/payment">subscribe</a> if you like the app and use it often.</p>
{{ end }}
{{ end }}
</div>
<div id="installPWA" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="10000" style="position: absolute; top: 1rem; right: 1rem;">
<div class="toast-header">
Expand Down

0 comments on commit 527ef4e

Please sign in to comment.