Miva Merchant Development by Scot's Scripts

MIVAPAY: Submit Payment Once and Disable Button

Miva Knowledge Base
MIVAPAY: Submit Payment Once and Disable Button
Important Notice: This information is for internal reference only. Use at your own risk.
Does Google actually understand your Miva Merchant store? Our JSON-LD schema generator makes sure it does. Contact us to get started. (more info)

MIVAPAY: Submit Payment Once and Disable Button

Scot Ranney • December 05, 2024


Updated April 2025 from working code on https://www.applesofgold.com

Add this code to the miva pay js (and possibly other) settings to disable the submit button. This code is from Nicholas Adkins. It was installed on https://www.applesofgold.com and appears to work great.

First see if the online version of the code is available, if so use it instead because it will be up to date: https://snippets.cacher.io/snippet/35096acc04a268d5a6ac

Miva Pay JS Settings

<input id="mivapay_response_xml" name="ResponseXML" type="hidden" value="" />
<input id="mivapay_response_signature" name="ResponseSignature" type="hidden" value="" />
<iframe id="mivapay_frame" name="MivaPay_Frame" src="" title="Payment Details" style="width: 100%; height: 0; border: 0 none; visibility: hidden;"></iframe>
<script type="text/javascript">
	function stoi(value) {
		return parseInt(value, 10);
	}
	function stoi_def_nonneg(value, default_value) {
		value = stoi(value);
		return ((isNaN(value) || (value < 0) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) ? default_value : value);
	}
	function AddEvent(obj, eventType, fn) {
		if (obj.addEventListener) {
			obj.addEventListener(eventType, fn, false);
			return true;
		}
		else if (obj.attachEvent) {
			var r = obj.attachEvent('on' + eventType, fn);
			return r;
		}
		else {
			return false;
		}
	}
	function parentForm(element) {
		while (element.nodeName.toLowerCase() !== 'form' && element.parentNode) {
			element = element.parentNode;
		}
		if (element.nodeName.toLowerCase() !== 'form') {
			return;
		}
		return element;
	}
	function MivaPay() {
		var self = this;
		this.submitting = false;
		this.request_xml = '&mvtj:mivapay:paymentcardfields:request_xml;';
		this.request_signature = '&mvtj:mivapay:paymentcardfields:request_signature;';
		this.event_receivemessage = function (event) {

			self.Receive_Message(event);

<mvt:comment>
#
# hide spinner when the miva pay stuff finally loads
#
</mvt:comment>

document.getElementById('mivapay-iframe-spinner').style.display='none';

		};
		AddEvent(window, 'message', this.event_receivemessage);
		this.element_frame = document.getElementById('mivapay_frame');
		this.element_response_xml = document.getElementById('mivapay_response_xml');
		this.element_response_signature = document.getElementById('mivapay_response_signature');
		if (this.element_form === document.getElementById('mivapay_form')) {
			this.element_form_request_xml = document.getElementById('mivapay_form_request_xml');
			this.element_form_request_signature = document.getElementById('mivapay_form_request_signature');
		}
		else {
			this.element_form = document.createElement('form');
			this.element_form.id = 'mivapay_form';
			this.element_form.target = 'MivaPay_Frame';
			this.element_form.method = 'POST';
			this.element_form.action = '&mvtj:paymentsettings:mivapay:payment_url_sep;';
			this.element_form_request_xml = document.createElement('input');
			this.element_form_request_xml.id = 'mivapay_form_request_xml';
			this.element_form_request_xml.type = 'hidden';
			this.element_form_request_xml.name = 'RequestXML';
			this.element_form_request_signature = document.createElement('input');
			this.element_form_request_signature.id = 'mivapay_form_request_signature';
			this.element_form_request_signature.type = 'hidden';
			this.element_form_request_signature.name = 'RequestSignature';
			this.element_form.appendChild(this.element_form_request_xml);
			this.element_form.appendChild(this.element_form_request_signature);
			document.body.appendChild(this.element_form);
		}
		this.element_form_request_xml.value = this.request_xml;
		this.element_form_request_signature.value = this.request_signature;
		this.element_form.submit();
	}
	MivaPay.prototype.Submit = function (callback) {
		this.submit_callback = callback;
		if (this.element_frame && !this.submitting) {
			this.submitting = true;
			this.element_frame.contentWindow.postMessage('submit', '&mvtj:paymentsettings:mivapay:base_url;');
			this.EnableDisableButtons();
		}
	};
	MivaPay.prototype.Receive_Message = function (event) {
		var origin, response, signature;
		origin = event.origin || event.originalEvent.origin;
		this.submitting = false;
		if ('&mvtj:paymentsettings:mivapay:base_url;/'.indexOf(origin + '/') !== 0) {
			return;
		}
		if (event && (typeof event.data === 'string')) {
			if (event.data.indexOf('dimensions:') === 0) {
				if (this.element_frame) {
					this.element_frame.style.width = '100%';
					this.element_frame.style.height = stoi_def_nonneg(event.data.split(':')[2], 0) + 'px';
					this.element_frame.style.visibility = 'visible';
				}
			}
			else if (event.data.indexOf('response:') === 0) {
				response = event.data.split(':')[1];
				signature = event.data.split(':')[2];
				this.element_response_xml.value = response;
				this.element_response_signature.value = signature;
				if (typeof this.submit_callback === 'function') {
					this.submit_callback();
				}
			}
			else if (event.data.indexOf('error:') === 0) {
				this.element_response_xml.value = '';
				this.element_response_signature.value = '';
				this.EnableDisableButtons();
				if (typeof this.submit_callback === 'function') {
					this.submit_callback();
				}
			}
			else {
				this.EnableDisableButtons();
			}
		}
	};
	MivaPay.prototype.EnableDisableButtons = function () {
		var self = this;
		self.checkout_buttons = document.querySelector('[data-hook="opay-form"]')?.querySelectorAll('[type="submit"]') || [];
		if (!self.checkout_buttons.length) {
			return;
		}
		var i;
		var enabled_disabled = self.submitting ? true : false;
		for (i = 0; i < self.checkout_buttons.length; i++) {
			const button = self.checkout_buttons[i];
			if (enabled_disabled) {
				button.disabled = true;
				button.value = 'Processing';
				if (button.nodeName === 'BUTTON') {
					button.innerText = 'Processing';
				} else {
					button.value = 'Processing';
				}
			} else {
				button.removeAttribute('disabled');
				if (button.nodeName === 'BUTTON') {
					button.innerText = 'Place Order';
				} else {
					button.value = 'Place Order';
				}
			}
		}
	}
	var MivaPay = new MivaPay();
