Implement Payment Methods #3
| @@ -15,7 +15,7 @@ | |||||||
|  * along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. |  * along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js"; | import { settings, elements, apiDelete, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js"; | ||||||
|  |  | ||||||
| (function() | (function() | ||||||
| { | { | ||||||
| @@ -27,10 +27,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	elements.balancePositive = "balance-positive"; | 	elements.balancePositive = "balance-positive"; | ||||||
| 	elements.balanceStatus = "balance-status"; | 	elements.balanceStatus = "balance-status"; | ||||||
| 	elements.billingActivity = "billing-activity"; | 	elements.billingActivity = "billing-activity"; | ||||||
| 	elements.ccNumber = "cc-number"; |  | ||||||
| 	elements.ccDuration = "cc-duration"; |  | ||||||
| 	elements.ccExpire = "cc-expire"; |  | ||||||
| 	elements.current = "current"; | 	elements.current = "current"; | ||||||
|  | 	elements.defaultPrefix = "default-payment-"; | ||||||
|  | 	elements.deletePrefix = "delete-payment-"; | ||||||
| 	elements.email = "email"; | 	elements.email = "email"; | ||||||
| 	elements.gdpr = "gdpr"; | 	elements.gdpr = "gdpr"; | ||||||
| 	elements.gdprDate = "gdpr-date"; | 	elements.gdprDate = "gdpr-date"; | ||||||
| @@ -41,6 +40,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	elements.managed = "managed"; | 	elements.managed = "managed"; | ||||||
| 	elements.managedButton = "managed-button"; | 	elements.managedButton = "managed-button"; | ||||||
| 	elements.pay = "pay"; | 	elements.pay = "pay"; | ||||||
|  | 	elements.paymentMethods = "payment-methods"; | ||||||
| 	elements.promotions = "promotions"; | 	elements.promotions = "promotions"; | ||||||
| 	elements.promotionsTable = "promotions-table"; | 	elements.promotionsTable = "promotions-table"; | ||||||
| 	elements.uninvoiced = "uninvoiced"; | 	elements.uninvoiced = "uninvoiced"; | ||||||
| @@ -50,7 +50,8 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	var data = {}; | 	var data = {}; | ||||||
| 	data.account = {}; | 	data.account = {}; | ||||||
| 	data.invoices = []; | 	data.invoices = []; | ||||||
| 	data.linodes = []; | 	data.numLinodes = 0; | ||||||
|  | 	data.paymentMethods = []; | ||||||
| 	data.payments = []; | 	data.payments = []; | ||||||
|  |  | ||||||
| 	// Static references to UI elements | 	// Static references to UI elements | ||||||
| @@ -60,9 +61,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	ui.balance = {}; | 	ui.balance = {}; | ||||||
| 	ui.balanceStatus = {}; | 	ui.balanceStatus = {}; | ||||||
| 	ui.billingActivity = {}; | 	ui.billingActivity = {}; | ||||||
| 	ui.ccNumber = {}; |  | ||||||
| 	ui.ccDuration = {}; |  | ||||||
| 	ui.ccExpire = {}; |  | ||||||
| 	ui.current = {}; | 	ui.current = {}; | ||||||
| 	ui.email = {}; | 	ui.email = {}; | ||||||
| 	ui.gdpr = []; | 	ui.gdpr = []; | ||||||
| @@ -70,6 +68,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	ui.managed = {}; | 	ui.managed = {}; | ||||||
| 	ui.managedButton = {}; | 	ui.managedButton = {}; | ||||||
| 	ui.pay = {}; | 	ui.pay = {}; | ||||||
|  | 	ui.paymentMethods = {}; | ||||||
| 	ui.promotions = []; | 	ui.promotions = []; | ||||||
| 	ui.promotionsTable = {}; | 	ui.promotionsTable = {}; | ||||||
| 	ui.uninvoiced = {}; | 	ui.uninvoiced = {}; | ||||||
| @@ -80,6 +79,78 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	state.haveInvoices = false; | 	state.haveInvoices = false; | ||||||
| 	state.havePayments = 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 | 	// Creates a row for the promotion table | ||||||
| 	var createPromoRow = function(promo) | 	var createPromoRow = function(promo) | ||||||
| 	{ | 	{ | ||||||
| @@ -165,6 +236,32 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 		return row; | 		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 | 	// Callback for account details API call | ||||||
| 	var displayAccount = function(response) | 	var displayAccount = function(response) | ||||||
| 	{ | 	{ | ||||||
| @@ -199,30 +296,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 		// Email | 		// Email | ||||||
| 		ui.email.innerHTML = data.account.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 | 		// Account balance | ||||||
| 		if (data.account.balance == 0) { | 		if (data.account.balance == 0) { | ||||||
| 			ui.balance.className = elements.balancePositive; | 			ui.balance.className = elements.balancePositive; | ||||||
| @@ -275,15 +348,23 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 	// Callback for linodes API call | 	// Callback for linodes API call | ||||||
| 	var displayLinodes = function(response) | 	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 | 		// Request the next page if there are more | ||||||
| 		if (response.page != response.pages) { | 		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; | 			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 | 	// Callback for payments API call | ||||||
| @@ -333,7 +414,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri | |||||||
| 		if (event.currentTarget.disabled) | 		if (event.currentTarget.disabled) | ||||||
| 			return; | 			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; | 			return; | ||||||
|  |  | ||||||
| 		apiPost("/account/settings/managed-enable", {}, function(response) | 		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.balance = document.getElementById(elements.balance); | ||||||
| 		ui.balanceStatus = document.getElementById(elements.balanceStatus); | 		ui.balanceStatus = document.getElementById(elements.balanceStatus); | ||||||
| 		ui.billingActivity = document.getElementById(elements.billingActivity); | 		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.current = document.getElementById(elements.current); | ||||||
| 		ui.email = document.getElementById(elements.email); | 		ui.email = document.getElementById(elements.email); | ||||||
| 		ui.gdpr = document.getElementsByClassName(elements.gdpr); | 		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.managed = document.getElementById(elements.managed); | ||||||
| 		ui.managedButton = document.getElementById(elements.managedButton); | 		ui.managedButton = document.getElementById(elements.managedButton); | ||||||
| 		ui.pay = document.getElementById(elements.pay); | 		ui.pay = document.getElementById(elements.pay); | ||||||
|  | 		ui.paymentMethods = document.getElementById(elements.paymentMethods); | ||||||
| 		ui.promotions = document.getElementsByClassName(elements.promotions); | 		ui.promotions = document.getElementsByClassName(elements.promotions); | ||||||
| 		ui.promotionsTable = document.getElementById(elements.promotionsTable); | 		ui.promotionsTable = document.getElementById(elements.promotionsTable); | ||||||
| 		ui.uninvoiced = document.getElementById(elements.uninvoiced); | 		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/settings", displaySettings, null); | ||||||
| 		apiGet("/account/invoices", displayInvoices, null); | 		apiGet("/account/invoices", displayInvoices, null); | ||||||
| 		apiGet("/account/payments", displayPayments, null); | 		apiGet("/account/payments", displayPayments, null); | ||||||
|  | 		apiGet("/account/payment-methods", displayMethods, null); | ||||||
| 		apiGet("/linode/instances", displayLinodes, null); | 		apiGet("/linode/instances", displayLinodes, null); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,15 +15,14 @@ | |||||||
|  * along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. |  * along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/global.js"; | import { settings, elements, apiPost, parseParams, setupHeader } from "/global.js"; | ||||||
|  |  | ||||||
| (function() | (function() | ||||||
| { | { | ||||||
| 	// Element names specific to this page | 	// Element names specific to this page | ||||||
| 	elements.ccCurrent = "cc-current"; |  | ||||||
| 	elements.ccNew = "cc-new"; | 	elements.ccNew = "cc-new"; | ||||||
| 	elements.cvv = "cvv"; | 	elements.cvv = "cvv"; | ||||||
| 	elements.expiryCurrent = "expiry-current"; | 	elements.default = "default"; | ||||||
| 	elements.expiryMonth = "expiry-month"; | 	elements.expiryMonth = "expiry-month"; | ||||||
| 	elements.expiryYear = "expiry-year"; | 	elements.expiryYear = "expiry-year"; | ||||||
| 	elements.updateButton = "update-button"; | 	elements.updateButton = "update-button"; | ||||||
| @@ -33,24 +32,13 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
|  |  | ||||||
| 	// Static references to UI elements | 	// Static references to UI elements | ||||||
| 	var ui = {}; | 	var ui = {}; | ||||||
| 	ui.ccCurrent = {}; |  | ||||||
| 	ui.ccNew = {}; | 	ui.ccNew = {}; | ||||||
| 	ui.cvv = {}; | 	ui.cvv = {}; | ||||||
| 	ui.expiryCurrent = {}; | 	ui.default = {}; | ||||||
| 	ui.expiryMonth = {}; | 	ui.expiryMonth = {}; | ||||||
| 	ui.expiryYear = {}; | 	ui.expiryYear = {}; | ||||||
| 	ui.updateButton = {}; | 	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 | 	// Click handler for update button | ||||||
| 	var handleUpdate = function(event) | 	var handleUpdate = function(event) | ||||||
| 	{ | 	{ | ||||||
| @@ -58,13 +46,17 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 			return; | 			return; | ||||||
|  |  | ||||||
| 		var req = { | 		var req = { | ||||||
| 			"card_number": ui.ccNew.value, | 			"data": { | ||||||
|  | 				"card_number": ui.ccNew.value.replaceAll("-", "").replaceAll(" ", ""), | ||||||
| 				"cvv": ui.cvv.value, | 				"cvv": ui.cvv.value, | ||||||
| 				"expiry_month": parseInt(ui.expiryMonth.value), | 				"expiry_month": parseInt(ui.expiryMonth.value), | ||||||
| 				"expiry_year": parseInt(ui.expiryYear.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"; | 			location.href = "/account"; | ||||||
| 		}); | 		}); | ||||||
| @@ -79,10 +71,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 		setupHeader(); | 		setupHeader(); | ||||||
|  |  | ||||||
| 		// Get element references | 		// Get element references | ||||||
| 		ui.ccCurrent = document.getElementById(elements.ccCurrent); |  | ||||||
| 		ui.ccNew = document.getElementById(elements.ccNew); | 		ui.ccNew = document.getElementById(elements.ccNew); | ||||||
| 		ui.cvv = document.getElementById(elements.cvv); | 		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.expiryMonth = document.getElementById(elements.expiryMonth); | ||||||
| 		ui.expiryYear = document.getElementById(elements.expiryYear); | 		ui.expiryYear = document.getElementById(elements.expiryYear); | ||||||
| 		ui.updateButton = document.getElementById(elements.updateButton); | 		ui.updateButton = document.getElementById(elements.updateButton); | ||||||
| @@ -99,9 +90,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
|  |  | ||||||
| 		// Register event handlers | 		// Register event handlers | ||||||
| 		ui.updateButton.addEventListener("click", handleUpdate); | 		ui.updateButton.addEventListener("click", handleUpdate); | ||||||
|  |  | ||||||
| 		// Get data from API |  | ||||||
| 		apiGet("/account", displayCC, null); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Attach onload handler | 	// Attach onload handler | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | |||||||
| <html lang="en"> | <html lang="en"> | ||||||
| 	<head> | 	<head> | ||||||
| 		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | 		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||||||
| 		<title>LMC - Account // Update Credit Card</title> | 		<title>LMC - Account // Add Credit Card</title> | ||||||
| 		<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /> | 		<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /> | ||||||
| 		<link rel="stylesheet" type="text/css" href="creditcard.css" /> | 		<link rel="stylesheet" type="text/css" href="creditcard.css" /> | ||||||
| 		<script src="creditcard.js" type="module"></script> | 		<script src="creditcard.js" type="module"></script> | ||||||
| @@ -27,35 +27,17 @@ along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | |||||||
| 		<!--#include virtual="/include/header.html"--> | 		<!--#include virtual="/include/header.html"--> | ||||||
| 		<!--#include virtual="/include/account_subnav.html"--> | 		<!--#include virtual="/include/account_subnav.html"--> | ||||||
| 		<div id="main-content" class="wrapper"> | 		<div id="main-content" class="wrapper"> | ||||||
| 			<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Update Credit Card</span></div> | 			<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Add Credit Card</span></div> | ||||||
| 			<div id="creditcard"> | 			<div id="creditcard"> | ||||||
| 				<table class="lmc-table"> | 				<table class="lmc-table"> | ||||||
| 					<thead> | 					<thead> | ||||||
| 						<tr> | 						<tr> | ||||||
| 							<td colspan="3">Update Credit Card</td> | 							<td colspan="3">Add Credit Card</td> | ||||||
| 						</tr> |  | ||||||
| 						<tr> |  | ||||||
| 							<td colspan="3">Current Card</td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 					</thead> | 					</thead> | ||||||
| 					<tbody> | 					<tbody> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td>Current Card</td> | 							<td>Card Number</td> | ||||||
| 							<td>xxxxxxxxxxxx<span id="cc-current"></span> Exp: <span id="expiry-current"></span></td> |  | ||||||
| 							<td></td> |  | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody class="lmc-tbody-head"> |  | ||||||
| 						<tr class="noshow"> |  | ||||||
| 							<td colspan="3"></td> |  | ||||||
| 						</tr> |  | ||||||
| 						<tr> |  | ||||||
| 							<td colspan="3">Update Card</td> |  | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody> |  | ||||||
| 						<tr class="lmc-tr3"> |  | ||||||
| 							<td>New Card Number</td> |  | ||||||
| 							<td><input id="cc-new" type="text" /></td> | 							<td><input id="cc-new" type="text" /></td> | ||||||
| 							<td class="info">Linode accepts Visa, MasterCard, American Express, and Discover</td> | 							<td class="info">Linode accepts Visa, MasterCard, American Express, and Discover</td> | ||||||
| 						</tr> | 						</tr> | ||||||
| @@ -85,9 +67,14 @@ along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | |||||||
| 							</td> | 							</td> | ||||||
| 							<td></td> | 							<td></td> | ||||||
| 						</tr> | 						</tr> | ||||||
|  | 						<tr class="lmc-tr3"> | ||||||
|  | 							<td>Make Default</td> | ||||||
|  | 							<td><input id="default" type="checkbox" /></td> | ||||||
|  | 							<td class="info">This card will become the new default payment method</td> | ||||||
|  | 						</tr> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td></td> | 							<td></td> | ||||||
| 							<td colspan="2"><button id="update-button" type="button">Update Credit Card</button> | 							<td colspan="2"><button id="update-button" type="button">Add Credit Card</button> | ||||||
| 						</tr> | 						</tr> | ||||||
| 					</tbody> | 					</tbody> | ||||||
| 				</table> | 				</table> | ||||||
|   | |||||||
| @@ -52,18 +52,10 @@ along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | |||||||
| 							<td colspan="3"></td> | 							<td colspan="3"></td> | ||||||
| 						</tr> | 						</tr> | ||||||
| 						<tr> | 						<tr> | ||||||
| 							<td colspan="3">Credit Card</td> | 							<td colspan="3">Payment Methods</td> | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody> |  | ||||||
| 						<tr class="lmc-tr3"> |  | ||||||
| 							<td>Credit Card</td> |  | ||||||
| 							<td colspan="2"> |  | ||||||
| 								xxxxxxxxxxxx<span id="cc-number"></span> Exp: <span id="cc-expire"></span><br /> |  | ||||||
| 								<span id="cc-duration"></span> |  | ||||||
| 							</td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 					</tbody> | 					</tbody> | ||||||
|  | 					<tbody id="payment-methods"></tbody> | ||||||
| 					<tbody class="lmc-tbody-head"> | 					<tbody class="lmc-tbody-head"> | ||||||
| 						<tr class="noshow"> | 						<tr class="noshow"> | ||||||
| 							<td colspan="3"></td> | 							<td colspan="3"></td> | ||||||
|   | |||||||
| @@ -32,62 +32,25 @@ along with Linode Manager Classic.  If not, see <https://www.gnu.org/licenses/>. | |||||||
| 				<table class="lmc-table"> | 				<table class="lmc-table"> | ||||||
| 					<thead> | 					<thead> | ||||||
| 						<tr> | 						<tr> | ||||||
| 							<td colspan="3">Make a Payment</td> | 							<td colspan="2">Make a Payment</td> | ||||||
| 						</tr> | 						</tr> | ||||||
| 					</thead> | 					</thead> | ||||||
| 					<tbody> | 					<tbody> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td>Current Balance</td> | 							<td>Current Balance</td> | ||||||
| 							<td id="balance" class="balance-positive"></td> | 							<td id="balance" class="balance-positive"></td> | ||||||
| 							<td></td> |  | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody class="lmc-tbody-head"> |  | ||||||
| 						<tr class="noshow"> |  | ||||||
| 							<td colspan="3"></td> |  | ||||||
| 						</tr> |  | ||||||
| 						<tr> |  | ||||||
| 							<td colspan="3">Make a Payment - via Credit Card</td> |  | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody> |  | ||||||
| 						<tr class="lmc-tr3"> |  | ||||||
| 							<td>Current Card</td> |  | ||||||
| 							<td>xxxxxxxxxxxx<span id="cc-number"></span> Exp: <span id="cc-exp"></span></td> |  | ||||||
| 							<td class="info"><a href="/account/creditcard">(update credit card)</a></td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td>Amount to Charge</td> | 							<td>Amount to Charge</td> | ||||||
| 							<td><input id="cc-amount" type="number" min="0" max="50000" step="0.01" value="0.00" /> <span class="info">(USD)</span></td> | 							<td><input id="cc-amount" type="number" min="5" max="2000" step="0.01" value="5.00" /> <span class="info">(USD)</span></td> | ||||||
| 							<td></td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td>CVV</td> | 							<td>Payment Method</td> | ||||||
| 							<td><input id="cvv" type="text" size="8" /> <span class="info">(optional)</span></td> | 							<td><select id="method"></select></td> | ||||||
| 							<td></td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 						<tr class="lmc-tr3"> | 						<tr class="lmc-tr3"> | ||||||
| 							<td></td> | 							<td></td> | ||||||
| 							<td colspan="2"><button disabled id="cc-charge" type="button">Charge Credit Card</button></td> | 							<td><button disabled id="cc-charge" type="button">Make Payment</button></td> | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody class="lmc-tbody-head"> |  | ||||||
| 						<tr class="noshow"> |  | ||||||
| 							<td colspan="3"></td> |  | ||||||
| 						</tr> |  | ||||||
| 						<tr> |  | ||||||
| 							<td colspan="3">Make a Payment - via PayPal</td> |  | ||||||
| 						</tr> |  | ||||||
| 					</tbody> |  | ||||||
| 					<tbody> |  | ||||||
| 						<tr class="lmc-tr3"> |  | ||||||
| 							<td>Amount to Pay</td> |  | ||||||
| 							<td><input id="paypal-amount" type="number" min="0" max="10000" step="0.01" value="0.00" /> <span class="info">(USD)</span></td> |  | ||||||
| 							<td></td> |  | ||||||
| 						</tr> |  | ||||||
| 						<tr class="lmc-tr3"> |  | ||||||
| 							<td></td> |  | ||||||
| 							<td colspan="2"><button disabled id="paypal-charge" type="button">Continue...</button></td> |  | ||||||
| 						</tr> | 						</tr> | ||||||
| 					</tbody> | 					</tbody> | ||||||
| 				</table> | 				</table> | ||||||
|   | |||||||
| @@ -24,25 +24,18 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 	elements.balanceNegative = "balance-negative"; | 	elements.balanceNegative = "balance-negative"; | ||||||
| 	elements.ccAmount = "cc-amount"; | 	elements.ccAmount = "cc-amount"; | ||||||
| 	elements.ccCharge = "cc-charge"; | 	elements.ccCharge = "cc-charge"; | ||||||
| 	elements.ccExp = "cc-exp"; | 	elements.method = "method"; | ||||||
| 	elements.ccNumber = "cc-number"; |  | ||||||
| 	elements.cvv = "cvv"; |  | ||||||
| 	elements.paypalAmount = "paypal-amount"; |  | ||||||
| 	elements.paypalCharge = "paypal-charge"; |  | ||||||
|  |  | ||||||
| 	// Data received from API calls | 	// Data received from API calls | ||||||
| 	var data = {}; | 	var data = {}; | ||||||
|  | 	data.methods = []; | ||||||
|  |  | ||||||
| 	// Static references to UI elements | 	// Static references to UI elements | ||||||
| 	var ui = {}; | 	var ui = {}; | ||||||
| 	ui.balance = {}; | 	ui.balance = {}; | ||||||
| 	ui.ccAmount = {}; | 	ui.ccAmount = {}; | ||||||
| 	ui.ccCharge = {}; | 	ui.ccCharge = {}; | ||||||
| 	ui.ccExp = {}; | 	ui.method = {}; | ||||||
| 	ui.ccNumber = {}; |  | ||||||
| 	ui.cvv = {}; |  | ||||||
| 	ui.paypalAmount = {}; |  | ||||||
| 	ui.paypalCharge = {}; |  | ||||||
|  |  | ||||||
| 	// Callback for account details API call | 	// Callback for account details API call | ||||||
| 	var displayAccount = function(response) | 	var displayAccount = function(response) | ||||||
| @@ -53,17 +46,55 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 		} else if (response.balance > 0) { | 		} else if (response.balance > 0) { | ||||||
| 			ui.balance.innerHTML += response.balance.toFixed(2) + " outstanding"; | 			ui.balance.innerHTML += response.balance.toFixed(2) + " outstanding"; | ||||||
| 			ui.balance.className = elements.balanceNegative; | 			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 { | 		} else { | ||||||
| 			ui.balance.innerHTML += response.balance.toFixed(2); | 			ui.balance.innerHTML += response.balance.toFixed(2); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (response.credit_card) { | 		ui.ccCharge.disabled = false; | ||||||
| 			ui.ccNumber.innerHTML = response.credit_card.last_four; | 	}; | ||||||
| 			ui.ccExp.innerHTML = response.credit_card.expiry; |  | ||||||
|  | 	// 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.ccCharge.disabled = false; | ||||||
| 		//ui.paypalCharge.disabled = false; |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Click handler for CC charge button | 	// Click handler for CC charge button | ||||||
| @@ -76,10 +107,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 			return; | 			return; | ||||||
|  |  | ||||||
| 		var req = { | 		var req = { | ||||||
|  | 			"payment_method_id": parseInt(ui.method.value), | ||||||
| 			"usd": ui.ccAmount.value | 			"usd": ui.ccAmount.value | ||||||
| 		}; | 		}; | ||||||
| 		if (ui.cvv.value.length) |  | ||||||
| 			req.cvv = ui.cvv.value; |  | ||||||
|  |  | ||||||
| 		apiPost("/account/payments", req, function(response) | 		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 | 	// Initial setup | ||||||
| 	var setup = function() | 	var setup = function() | ||||||
| 	{ | 	{ | ||||||
| @@ -106,18 +129,14 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/ | |||||||
| 		ui.balance = document.getElementById(elements.balance); | 		ui.balance = document.getElementById(elements.balance); | ||||||
| 		ui.ccAmount = document.getElementById(elements.ccAmount); | 		ui.ccAmount = document.getElementById(elements.ccAmount); | ||||||
| 		ui.ccCharge = document.getElementById(elements.ccCharge); | 		ui.ccCharge = document.getElementById(elements.ccCharge); | ||||||
| 		ui.ccExp = document.getElementById(elements.ccExp); | 		ui.method = document.getElementById(elements.method); | ||||||
| 		ui.ccNumber = document.getElementById(elements.ccNumber); |  | ||||||
| 		ui.cvv = document.getElementById(elements.cvv); |  | ||||||
| 		ui.paypalAmount = document.getElementById(elements.paypalAmount); |  | ||||||
| 		ui.paypalCharge = document.getElementById(elements.paypalCharge); |  | ||||||
|  |  | ||||||
| 		// Register event handlers | 		// Register event handlers | ||||||
| 		ui.ccCharge.addEventListener("click", handleCharge); | 		ui.ccCharge.addEventListener("click", handleCharge); | ||||||
| 		ui.paypalCharge.addEventListener("click", handlePayPal); |  | ||||||
|  |  | ||||||
| 		// Get data from API | 		// Get data from API | ||||||
| 		apiGet("/account", displayAccount, null); | 		apiGet("/account", displayAccount, null); | ||||||
|  | 		apiGet("/account/payment-methods", displayMethods, null); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Attach onload handler | 	// Attach onload handler | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| <nav id="subnav" class="wrapper"> | <nav id="subnav" class="wrapper"> | ||||||
| 	<a class="subnav-link" href="/account">Account</a> | 	<a class="subnav-link" href="/account">Account</a> | ||||||
| 	<a class="subnav-link" href="/account/contact">Contact Info</a> | 	<a class="subnav-link" href="/account/contact">Contact Info</a> | ||||||
| 	<a class="subnav-link" href="/account/creditcard">Update Credit Card</a> | 	<a class="subnav-link" href="/account/creditcard">Add Credit Card</a> | ||||||
| 	<a class="subnav-link" href="/account/make_a_payment">Make A Payment</a> | 	<a class="subnav-link" href="/account/make_a_payment">Make A Payment</a> | ||||||
| 	<a class="subnav-link" href="/account/billing_history">Billing History</a> | 	<a class="subnav-link" href="/account/billing_history">Billing History</a> | ||||||
| 	<a class="subnav-link" href="/user">Users</a> | 	<a class="subnav-link" href="/user">Users</a> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user