From 59889d691066ae11776a4016392af032e27483c5 Mon Sep 17 00:00:00 2001 From: "L. Bradley LaBoon" Date: Mon, 17 Jun 2024 18:21:01 -0400 Subject: [PATCH 1/2] Switch to new payment method endpoints --- account/account.js | 158 +++++++++++++++++------ account/creditcard/creditcard.js | 38 ++---- account/creditcard/index.shtml | 33 ++--- account/index.shtml | 12 +- account/make_a_payment/index.shtml | 47 +------ account/make_a_payment/make_a_payment.js | 77 ++++++----- include/account_subnav.html | 2 +- 7 files changed, 198 insertions(+), 169 deletions(-) diff --git a/account/account.js b/account/account.js index 8188a77..3fc941d 100644 --- a/account/account.js +++ b/account/account.js @@ -15,7 +15,7 @@ * along with Linode Manager Classic. If not, see . */ -import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js"; +import { settings, elements, apiDelete, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js"; (function() { @@ -27,10 +27,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri elements.balancePositive = "balance-positive"; elements.balanceStatus = "balance-status"; elements.billingActivity = "billing-activity"; - elements.ccNumber = "cc-number"; - elements.ccDuration = "cc-duration"; - elements.ccExpire = "cc-expire"; elements.current = "current"; + elements.defaultPrefix = "default-payment-"; + elements.deletePrefix = "delete-payment-"; elements.email = "email"; elements.gdpr = "gdpr"; elements.gdprDate = "gdpr-date"; @@ -41,6 +40,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri elements.managed = "managed"; elements.managedButton = "managed-button"; elements.pay = "pay"; + elements.paymentMethods = "payment-methods"; elements.promotions = "promotions"; elements.promotionsTable = "promotions-table"; elements.uninvoiced = "uninvoiced"; @@ -50,7 +50,8 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri var data = {}; data.account = {}; data.invoices = []; - data.linodes = []; + data.numLinodes = 0; + data.paymentMethods = []; data.payments = []; // Static references to UI elements @@ -60,9 +61,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri ui.balance = {}; ui.balanceStatus = {}; ui.billingActivity = {}; - ui.ccNumber = {}; - ui.ccDuration = {}; - ui.ccExpire = {}; ui.current = {}; ui.email = {}; ui.gdpr = []; @@ -70,6 +68,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri ui.managed = {}; ui.managedButton = {}; ui.pay = {}; + ui.paymentMethods = {}; ui.promotions = []; ui.promotionsTable = {}; ui.uninvoiced = {}; @@ -80,6 +79,78 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri state.haveInvoices = false; state.havePayments = false; + // Creates a row for the payment methods table + var createMethodRow = function(method) + { + var typeNames = { + "credit_card": "Credit Card", + "google_pay": "Google Pay", + "paypal": "PayPal" + }; + + var row = document.createElement("tr"); + row.className = elements.lmcRowStandard; + + var type = document.createElement("td"); + if (typeNames[method.type]) + type.innerHTML = typeNames[method.type]; + else + type.innerHTML = method.type; + row.appendChild(type); + + var details = document.createElement("td"); + if (method.type == "credit_card" || method.type == "google_pay") { + var ccInfo = document.createElement("span"); + ccInfo.innerHTML = method.data.card_type + " xxxxxxxxxxxx" + method.data.last_four + " Exp: " + method.data.expiry; + var br = document.createElement("br"); + details.appendChild(ccInfo); + details.appendChild(br); + var monthYear = method.data.expiry.split("/"); + var expireDate = new Date(parseInt(monthYear[1]), parseInt(monthYear[0]), 0); + var now = new Date(); + if (expireDate - now > 0) { + var duration = document.createElement("span"); + duration.innerHTML = "Expires " + timeString(now - expireDate, true); + details.appendChild(duration); + } else { + var expired = document.createElement("strong"); + expired.innerHTML = "Expired!"; + details.appendChild(expired); + } + } else if (method.type == "paypal") { + var email = document.createElement("span"); + email.innerHTML = method.data.email; + details.appendChild(email); + } + row.appendChild(details); + + var options = document.createElement("td"); + if (method.is_default) { + var defaultMsg = document.createElement("strong"); + defaultMsg.innerHTML = "This is the default payment method"; + options.appendChild(defaultMsg); + } else { + var defaultLink = document.createElement("a"); + defaultLink.id = elements.defaultPrefix + method.id; + defaultLink.href = "#"; + defaultLink.innerHTML = "Make default"; + defaultLink.addEventListener("click", defaultMethod); + var separator = document.createElement("span"); + separator.innerHTML = " | "; + var deleteLink = document.createElement("a"); + deleteLink.id = elements.deletePrefix + method.id; + deleteLink.href = "#"; + deleteLink.innerHTML = "Delete"; + deleteLink.addEventListener("click", deleteMethod); + options.appendChild(defaultLink); + options.appendChild(separator); + options.appendChild(deleteLink); + } + row.appendChild(options); + + return row; + }; + // Creates a row for the promotion table var createPromoRow = function(promo) { @@ -165,6 +236,32 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri return row; }; + // Handler for making a payment method default + var defaultMethod = function(event) + { + var id = event.currentTarget.id.slice(elements.defaultPrefix.length); + if (!confirm("Make this the default payment method?")) + return; + + apiPost("/account/payment-methods/" + id + "/make-default", {}, function(response) + { + location.reload(); + }); + }; + + // Handler for deleting a payment method + var deleteMethod = function(event) + { + var id = event.currentTarget.id.slice(elements.deletePrefix.length); + if (!confirm("Delete this payment method?")) + return; + + apiDelete("/account/payment-methods/" + id, function(response) + { + location.reload(); + }); + }; + // Callback for account details API call var displayAccount = function(response) { @@ -199,30 +296,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri // Email ui.email.innerHTML = data.account.email; - // CC info - ui.ccNumber.innerHTML = data.account.credit_card.last_four; - var expired = document.createElement("span"); - var strong = document.createElement("strong"); - strong.innerHTML = "Expired!"; - var dash = document.createElement("span"); - dash.innerHTML = " - "; - var updateLink = document.createElement("a"); - updateLink.href = "/account/creditcard"; - updateLink.innerHTML = "update credit card"; - expired.appendChild(strong); - expired.appendChild(dash); - expired.appendChild(updateLink); - if (data.account.credit_card.expiry) { - ui.ccExpire.innerHTML = data.account.credit_card.expiry; - var monthYear = data.account.credit_card.expiry.split("/"); - var expireDate = new Date(parseInt(monthYear[1]), parseInt(monthYear[0]), 0); - var now = new Date(); - if (expireDate - now > 0) - ui.ccDuration.innerHTML = "Expires " + timeString(now - expireDate, true); - else - ui.ccDuration.appendChild(expired); - } - // Account balance if (data.account.balance == 0) { ui.balance.className = elements.balancePositive; @@ -275,15 +348,23 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri // Callback for linodes API call var displayLinodes = function(response) { - data.linodes = data.linodes.concat(response.data); + data.numLinodes = response.results; + ui.managedButton.disabled = false; + }; + + // Callback for payment methods API call + var displayMethods = function(response) + { + data.paymentMethods = data.paymentMethods.concat(response.data); // Request the next page if there are more if (response.page != response.pages) { - apiGet("/linode/instances?page=" + (response.page + 1), displayLinodes, null); + apiGet("/account/payment-methods?page=" + (response.page + 1), displayMethods, null); return; } - ui.managedButton.disabled = false; + for (var i = 0; i < data.paymentMethods.length; i++) + ui.paymentMethods.appendChild(createMethodRow(data.paymentMethods[i])); }; // Callback for payments API call @@ -333,7 +414,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri if (event.currentTarget.disabled) return; - if (!confirm("Linode Managed costs an additional $100/mo per Linode. This will increase your projected monthly bill by $" + (data.linodes.length * 100) + ". Are you sure?")) + if (!confirm("Linode Managed costs an additional $100/mo per Linode. This will increase your projected monthly bill by $" + (data.numLinodes * 100) + ". Are you sure?")) return; apiPost("/account/settings/managed-enable", {}, function(response) @@ -356,9 +437,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri ui.balance = document.getElementById(elements.balance); ui.balanceStatus = document.getElementById(elements.balanceStatus); ui.billingActivity = document.getElementById(elements.billingActivity); - ui.ccNumber = document.getElementById(elements.ccNumber); - ui.ccDuration = document.getElementById(elements.ccDuration); - ui.ccExpire = document.getElementById(elements.ccExpire); ui.current = document.getElementById(elements.current); ui.email = document.getElementById(elements.email); ui.gdpr = document.getElementsByClassName(elements.gdpr); @@ -366,6 +444,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri ui.managed = document.getElementById(elements.managed); ui.managedButton = document.getElementById(elements.managedButton); ui.pay = document.getElementById(elements.pay); + ui.paymentMethods = document.getElementById(elements.paymentMethods); ui.promotions = document.getElementsByClassName(elements.promotions); ui.promotionsTable = document.getElementById(elements.promotionsTable); ui.uninvoiced = document.getElementById(elements.uninvoiced); @@ -379,6 +458,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri apiGet("/account/settings", displaySettings, null); apiGet("/account/invoices", displayInvoices, null); apiGet("/account/payments", displayPayments, null); + apiGet("/account/payment-methods", displayMethods, null); apiGet("/linode/instances", displayLinodes, null); }; diff --git a/account/creditcard/creditcard.js b/account/creditcard/creditcard.js index 749a456..34eb6fc 100644 --- a/account/creditcard/creditcard.js +++ b/account/creditcard/creditcard.js @@ -15,15 +15,14 @@ * along with Linode Manager Classic. If not, see . */ -import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/global.js"; +import { settings, elements, apiPost, parseParams, setupHeader } from "/global.js"; (function() { // Element names specific to this page - elements.ccCurrent = "cc-current"; elements.ccNew = "cc-new"; elements.cvv = "cvv"; - elements.expiryCurrent = "expiry-current"; + elements.default = "default"; elements.expiryMonth = "expiry-month"; elements.expiryYear = "expiry-year"; elements.updateButton = "update-button"; @@ -33,24 +32,13 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ // Static references to UI elements var ui = {}; - ui.ccCurrent = {}; ui.ccNew = {}; ui.cvv = {}; - ui.expiryCurrent = {}; + ui.default = {}; ui.expiryMonth = {}; ui.expiryYear = {}; ui.updateButton = {}; - // Callback for account details API call - var displayCC = function(response) - { - if (!response.credit_card) - return; - - ui.ccCurrent.innerHTML = response.credit_card.last_four; - ui.expiryCurrent.innerHTML = response.credit_card.expiry; - }; - // Click handler for update button var handleUpdate = function(event) { @@ -58,13 +46,17 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ return; var req = { - "card_number": ui.ccNew.value, - "cvv": ui.cvv.value, - "expiry_month": parseInt(ui.expiryMonth.value), - "expiry_year": parseInt(ui.expiryYear.value) + "data": { + "card_number": ui.ccNew.value.replaceAll("-", "").replaceAll(" ", ""), + "cvv": ui.cvv.value, + "expiry_month": parseInt(ui.expiryMonth.value), + "expiry_year": parseInt(ui.expiryYear.value) + }, + "is_default": ui.default.checked, + "type": "credit_card" }; - apiPost("/account/credit-card", req, function(response) + apiPost("/account/payment-methods", req, function(response) { location.href = "/account"; }); @@ -79,10 +71,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ setupHeader(); // Get element references - ui.ccCurrent = document.getElementById(elements.ccCurrent); ui.ccNew = document.getElementById(elements.ccNew); ui.cvv = document.getElementById(elements.cvv); - ui.expiryCurrent = document.getElementById(elements.expiryCurrent); + ui.default = document.getElementById(elements.default); ui.expiryMonth = document.getElementById(elements.expiryMonth); ui.expiryYear = document.getElementById(elements.expiryYear); ui.updateButton = document.getElementById(elements.updateButton); @@ -99,9 +90,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ // Register event handlers ui.updateButton.addEventListener("click", handleUpdate); - - // Get data from API - apiGet("/account", displayCC, null); }; // Attach onload handler diff --git a/account/creditcard/index.shtml b/account/creditcard/index.shtml index 17d1b35..98e9e14 100644 --- a/account/creditcard/index.shtml +++ b/account/creditcard/index.shtml @@ -18,7 +18,7 @@ along with Linode Manager Classic. If not, see . - LMC - Account // Update Credit Card + LMC - Account // Add Credit Card @@ -27,35 +27,17 @@ along with Linode Manager Classic. If not, see .
- +
- - - - + - - - - - - - - - - - - - - - - + @@ -85,9 +67,14 @@ along with Linode Manager Classic. If not, see . + + + + + -
Update Credit Card
Current CardAdd Credit Card
Current Cardxxxxxxxxxxxx Exp:
Update Card
New Card NumberCard Number Linode accepts Visa, MasterCard, American Express, and Discover
Make DefaultThis card will become the new default payment method
+
diff --git a/account/index.shtml b/account/index.shtml index 87e4666..326e0cc 100644 --- a/account/index.shtml +++ b/account/index.shtml @@ -52,18 +52,10 @@ along with Linode Manager Classic. If not, see . - Credit Card - - - - - Credit Card - - xxxxxxxxxxxx Exp:
- - + Payment Methods + diff --git a/account/make_a_payment/index.shtml b/account/make_a_payment/index.shtml index 157d161..469edf9 100644 --- a/account/make_a_payment/index.shtml +++ b/account/make_a_payment/index.shtml @@ -32,62 +32,25 @@ along with Linode Manager Classic. If not, see . - + - - - - - - - - - - - - - - - - - - + - - - + + - - - - - - - - - - - - - - - - - - - - +
Make a PaymentMake a Payment
Current Balance
Make a Payment - via Credit Card
Current Cardxxxxxxxxxxxx Exp: (update credit card)
Amount to Charge (USD) (USD)
CVV (optional)Payment Method
Make a Payment - via PayPal
Amount to Pay (USD)
diff --git a/account/make_a_payment/make_a_payment.js b/account/make_a_payment/make_a_payment.js index afcdc25..0c1181c 100644 --- a/account/make_a_payment/make_a_payment.js +++ b/account/make_a_payment/make_a_payment.js @@ -24,25 +24,18 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ elements.balanceNegative = "balance-negative"; elements.ccAmount = "cc-amount"; elements.ccCharge = "cc-charge"; - elements.ccExp = "cc-exp"; - elements.ccNumber = "cc-number"; - elements.cvv = "cvv"; - elements.paypalAmount = "paypal-amount"; - elements.paypalCharge = "paypal-charge"; + elements.method = "method"; // Data received from API calls var data = {}; + data.methods = []; // Static references to UI elements var ui = {}; ui.balance = {}; ui.ccAmount = {}; ui.ccCharge = {}; - ui.ccExp = {}; - ui.ccNumber = {}; - ui.cvv = {}; - ui.paypalAmount = {}; - ui.paypalCharge = {}; + ui.method = {}; // Callback for account details API call var displayAccount = function(response) @@ -53,17 +46,55 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ } else if (response.balance > 0) { ui.balance.innerHTML += response.balance.toFixed(2) + " outstanding"; ui.balance.className = elements.balanceNegative; + if (response.balance < 5) { + ui.ccAmount.min = response.balance.toFixed(2); + ui.ccAmount.value = response.balance.toFixed(2); + } else if (response.balance > 2000) { + ui.ccAmount.max = Math.min(50000, response.balance.toFixed(2)); + } } else { ui.balance.innerHTML += response.balance.toFixed(2); } - if (response.credit_card) { - ui.ccNumber.innerHTML = response.credit_card.last_four; - ui.ccExp.innerHTML = response.credit_card.expiry; + ui.ccCharge.disabled = false; + }; + + // Callback for payment methods API call + var displayMethods = function(response) + { + data.methods = data.methods.concat(response.data); + + // Request the next page if there are more + if (response.page != response.pages) { + apiGet("/account/payment-methods?page=" + (response.page + 1), displayMethods, null); + return; + } + + var typeNames = { + "credit_card": "Credit Card", + "google_pay": "Google Pay", + "paypal": "PayPal" + }; + + for (var i = 0; i < data.methods.length; i++) { + var option = document.createElement("option"); + option.value = data.methods[i].id; + if (typeNames[data.methods[i].type]) + option.innerHTML = typeNames[data.methods[i].type]; + else + option.innerHTML = data.methods[i].type; + if (data.methods[i].type == "credit_card" || data.methods[i].type == "google_pay") + option.innerHTML += ": " + data.methods[i].data.card_type + " ****" + data.methods[i].data.last_four; + else if (data.methods[i].type == "paypal") + option.innerHTML += ": " + data.methods[i].data.email; + ui.method.appendChild(option); + if (data.methods[i].is_default) { + option.innerHTML += " (default)"; + ui.method.value = data.methods[i].id; + } } ui.ccCharge.disabled = false; - //ui.paypalCharge.disabled = false; }; // Click handler for CC charge button @@ -76,10 +107,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ return; var req = { + "payment_method_id": parseInt(ui.method.value), "usd": ui.ccAmount.value }; - if (ui.cvv.value.length) - req.cvv = ui.cvv.value; apiPost("/account/payments", req, function(response) { @@ -87,13 +117,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ }); }; - // Click handler for PayPal button - var handlePayPal = function(event) - { - if (event.currentTarget.disabled) - return; - }; - // Initial setup var setup = function() { @@ -106,18 +129,14 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ ui.balance = document.getElementById(elements.balance); ui.ccAmount = document.getElementById(elements.ccAmount); ui.ccCharge = document.getElementById(elements.ccCharge); - ui.ccExp = document.getElementById(elements.ccExp); - ui.ccNumber = document.getElementById(elements.ccNumber); - ui.cvv = document.getElementById(elements.cvv); - ui.paypalAmount = document.getElementById(elements.paypalAmount); - ui.paypalCharge = document.getElementById(elements.paypalCharge); + ui.method = document.getElementById(elements.method); // Register event handlers ui.ccCharge.addEventListener("click", handleCharge); - ui.paypalCharge.addEventListener("click", handlePayPal); // Get data from API apiGet("/account", displayAccount, null); + apiGet("/account/payment-methods", displayMethods, null); }; // Attach onload handler diff --git a/include/account_subnav.html b/include/account_subnav.html index db148e8..1890319 100644 --- a/include/account_subnav.html +++ b/include/account_subnav.html @@ -2,7 +2,7 @@