From 0ce1a066cc1cfc1a9455c6a0fef33c9003319a65 Mon Sep 17 00:00:00 2001
From: Immanuel Onyeka <immanuel@debian-BULLSEYE-live-builder-AMD64>
Date: Tue, 23 Jan 2024 18:21:50 -0500
Subject: [PATCH] Process and complete Stripe payments

---
 grav-admin/user/js/registration/billing.vue   | 36 ++++++++++++++--
 grav-admin/user/js/registration/completed.vue | 19 +++++++++
 .../user/js/registration/registration.vue     | 29 ++++++++++---
 migrations/0_29092022_setup_tables.sql        |  1 +
 skouter.go                                    | 41 ++++++++++++++-----
 5 files changed, 107 insertions(+), 19 deletions(-)
 create mode 100644 grav-admin/user/js/registration/completed.vue

diff --git a/grav-admin/user/js/registration/billing.vue b/grav-admin/user/js/registration/billing.vue
index 7b312d9..2ca4fae 100644
--- a/grav-admin/user/js/registration/billing.vue
+++ b/grav-admin/user/js/registration/billing.vue
@@ -1,13 +1,43 @@
 <template>
 <div>
 
-<h4>hello</h4>
+<h4>Billing</h4>
+<div id="payment-element"></div>
+<div id="message"></div>
+<button @click="submit" class="btn btn-primary">Submit</button>
 
 </div>
 </template>
 
 <script setup>
-import { ref } from "vue"
+import { ref, onMounted } from "vue"
 
-let stripe = Stripe(process.env.STRIPE_KEY)
+const props = defineProps(["sub"])
+const stripe = Stripe(process.env.STRIPE_KEY)
+const options = { clientSecret: props.sub.clientSecret }
+const elements = stripe.elements(options)
+const payEl = elements.create("payment")
+
+function submit() {
+    let result = stripe.confirmPayment({
+    //`Elements` instance that was used to create the Payment Element
+    elements,
+    confirmParams: {
+      return_url: "https://skouter.net/register",
+    }
+  })
+}
+
+onMounted(() => {
+  checkPayment()
+  payEl.mount("#payment-element")
+})
 </script>
+
+<style scoped>
+button.btn {
+  margin: 10px auto;
+  display: block;
+  min-width: 90px;
+}
+</style>
diff --git a/grav-admin/user/js/registration/completed.vue b/grav-admin/user/js/registration/completed.vue
new file mode 100644
index 0000000..6929343
--- /dev/null
+++ b/grav-admin/user/js/registration/completed.vue
@@ -0,0 +1,19 @@
+<template>
+<section>
+  <h4 v-if="status == 'succeeded'">Payment succeeded!</h4>
+  <h4 v-if="status == 'processing'">Payment still processing. Try again later.</h4>
+</section>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue"
+
+const props = defineProps(["status"])
+
+</script>
+
+<style>
+h4 {
+  text-align: center;
+}
+</style>
diff --git a/grav-admin/user/js/registration/registration.vue b/grav-admin/user/js/registration/registration.vue
index 764de78..dee7615 100644
--- a/grav-admin/user/js/registration/registration.vue
+++ b/grav-admin/user/js/registration/registration.vue
@@ -1,8 +1,9 @@
 <template>
 <section class="shadowbox">
 <h2>Register</h2>
-<account v-if="!step" :err="err" @submit="create" />
-<billing v-if="step" :err="err" @submit="create" />
+<account v-if="step == 1" :err="err" @submit="create" />
+<billing v-if="step == 2" :err="err" :sub="sub"/>
+<completed v-if="step == 3" :err="err" :status="sub.paymentStatus"/>
 </section>
 </template>
 
@@ -10,10 +11,18 @@
 import { ref, onMounted } from "vue"
 import Account from "./account.vue"
 import Billing from "./billing.vue"
+import Completed from "./completed.vue"
 
 let err = ref("")
-const step = ref(0)
+const stripe = Stripe(process.env.STRIPE_KEY)
+const step = ref(1)
 const token = ref("")