</script>

Miva Pay CSS

{
	"selector": "html",
	"properties": {
		"box-sizing": "border-box",
		"height": "100%",
		"min-height": "100%",
		"font-family": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif",
		"font-size": "13px",
		"-webkit-font-smoothing": "antialiased",
		"-moz-osx-font-smoothing": "grayscale",
		"line-height": "1.5",
		"-webkit-text-size-adjust": "100%",
		"-ms-text-size-adjust": "100%",
		"color": "#111",
		"-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)",
		"touch-action": "manipulation"
	}
},
{
	"selector": "body",
	"properties": {
		"margin": "0"
	}
},
{
	"selector": ".form_row.invalid input[type=\"text\"],.form_row.invalid select",
	"properties": {
		"border-color": "#b13138"
	}
},
{
	"selector": ".form_row.invalid label",
	"properties": {
		"color": "#b13138"
	}
},
{
	"selector": ".bold",
	"properties": {
		"font-weight": "600"
	}
},
{
	"selector": "*, *::before, *::after ",
	"properties": {
		"box-sizing": "inherit"
	}
},
{
	"selector": "#document-sizer",
	"properties": {
		"min-width": "100%"
	}
},
{
	"selector": "#payment-fields",
	"properties": {
		"display": "flex",
		"flex-wrap": "wrap",
		"gap": "1rem",
		"justify-content": "space-between",
		"padding": "0 0.25rem",
		"width": "100%"
	}
},
{
	"selector": "#mivapay_form",
	"properties": {
		"position": "relative",
		"height": "auto",
		"clear": "both",
		"overflow": "auto",
		"zoom": "1"
	}
},
{
	"selector": ".form_row",
	"properties": {
		"display": "flex",
		"flex": "1",
		"flex-wrap": "wrap",
		"gap": "0 0.5rem"
	}
},
{
	"selector": ".form_row label",
	"properties": {
		"display": "inline-flex",
		"flex-basis": "100%",
		"font-size": "13px",
		"margin-bottom": "0.5em"
	}
},
{
	"selector": "input[type='text']",
	"properties": {
		"appearance": "none",
		"background-color": "#fff",
		"border": "1px solid #ccc",
		"border-radius": "0.25rem",
		"display": "inline-block",
		"flex": "1",
		"font-family": "inherit",
		 "font-size": "13px",
		"line-height": "1",
		"margin-bottom": "0.25em",
		"padding": "calc(1.23em - 1px) calc(1.7em - 1px)"
	}
},
{
	"selector": "select",
	"properties": {
		"appearance": "none",
		"background-color": "#fff",
		"border": "1px solid #ccc",
		"border-radius": "0.25rem",
		"box-shadow": "0 0 0 0 rgba(17, 17, 17, 0.2)",
		"cursor": "pointer",
		"flex": "1",
		"font-family": "inherit",
		"margin-bottom": "0.25em",
		"padding": "calc(1.23em - 1px) calc(1.7em - 1px)",
		"transition": "box-shadow 0.25s ease, border-color 0.25s ease"
	}
},
{
	"selector": "input[type='text']:focus:not(:focus-visible), select:focus:not(:focus-visible)",
	"properties": {
		"outline": "none"
	}
},
{
	"selector": "input[type='text']:focus-visible, select:focus-visible",
	"properties": {
		"outline": "2px solid #3a58fc",
		"outline-offset": "1px"
	}
},
{
	"selector": ".mvp_exp_date_container",
	"properties": {
		"display": "grid",
		"grid-template-columns": "repeat(2, 1fr)"
	}
},
{
	"selector": ".mvp_exp_date_container label",
	"properties": {
		"grid-column": "span 2"
	}
},
{
	"selector": ".mvp_cvv_container",
	"properties": {
		"flex": "0"
	}
},
{
	"selector": "#mvp_addressfields_container.mvp_addressfields_container",
	"properties": {
		"display": "flex",
		"width": "100%",
		"align-items": "center",
		"flex-direction": "column"
	}
},
{
	"selector": "#mvp_addressfields_container.mvp_addressfields_hide",
	"properties": {
		"display": "none"
	}
},
{
	"selector": ".mvp_storetoken_container",
	"properties": {
		"justify-content": "flex-start"
	}
},
{
	"selector": ".mvp_storetoken",
	"properties": {
		"filter": "grayscale(1)",
		"margin-right": "1em"
	}
},
{
	"selector": "::placeholder",
	"properties": {
		"line-height": "normal"
	}
},
{
"at-rule": "media",
"media": "screen and (max-width: 747px)",
"selectors":
	[
		{
			"selector": ".mvp_exp_date_container",
			"properties": {
				"flex-basis": "auto"
			}
		}
	]
},
{
"at-rule": "media",
"media": "screen and (min-width: 748px)",
"selectors":
	[
		{
			"selector": "#mvp_cardtype_container",
			"properties": {
				"float": "none"
			}
		}
	]
}

