diff --git a/cmd/api/handlers-api.go b/cmd/api/handlers-api.go index f812bc4..7f64bab 100644 --- a/cmd/api/handlers-api.go +++ b/cmd/api/handlers-api.go @@ -164,6 +164,8 @@ func (app *application) CreateCustomerAndSubscribeToPlan(w http.ResponseWriter, ExpiryMonth: data.ExpiryMonth, ExpiryYear: data.ExpiryYear, TransactionStatusID: 2, + PaymentIntent: subscription.ID, + PaymentMethod: data.PaymentMethod, } txnID, err := app.SaveTransaction(txn) if err != nil { @@ -587,3 +589,50 @@ func (app *application) RefundCharge(w http.ResponseWriter, r *http.Request) { app.writeJSON(w, http.StatusOK, resp) } + +func (app *application) CancelSubscription(w http.ResponseWriter, r *http.Request) { + var subToCancel struct { + ID int `json:"id"` + PaymentIntent string `json:"pi"` + Currency string `json:"currency"` + } + + err := app.readJSON(w, r, &subToCancel) + if err != nil { + app.errorLog.Println(err) + app.badRequest(w, r, err) + return + } + + // validate + + card := cards.Card{ + Secret: app.config.stripe.secret, + Key: app.config.stripe.key, + Currency: subToCancel.Currency, + } + + err = card.CancelSubscription(subToCancel.PaymentIntent) + if err != nil { + app.errorLog.Println(err) + app.badRequest(w, r, err) + return + } + + // update status in DB + err = app.DB.UpdateOrderStatus(subToCancel.ID, 3) + if err != nil { + app.badRequest( + w, + r, + errors.New("the subscription was refunded, but the database could not be updated"), + ) + return + } + + var resp jsonResponse + resp.OK = true + resp.Message = "Subscription canceled" + + app.writeJSON(w, http.StatusOK, resp) +} diff --git a/cmd/api/helpers.go b/cmd/api/helpers.go index 5dc58a2..e543944 100644 --- a/cmd/api/helpers.go +++ b/cmd/api/helpers.go @@ -53,12 +53,9 @@ func (app *application) writeJSON( } func (app *application) badRequest(w http.ResponseWriter, r *http.Request, err error) error { - var payload struct { - Error bool `json:"error"` - Message string `json:"message"` - } + var payload jsonResponse - payload.Error = true + payload.OK = false payload.Message = err.Error() out, err := json.MarshalIndent(payload, "", "\t") @@ -73,11 +70,8 @@ func (app *application) badRequest(w http.ResponseWriter, r *http.Request, err e } func (app *application) invalidCredentials(w http.ResponseWriter) error { - var payload struct { - Error bool `json:"error"` - Message string `json:"message"` - } - payload.Error = true + var payload jsonResponse + payload.OK = false payload.Message = "invalid authentication credentials" err := app.writeJSON(w, http.StatusUnauthorized, payload) diff --git a/cmd/api/routes-api.go b/cmd/api/routes-api.go index 6416be2..bbd3c74 100644 --- a/cmd/api/routes-api.go +++ b/cmd/api/routes-api.go @@ -36,6 +36,7 @@ func (app *application) routes() http.Handler { mux.Post("/all-subscriptions", app.AllSubscriptions) mux.Post("/get-sale/{id}", app.GetSale) mux.Post("/refund", app.RefundCharge) + mux.Post("/cancel-subscription", app.CancelSubscription) }) mux.Post("/api/forgot-password", app.SendPasswordResetEmail) mux.Post("/api/reset-password", app.ResetPassword) diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index d5a3920..9852de2 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -384,9 +384,16 @@ func (app *application) ShowSale(w http.ResponseWriter, r *http.Request) { stringMap := make(map[string]string) stringMap["title"] = "Sale" stringMap["cancel"] = "/admin/all-sales" + stringMap["refund-url"] = "/api/admin/refund" + stringMap["refund-btn"] = "Refund Order" + stringMap["refund-badge"] = "Refunded" + + intMap := make(map[string]int) + intMap["isRefund"] = 1 if err := app.renderTemplate(w, r, "sale", &templateData{ StringMap: stringMap, + IntMap: intMap, }); err != nil { app.errorLog.Println(err) } @@ -396,9 +403,16 @@ func (app *application) ShowSubscriptions(w http.ResponseWriter, r *http.Request stringMap := make(map[string]string) stringMap["title"] = "Subscriptions" stringMap["cancel"] = "/admin/all-subscriptions" + stringMap["refund-url"] = "/api/admin/cancel-subscription" + stringMap["refund-btn"] = "Cancel Subscription" + stringMap["refund-badge"] = "Cancelled" + + intMap := make(map[string]int) + intMap["is-refund"] = 0 if err := app.renderTemplate(w, r, "sale", &templateData{ StringMap: stringMap, + IntMap: intMap, }); err != nil { app.errorLog.Println(err) } diff --git a/cmd/web/templates/all-subscriptions.page.gohtml b/cmd/web/templates/all-subscriptions.page.gohtml index e928b79..db26735 100644 --- a/cmd/web/templates/all-subscriptions.page.gohtml +++ b/cmd/web/templates/all-subscriptions.page.gohtml @@ -14,6 +14,7 @@ All Subscriptions Customer Product Amount + Status diff --git a/cmd/web/templates/sale.page.gohtml b/cmd/web/templates/sale.page.gohtml index 741f0a5..59f239e 100644 --- a/cmd/web/templates/sale.page.gohtml +++ b/cmd/web/templates/sale.page.gohtml @@ -4,7 +4,7 @@ {{ end }} {{ define "content" }}

Sale

-Refunded +{{index .StringMap "refund-badge"}} Charged
@@ -22,7 +22,7 @@
Cancel -Refund Order +{{index .StringMap "refund-btn"}} @@ -32,8 +32,10 @@ {{ end }} diff --git a/internal/cards/cards.go b/internal/cards/cards.go index 8023a6b..0bd3ad1 100644 --- a/internal/cards/cards.go +++ b/internal/cards/cards.go @@ -135,6 +135,20 @@ func (c *Card) Refund(pi string, amount int) error { return nil } +func (c *Card) CancelSubscription(subID string) error { + stripe.Key = c.Secret + + params := &stripe.SubscriptionParams{ + CancelAtPeriodEnd: stripe.Bool(true), + } + + _, err := subscription.Update(subID, params) + if err != nil { + return err + } + return nil +} + func cardErrorMessage(code stripe.ErrorCode) string { msg := "" diff --git a/static/js/all-subscriptions.js b/static/js/all-subscriptions.js index 4162876..b23bca2 100644 --- a/static/js/all-subscriptions.js +++ b/static/js/all-subscriptions.js @@ -1,4 +1,4 @@ -import {formatCurrency} from "./common.js" +import { formatCurrency } from "./common.js" export function showTable(api) { let token = localStorage.getItem("token"); @@ -17,26 +17,32 @@ export function showTable(api) { .then(response => response.json()) .then(function (data) { if (data) { - data.forEach(function (i) { - let newRow = tbody.insertRow(); - let newCell = newRow.insertCell(); + data.forEach(function (i) { + let newRow = tbody.insertRow(); + let newCell = newRow.insertCell(); - newCell.innerHTML = `Order ${i.id}`; + newCell.innerHTML = `Order ${i.id}`; - newCell = newRow.insertCell(); - let item = document.createTextNode(i.customer.last_name + ", " + i.customer.first_name); - newCell.appendChild(item) + newCell = newRow.insertCell(); + let item = document.createTextNode(i.customer.last_name + ", " + i.customer.first_name); + newCell.appendChild(item) - newCell = newRow.insertCell(); - item = document.createTextNode(i.widget.name); - newCell.appendChild(item) + newCell = newRow.insertCell(); + item = document.createTextNode(i.widget.name); + newCell.appendChild(item) - let cur = formatCurrency(i.transaction.amount) - newCell = newRow.insertCell(); - item = document.createTextNode(cur + "/month"); - newCell.appendChild(item) + let cur = formatCurrency(i.transaction.amount) + newCell = newRow.insertCell(); + item = document.createTextNode(cur + "/month"); + newCell.appendChild(item) - }); + newCell = newRow.insertCell(); + if (i.status_id != 1) { + newCell.innerHTML = `Cancelled` + } else { + newCell.innerHTML = `Charged` + } + }); } else { let newRow = tbody.insertRow(); let newCell = newRow.insertCell(); diff --git a/static/js/sale.js b/static/js/sale.js index d6cb434..08c645b 100644 --- a/static/js/sale.js +++ b/static/js/sale.js @@ -39,7 +39,7 @@ export function showInfo(api) { }); } -export function refund(api) { +export function refund(api, isRefund) { Swal.fire({ title: "Are you sure?", text: "You won't be able to undo this!", @@ -47,7 +47,7 @@ export function refund(api) { showCancelButton: true, confirmButtonColor: "#3085d6", cancelButtonColor: "#d33", - confirmButtonText: "Refund" + confirmButtonText: isRefund === 1 ? "Refund" : "Cancel subscription" }).then((result) => { if (result.isConfirmed) { let payload = { @@ -66,15 +66,15 @@ export function refund(api) { body: JSON.stringify(payload), }; - fetch(api + "/api/admin/refund", requestOptions) + fetch(api, requestOptions) .then(response => response.json()) .then(function (data) { console.log(data); if (!data.ok) { - showError(data.message) + showError("messages", data.message) } else { - showSuccess("messages", "Refunded!") + showSuccess("messages", isRefund === 1 ? "Refunded" : "Subscription cancelled") document.getElementById("refund-btn").classList.add("d-none"); document.getElementById("refunded").classList.remove("d-none"); document.getElementById("charged").classList.add("d-none");