Browse Source

Moved from stripe checkout to using a custom workflow using elements (prep for owncast modals)

pull/61/head
Atridad Lahiji 2 weeks ago
parent
commit
3e2937033e
  1. 26
      frontend/package-lock.json
  2. 4
      frontend/package.json
  3. 2
      frontend/src/App.vue
  4. 108
      frontend/src/components/Index.vue
  5. 19
      frontend/src/main.js
  6. 2
      go.mod
  7. 2
      go.sum
  8. 75
      main.go

26
frontend/package-lock.json

@ -1884,6 +1884,22 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true
},
"@vue-stripe/vue-stripe": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vue-stripe/vue-stripe/-/vue-stripe-4.1.8.tgz",
"integrity": "sha512-rc6+p6E9eMaUbulO8JCWwL3TTDLpmdK/h7ppEu9IlcKBecNbnoehi0UpA89cnsZZeswkvWNZNTf3l/xYjwKLdA==",
"requires": {
"@stripe/stripe-js": "^1.13.2",
"vue-coerce-props": "^1.0.0"
},
"dependencies": {
"@stripe/stripe-js": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.13.2.tgz",
"integrity": "sha512-fycUk7ECukNc31lD5apcrUgdRC1BfiIacs+CpacoCjOgo3ablolnWCvDQWMmVWtODYa8bBv2dlBla+Edc5OvZg=="
}
}
},
"@vue/babel-helper-vue-jsx-merge-props": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
@ -15563,6 +15579,11 @@
}
}
},
"vue-coerce-props": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vue-coerce-props/-/vue-coerce-props-1.0.0.tgz",
"integrity": "sha512-4fdRMXO6FHzmE7H4soAph6QmPg3sL/RiGdd+axuxuU07f02LNMns0jMM88fmt1bvSbN+2Wyd8raho6p6nXUzag=="
},
"vue-eslint-parser": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz",
@ -15627,11 +15648,6 @@
"vue-template-es2015-compiler": "^1.6.0"
}
},
"vue-stripe-checkout": {
"version": "3.5.12",
"resolved": "https://registry.npmjs.org/vue-stripe-checkout/-/vue-stripe-checkout-3.5.12.tgz",
"integrity": "sha512-q3LmhimWjDLgHZUJ8mZv26rOMgeowfeiMPLTQajKXWJyqs9oFJCtgZEzcPZOQxXM/NDnHtO7QK44Y6MjpOj78w=="
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",

4
frontend/package.json

@ -13,6 +13,7 @@
"dependencies": {
"@babel/polyfill": "^7.11.5",
"@stripe/stripe-js": "^1.11.0",
"@vue-stripe/vue-stripe": "^4.1.8",
"axios": "^0.21.0",
"bootstrap": "^4.5.3",
"bootstrap-vue": "^2.17.3",
@ -20,8 +21,7 @@
"mutationobserver-shim": "^0.3.7",
"vue": "^2.6.11",
"vue-axios": "^2.1.4",
"vue-feather-icons": "^5.1.0",
"vue-stripe-checkout": "^3.5.12"
"vue-feather-icons": "^5.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",

2
frontend/src/App.vue

@ -252,7 +252,7 @@ legend,label,h1,h2,h3,h4,h5,h6,p {
color: #ffffff !important;
}
.form-item {
width: 75%;
width: 100%;
margin: auto;
}
</style>

108
frontend/src/components/Index.vue

@ -1,5 +1,19 @@
<template>
<b-container>
<b-alert
:show="paymentSuccess"
dismissible
variant="success"
>
Payment was successful!
</b-alert>
<b-alert
:show="paymentError"
dismissible
variant="danger"
>
There was an error making your payment. Please try again.
</b-alert>
<img alt="Vue logo" src="/assets/img/logo.png" width="200px">
<h2>{{ title }}</h2>
@ -67,39 +81,42 @@
size="sm"
/>
<stripe-checkout
ref="sessionRef"
<stripe-element-card
ref="elementRef"
v-if="secretRecieved"
:pk="publishableKey"
:session-id="sessionID"
:elementsOptions="elementsOptions"
@token="tokenCreated"
/>
<b-btn
v-if="secretRecieved"
@click="submitPayment"
class="my-2 form-item"
size="sm"
variant="theme"
>
<template slot="checkout-button">
<b-spinner
v-if="loading"
lass="my-2"
variant="success"
/>
<b-btn
v-else
@click="submit"
class="my-2 form-item"
size="sm"
variant="theme"
>
Donate
</b-btn>
</template>
</stripe-checkout>
Confirm Donation
</b-btn>
<b-btn
v-else
@click="getSecret"
class="my-2 form-item"
size="sm"
variant="theme"
>
Donate
</b-btn>
</b-col>
</b-row>
</b-container>
</template>
<script>
import { StripeCheckout } from 'vue-stripe-checkout';
import { StripeElementCard } from '@vue-stripe/vue-stripe';
export default {
components: {
StripeCheckout,
StripeElementCard,
},
props: {
title: String,
@ -108,8 +125,13 @@ export default {
data: () => ({
amount: 0,
publishableKey: '',
sessionID: '',
currency: 'usd',
clientSecret: '',
currency: 'cad',
elementsOptions: {
locale: 'auto',
},
paymentSuccess: false,
paymentError: false,
currencyOptions: [
{ value: 'usd', text: 'USD - United States Dollar' },
{ value: 'cad', text: 'CAD - Canadian Dollar' },
@ -124,27 +146,41 @@ export default {
{ text: '20', value: 20 },
],
paymentType: 0,
paymentEndpointArray: [
'/api/pay/getsecret',
'/api/pay/monthly/getsecret',
'/api/pay/annual/getsecret',
],
loading: false,
secretRecieved: false,
}),
mounted() {
this.amount = this.paymentIncrementOptions[1].value;
},
methods: {
submit() {
this.axios.post('/api/pay/getsecret', {
getSecret() {
this.axios.post('/api/pay/secret', {
amount: this.amount * 100,
currency: this.currency,
}).then((response) => {
this.sessionID = response.data.SessionID;
this.publishableKey = response.data.StripePK;
this.loading = true;
this.clientSecret = response.data.client_secret;
this.publishableKey = response.data.publishable_key;
}).then(() => {
this.$refs.sessionRef.redirectToCheckout();
this.secretRecieved = true;
});
},
submitPayment() {
this.$refs.elementRef.submit();
},
tokenCreated(token) {
this.$stripe.confirmCardPayment(this.clientSecret, {
payment_method: {
card: {
token: token.id,
},
},
}).then((result) => {
if (result.error) {
this.paymentError = true;
this.secretRecieved = false;
} else if (result.paymentIntent.status === 'succeeded') {
this.paymentSuccess = true;
this.secretRecieved = false;
}
});
},
},

19
frontend/src/main.js

@ -2,6 +2,7 @@ import '@babel/polyfill';
import 'mutationobserver-shim';
import '@stripe/stripe-js';
import Vue from 'vue';
import { StripePlugin } from '@vue-stripe/vue-stripe';
import axios from 'axios';
import VueAxios from 'vue-axios';
import './plugins/bootstrap-vue';
@ -9,8 +10,18 @@ import App from './App.vue';
Vue.config.productionTip = false;
Vue.use(VueAxios, axios);
axios.get('/api/pay/config').then((response) => {
const options = {
pk: response.data.StripePK,
stripeAccount: response.data.StripeACC,
apiVersion: response.data.StripeApiVersion,
locale: response.data.StripeLocale,
};
Vue.use(StripePlugin, options);
new Vue({
render: (h) => h(App),
}).$mount('#app');
Vue.use(VueAxios, axios);
new Vue({
render: (h) => h(App),
}).$mount('#app');
});

2
go.mod

@ -8,6 +8,6 @@ require (
github.com/campoy/svg-badge v0.0.0-20180116034456-40c38fcad9f6 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/stripe/stripe-go/v72 v72.37.0 // indirect
github.com/stripe/stripe-go/v72 v72.41.0 // indirect
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect
)

2
go.sum

@ -35,6 +35,8 @@ github.com/stripe/stripe-go/v71 v71.48.0 h1:xSmbjHB1fdt6ieIf9yCGggafbzbXHPIhQj+R
github.com/stripe/stripe-go/v71 v71.48.0/go.mod h1:BXYwMQe+xjYomcy5/qaTGyoyVMTP3wDCHa7DVFvg8+Y=
github.com/stripe/stripe-go/v72 v72.37.0 h1:y/PW0SeIk17S1uq6tQ0RdyeizG1anZlvowMZ4AQ17YY=
github.com/stripe/stripe-go/v72 v72.37.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0=
github.com/stripe/stripe-go/v72 v72.41.0 h1:HkyJew+GkD/ClBT306+5vKLjBE4PRCJDiZ1enQyxeGQ=
github.com/stripe/stripe-go/v72 v72.41.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0=
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=

75
main.go

@ -10,8 +10,8 @@ import (
rice "github.com/GeertJohan/go.rice"
"github.com/campoy/svg-badge/badge"
"github.com/joho/godotenv"
"github.com/stripe/stripe-go/v72"
"github.com/stripe/stripe-go/v72/checkout/session"
stripe "github.com/stripe/stripe-go/v72"
"github.com/stripe/stripe-go/v72/paymentintent"
"golang.org/x/image/font/gofont/goregular"
)
@ -22,6 +22,34 @@ func loadEnv() {
}
}
func stripeConfigHandler(w http.ResponseWriter, r *http.Request) {
type Config struct {
StripePK string
StripeACC string
StripeApiVersion string
StripeLocale string
}
switch r.Method {
case http.MethodGet:
config := Config{
os.Getenv("STRIPE_PK"),
os.Getenv("STRIPE_ACC"),
os.Getenv("STRIPE_API_VERSION"),
os.Getenv("STRIPE_LOCALE"),
}
// log.Fatal(configJson)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(config)
default:
http.Error(w, "This endpoint only supports GET requrests.", http.StatusMethodNotAllowed)
return
}
}
func configHandler(w http.ResponseWriter, r *http.Request) {
type Config struct {
MetaTitle string
@ -90,6 +118,11 @@ func getSecretHandler(w http.ResponseWriter, r *http.Request) {
Currency string
}
type CheckoutData struct {
ClientSecret string `json:"client_secret"`
PublishableKey string `json:"publishable_key"`
}
type Response struct {
SessionID string
StripePK string
@ -97,35 +130,24 @@ func getSecretHandler(w http.ResponseWriter, r *http.Request) {
var items LineItems
_ = json.NewDecoder(r.Body).Decode(&items)
log.Println(items)
params := &stripe.CheckoutSessionParams{
SuccessURL: stripe.String(os.Getenv("SUCCESS_LINK")),
CancelURL: stripe.String(os.Getenv("CANCEL_LINK")),
PaymentMethodTypes: stripe.StringSlice([]string{
"card",
}),
LineItems: []*stripe.CheckoutSessionLineItemParams{
&stripe.CheckoutSessionLineItemParams{
Name: stripe.String(os.Getenv("OWNER_NAME")),
Amount: stripe.Int64(items.Amount),
Quantity: stripe.Int64(1),
Currency: stripe.String(items.Currency),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
params := &stripe.PaymentIntentParams{
Amount: stripe.Int64(items.Amount),
Currency: stripe.String(items.Currency),
}
s, _ := session.New(params)
intent, _ := paymentintent.New(params)
data := CheckoutData{
ClientSecret: intent.ClientSecret,
PublishableKey: os.Getenv("STRIPE_PK"),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Response{
s.ID,
os.Getenv("STRIPE_PK"),
})
json.NewEncoder(w).Encode(data)
default:
http.Error(w, "This endpoint only supports GET requrests.", http.StatusMethodNotAllowed)
http.Error(w, "This endpoint only supports POST requrests.", http.StatusMethodNotAllowed)
return
}
}
@ -162,7 +184,8 @@ func main() {
http.Handle("/", http.FileServer(rice.MustFindBox("frontend/dist").HTTPBox()))
http.Handle("/assets/", assets)
http.HandleFunc("/api/site/config", configHandler)
http.HandleFunc("/api/pay/getsecret", getSecretHandler)
http.HandleFunc("/api/pay/config", stripeConfigHandler)
http.HandleFunc("/api/pay/secret", getSecretHandler)
http.HandleFunc("/api/badge", badgeHandler)
http.HandleFunc("/api/healthcheck", healthCheckHandler)

Loading…
Cancel
Save