Add to cart and related HTML - this isn't the whole thing, just shows you certain aspects of ids and whatnot. I think stock html probably works fine.

<form id="js-opay-form" data-hook="opay-form" method="post" action="&mvte:url;">
					<fieldset>
						<legend>&mvt:page:name;</legend>
						<mvt:if expr="ISNULL l.settings:payment:payment_url">
							<input type="hidden" name="Action" value="AUTH" />
						</mvt:if>
						<mvt:item name="payment" />
						<input data-hook="payment-method" type="hidden" name="PaymentMethod" value="&mvte:global:PaymentMethod;">
						<input type="hidden" name="SplitPaymentData" value="&mvte:global:SplitPaymentData;" />

						<p class="c-heading-foxtrot">Payment Information</p>
<mvt:comment>
#
# add a spinner that takes up the space until mivapay payment fields show up
#
</mvt:comment>
<div class="text-center" id="mivapay-iframe-spinner" style="margin-top: 30px; margin-bottom: 30px;">
	<div class="spinner-border spinner-border-sm text-secondary" style="height: 3em; width: 3em;" role="status" >
		<span class="sr-only">Loading...</span>
	</div>
</div>

						<mvt:if expr="NOT ISNULL l.settings:payment:message">
							<p class="x-messages x-messages--info">&mvt:payment:message;</p>
						</mvt:if>
						<mvt:if expr="l.settings:paymentsettings:mivapay:enabled AND ( l.settings:mivapay:paymentcardtype:id OR l.settings:mivapay:paymentcard:id )">
							<div class="c-form-list">
								<div class="c-form-list__item c-form-list__item--full">
									<div class="t-payment-form t-&mvte:global:payment_module_class;">
										<div class="t-payment-form__heading">
											<span class="u-text-medium">
												Payment Method: <span data-hook="payment-method-display">&mvte:payment:desc;</span>
												<a class="u-text-regular" href="&mvte:urls:OSEL:secure;" title="Edit Payment Method">Edit</a>
											</span>
											<span class="c-heading--subheading--x-small u-icon-secure" aria-hidden="true"></span>
										</div>
										<div class="t-payment-form__fields">
											<mvt:item name="mivapay"/>
										</div>
									</div>
								</div>
							</div>
etc...
</form>

https://www.scotsscripts.com/mvblog/mivapay-submit-payment-once-and-disable-button.html

mvkb_mivapay mvkb_opay