+const user = ref(null)
+const sub = ref(null)
+
+const clientSecret = new URLSearchParams(window.location.search).get(
+  'payment_intent_client_secret'
+);
 
 function getCookie(name) {
 	var re = new RegExp(name + "=([^;]+)")
@@ -68,9 +77,17 @@ function intent(user) {
     		},
         }).then(resp => {
             if (resp.ok) {
-              return resp.json().then(u => {
+              resp.json().then(s => {
                 err.value = ""
-                console.log(u)
+                console.log(s)
+                sub.value = s
+                step.value++
+                if (["processing", "succeeded"].includes(s.paymentStatus) &&
+                clientSecret == s.clientSecret) {
+                  step.value++
+                } else {
+                  step.value = 0
+                }
               })
             } else {
               resp.text().then( e => err.value = e)
@@ -80,7 +97,7 @@ function intent(user) {
 
 onMounted(() => {
   getUser().then( u => {
-    if (u) intent(u)
+    if (u) { user.value = u; intent(u) }
   })
 })
 </script>
diff --git a/migrations/0_29092022_setup_tables.sql b/migrations/0_29092022_setup_tables.sql
index 08b8ab7..0bb57b9 100644
--- a/migrations/0_29092022_setup_tables.sql
+++ b/migrations/0_29092022_setup_tables.sql
@@ -64,6 +64,7 @@ CREATE TABLE subscription (
 	user_id 		INT,
 	customer_id 	VARCHAR(255) NOT NULL,
 	client_secret 	VARCHAR(255) NOT NULL,
+	payment_status	VARCHAR(50) NOT NULL,
 	current_period_end 		INT DEFAULT 0,
 	current_period_start 		INT DEFAULT 0,
 	PRIMARY KEY (`id`),
diff --git a/skouter.go b/skouter.go
index 7cc27c6..24f84d2 100644
--- a/skouter.go
+++ b/skouter.go
@@ -72,6 +72,7 @@ type Subscription struct {
 	Start	int     `json:"start"`
 	End	int     `json:"end"`
 	ClientSecret	string  `json:"clientSecret,omitempty"`
+	PaymentStatus	string  `json:"paymentStatus"`
 }
 
 type User struct {
@@ -1262,7 +1263,8 @@ func querySub(db *sql.DB, id int) (Subscription, error) {
 	customer_id,
 	current_period_end,
 	current_period_start,
-	client_secret
+	client_secret,
+	payment_status
 	FROM subscription WHERE id = ?
 	`
 	row := db.QueryRow(query, id)
@@ -1274,6 +1276,7 @@ func querySub(db *sql.DB, id int) (Subscription, error) {
 		&s.End,
 		&s.Start,
 		&s.ClientSecret,
+		&s.PaymentStatus,
 	)
 
 	return s, err
@@ -1290,7 +1293,8 @@ func (user *User) querySub(db *sql.DB) error {
 	customer_id,
 	current_period_end,
 	current_period_start,
-	client_secret
+	client_secret,
+	payment_status
 	FROM subscription WHERE user_id = ?
 	`
 	row := db.QueryRow(query, user.Id)
@@ -1303,6 +1307,7 @@ func (user *User) querySub(db *sql.DB) error {
 		&user.Sub.End,
 		&user.Sub.Start,
 		&user.Sub.ClientSecret,
+		&user.Sub.PaymentStatus,
 	)
 
 	return err
@@ -1421,9 +1426,10 @@ func (sub *Subscription) insertSub(db *sql.DB) (error) {
 		customer_id,
 		current_period_end,
 		current_period_start,
-		client_secret
+		client_secret,
+		payment_status
 	)
-	VALUES (?, ?, ?, ?, ?, ?)
+	VALUES (?, ?, ?, ?, ?, ?, ?)
 	RETURNING id
 	`
 	
@@ -1434,18 +1440,19 @@ func (sub *Subscription) insertSub(db *sql.DB) (error) {
 		sub.End,
 		sub.Start,
 		sub.ClientSecret,
+		sub.PaymentStatus,
 	)
 
 	err = row.Scan(&sub.Id)
 	return err
 }
 
-func (sub *Subscription) updateSubSecret(db *sql.DB) error {
+func (sub *Subscription) updateSub(db *sql.DB) error {
 	var query string
 	var err error
 
-	query = `UPDATE subscription SET
-	client_secret = ?
+	query = `UPDATE subscription
+	SET client_secret = ?, payment_status = ?
 	WHERE id = ?
 	`
 	
@@ -1459,11 +1466,15 @@ func (sub *Subscription) updateSubSecret(db *sql.DB) error {
 	&stripe.PaymentIntentParams{})
 	if err != nil { return err }
 	
-
 	_, err = db.Exec(query,
 		p.ClientSecret,
+		p.Status,
 		sub.Id,
 	)
+	if err != nil { return err }
+	
+	sub.ClientSecret = p.ClientSecret
+	sub.PaymentStatus = string(p.Status)
 
 	return err
 }
@@ -2972,7 +2983,7 @@ func subscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) {
 			return
 		}
 	}
-	
+
 	if user.Sub.Id == 0 {
 		s, err := createSubscription(user.CustomerId)
 		if err != nil {
@@ -2986,10 +2997,20 @@ func subscribe(w http.ResponseWriter, db *sql.DB, r *http.Request) {
 		user.Sub.End = int(s.CurrentPeriodEnd)
 		user.Sub.Start = int(s.CurrentPeriodStart)
 		user.Sub.ClientSecret = s.LatestInvoice.PaymentIntent.ClientSecret
+		user.Sub.PaymentStatus = string(s.LatestInvoice.PaymentIntent.Status)
 		
-		user.Sub.insertSub(db)
+		err = user.Sub.insertSub(db)
+		if err != nil {
+			http.Error(w, err.Error(), 500)
+			return
+		}
 		json.NewEncoder(w).Encode(user.Sub)
 	} else {
+		err = user.Sub.updateSub(db)
+		if err != nil {
+			http.Error(w, err.Error(), 500)
+			return
+		}
 		json.NewEncoder(w).Encode(user.Sub)
 	}