Includes the associated Go functions for inserting and selecting given a specific user ID or subscription ID. Also adds required HTML tags and HTTP queries for subscription process during user registration.master
@@ -9,4 +9,5 @@ require ( | |||||
github.com/dustin/go-humanize v1.0.1 | github.com/dustin/go-humanize v1.0.1 | ||||
github.com/go-sql-driver/mysql v1.6.0 | github.com/go-sql-driver/mysql v1.6.0 | ||||
github.com/golang-jwt/jwt/v4 v4.5.0 | github.com/golang-jwt/jwt/v4 v4.5.0 | ||||
github.com/stripe/stripe-go/v76 v76.12.0 | |||||
) | ) |
@@ -2,6 +2,7 @@ github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 h1:DNrExYwvyyI404SxdUCCANAj9 | |||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= | ||||
github.com/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= | github.com/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= | ||||
github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= | github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= | ||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= | github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= | ||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= | github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= | ||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | ||||
@@ -10,3 +11,16 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC | |||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | ||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | ||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |||||
github.com/stripe/stripe-go/v76 v76.12.0 h1:TzkkQ1yXEZqO8+WS4Qun/mYKRLFQpPX8bti3VUEoe30= | |||||
github.com/stripe/stripe-go/v76 v76.12.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= | |||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@@ -64,7 +64,7 @@ import { ref } from "vue" | |||||
import Dropdown from "./dropdown.vue" | import Dropdown from "./dropdown.vue" | ||||
const addresses = ref([]) | const addresses = ref([]) | ||||
const user = ref({}) | |||||
const user = ref({country: "USA", title: "Loan Officer"}) | |||||
const address = ref({}) | const address = ref({}) | ||||
const locationsId = ref(null) | const locationsId = ref(null) | ||||
const props = defineProps(['err']) | const props = defineProps(['err']) | ||||
@@ -0,0 +1,13 @@ | |||||
<template> | |||||
<div> | |||||
<h4>hello</h4> | |||||
</div> | |||||
</template> | |||||
<script setup> | |||||
import { ref } from "vue" | |||||
let stripe = Stripe(process.env.STRIPE_KEY) | |||||
</script> |
@@ -2,18 +2,21 @@ | |||||
<section class="shadowbox"> | <section class="shadowbox"> | ||||
<h2>Register</h2> | <h2>Register</h2> | ||||
<account v-if="!step" :err="err" @submit="create" /> | <account v-if="!step" :err="err" @submit="create" /> | ||||
<billing v-if="step" :err="err" @submit="create" /> | |||||
</section> | </section> | ||||
</template> | </template> | ||||
<script setup> | <script setup> | ||||
import { ref } from "vue" | import { ref } from "vue" | ||||
import Account from "./account.vue" | import Account from "./account.vue" | ||||
import Billing from "./billing.vue" | |||||
let err = ref("") | let err = ref("") | ||||
const step = ref(0) | const step = ref(0) | ||||
function create(user) { | function create(user) { | ||||
console.log(user) | console.log(user) | ||||
fetch(`/api/user`, | fetch(`/api/user`, | ||||
{method: 'POST', | {method: 'POST', | ||||
body: JSON.stringify(user), | body: JSON.stringify(user), | ||||
@@ -27,6 +27,7 @@ | |||||
{% endblock %} | {% endblock %} | ||||
{% block javascripts %} | {% block javascripts %} | ||||
<script src="https://js.stripe.com/v3/"></script> | |||||
{% do assets.addJs('jquery', 101) %} | {% do assets.addJs('jquery', 101) %} | ||||
{% do assets.addJs('theme://js/jquery.treemenu.js', {group:'bottom'}) %} | {% do assets.addJs('theme://js/jquery.treemenu.js', {group:'bottom'}) %} | ||||
{% do assets.addJs('theme://js/site.js', {group:'bottom'}) %} | {% do assets.addJs('theme://js/site.js', {group:'bottom'}) %} | ||||
@@ -56,6 +56,17 @@ CREATE TABLE user ( | |||||
FOREIGN KEY (address) REFERENCES address(id) | FOREIGN KEY (address) REFERENCES address(id) | ||||
); | ); | ||||
CREATE TABLE subscription ( | |||||
id INT AUTO_INCREMENT, | |||||
stripe_id VARCHAR(255) DEFAULT '', | |||||
user_id INT, | |||||
customer_id VARCHAR(255) NOT NULL, | |||||
current_period_end INT DEFAULT 0, | |||||
current_period_start INT DEFAULT 0, | |||||
PRIMARY KEY (`id`), | |||||
FOREIGN KEY (user_id) REFERENCES user(id) | |||||
); | |||||
CREATE TABLE license ( | CREATE TABLE license ( | ||||
id INT AUTO_INCREMENT, | id INT AUTO_INCREMENT, | ||||
user_id INT NOT NULL, | user_id INT NOT NULL, | ||||
@@ -8,6 +8,7 @@ DROP TABLE IF EXISTS borrower; | |||||
DROP TABLE IF EXISTS estimate; | DROP TABLE IF EXISTS estimate; | ||||
DROP TABLE IF EXISTS loan_type; | DROP TABLE IF EXISTS loan_type; | ||||
DROP TABLE IF EXISTS license; | DROP TABLE IF EXISTS license; | ||||
DROP TABLE IF EXISTS subscription; | |||||
DROP TABLE IF EXISTS user; | DROP TABLE IF EXISTS user; | ||||
DROP TABLE IF EXISTS branch; | DROP TABLE IF EXISTS branch; | ||||
DROP TABLE IF EXISTS address; | DROP TABLE IF EXISTS address; |
@@ -28,6 +28,10 @@ import ( | |||||
"github.com/disintegration/gift" | "github.com/disintegration/gift" | ||||
"github.com/dustin/go-humanize" | "github.com/dustin/go-humanize" | ||||
"github.com/golang-jwt/jwt/v4" | "github.com/golang-jwt/jwt/v4" | ||||
"github.com/stripe/stripe-go/v76" | |||||
"github.com/stripe/stripe-go/v76/customer" | |||||
"github.com/stripe/stripe-go/v76/subscription" | |||||
// "github.com/stripe/stripe-go/v76/paymentintent" | |||||
"image" | "image" | ||||
_ "image/jpeg" | _ "image/jpeg" | ||||
"image/png" | "image/png" | ||||
@@ -59,6 +63,16 @@ type Branch struct { | |||||
Address Address `json:"address"` | Address Address `json:"address"` | ||||
} | } | ||||
type Subscription struct { | |||||
Id int `json:"id"` | |||||
UserId int `json:"userId"` | |||||
StripeId string `json:"stripeId"` | |||||
CustomerId string `json:"customerId"` | |||||
Start int `json:"start"` | |||||
End int `json:"end"` | |||||
ClientSecret string `json:"clientSecret,omitempty"` | |||||
} | |||||
type User struct { | type User struct { | ||||
Id int `json:"id"` | Id int `json:"id"` | ||||
Email string `json:"email"` | Email string `json:"email"` | ||||
@@ -68,12 +82,14 @@ type User struct { | |||||
Address Address `json:"address"` | Address Address `json:"address"` | ||||
Branch Branch `json:"branch"` | Branch Branch `json:"branch"` | ||||
License License `json:"license"` | License License `json:"license"` | ||||
Sub Subscription `json:"sub"` | |||||
Status string `json:"status"` | Status string `json:"status"` | ||||
Country string `json:"country"` | Country string `json:"country"` | ||||
Title string `json:"title"` | Title string `json:"title"` | ||||
Verified bool `json:"verified"` | Verified bool `json:"verified"` | ||||
Role string `json:"role"` | Role string `json:"role"` | ||||
Password string `json:"password,omitempty"` | Password string `json:"password,omitempty"` | ||||
CustomerId string `json:"customerId"` | |||||
} | } | ||||
type License struct { | type License struct { | ||||
@@ -1107,6 +1123,7 @@ func queryUser(db *sql.DB, id int) (User, error) { | |||||
u.country, | u.country, | ||||
u.title, | u.title, | ||||
coalesce(u.status, ''), | coalesce(u.status, ''), | ||||
coalesce(u.customer_id, '') | |||||
u.verified, | u.verified, | ||||
u.role, | u.role, | ||||
u.address, | u.address, | ||||
@@ -1128,6 +1145,7 @@ func queryUser(db *sql.DB, id int) (User, error) { | |||||
&user.Country, | &user.Country, | ||||
&user.Title, | &user.Title, | ||||
&user.Status, | &user.Status, | ||||
&user.CustomerId, | |||||
&user.Verified, | &user.Verified, | ||||
&user.Role, | &user.Role, | ||||
&user.Address.Id, | &user.Address.Id, | ||||
@@ -1222,6 +1240,60 @@ func queryUsers(db *sql.DB, id int) ([]User, error) { | |||||
return users, nil | return users, nil | ||||
} | } | ||||
func querySub(db *sql.DB, id int) (Subscription, error) { | |||||
var query string | |||||
var err error | |||||
var s Subscription | |||||
query = `SELECT | |||||
id, | |||||
stripe_id, | |||||
user_id, | |||||
customer_id, | |||||
current_period_end, | |||||
current_period_start | |||||
FROM subscription WHERE id = ? | |||||
` | |||||
row := db.QueryRow(query, id) | |||||
err = row.Scan( | |||||
&s.Id, | |||||
&s.StripeId, | |||||
&s.CustomerId, | |||||
&s.End, | |||||
&s.Start, | |||||
) | |||||
return s, err | |||||
} | |||||
func (user *User) querySub(db *sql.DB) error { | |||||
var query string | |||||
var err error | |||||
query = `SELECT | |||||
id, | |||||
stripe_id, | |||||
user_id, | |||||
customer_id, | |||||
current_period_end, | |||||
current_period_start | |||||
FROM subscription WHERE user_id = ? | |||||
` | |||||
row := db.QueryRow(query, user.Id) | |||||
err = row.Scan( | |||||
&user.Sub.Id, | |||||
&user.Sub.StripeId, | |||||
&user.Sub.UserId, | |||||
&user.Sub.CustomerId, | |||||
&user.Sub.End, | |||||
&user.Sub.Start, | |||||
) | |||||
return err | |||||
} | |||||
func (estimate *Estimate) insertResults(db *sql.DB) error { | func (estimate *Estimate) insertResults(db *sql.DB) error { | ||||
var query string | var query string | ||||
var row *sql.Row | var row *sql.Row | ||||
@@ -1322,6 +1394,51 @@ func insertUser(db *sql.DB, user User) (int, error) { | |||||
return id, nil | return id, nil | ||||
} | } | ||||
// Insert user returning it's ID or any error | |||||
func (sub *Subscription) insertSub(db *sql.DB) (error) { | |||||
var query string | |||||
var err error | |||||
query = `INSERT INTO subscription | |||||
( | |||||
stripe_id, | |||||
user_id, | |||||
customer_id, | |||||
current_period_end, | |||||
current_period_start | |||||
) | |||||
VALUES (?, ?, ?, ?, ?) | |||||
` | |||||
_, err = db.Exec(query, | |||||
sub.StripeId, | |||||
sub.UserId, | |||||
sub.CustomerId, | |||||
sub.End, | |||||
sub.Start, | |||||
) | |||||
return err | |||||
} | |||||
// Updates a user's stripe customer ID. | |||||
func (user *User) updateCustomerId(db *sql.DB, cid string) (error) { | |||||
var query string | |||||
var err error | |||||
query = `UPDATE user SET | |||||
customer_id = ? | |||||
WHERE id = ? | |||||
` | |||||
_, err = db.Exec(query, | |||||
cid, | |||||
user.Id, | |||||
) | |||||
return err | |||||
} | |||||
func updateAddress(address Address, db *sql.DB) error { | func updateAddress(address Address, db *sql.DB) error { | ||||
query := ` | query := ` | ||||
UPDATE address | UPDATE address | ||||
@@ -1378,12 +1495,14 @@ func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
w.WriteHeader(500) | w.WriteHeader(500) | ||||
return | return | ||||
} | } | ||||
user, err := queryUser(db, claims.Id) | user, err := queryUser(db, claims.Id) | ||||
if err != nil { | if err != nil { | ||||
w.WriteHeader(422) | w.WriteHeader(422) | ||||
log.Println(err) | log.Println(err) | ||||
return | return | ||||
} | } | ||||
json.NewEncoder(w).Encode(user) | json.NewEncoder(w).Encode(user) | ||||
} | } | ||||
@@ -2731,6 +2850,74 @@ func clipLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
func createCustomer(name string, email string, address Address) ( | |||||
stripe.Customer, error) { | |||||
params := &stripe.CustomerParams{ | |||||
Email: stripe.String(email), | |||||
Name: stripe.String(name), | |||||
Address: &stripe.AddressParams{ | |||||
City: stripe.String(address.City), | |||||
Country: stripe.String(address.Country), | |||||
Line1: stripe.String(address.Street), | |||||
PostalCode: stripe.String(address.Zip), | |||||
State: stripe.String(address.Region), | |||||
}, | |||||
}; | |||||
result, err := customer.New(params) | |||||
return *result, err | |||||
} | |||||
func createSubscription(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
claims, err := getClaims(r) | |||||
user, err := queryUser(db, claims.Id) | |||||
if err != nil { | |||||
w.WriteHeader(422) | |||||
return | |||||
} | |||||
var name string = user.FirstName + " " + user.LastName | |||||
c, err := createCustomer(name, user.Email, user.Address) | |||||
if err != nil { | |||||
http.Error(w, err.Error(), 422) | |||||
return | |||||
} | |||||
err = user.updateCustomerId(db, c.ID) | |||||
// Automatically save the payment method to the subscription | |||||
// when the first payment is successful. | |||||
paymentSettings := &stripe.SubscriptionPaymentSettingsParams{ | |||||
SaveDefaultPaymentMethod: stripe.String("on_subscription"), | |||||
} | |||||
// Create the subscription. Note we're expanding the Subscription's | |||||
// latest invoice and that invoice's payment_intent | |||||
// so we can pass it to the front end to confirm the payment | |||||
subscriptionParams := &stripe.SubscriptionParams{ | |||||
Customer: stripe.String(c.ID), | |||||
Items: []*stripe.SubscriptionItemsParams{ | |||||
{ | |||||
Price: stripe.String("price_1OZLK9BPMoXn2pf9kuTAf8rs"), | |||||
}, | |||||
}, | |||||
PaymentSettings: paymentSettings, | |||||
PaymentBehavior: stripe.String("default_incomplete"), | |||||
} | |||||
subscriptionParams.AddExpand("latest_invoice.payment_intent") | |||||
s, err := subscription.New(subscriptionParams) | |||||
if err != nil { | |||||
http.Error(w, err.Error(), 500) | |||||
return | |||||
} | |||||
json.NewEncoder(w).Encode(s) | |||||
} | |||||
func api(w http.ResponseWriter, r *http.Request) { | func api(w http.ResponseWriter, r *http.Request) { | ||||
var args []string | var args []string | ||||
@@ -3176,6 +3363,7 @@ func dev(args []string) { | |||||
os.Setenv("DBName", "skouter_dev") | os.Setenv("DBName", "skouter_dev") | ||||
os.Setenv("DBUser", "tester") | os.Setenv("DBUser", "tester") | ||||
os.Setenv("DBPass", "test123") | os.Setenv("DBPass", "test123") | ||||
stripe.Key = os.Getenv("STRIPE_SECRET_KEY") | |||||
db, err := sql.Open("mysql", | db, err := sql.Open("mysql", | ||||
fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?multiStatements=true", | fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?multiStatements=true", | ||||