installed plugin Easy Digital Downloads
version 3.1.0.3
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=102)}({102:function(e,n,t){(function(e){jQuery((function(){jQuery(".edds-admin-notice").each((function(){var n=e(this),t=n.data("id"),r=n.data("nonce");n.on("click",".notice-dismiss",(function(e){return e.preventDefault(),e.stopPropagation(),wp.ajax.post("edds_admin_notices_dismiss_ajax",{id:t,nonce:r})}))}))}))}).call(this,t(5))},5:function(e,n){e.exports=jQuery}});
|
@ -0,0 +1,104 @@
|
||||
/* global $, edd_stripe_admin */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import './../../../css/src/admin.scss';
|
||||
import './settings/index.js';
|
||||
|
||||
let testModeCheckbox;
|
||||
let testModeToggleNotice;
|
||||
|
||||
$( document ).ready( function() {
|
||||
testModeCheckbox = document.getElementById( 'edd_settings[test_mode]' );
|
||||
if ( testModeCheckbox ) {
|
||||
testModeToggleNotice = document.getElementById( 'edd_settings[stripe_connect_test_mode_toggle_notice]' );
|
||||
EDD_Stripe_Connect_Scripts.init();
|
||||
}
|
||||
|
||||
// Toggle API keys.
|
||||
$( '.edds-api-key-toggle button' ).on( 'click', function( event ) {
|
||||
event.preventDefault();
|
||||
|
||||
$( '.edds-api-key-toggle, .edds-api-key-row' )
|
||||
.toggleClass( 'edd-hidden' );
|
||||
} );
|
||||
} );
|
||||
|
||||
const EDD_Stripe_Connect_Scripts = {
|
||||
|
||||
init() {
|
||||
this.listeners();
|
||||
},
|
||||
|
||||
listeners() {
|
||||
const self = this;
|
||||
|
||||
testModeCheckbox.addEventListener( 'change', function() {
|
||||
// Don't run these events if Stripe is not enabled.
|
||||
if ( ! edd_stripe_admin.stripe_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.checked ) {
|
||||
if ( 'false' === edd_stripe_admin.test_key_exists ) {
|
||||
self.showNotice( testModeToggleNotice, 'warning' );
|
||||
self.addHiddenMarker();
|
||||
} else {
|
||||
self.hideNotice( testModeToggleNotice );
|
||||
const hiddenMarker = document.getElementById( 'edd-test-mode-toggled' );
|
||||
if ( hiddenMarker ) {
|
||||
hiddenMarker.parentNode.removeChild( hiddenMarker );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! this.checked ) {
|
||||
if ( 'false' === edd_stripe_admin.live_key_exists ) {
|
||||
self.showNotice( testModeToggleNotice, 'warning' );
|
||||
self.addHiddenMarker();
|
||||
} else {
|
||||
self.hideNotice( testModeToggleNotice );
|
||||
const hiddenMarker = document.getElementById( 'edd-test-mode-toggled' );
|
||||
if ( hiddenMarker ) {
|
||||
hiddenMarker.parentNode.removeChild( hiddenMarker );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
addHiddenMarker() {
|
||||
const submit = document.getElementById( 'submit' );
|
||||
|
||||
if ( ! submit ) {
|
||||
return;
|
||||
}
|
||||
|
||||
submit.parentNode.insertAdjacentHTML( 'beforeend', '<input type="hidden" class="edd-hidden" id="edd-test-mode-toggled" name="edd-test-mode-toggled" />' );
|
||||
},
|
||||
|
||||
showNotice( element = false, type = 'error' ) {
|
||||
if ( ! element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( typeof element !== 'object' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.className = 'notice notice-' + type;
|
||||
},
|
||||
|
||||
hideNotice( element = false ) {
|
||||
if ( ! element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( typeof element !== 'object' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.className = 'edd-hidden';
|
||||
},
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
/* global wp, jQuery */
|
||||
|
||||
/**
|
||||
* Handle dismissing admin notices.
|
||||
*/
|
||||
jQuery( () => {
|
||||
/**
|
||||
* Loops through each admin notice on the page for processing.
|
||||
*
|
||||
* @param {HTMLElement} noticeEl Notice element.
|
||||
*/
|
||||
jQuery( '.edds-admin-notice' ).each( function() {
|
||||
const notice = $( this );
|
||||
const id = notice.data( 'id' );
|
||||
const nonce = notice.data( 'nonce' );
|
||||
|
||||
/**
|
||||
* Listens for a click event on the dismiss button, and dismisses the notice.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
* @return {jQuery.Deferred} Deferred object.
|
||||
*/
|
||||
notice.on( 'click', '.notice-dismiss', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
return wp.ajax.post(
|
||||
'edds_admin_notices_dismiss_ajax',
|
||||
{
|
||||
id,
|
||||
nonce,
|
||||
}
|
||||
);
|
||||
} );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './requirements.js';
|
||||
import './stripe-connect.js';
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { domReady } from 'utils';
|
||||
|
||||
/**
|
||||
* Hides "Save Changes" button if showing the special settings placeholder.
|
||||
*/
|
||||
domReady( () => {
|
||||
const containerEl = document.querySelector( '.edds-requirements-not-met' );
|
||||
|
||||
if ( ! containerEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide "Save Changes" button.
|
||||
document.querySelector( '.edd-settings-wrap .submit' ).style.display = 'none';
|
||||
} );
|
||||
|
||||
/**
|
||||
* Moves "Payment Gateways" notice under Stripe.
|
||||
* Disables/unchecks the checkbox.
|
||||
*/
|
||||
domReady( () => {
|
||||
const noticeEl = document.getElementById( 'edds-payment-gateways-stripe-unmet-requirements' );
|
||||
|
||||
if ( ! noticeEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stripeLabel = document.querySelector( 'label[for="edd_settings[gateways][stripe]"]' );
|
||||
stripeLabel.parentNode.insertBefore( noticeEl, stripeLabel.nextSibling );
|
||||
|
||||
const stripeCheck = document.getElementById( 'edd_settings[gateways][stripe]' );
|
||||
stripeCheck.disabled = true;
|
||||
stripeCheck.checked = false;
|
||||
|
||||
noticeEl.insertBefore( stripeCheck, noticeEl.querySelector( 'p' ) );
|
||||
noticeEl.insertBefore( stripeLabel, noticeEl.querySelector( 'p' ) );
|
||||
} );
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { domReady, apiRequest } from 'utils';
|
||||
|
||||
// Wait for DOM.
|
||||
domReady( () => {
|
||||
const containerEl = document.getElementById( 'edds-stripe-connect-account' );
|
||||
const actionsEl = document.getElementById( 'edds-stripe-disconnect-reconnect' );
|
||||
|
||||
if ( ! containerEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return apiRequest( 'edds_stripe_connect_account_info', {
|
||||
...containerEl.dataset,
|
||||
} )
|
||||
.done( ( response ) => {
|
||||
containerEl.innerHTML = response.message;
|
||||
containerEl.classList.add( `notice-${ response.status }` );
|
||||
if ( response.actions ) {
|
||||
actionsEl.innerHTML = response.actions;
|
||||
}
|
||||
} )
|
||||
.fail( ( error ) => {
|
||||
containerEl.innerHTML = error.message;
|
||||
containerEl.classList.add( 'notice-error' );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,2 @@
|
||||
export { default as Modal } from './modal';
|
||||
export { paymentMethods } from './payment-methods';
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
// Import Polyfills for MicroModal IE 11 support.
|
||||
// https://github.com/Ghosh/micromodal#ie-11-and-below
|
||||
// https://github.com/ghosh/Micromodal/issues/49#issuecomment-424213347
|
||||
// https://github.com/ghosh/Micromodal/issues/49#issuecomment-517916416
|
||||
import 'core-js/modules/es.object.assign';
|
||||
import 'core-js/modules/es.array.from';
|
||||
|
||||
import MicroModal from 'micromodal';
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
disableScroll: true,
|
||||
awaitOpenAnimation: true,
|
||||
awaitCloseAnimation: true,
|
||||
};
|
||||
|
||||
function setup( options ) {
|
||||
const config = {
|
||||
...DEFAULT_CONFIG,
|
||||
...options,
|
||||
};
|
||||
|
||||
MicroModal.init( config );
|
||||
}
|
||||
|
||||
function open( modalId, options ) {
|
||||
const config = {
|
||||
...DEFAULT_CONFIG,
|
||||
...options,
|
||||
};
|
||||
|
||||
MicroModal.show( modalId, config );
|
||||
}
|
||||
|
||||
function close( modalId ) {
|
||||
MicroModal.close( modalId );
|
||||
}
|
||||
|
||||
export default {
|
||||
setup,
|
||||
open,
|
||||
close,
|
||||
};
|
@ -0,0 +1,259 @@
|
||||
/* global $ */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { forEach, getNextSiblings } from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function paymentMethods() {
|
||||
// Toggle only shows if using Full Address (for some reason).
|
||||
if ( getBillingFieldsToggle() ) {
|
||||
// Hide fields initially.
|
||||
toggleBillingFields( false );
|
||||
|
||||
/**
|
||||
* Binds change event to "Update billing address" toggle to show/hide address fields.
|
||||
*
|
||||
* @param {Event} e Change event.
|
||||
*/
|
||||
getBillingFieldsToggle().addEventListener( 'change', function( e ) {
|
||||
return toggleBillingFields( e.target.checked );
|
||||
} );
|
||||
}
|
||||
|
||||
// Payment method toggles.
|
||||
const existingPaymentMethods = document.querySelectorAll( '.edd-stripe-existing-card' );
|
||||
|
||||
if ( 0 !== existingPaymentMethods.length ) {
|
||||
forEach( existingPaymentMethods, function( existingPaymentMethod ) {
|
||||
/**
|
||||
* Binds change event to credit card toggles.
|
||||
*
|
||||
* @param {Event} e Change event.
|
||||
*/
|
||||
return existingPaymentMethod.addEventListener( 'change', function( e ) {
|
||||
return onPaymentSourceChange( e.target );
|
||||
} );
|
||||
} );
|
||||
|
||||
// Simulate change of payment method to populate current fields.
|
||||
let currentPaymentMethod = document.querySelector( '.edd-stripe-existing-card:checked' );
|
||||
|
||||
if ( ! currentPaymentMethod ) {
|
||||
currentPaymentMethod = document.querySelector( '.edd-stripe-existing-card:first-of-type' );
|
||||
currentPaymentMethod.checked = true;
|
||||
}
|
||||
|
||||
const paymentMethodChangeEvent = document.createEvent( 'Event' );
|
||||
paymentMethodChangeEvent.initEvent( 'change', true, false );
|
||||
currentPaymentMethod.dispatchEvent( paymentMethodChangeEvent );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the billing fields can be toggled.
|
||||
*
|
||||
* @return {Bool} True if the toggle exists.
|
||||
*/
|
||||
function getBillingFieldsToggle() {
|
||||
return document.getElementById( 'edd-stripe-update-billing-address' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles billing fields visiblity.
|
||||
*
|
||||
* Assumes the toggle control is the first item in the "Billing Details" fieldset.
|
||||
*
|
||||
* @param {Bool} isVisible Billing item visibility.
|
||||
*/
|
||||
function toggleBillingFields( isVisible ) {
|
||||
const updateAddressWrapperEl = document.querySelector( '.edd-stripe-update-billing-address-wrapper' );
|
||||
|
||||
if ( ! updateAddressWrapperEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all elements after the toggle.
|
||||
const billingFieldWrappers = getNextSiblings( updateAddressWrapperEl );
|
||||
const billingAddressPreview = document.querySelector( '.edd-stripe-update-billing-address-current' );
|
||||
|
||||
billingFieldWrappers.forEach( function( wrap ) {
|
||||
wrap.style.display = isVisible ? 'block' : 'none';
|
||||
} );
|
||||
|
||||
// Hide address preview.
|
||||
if ( billingAddressPreview ) {
|
||||
billingAddressPreview.style.display = isVisible ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages UI state when the payment source changes.
|
||||
*
|
||||
* @param {HTMLElement} paymentSource Selected payment source. (Radio element with data).
|
||||
*/
|
||||
function onPaymentSourceChange( paymentSource ) {
|
||||
const isNew = 'new' === paymentSource.value;
|
||||
const newCardForm = document.querySelector( '.edd-stripe-new-card' );
|
||||
const billingAddressToggle = document.querySelector( '.edd-stripe-update-billing-address-wrapper' );
|
||||
|
||||
// Toggle card details field.
|
||||
newCardForm.style.display = isNew ? 'block' : 'none';
|
||||
|
||||
if ( billingAddressToggle ) {
|
||||
billingAddressToggle.style.display = isNew ? 'none' : 'block';
|
||||
}
|
||||
|
||||
// @todo don't be lazy.
|
||||
$( '.edd-stripe-card-radio-item' ).removeClass( 'selected' );
|
||||
$( paymentSource ).closest( '.edd-stripe-card-radio-item' ).addClass( 'selected' );
|
||||
|
||||
const addressFieldMap = {
|
||||
card_address: 'address_line1',
|
||||
card_address_2: 'address_line2',
|
||||
card_city: 'address_city',
|
||||
card_state: 'address_state',
|
||||
card_zip: 'address_zip',
|
||||
billing_country: 'address_country',
|
||||
};
|
||||
|
||||
// New card is being used, show fields and reset them.
|
||||
if ( isNew ) {
|
||||
// Reset all fields.
|
||||
for ( const addressEl in addressFieldMap ) {
|
||||
if ( ! addressFieldMap.hasOwnProperty( addressEl ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addressField = document.getElementById( addressEl );
|
||||
|
||||
if ( addressField ) {
|
||||
addressField.value = '';
|
||||
addressField.selected = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate taxes.
|
||||
if ( window.EDD_Checkout.recalculate_taxes ) {
|
||||
window.EDD_Checkout.recalculate_taxes();
|
||||
}
|
||||
|
||||
// Show billing fields.
|
||||
toggleBillingFields( true );
|
||||
|
||||
// Existing card is being used.
|
||||
// Ensure the billing fields are hidden, and update their values with saved information.
|
||||
} else {
|
||||
const addressString = [];
|
||||
const billingDetailsEl = document.getElementById( paymentSource.id + '-billing-details' );
|
||||
|
||||
if ( ! billingDetailsEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide billing fields.
|
||||
toggleBillingFields( false );
|
||||
|
||||
// Uncheck "Update billing address"
|
||||
if ( getBillingFieldsToggle() ) {
|
||||
getBillingFieldsToggle().checked = false;
|
||||
}
|
||||
|
||||
// Update billing address fields with saved card values.
|
||||
const billingDetails = billingDetailsEl.dataset;
|
||||
|
||||
for ( const addressEl in addressFieldMap ) {
|
||||
if ( ! addressFieldMap.hasOwnProperty( addressEl ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const addressField = document.getElementById( addressEl );
|
||||
|
||||
if ( ! addressField ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = billingDetails[ addressFieldMap[ addressEl ] ];
|
||||
|
||||
// Set field value.
|
||||
addressField.value = value;
|
||||
|
||||
// Generate an address string from values.
|
||||
if ( '' !== value ) {
|
||||
addressString.push( value );
|
||||
}
|
||||
|
||||
// This field is required but does not have a saved value, show all fields.
|
||||
if ( addressField.required && '' === value ) {
|
||||
// @todo DRY up some of this DOM usage.
|
||||
toggleBillingFields( true );
|
||||
|
||||
if ( getBillingFieldsToggle() ) {
|
||||
getBillingFieldsToggle().checked = true;
|
||||
}
|
||||
|
||||
if ( billingAddressToggle ) {
|
||||
billingAddressToggle.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change event when the Country field is updated.
|
||||
if ( 'billing_country' === addressEl ) {
|
||||
const changeEvent = document.createEvent( 'Event' );
|
||||
changeEvent.initEvent( 'change', true, true );
|
||||
addressField.dispatchEvent( changeEvent );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor AJAX requests for address changes.
|
||||
*
|
||||
* Wait for the "State" field to be updated based on the "Country" field's
|
||||
* change event. Once there is an AJAX response fill the "State" field with the
|
||||
* saved card's State data and recalculate taxes.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param {Object} event
|
||||
* @param {Object} xhr
|
||||
* @param {Object} options
|
||||
*/
|
||||
$( document ).ajaxSuccess( function( event, xhr, options ) {
|
||||
if ( ! options || ! options.data || ! xhr ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
options.data.includes( 'action=edd_get_shop_states' ) &&
|
||||
options.data.includes( 'field_name=card_state' ) &&
|
||||
( xhr.responseText && xhr.responseText.includes( 'card_state' ) )
|
||||
) {
|
||||
const stateField = document.getElementById( 'card_state' );
|
||||
|
||||
if ( stateField ) {
|
||||
stateField.value = billingDetails.address_state;
|
||||
|
||||
// Recalculate taxes.
|
||||
if ( window.EDD_Checkout.recalculate_taxes ) {
|
||||
window.EDD_Checkout.recalculate_taxes( stateField.value );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
// Update address string summary.
|
||||
const billingAddressPreview = document.querySelector( '.edd-stripe-update-billing-address-current' );
|
||||
|
||||
if ( billingAddressPreview ) {
|
||||
billingAddressPreview.innerText = addressString.join( ', ' );
|
||||
|
||||
const { brand, last4 } = billingDetails;
|
||||
|
||||
document.querySelector( '.edd-stripe-update-billing-address-brand' ).innerHTML = brand;
|
||||
document.querySelector( '.edd-stripe-update-billing-address-last4' ).innerHTML = last4;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* global Stripe, edd_stripe_vars */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './../../../css/src/frontend.scss';
|
||||
import { domReady, apiRequest, generateNotice } from 'utils';
|
||||
|
||||
import {
|
||||
setupCheckout,
|
||||
setupProfile,
|
||||
setupPaymentHistory,
|
||||
setupBuyNow,
|
||||
setupDownloadPRB,
|
||||
setupCheckoutPRB,
|
||||
} from 'frontend/payment-forms';
|
||||
|
||||
import {
|
||||
paymentMethods,
|
||||
} from 'frontend/components/payment-methods';
|
||||
|
||||
import {
|
||||
mountCardElement,
|
||||
createPaymentForm as createElementsPaymentForm,
|
||||
getBillingDetails,
|
||||
getPaymentMethod,
|
||||
confirm as confirmIntent,
|
||||
handle as handleIntent,
|
||||
retrieve as retrieveIntent,
|
||||
} from 'frontend/stripe-elements';
|
||||
// eslint-enable @wordpress/dependency-group
|
||||
|
||||
( () => {
|
||||
try {
|
||||
window.eddStripe = new Stripe( edd_stripe_vars.publishable_key );
|
||||
|
||||
// Alias some functionality for external plugins.
|
||||
window.eddStripe._plugin = {
|
||||
domReady,
|
||||
apiRequest,
|
||||
generateNotice,
|
||||
mountCardElement,
|
||||
createElementsPaymentForm,
|
||||
getBillingDetails,
|
||||
getPaymentMethod,
|
||||
confirmIntent,
|
||||
handleIntent,
|
||||
retrieveIntent,
|
||||
paymentMethods,
|
||||
};
|
||||
|
||||
// Setup frontend components when DOM is ready.
|
||||
domReady(
|
||||
setupCheckout,
|
||||
setupProfile,
|
||||
setupPaymentHistory,
|
||||
setupBuyNow,
|
||||
setupDownloadPRB,
|
||||
setupCheckoutPRB,
|
||||
);
|
||||
} catch ( error ) {
|
||||
alert( error.message );
|
||||
}
|
||||
} )();
|
@ -0,0 +1,205 @@
|
||||
/* global jQuery, edd_scripts, edd_stripe_vars */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { forEach, domReady, apiRequest } from 'utils';
|
||||
import { Modal, paymentMethods } from 'frontend/components';
|
||||
import { paymentForm } from 'frontend/payment-forms/checkout'
|
||||
|
||||
/**
|
||||
* Adds a Download to the Cart.
|
||||
*
|
||||
* @param {number} downloadId Download ID.
|
||||
* @param {number} priceId Download Price ID.
|
||||
* @param {number} quantity Download quantity.
|
||||
* @param {string} nonce Nonce token.
|
||||
* @param {HTMLElement} addToCartForm Add to cart form.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
function addToCart( downloadId, priceId, quantity, nonce, addToCartForm ) {
|
||||
const data = {
|
||||
download_id: downloadId,
|
||||
price_id: priceId,
|
||||
quantity: quantity,
|
||||
nonce,
|
||||
post_data: jQuery( addToCartForm ).serialize(),
|
||||
};
|
||||
|
||||
return apiRequest( 'edds_add_to_cart', data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the Cart.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
function emptyCart() {
|
||||
return apiRequest( 'edds_empty_cart' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Buy Now modal.
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {number} args.downloadId Download ID.
|
||||
* @param {number} args.priceId Download Price ID.
|
||||
* @param {number} args.quantity Download quantity.
|
||||
* @param {string} args.nonce Nonce token.
|
||||
* @param {HTMLElement} args.addToCartForm Add to cart form.
|
||||
*/
|
||||
function buyNowModal( args ) {
|
||||
const modalContent = document.querySelector( '#edds-buy-now-modal-content' );
|
||||
const modalLoading = '<span class="edd-loading-ajax edd-loading"></span>';
|
||||
|
||||
// Show modal.
|
||||
Modal.open( 'edds-buy-now', {
|
||||
/**
|
||||
* Adds the item to the Cart when opening.
|
||||
*/
|
||||
onShow() {
|
||||
modalContent.innerHTML = modalLoading;
|
||||
|
||||
const {
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
nonce,
|
||||
addToCartForm,
|
||||
} = args;
|
||||
|
||||
addToCart(
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
nonce,
|
||||
addToCartForm
|
||||
)
|
||||
.then( ( { checkout } ) => {
|
||||
// Show Checkout HTML.
|
||||
modalContent.innerHTML = checkout;
|
||||
|
||||
// Reinitialize core JS.
|
||||
window.EDD_Checkout.init();
|
||||
|
||||
const totalEl = document.querySelector( '#edds-buy-now-modal-content .edd_cart_amount' );
|
||||
const total = parseFloat( totalEl.dataset.total );
|
||||
|
||||
// Reinitialize Stripe JS if a payment is required.
|
||||
if ( total > 0 ) {
|
||||
paymentForm();
|
||||
paymentMethods();
|
||||
}
|
||||
} )
|
||||
.fail( ( { message } ) => {
|
||||
// Show error message.
|
||||
document.querySelector( '#edds-buy-now-modal-content' ).innerHTML = message;
|
||||
} );
|
||||
},
|
||||
/**
|
||||
* Empties Cart on close.
|
||||
*/
|
||||
onClose() {
|
||||
emptyCart();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// DOM ready.
|
||||
export function setup() {
|
||||
|
||||
// Find all "Buy Now" links on the page.
|
||||
forEach( document.querySelectorAll( '.edds-buy-now' ), ( el ) => {
|
||||
|
||||
// Don't use modal if "Free Downloads" is active and available for this download.
|
||||
// https://easydigitaldownloads.com/downloads/free-downloads/
|
||||
if ( el.classList.contains( 'edd-free-download' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches "Buy Now" modal when clicking "Buy Now" link.
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
el.addEventListener( 'click', ( e ) => {
|
||||
const { downloadId, nonce } = e.currentTarget.dataset;
|
||||
|
||||
// Stop other actions if a Download ID is found.
|
||||
if ( ! downloadId ) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
// Gather Download information.
|
||||
let priceId = 0;
|
||||
let quantity = 1;
|
||||
|
||||
const addToCartForm = e.currentTarget.closest(
|
||||
'.edd_download_purchase_form'
|
||||
);
|
||||
|
||||
// Price ID.
|
||||
const priceIdEl = addToCartForm.querySelector(
|
||||
`.edd_price_option_${downloadId}:checked`
|
||||
);
|
||||
|
||||
if ( priceIdEl ) {
|
||||
priceId = priceIdEl.value;
|
||||
}
|
||||
|
||||
// Quantity.
|
||||
const quantityEl = addToCartForm.querySelector(
|
||||
'input[name="edd_download_quantity"]'
|
||||
);
|
||||
|
||||
if ( quantityEl ) {
|
||||
quantity = quantityEl.value;
|
||||
}
|
||||
|
||||
buyNowModal( {
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
nonce,
|
||||
addToCartForm
|
||||
} );
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
/**
|
||||
* Replaces submit button text after validation errors.
|
||||
*
|
||||
* If there are no other items in the cart the core javascript will replace
|
||||
* the button text with the value for a $0 cart (usually "Free Download")
|
||||
* because the script variables were constructed when nothing was in the cart.
|
||||
*/
|
||||
jQuery( document.body ).on( 'edd_checkout_error', () => {
|
||||
const submitButtonEl = document.querySelector(
|
||||
'#edds-buy-now #edd-purchase-button'
|
||||
);
|
||||
|
||||
if ( ! submitButtonEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { i18n: { completePurchase } } = edd_stripe_vars;
|
||||
|
||||
const amountEl = document.querySelector( '.edd_cart_amount' );
|
||||
const { total, totalCurrency } = amountEl.dataset;
|
||||
|
||||
if ( '0' === total ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For some reason a delay is needed to override the value set by
|
||||
// https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/assets/js/edd-ajax.js#L414
|
||||
setTimeout( () => {
|
||||
submitButtonEl.value = `${ totalCurrency } - ${ completePurchase }`;
|
||||
}, 10 );
|
||||
} );
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* global $, edd_scripts */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
// eslint-disable @wordpress/dependency-group
|
||||
import { paymentMethods } from 'frontend/components';
|
||||
// eslint-enable @wordpress/dependency-group
|
||||
|
||||
import { paymentForm } from './payment-form.js';
|
||||
|
||||
export * from './payment-form.js';
|
||||
|
||||
export function setup() {
|
||||
if ( '1' !== edd_scripts.is_checkout ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial load for single gateway.
|
||||
const singleGateway = document.querySelector( 'input[name="edd-gateway"]' );
|
||||
|
||||
if ( singleGateway && 'stripe' === singleGateway.value ) {
|
||||
paymentForm();
|
||||
paymentMethods();
|
||||
}
|
||||
|
||||
// Gateway switch.
|
||||
$( document.body ).on( 'edd_gateway_loaded', ( e, gateway ) => {
|
||||
if ( 'stripe' !== gateway ) {
|
||||
return;
|
||||
}
|
||||
|
||||
paymentForm();
|
||||
paymentMethods();
|
||||
} );
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
/* global $, edd_stripe_vars, edd_global_vars */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
createPaymentForm as createElementsPaymentForm,
|
||||
getPaymentMethod,
|
||||
capture as captureIntent,
|
||||
handle as handleIntent,
|
||||
} from 'frontend/stripe-elements'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
import { apiRequest, generateNotice } from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
* Binds Payment submission functionality.
|
||||
*
|
||||
* Resets before rebinding to avoid duplicate events
|
||||
* during gateway switching.
|
||||
*/
|
||||
export function paymentForm() {
|
||||
// Mount Elements.
|
||||
createElementsPaymentForm( window.eddStripe.elements() );
|
||||
|
||||
// Bind form submission.
|
||||
// Needs to be jQuery since that is what core submits against.
|
||||
$( '#edd_purchase_form' ).off( 'submit', onSubmit );
|
||||
$( '#edd_purchase_form' ).on( 'submit', onSubmit );
|
||||
|
||||
// SUPER ghetto way to watch for core form validation because no events are in place.
|
||||
// Called after the purchase form is submitted (via `click` or `submit`)
|
||||
$( document ).off( 'ajaxSuccess', watchInitialValidation );
|
||||
$( document ).on( 'ajaxSuccess', watchInitialValidation );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes Stripe gateway-specific functionality after core AJAX validation has run.
|
||||
*/
|
||||
async function onSubmitDelay() {
|
||||
try {
|
||||
// Form data to send to intent requests.
|
||||
let formData = $( '#edd_purchase_form' ).serialize(),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
// Retrieve or create a PaymentMethod.
|
||||
const paymentMethod = await getPaymentMethod( document.getElementById( 'edd_purchase_form' ), window.eddStripe.cardElement );
|
||||
|
||||
// Run the modified `_edds_process_purchase_form` and create an Intent.
|
||||
const {
|
||||
intent: initialIntent,
|
||||
nonce: refreshedNonce
|
||||
} = await processForm( paymentMethod.id, paymentMethod.exists );
|
||||
|
||||
// Update existing nonce value in DOM form data in case data is retrieved
|
||||
// again directly from the DOM.
|
||||
$( '#edd-process-checkout-nonce' ).val( refreshedNonce );
|
||||
|
||||
// Handle any actions required by the Intent State Machine (3D Secure, etc).
|
||||
const handledIntent = await handleIntent(
|
||||
initialIntent,
|
||||
{
|
||||
form_data: formData += `&edd-process-checkout-nonce=${ refreshedNonce }`,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
}
|
||||
);
|
||||
|
||||
// Create an EDD payment record.
|
||||
const { intent, nonce } = await createPayment( handledIntent );
|
||||
|
||||
// Capture any unpcaptured intents.
|
||||
const finalIntent = await captureIntent(
|
||||
intent,
|
||||
{},
|
||||
nonce
|
||||
);
|
||||
|
||||
// Attempt to transition payment status and redirect.
|
||||
// @todo Maybe confirm payment status as well? Would need to generate a custom
|
||||
// response because the private EDD_Payment properties are not available.
|
||||
if (
|
||||
( 'succeeded' === finalIntent.status ) ||
|
||||
( 'canceled' === finalIntent.status && 'abandoned' === finalIntent.cancellation_reason )
|
||||
) {
|
||||
await completePayment( finalIntent, nonce );
|
||||
|
||||
window.location.replace( edd_stripe_vars.successPageUri );
|
||||
} else {
|
||||
window.location.replace( edd_stripe_vars.failurePageUri );
|
||||
}
|
||||
} catch ( error ) {
|
||||
handleException( error );
|
||||
enableForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the purchase form.
|
||||
*
|
||||
* Generates purchase data for the current session and
|
||||
* uses the PaymentMethod to generate an Intent based on data.
|
||||
*
|
||||
* @param {string} paymentMethodId PaymentMethod ID.
|
||||
* @param {Bool} paymentMethodExists If the PaymentMethod has already been attached to a customer.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function processForm( paymentMethodId, paymentMethodExists ) {
|
||||
let tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
return apiRequest( 'edds_process_purchase_form', {
|
||||
// Send available form data.
|
||||
form_data: $( '#edd_purchase_form' ).serialize(),
|
||||
payment_method_id: paymentMethodId,
|
||||
payment_method_exists: paymentMethodExists,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a Payment in EDD.
|
||||
*
|
||||
* @param {object} intent Intent.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function createPayment( intent ) {
|
||||
const paymentForm = $( '#edd_purchase_form' ),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
let formData = paymentForm.serialize();
|
||||
|
||||
// Attempt to find the Checkout nonce directly.
|
||||
if ( paymentForm.length === 0 ) {
|
||||
const nonce = $( '#edd-process-checkout-nonce' ).val();
|
||||
formData = `edd-process-checkout-nonce=${ nonce }`
|
||||
}
|
||||
|
||||
return apiRequest( 'edds_create_payment', {
|
||||
form_data: formData,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
intent,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a Payment in EDD.
|
||||
*
|
||||
* @param {object} intent Intent.
|
||||
* @param {string} refreshedNonce A refreshed nonce that might be needed if the
|
||||
* user logged in.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function completePayment( intent, refreshedNonce ) {
|
||||
const paymentForm = $( '#edd_purchase_form' );
|
||||
let formData = paymentForm.serialize(),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
// Attempt to find the Checkout nonce directly.
|
||||
if ( paymentForm.length === 0 ) {
|
||||
const nonce = $( '#edd-process-checkout-nonce' ).val();
|
||||
formData = `edd-process-checkout-nonce=${ nonce }`;
|
||||
}
|
||||
|
||||
// Add the refreshed nonce if available.
|
||||
if ( refreshedNonce ) {
|
||||
formData += `&edd-process-checkout-nonce=${ refreshedNonce }`;
|
||||
}
|
||||
|
||||
return apiRequest( 'edds_complete_payment', {
|
||||
form_data: formData,
|
||||
intent,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listen for initial EDD core validation.
|
||||
*
|
||||
* @param {Object} event Event.
|
||||
* @param {Object} xhr AJAX request.
|
||||
* @param {Object} options Request options.
|
||||
*/
|
||||
function watchInitialValidation( event, xhr, options ) {
|
||||
if ( ! options || ! options.data || ! xhr ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
options.data.includes( 'action=edd_process_checkout' ) &&
|
||||
options.data.includes( 'edd-gateway=stripe' ) &&
|
||||
( xhr.responseText && 'success' === xhr.responseText.trim() )
|
||||
) {
|
||||
return onSubmitDelay();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EDD core listens to a a `click` event on the Checkout form submit button.
|
||||
*
|
||||
* This submit event handler captures true submissions and triggers a `click`
|
||||
* event so EDD core can take over as normoal.
|
||||
*
|
||||
* @param {Object} event submit Event.
|
||||
*/
|
||||
function onSubmit( event ) {
|
||||
// Ensure we are dealing with the Stripe gateway.
|
||||
if ( ! (
|
||||
// Stripe is selected gateway and total is larger than 0.
|
||||
$( 'input[name="edd-gateway"]' ).val() === 'stripe' &&
|
||||
$( '.edd_cart_total .edd_cart_amount' ).data( 'total' ) > 0
|
||||
) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// While this function is tied to the submit event, block submission.
|
||||
event.preventDefault();
|
||||
|
||||
// Simulate a mouse click on the Submit button.
|
||||
//
|
||||
// If the form is submitted via the "Enter" key we need to ensure the core
|
||||
// validation is run.
|
||||
//
|
||||
// When that is run and then the form is resubmitted
|
||||
// the click event won't do anything because the button will be disabled.
|
||||
$( '#edd_purchase_form #edd_purchase_submit [type=submit]' ).trigger( 'click' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the Checkout form for further submissions.
|
||||
*/
|
||||
function enableForm() {
|
||||
// Update button text.
|
||||
document.querySelector( '#edd_purchase_form #edd_purchase_submit [type=submit]' ).value = edd_global_vars.complete_purchase;
|
||||
|
||||
// Enable form.
|
||||
$( '.edd-loading-ajax' ).remove();
|
||||
$( '.edd_errors' ).remove();
|
||||
$( '.edd-error' ).hide();
|
||||
$( '#edd-purchase-button' ).attr( 'disabled', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles error output for stripe.js promises, or jQuery AJAX promises.
|
||||
*
|
||||
* @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/assets/js/edd-ajax.js#L390
|
||||
*
|
||||
* @param {Object} error Error data.
|
||||
*/
|
||||
function handleException( error ) {
|
||||
let { code, message } = error;
|
||||
const { elementsOptions: { i18n: { errorMessages } } } = window.edd_stripe_vars;
|
||||
|
||||
if ( ! message ) {
|
||||
message = edd_stripe_vars.generic_error;
|
||||
}
|
||||
|
||||
const localizedMessage = code && errorMessages[code] ? errorMessages[code] : message;
|
||||
|
||||
const notice = generateNotice( localizedMessage );
|
||||
|
||||
// Hide previous messages.
|
||||
// @todo These should all be in a container, but that's not how core works.
|
||||
$( '.edd-stripe-alert' ).remove();
|
||||
$( edd_global_vars.checkout_error_anchor ).before( notice );
|
||||
$( document.body ).trigger( 'edd_checkout_error', [ error ] );
|
||||
|
||||
if ( window.console && error.responseText ) {
|
||||
window.console.error( error.responseText );
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export { setup as setupCheckout, createPayment, completePayment } from './checkout';
|
||||
export { setup as setupProfile } from './profile-editor';
|
||||
export { setup as setupPaymentHistory } from './payment-receipt';
|
||||
export { setup as setupBuyNow } from './buy-now';
|
||||
export {
|
||||
setupDownload as setupDownloadPRB,
|
||||
setupCheckout as setupCheckoutPRB,
|
||||
} from './payment-request';
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
// eslint-disable @wordpress/dependency-group
|
||||
import { paymentMethods } from 'frontend/components/payment-methods';
|
||||
// eslint-enable @wordpress/dependency-group
|
||||
|
||||
import { paymentForm } from './payment-form.js';
|
||||
|
||||
export function setup() {
|
||||
if ( ! document.getElementById( 'edds-update-payment-method' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
paymentForm();
|
||||
paymentMethods();
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/* global edd_stripe_vars */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
// eslint-disable @wordpress/dependency-group
|
||||
import {
|
||||
getPaymentMethod,
|
||||
createPaymentForm as createElementsPaymentForm,
|
||||
handle as handleIntent,
|
||||
retrieve as retrieveIntent,
|
||||
} from 'frontend/stripe-elements';
|
||||
|
||||
import { generateNotice, apiRequest } from 'utils';
|
||||
// eslint-enable @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
* Binds events and sets up "Update Payment Method" form.
|
||||
*/
|
||||
export function paymentForm() {
|
||||
// Mount Elements.
|
||||
createElementsPaymentForm( window.eddStripe.elements() );
|
||||
|
||||
document.getElementById( 'edds-update-payment-method' ).addEventListener( 'submit', onAuthorizePayment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup PaymentMethods.
|
||||
*
|
||||
* Moves the active item to the currently authenticating PaymentMethod.
|
||||
*/
|
||||
function setPaymentMethod() {
|
||||
const form = document.getElementById( 'edds-update-payment-method' );
|
||||
const input = document.getElementById( form.dataset.paymentMethod );
|
||||
|
||||
// Select the correct PaymentMethod after load.
|
||||
if ( input ) {
|
||||
const changeEvent = document.createEvent( 'Event' );
|
||||
|
||||
changeEvent.initEvent( 'change', true, true );
|
||||
input.checked = true;
|
||||
input.dispatchEvent( changeEvent );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize a PaymentIntent.
|
||||
*
|
||||
* @param {Event} e submtit event.
|
||||
*/
|
||||
async function onAuthorizePayment( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = document.getElementById( 'edds-update-payment-method' );
|
||||
|
||||
disableForm();
|
||||
|
||||
try {
|
||||
const paymentMethod = await getPaymentMethod( form, window.eddStripe.cardElement );
|
||||
|
||||
// Handle PaymentIntent.
|
||||
const intent = await retrieveIntent( form.dataset.paymentIntent, 'payment_method' );
|
||||
|
||||
const handledIntent = await handleIntent( intent, {
|
||||
payment_method: paymentMethod.id,
|
||||
} );
|
||||
|
||||
// Attempt to transition payment status and redirect.
|
||||
const authorization = await completeAuthorization( handledIntent.id );
|
||||
|
||||
if ( authorization.payment ) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw authorization;
|
||||
}
|
||||
} catch ( error ) {
|
||||
handleException( error );
|
||||
enableForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a Payment after the Intent has been authorized.
|
||||
*
|
||||
* @param {string} intentId Intent ID.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function completeAuthorization( intentId ) {
|
||||
return apiRequest( 'edds_complete_payment_authorization', {
|
||||
intent_id: intentId,
|
||||
'edds-complete-payment-authorization': document.getElementById(
|
||||
'edds-complete-payment-authorization'
|
||||
).value
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables "Add New" form.
|
||||
*/
|
||||
function disableForm() {
|
||||
const submit = document.getElementById( 'edds-update-payment-method-submit' );
|
||||
|
||||
submit.value = submit.dataset.loading;
|
||||
submit.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables "Add New" form.
|
||||
*/
|
||||
function enableForm() {
|
||||
const submit = document.getElementById( 'edds-update-payment-method-submit' );
|
||||
|
||||
submit.value = submit.dataset.submit;
|
||||
submit.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a notice (success or error) for authorizing a card.
|
||||
*
|
||||
* @param {Object} error Error with message to output.
|
||||
*/
|
||||
export function handleException( error ) {
|
||||
// Create the new notice.
|
||||
const notice = generateNotice(
|
||||
( error && error.message ) ? error.message : edd_stripe_vars.generic_error,
|
||||
'error'
|
||||
);
|
||||
|
||||
const container = document.getElementById( 'edds-update-payment-method-errors' );
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild( notice );
|
||||
}
|
@ -0,0 +1,458 @@
|
||||
/* global edd_scripts, jQuery */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { parseDataset } from './';
|
||||
import { apiRequest, forEach, outputNotice, clearNotice } from 'utils';
|
||||
import { handle as handleIntent } from 'frontend/stripe-elements';
|
||||
import { createPayment, completePayment } from 'frontend/payment-forms';
|
||||
|
||||
let IS_PRB_GATEWAY;
|
||||
|
||||
/**
|
||||
* Disables the "Express Checkout" payment gateway.
|
||||
* Switches to the next in the list.
|
||||
*/
|
||||
function hideAndSwitchGateways() {
|
||||
IS_PRB_GATEWAY = false;
|
||||
|
||||
const gatewayRadioEl = document.getElementById( 'edd-gateway-option-stripe-prb' );
|
||||
|
||||
if ( ! gatewayRadioEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove radio option.
|
||||
gatewayRadioEl.remove();
|
||||
|
||||
// Recount available gateways and hide selector if needed.
|
||||
const gateways = document.querySelectorAll( '.edd-gateway-option' );
|
||||
const nextGateway = gateways[0];
|
||||
const nextGatewayInput = nextGateway.querySelector( 'input' );
|
||||
|
||||
// Toggle radio.
|
||||
nextGatewayInput.checked = true;
|
||||
nextGateway.classList.add( 'edd-gateway-option-selected' );
|
||||
|
||||
// Load gateway.
|
||||
edd_load_gateway( nextGatewayInput.value );
|
||||
|
||||
// Hide wrapper.
|
||||
if ( 1 === gateways.length ) {
|
||||
document.getElementById( 'edd_payment_mode_select_wrap' ).remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the click event on the Payment Request Button.
|
||||
*
|
||||
* @param {Event} event Click event.
|
||||
*/
|
||||
function onClick( event ) {
|
||||
const errorContainer = document.getElementById( 'edds-prb-error-wrap' );
|
||||
const {
|
||||
checkout_agree_to_terms,
|
||||
checkout_agree_to_privacy,
|
||||
} = edd_stripe_vars;
|
||||
|
||||
const termsEl = document.getElementById( 'edd_agree_to_terms' );
|
||||
|
||||
if ( termsEl ) {
|
||||
if ( false === termsEl.checked ) {
|
||||
event.preventDefault();
|
||||
|
||||
outputNotice( {
|
||||
errorMessage: checkout_agree_to_terms,
|
||||
errorContainer,
|
||||
} );
|
||||
} else {
|
||||
clearNotice( errorContainer );
|
||||
}
|
||||
}
|
||||
|
||||
const privacyEl = document.getElementById( 'edd-agree-to-privacy-policy' );
|
||||
|
||||
if ( privacyEl && false === privacyEl.checked ) {
|
||||
if ( false === privacyEl.checked ) {
|
||||
event.preventDefault();
|
||||
|
||||
outputNotice( {
|
||||
errorMessage: checkout_agree_to_privacy,
|
||||
errorContainer,
|
||||
} );
|
||||
} else {
|
||||
clearNotice( errorContainer );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to the purchase link form by updating the Payment Request object.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} checkoutForm Checkout form.
|
||||
*/
|
||||
async function onChange( paymentRequest, checkoutForm ) {
|
||||
try {
|
||||
// Calculate and gather price information.
|
||||
const {
|
||||
'display-items': displayItems,
|
||||
...paymentRequestData
|
||||
} = await apiRequest( 'edds_prb_ajax_get_options' );
|
||||
|
||||
// Update the Payment Request with server-side data.
|
||||
paymentRequest.update( {
|
||||
displayItems,
|
||||
...paymentRequestData,
|
||||
} )
|
||||
} catch ( error ) {
|
||||
outputNotice( {
|
||||
errorMessage: '',
|
||||
errorContainer: document.getElementById( 'edds-prb-checkout' ),
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Payment Method errors.
|
||||
*
|
||||
* @param {Object} event Payment Request event.
|
||||
* @param {Object} error Error.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
function onPaymentMethodError( event, error, checkoutForm ) {
|
||||
// Complete the Payment Request to hide the payment sheet.
|
||||
event.complete( 'success' );
|
||||
|
||||
// Remove spinner.
|
||||
const spinner = checkoutForm.querySelector( '.edds-prb-spinner' );
|
||||
|
||||
if ( spinner ) {
|
||||
spinner.parentNode.removeChild( spinner );
|
||||
}
|
||||
|
||||
// Release loading state.
|
||||
checkoutForm.classList.remove( 'loading' );
|
||||
|
||||
// Add notice.
|
||||
outputNotice( {
|
||||
errorMessage: error.message,
|
||||
errorContainer: document.getElementById( 'edds-prb-checkout' ),
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles recieving a Payment Method from the Payment Request.
|
||||
*
|
||||
* Adds an item to the cart and processes the Checkout as if we are
|
||||
* in normal Checkout context.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} checkoutForm Checkout form.
|
||||
* @param {Object} event paymentmethod event.
|
||||
*/
|
||||
async function onPaymentMethod( paymentRequest, checkoutForm, event ) {
|
||||
try {
|
||||
// Retrieve information from the PRB event.
|
||||
const { paymentMethod, payerEmail, payerName } = event;
|
||||
|
||||
// Loading state. Block interaction.
|
||||
checkoutForm.classList.add( 'loading' );
|
||||
|
||||
// Create and append a spinner.
|
||||
const spinner = document.createElement( 'span' );
|
||||
[ 'edd-loading-ajax', 'edd-loading', 'edds-prb-spinner' ].forEach(
|
||||
( className ) => spinner.classList.add( className )
|
||||
);
|
||||
checkoutForm.appendChild( spinner );
|
||||
|
||||
const data = {
|
||||
email: payerEmail,
|
||||
name: payerName,
|
||||
paymentMethod,
|
||||
context: 'checkout',
|
||||
};
|
||||
|
||||
const tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
// Start the processing.
|
||||
//
|
||||
// Shims $_POST data to align with the standard Checkout context.
|
||||
//
|
||||
// This calls `_edds_process_purchase_form()` server-side which
|
||||
// creates and returns a PaymentIntent -- just like the first step
|
||||
// of a true Checkout.
|
||||
const {
|
||||
intent,
|
||||
intent: {
|
||||
client_secret: clientSecret,
|
||||
object: intentType,
|
||||
},
|
||||
nonce: refreshedNonce,
|
||||
} = await apiRequest( 'edds_prb_ajax_process_checkout', {
|
||||
name: payerName,
|
||||
paymentMethod,
|
||||
form_data: $( '#edd_purchase_form' ).serialize(),
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
} );
|
||||
|
||||
// Update existing nonce value in DOM form data in case data is retrieved
|
||||
// again directly from the DOM.
|
||||
$( '#edd-process-checkout-nonce' ).val( refreshedNonce );
|
||||
|
||||
// Complete the Payment Request to hide the payment sheet.
|
||||
event.complete( 'success' );
|
||||
|
||||
// Confirm the card (SCA, etc).
|
||||
const confirmFunc = 'setup_intent' === intentType
|
||||
? 'confirmCardSetup'
|
||||
: 'confirmCardPayment';
|
||||
|
||||
eddStripe[ confirmFunc ](
|
||||
clientSecret,
|
||||
{
|
||||
payment_method: paymentMethod.id
|
||||
},
|
||||
{
|
||||
handleActions: false,
|
||||
}
|
||||
)
|
||||
.then( ( { error } ) => {
|
||||
// Something went wrong. Alert the Payment Request.
|
||||
if ( error ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Confirm again after the Payment Request dialog has been hidden.
|
||||
// For cards that do not require further checks this will throw a 400
|
||||
// error (in the Stripe API) and a log console error but not throw
|
||||
// an actual Exception. This can be ignored.
|
||||
//
|
||||
// https://github.com/stripe/stripe-payments-demo/issues/133#issuecomment-632593669
|
||||
eddStripe[ confirmFunc ]( clientSecret )
|
||||
.then( async ( { error } ) => {
|
||||
try {
|
||||
if ( error ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Create an EDD Payment.
|
||||
const { intent: updatedIntent, nonce } = await createPayment( intent );
|
||||
|
||||
// Complete the EDD Payment with the updated PaymentIntent.
|
||||
await completePayment( updatedIntent, nonce );
|
||||
|
||||
// Redirect on completion.
|
||||
window.location.replace( edd_stripe_vars.successPageUri );
|
||||
|
||||
// Something went wrong, output a notice.
|
||||
} catch ( error ) {
|
||||
onPaymentMethodError( event, error, checkoutForm );
|
||||
}
|
||||
} );
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
onPaymentMethodError( event, error, checkoutForm );
|
||||
} );
|
||||
|
||||
// Something went wrong, output a notice.
|
||||
} catch ( error ) {
|
||||
onPaymentMethodError( event, error, checkoutForm );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a full page reload is needed when applying a discount.
|
||||
*
|
||||
* A 100% discount switches to the "manual" gateway, bypassing the Stripe,
|
||||
* however we are still bound to the Payment Request button and a standard
|
||||
* Purchase button is not present in the DOM to switch back to.add-new-card
|
||||
*
|
||||
* @param {Event} e edd_discount_applied event.
|
||||
* @param {Object} response Discount application response.
|
||||
* @param {int} response.total_plain Cart total after discount.
|
||||
*/
|
||||
function onApplyDiscount( e, { total_plain: total } ) {
|
||||
if ( true === IS_PRB_GATEWAY && 0 === total ) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds purchase link form events.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} checkoutForm Checkout form.
|
||||
*/
|
||||
function bindEvents( paymentRequest, checkoutForm ) {
|
||||
const $body = jQuery( document.body );
|
||||
|
||||
// Cart quantities have changed.
|
||||
$body.on( 'edd_quantity_updated', () => onChange( paymentRequest, checkoutForm ) );
|
||||
|
||||
// Discounts have changed.
|
||||
$body.on( 'edd_discount_applied', () => onChange( paymentRequest, checkoutForm ) );
|
||||
$body.on( 'edd_discount_removed', () => onChange( paymentRequest, checkoutForm ) );
|
||||
|
||||
// Handle a PaymentMethod when available.
|
||||
paymentRequest.on( 'paymentmethod', ( event ) => {
|
||||
onPaymentMethod( paymentRequest, checkoutForm, event );
|
||||
} );
|
||||
|
||||
// Handle 100% discounts that require a full gateway refresh.
|
||||
$body.on( 'edd_discount_applied', onApplyDiscount );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts Payment Request buttons (if possible).
|
||||
*
|
||||
* @param {HTMLElement} element Payment Request button mount wrapper.
|
||||
*/
|
||||
function mount( element ) {
|
||||
const { eddStripe } = window;
|
||||
|
||||
const checkoutForm = document.getElementById( 'edd_checkout_form_wrap' );
|
||||
|
||||
try {
|
||||
// Gather initial data.
|
||||
const { 'display-items': displayItems, ...data } = parseDataset( element.dataset );
|
||||
|
||||
// Create a Payment Request object.
|
||||
const paymentRequest = eddStripe.paymentRequest( {
|
||||
// Only requested to prompt full address information collection for Apple Pay.
|
||||
//
|
||||
// On-page name fields are used to update the Easy Digital Downloads Customer.
|
||||
// The Payment Request's Payment Method populate the Customer's Billing Details.
|
||||
//
|
||||
// @link https://stripe.com/docs/js/payment_request/create#stripe_payment_request-options-requestPayerName
|
||||
requestPayerName: true,
|
||||
displayItems,
|
||||
...data,
|
||||
} );
|
||||
|
||||
// Create a Payment Request button.
|
||||
const elements = eddStripe.elements();
|
||||
const prButton = elements.create( 'paymentRequestButton', {
|
||||
paymentRequest: paymentRequest,
|
||||
} );
|
||||
|
||||
const wrapper = document.querySelector( `#${ element.id }` );
|
||||
|
||||
// Check the availability of the Payment Request API.
|
||||
paymentRequest.canMakePayment()
|
||||
// Attempt to mount.
|
||||
.then( function( result ) {
|
||||
// Hide wrapper if nothing can be mounted.
|
||||
if ( ! result ) {
|
||||
return hideAndSwitchGateways();
|
||||
}
|
||||
|
||||
// Hide wrapper if using Apple Pay but in Test Mode.
|
||||
// The verification for Connected accounts in Test Mode is not reliable.
|
||||
if ( true === result.applePay && 'true' === edd_stripe_vars.isTestMode ) {
|
||||
return hideAndSwitchGateways();
|
||||
}
|
||||
|
||||
// Mount.
|
||||
wrapper.style.display = 'block';
|
||||
checkoutForm.classList.add( 'edd-prb--is-active' );
|
||||
prButton.mount( `#${ element.id } .edds-prb__button` );
|
||||
|
||||
// Bind variable pricing/quantity events.
|
||||
bindEvents( paymentRequest, checkoutForm );
|
||||
|
||||
// Handle "Terms of Service" and "Privacy Policy" client validation.
|
||||
prButton.on( 'click', onClick );
|
||||
} );
|
||||
} catch ( error ) {
|
||||
outputNotice( {
|
||||
errorMessage: error.message,
|
||||
errorContainer: document.querySelector( '#edds-prb-checkout' ),
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs an initial check for Payment Request support.
|
||||
*
|
||||
* Used when Stripe is not the default gateway (and therefore Express Checkout is not
|
||||
* loaded by default) to do a "background" check of support while a different initial
|
||||
* gateway is loaded.
|
||||
*
|
||||
* @link https://github.com/easydigitaldownloads/edd-stripe/issues/652
|
||||
*/
|
||||
function paymentRequestPrecheck() {
|
||||
const {
|
||||
eddStripe: stripe,
|
||||
edd_stripe_vars: config
|
||||
} = window;
|
||||
|
||||
if ( ! config || ! stripe ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { currency, country, checkoutHasPaymentRequest } = config;
|
||||
|
||||
if ( 'false' === checkoutHasPaymentRequest ) {
|
||||
return;
|
||||
}
|
||||
|
||||
stripe.paymentRequest( {
|
||||
country,
|
||||
currency: currency.toLowerCase(),
|
||||
total: {
|
||||
label: 'Easy Digital Downloads',
|
||||
amount: 100,
|
||||
}
|
||||
} )
|
||||
.canMakePayment()
|
||||
.then( ( result ) => {
|
||||
if ( null === result ) {
|
||||
hideAndSwitchGateways();
|
||||
}
|
||||
|
||||
const checkoutForm = document.getElementById( 'edd_checkout_form_wrap' );
|
||||
checkoutForm.classList.add( 'edd-prb--is-active' );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Payment Request functionality for single purchase links.
|
||||
*/
|
||||
export function setup() {
|
||||
if ( '1' !== edd_scripts.is_checkout ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts PRB when the gateway has loaded.
|
||||
*
|
||||
* @param {Event} e Gateway loaded event.
|
||||
* @param {string} gateway Gateway ID.
|
||||
*/
|
||||
jQuery( document.body ).on( 'edd_gateway_loaded', ( e, gateway ) => {
|
||||
if ( 'stripe-prb' !== gateway ) {
|
||||
IS_PRB_GATEWAY = false;
|
||||
|
||||
// Always check for Payment Request support if Stripe is active.
|
||||
paymentRequestPrecheck();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const prbEl = document.querySelector( '.edds-prb.edds-prb--checkout' );
|
||||
|
||||
if ( ! prbEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
IS_PRB_GATEWAY = true;
|
||||
|
||||
mount( prbEl );
|
||||
} );
|
||||
}
|
@ -0,0 +1,416 @@
|
||||
/* global edd_stripe_vars, jQuery */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { parseDataset } from './';
|
||||
import { apiRequest, forEach, outputNotice } from 'utils';
|
||||
import { handle as handleIntent } from 'frontend/stripe-elements';
|
||||
import { createPayment, completePayment } from 'frontend/payment-forms';
|
||||
|
||||
/**
|
||||
* Finds the Download ID, Price ID, and quantity values for single Download.
|
||||
*
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
* @return {Object}
|
||||
*/
|
||||
function getDownloadData( purchaseLink ) {
|
||||
let downloadId, priceId = false, quantity = 1;
|
||||
|
||||
// Download ID.
|
||||
const downloadIdEl = purchaseLink.querySelector( '[name="download_id"]' );
|
||||
downloadId = parseFloat( downloadIdEl.value );
|
||||
|
||||
// Price ID.
|
||||
const priceIdEl = purchaseLink.querySelector(
|
||||
`.edd_price_option_${downloadId}:checked`
|
||||
);
|
||||
|
||||
if ( priceIdEl ) {
|
||||
priceId = parseFloat( priceIdEl.value );
|
||||
}
|
||||
|
||||
// Quantity.
|
||||
const quantityEl = purchaseLink.querySelector(
|
||||
'input[name="edd_download_quantity"]'
|
||||
);
|
||||
|
||||
if ( quantityEl ) {
|
||||
quantity = parseFloat( quantityEl.value );
|
||||
}
|
||||
|
||||
return {
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to the purchase link form by updating the Payment Request object.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
async function onChange( paymentRequest, purchaseLink ) {
|
||||
const { downloadId, priceId, quantity } = getDownloadData( purchaseLink );
|
||||
|
||||
try {
|
||||
// Calculate and gather price information.
|
||||
const {
|
||||
'display-items': displayItems,
|
||||
...paymentRequestData
|
||||
} = await apiRequest( 'edds_prb_ajax_get_options', {
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
} )
|
||||
|
||||
// Update the Payment Request with server-side data.
|
||||
paymentRequest.update( {
|
||||
displayItems,
|
||||
...paymentRequestData,
|
||||
} )
|
||||
} catch ( error ) {
|
||||
outputNotice( {
|
||||
errorMessage: '',
|
||||
errorContainer: purchaseLink,
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Payment Request amount when the "Custom Amount" input changes.
|
||||
*
|
||||
* @param {HTMLElement} addToCartEl Add to cart button.
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
async function onChangeCustomPrice( addToCartEl, paymentRequest, purchaseLink ) {
|
||||
const { price } = addToCartEl.dataset;
|
||||
const { downloadId, priceId, quantity } = getDownloadData( purchaseLink );
|
||||
|
||||
try {
|
||||
// Calculate and gather price information.
|
||||
const {
|
||||
'display-items': displayItems,
|
||||
...paymentRequestData
|
||||
} = await apiRequest( 'edds_prb_ajax_get_options', {
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
} )
|
||||
|
||||
// Find the "Custom Amount" price.
|
||||
const { is_zero_decimal: isZeroDecimal } = edd_stripe_vars;
|
||||
let amount = parseFloat( price );
|
||||
|
||||
if ( 'false' === isZeroDecimal ) {
|
||||
amount = Math.round( amount * 100 );
|
||||
}
|
||||
|
||||
// Update the Payment Request with the returned server-side data.
|
||||
// Force update the `amount` in all `displayItems` and `total`.
|
||||
//
|
||||
// "Custom Prices" does not support quantities and Payment Requests
|
||||
// do not support taxes so the same amount applies across the board.
|
||||
paymentRequest.update( {
|
||||
displayItems: displayItems.map( ( { label } ) => ( {
|
||||
label,
|
||||
amount,
|
||||
} ) ),
|
||||
...paymentRequestData,
|
||||
total: {
|
||||
label: paymentRequestData.total.label,
|
||||
amount,
|
||||
},
|
||||
} )
|
||||
} catch ( error ) {
|
||||
outputNotice( {
|
||||
errorMessage: '',
|
||||
errorContainer: purchaseLink,
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Payment Method errors.
|
||||
*
|
||||
* @param {Object} event Payment Request event.
|
||||
* @param {Object} error Error.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
function onPaymentMethodError( event, error, purchaseLink ) {
|
||||
// Complete the Payment Request to hide the payment sheet.
|
||||
event.complete( 'success' );
|
||||
|
||||
// Release loading state.
|
||||
purchaseLink.classList.remove( 'loading' );
|
||||
|
||||
outputNotice( {
|
||||
errorMessage: error.message,
|
||||
errorContainer: purchaseLink,
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
|
||||
// Item is in the cart at this point, so change the Purchase button to Checkout.
|
||||
//
|
||||
// Using jQuery which will preserve the previously set display value in order
|
||||
// to provide better theme compatibility.
|
||||
jQuery( 'a.edd-add-to-cart', purchaseLink ).hide();
|
||||
jQuery( '.edd_download_quantity_wrapper', purchaseLink ).hide();
|
||||
jQuery( '.edd_price_options', purchaseLink ).hide();
|
||||
jQuery( '.edd_go_to_checkout', purchaseLink )
|
||||
.show().removeAttr( 'data-edd-loading' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles recieving a Payment Method from the Payment Request.
|
||||
*
|
||||
* Adds an item to the cart and processes the Checkout as if we are
|
||||
* in normal Checkout context.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
* @param {Object} event paymentmethod event.
|
||||
*/
|
||||
async function onPaymentMethod( paymentRequest, purchaseLink, event ) {
|
||||
try {
|
||||
// Retrieve the latest data (price ID, quantity, etc).
|
||||
const { downloadId, priceId, quantity } = getDownloadData( purchaseLink );
|
||||
|
||||
// Retrieve information from the PRB event.
|
||||
const { paymentMethod, payerEmail, payerName } = event;
|
||||
|
||||
const tokenInput = jQuery( '#edd-process-stripe-token-' + downloadId );
|
||||
|
||||
// Start the processing.
|
||||
//
|
||||
// Adds the single Download to the cart and then shims $_POST
|
||||
// data to align with the standard Checkout context.
|
||||
//
|
||||
// This calls `_edds_process_purchase_form()` server-side which
|
||||
// creates and returns a PaymentIntent -- just like the first step
|
||||
// of a true Checkout.
|
||||
const {
|
||||
intent,
|
||||
intent: {
|
||||
client_secret: clientSecret,
|
||||
object: intentType,
|
||||
}
|
||||
} = await apiRequest( 'edds_prb_ajax_process_checkout', {
|
||||
email: payerEmail,
|
||||
name: payerName,
|
||||
paymentMethod,
|
||||
downloadId,
|
||||
priceId,
|
||||
quantity,
|
||||
context: 'download',
|
||||
post_data: jQuery( purchaseLink ).serialize(),
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
} );
|
||||
|
||||
// Complete the Payment Request to hide the payment sheet.
|
||||
event.complete( 'success' );
|
||||
|
||||
// Loading state. Block interaction.
|
||||
purchaseLink.classList.add( 'loading' );
|
||||
|
||||
// Confirm the card (SCA, etc).
|
||||
const confirmFunc = 'setup_intent' === intentType
|
||||
? 'confirmCardSetup'
|
||||
: 'confirmCardPayment';
|
||||
|
||||
eddStripe[ confirmFunc ](
|
||||
clientSecret,
|
||||
{
|
||||
payment_method: paymentMethod.id
|
||||
},
|
||||
{
|
||||
handleActions: false,
|
||||
}
|
||||
)
|
||||
.then( ( { error } ) => {
|
||||
// Something went wrong. Alert the Payment Request.
|
||||
if ( error ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Confirm again after the Payment Request dialog has been hidden.
|
||||
// For cards that do not require further checks this will throw a 400
|
||||
// error (in the Stripe API) and a log console error but not throw
|
||||
// an actual Exception. This can be ignored.
|
||||
//
|
||||
// https://github.com/stripe/stripe-payments-demo/issues/133#issuecomment-632593669
|
||||
eddStripe[ confirmFunc ]( clientSecret )
|
||||
.then( async ( { error } ) => {
|
||||
try {
|
||||
if ( error ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Create an EDD Payment.
|
||||
const { intent: updatedIntent, nonce } = await createPayment( intent );
|
||||
|
||||
// Complete the EDD Payment with the updated PaymentIntent.
|
||||
await completePayment( updatedIntent, nonce );
|
||||
|
||||
// Redirect on completion.
|
||||
window.location.replace( edd_stripe_vars.successPageUri );
|
||||
|
||||
// Something went wrong, output a notice.
|
||||
} catch ( error ) {
|
||||
onPaymentMethodError( event, error, purchaseLink );
|
||||
}
|
||||
} );
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
onPaymentMethodError( event, error, purchaseLink );
|
||||
} );
|
||||
|
||||
// Something went wrong, output a notice.
|
||||
} catch ( error ) {
|
||||
onPaymentMethodError( event, error, purchaseLink );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes to the "Add to Cart" button.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
function observeAddToCartChanges( paymentRequest, purchaseLink ) {
|
||||
const addToCartEl = purchaseLink.querySelector( '.edd-add-to-cart' );
|
||||
|
||||
if ( ! addToCartEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver( ( mutations ) => {
|
||||
mutations.forEach( ( { type, attributeName, target } ) => {
|
||||
if ( type !== 'attributes' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Payment Request if the price has changed.
|
||||
// Used for "Custom Prices" extension.
|
||||
if ( 'data-price' === attributeName ) {
|
||||
onChangeCustomPrice( target, paymentRequest, purchaseLink );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
observer.observe( addToCartEl, {
|
||||
attributes: true,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds purchase link form events.
|
||||
*
|
||||
* @param {PaymentRequest} paymentRequest Payment Request object.
|
||||
* @param {HTMLElement} purchaseLink Purchase link form.
|
||||
*/
|
||||
function bindEvents( paymentRequest, purchaseLink ) {
|
||||
// Price option change.
|
||||
const priceOptionsEls = purchaseLink.querySelectorAll( '.edd_price_options input[type="radio"]' );
|
||||
|
||||
forEach( priceOptionsEls, ( priceOption ) => {
|
||||
priceOption.addEventListener( 'change', () => onChange( paymentRequest, purchaseLink ) );
|
||||
} );
|
||||
|
||||
// Quantity change.
|
||||
const quantityEl = purchaseLink.querySelector( 'input[name="edd_download_quantity"]' );
|
||||
|
||||
if ( quantityEl ) {
|
||||
quantityEl.addEventListener( 'change', () => onChange( paymentRequest, purchaseLink ) );
|
||||
}
|
||||
|
||||
// Changes to "Add to Cart" button.
|
||||
observeAddToCartChanges( paymentRequest, purchaseLink );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts Payment Request buttons (if possible).
|
||||
*
|
||||
* @param {HTMLElement} element Payment Request button mount wrapper.
|
||||
*/
|
||||
function mount( element ) {
|
||||
const { eddStripe } = window;
|
||||
|
||||
try {
|
||||
// Gather initial data.
|
||||
const { 'display-items': displayItems, ...data } = parseDataset( element.dataset );
|
||||
|
||||
// Find the purchase link form.
|
||||
const purchaseLink = element.closest(
|
||||
'.edd_download_purchase_form'
|
||||
);
|
||||
|
||||
// Create a Payment Request object.
|
||||
const paymentRequest = eddStripe.paymentRequest( {
|
||||
// Requested to prompt full address information collection for Apple Pay.
|
||||
//
|
||||
// Collected email address is used to create/update Easy Digital Downloads Customer.
|
||||
//
|
||||
// @link https://stripe.com/docs/js/payment_request/create#stripe_payment_request-options-requestPayerName
|
||||
requestPayerEmail: true,
|
||||
displayItems,
|
||||
...data,
|
||||
} );
|
||||
|
||||
// Create a Payment Request button.
|
||||
const elements = eddStripe.elements();
|
||||
const prButton = elements.create( 'paymentRequestButton', {
|
||||
paymentRequest: paymentRequest,
|
||||
} );
|
||||
|
||||
const wrapper = document.querySelector( `#${ element.id }` );
|
||||
|
||||
// Check the availability of the Payment Request API.
|
||||
paymentRequest.canMakePayment()
|
||||
// Attempt to mount.
|
||||
.then( function( result ) {
|
||||
// Hide wrapper if nothing can be mounted.
|
||||
if ( ! result ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide wrapper if using Apple Pay but in Test Mode.
|
||||
// The verification for Connected accounts in Test Mode is not reliable.
|
||||
if ( true === result.applePay && 'true' === edd_stripe_vars.isTestMode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mount.
|
||||
wrapper.style.display = 'block';
|
||||
purchaseLink.classList.add( 'edd-prb--is-active' );
|
||||
prButton.mount( `#${ element.id } .edds-prb__button` );
|
||||
|
||||
// Bind variable pricing/quantity events.
|
||||
bindEvents( paymentRequest, purchaseLink );
|
||||
} );
|
||||
|
||||
// Handle a PaymentMethod when available.
|
||||
paymentRequest.on( 'paymentmethod', ( event ) => {
|
||||
onPaymentMethod( paymentRequest, purchaseLink, event );
|
||||
} );
|
||||
} catch ( error ) {
|
||||
outputNotice( {
|
||||
errorMessage: error.message,
|
||||
errorContainer: purchaseLink,
|
||||
errorContainerReplace: false,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up Payment Request functionality for single purchase links.
|
||||
*/
|
||||
export function setup() {
|
||||
forEach( document.querySelectorAll( '.edds-prb.edds-prb--download' ), mount );
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
export { setup as setupDownload } from './download.js';
|
||||
export { setup as setupCheckout } from './checkout.js';
|
||||
|
||||
/**
|
||||
* Parses an HTML dataset and decodes JSON values.
|
||||
*
|
||||
* @param {Object} dataset HTML data attributes.
|
||||
* @return {Object}
|
||||
*/
|
||||
export function parseDataset( dataset ) {
|
||||
let data = {};
|
||||
|
||||
for ( const [ key, value ] of Object.entries( dataset ) ) {
|
||||
let parsedValue = value;
|
||||
|
||||
try {
|
||||
parsedValue = JSON.parse( value );
|
||||
} catch ( e ) {}
|
||||
|
||||
data[ key ] = parsedValue;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { paymentMethodActions } from './payment-method-actions.js';
|
||||
import { paymentForm } from './payment-form.js';
|
||||
|
||||
export function setup() {
|
||||
if ( ! document.getElementById( 'edd-stripe-manage-cards' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
paymentMethodActions();
|
||||
paymentForm();
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/* global edd_stripe_vars, location */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import {
|
||||
createPaymentForm as createElementsPaymentForm,
|
||||
getBillingDetails
|
||||
} from 'frontend/stripe-elements';
|
||||
|
||||
import {
|
||||
apiRequest,
|
||||
hasValidInputs,
|
||||
triggerBrowserValidation,
|
||||
generateNotice,
|
||||
forEach
|
||||
} from 'utils';
|
||||
|
||||
/**
|
||||
* Binds events and sets up "Add New" form.
|
||||
*/
|
||||
export function paymentForm() {
|
||||
// Mount Elements.
|
||||
createElementsPaymentForm( window.eddStripe.elements() );
|
||||
|
||||
// Toggles and submission.
|
||||
document.querySelector( '.edd-stripe-add-new' ).addEventListener( 'click', onToggleForm );
|
||||
document.getElementById( 'edd-stripe-add-new-cancel' ).addEventListener( 'click', onToggleForm );
|
||||
document.getElementById( 'edd-stripe-add-new-card' ).addEventListener( 'submit', onAddPaymentMethod );
|
||||
|
||||
// Set "Card Name" field as required by HTML5
|
||||
document.getElementById( 'card_name' ).required = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles toggling of "Add New" form button and submission.
|
||||
*
|
||||
* @param {Event} e click event.
|
||||
*/
|
||||
function onToggleForm( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = document.getElementById( 'edd-stripe-add-new-card' );
|
||||
const formFields = form.querySelector( '.edd-stripe-add-new-card' );
|
||||
const isFormVisible = 'block' === formFields.style.display;
|
||||
|
||||
const cancelButton = form.querySelector( '#edd-stripe-add-new-cancel' );
|
||||
|
||||
// Trigger a `submit` event.
|
||||
if ( isFormVisible && cancelButton !== e.target ) {
|
||||
const submitEvent = document.createEvent( 'Event' );
|
||||
|
||||
submitEvent.initEvent( 'submit', true, true );
|
||||
form.dispatchEvent( submitEvent );
|
||||
// Toggle form.
|
||||
} else {
|
||||
formFields.style.display = ! isFormVisible ? 'block' : 'none';
|
||||
cancelButton.style.display = ! isFormVisible ? 'inline-block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new Source to the Customer.
|
||||
*
|
||||
* @param {Event} e submit event.
|
||||
*/
|
||||
function onAddPaymentMethod( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.target;
|
||||
|
||||
if ( ! hasValidInputs( form ) ) {
|
||||
triggerBrowserValidation( form );
|
||||
} else {
|
||||
try {
|
||||
disableForm();
|
||||
|
||||
createPaymentMethod( form )
|
||||
.then( addPaymentMethod )
|
||||
.catch( ( error ) => {
|
||||
handleNotice( error );
|
||||
enableForm();
|
||||
} );
|
||||
} catch ( error ) {
|
||||
handleNotice( error );
|
||||
enableForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a PaymentMethod.
|
||||
*
|
||||
* @param {Object} paymentMethod
|
||||
*/
|
||||
export function addPaymentMethod( paymentMethod ) {
|
||||
var tokenInput = document.getElementById( '#edd-process-stripe-token' );
|
||||
|
||||
apiRequest( 'edds_add_payment_method', {
|
||||
payment_method_id: paymentMethod.id,
|
||||
nonce: document.getElementById( 'edd-stripe-add-card-nonce' ).value,
|
||||
timestamp: tokenInput ? tokenInput.dataset.timestamp : '',
|
||||
token: tokenInput ? tokenInput.dataset.token : '',
|
||||
} )
|
||||
/**
|
||||
* Shows an error when the API request fails.
|
||||
*
|
||||
* @param {Object} response API Request response.
|
||||
*/
|
||||
.fail( handleNotice )
|
||||
/**
|
||||
* Shows a success notice and automatically redirect.
|
||||
*
|
||||
* @param {Object} response API Request response.
|
||||
*/
|
||||
.done( function( response ) {
|
||||
handleNotice( response, 'success' );
|
||||
|
||||
// Automatically redirect on success.
|
||||
setTimeout( function() {
|
||||
location.reload();
|
||||
}, 1500 );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PaymentMethod from a card and billing form.
|
||||
*
|
||||
* @param {HTMLElement} billingForm Form with billing fields to retrieve data from.
|
||||
* @return {Object} Stripe PaymentMethod.
|
||||
*/
|
||||
function createPaymentMethod( billingForm ) {
|
||||
return window.eddStripe
|
||||
// Create a PaymentMethod with stripe.js
|
||||
.createPaymentMethod(
|
||||
'card',
|
||||
window.eddStripe.cardElement,
|
||||
{
|
||||
billing_details: getBillingDetails( billingForm ),
|
||||
}
|
||||
)
|
||||
/**
|
||||
* Handles PaymentMethod creation response.
|
||||
*
|
||||
* @param {Object} result PaymentMethod creation result.
|
||||
*/
|
||||
.then( function( result ) {
|
||||
if ( result.error ) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
return result.paymentMethod;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables "Add New" form.
|
||||
*/
|
||||
function disableForm() {
|
||||
const submit = document.querySelector( '.edd-stripe-add-new' );
|
||||
|
||||
submit.value = submit.dataset.loading;
|
||||
submit.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables "Add New" form.
|
||||
*/
|
||||
function enableForm() {
|
||||
const submit = document.querySelector( '.edd-stripe-add-new' );
|
||||
|
||||
submit.value = submit.dataset.submit;
|
||||
submit.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a notice (success or error) for card actions.
|
||||
*
|
||||
* @param {Object} error Error with message to output.
|
||||
* @param {string} type Notice type.
|
||||
*/
|
||||
export function handleNotice( error, type = 'error' ) {
|
||||
// Create the new notice.
|
||||
const notice = generateNotice(
|
||||
( error && error.message ) ? error.message : edd_stripe_vars.generic_error,
|
||||
type
|
||||
);
|
||||
|
||||
// Hide previous notices.
|
||||
forEach( document.querySelectorAll( '.edd-stripe-alert' ), function( alert ) {
|
||||
alert.remove();
|
||||
} );
|
||||
|
||||
// Show new notice.
|
||||
document.querySelector( '.edd-stripe-add-card-actions' )
|
||||
.insertBefore( notice, document.querySelector( '.edd-stripe-add-new' ) );
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/* global edd_stripe_vars, location */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { apiRequest, generateNotice, fieldValueOrNull, forEach } from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
* Binds events for card actions.
|
||||
*/
|
||||
export function paymentMethodActions() {
|
||||
// Update.
|
||||
forEach( document.querySelectorAll( '.edd-stripe-update-card' ), function( updateButton ) {
|
||||
updateButton.addEventListener( 'click', onToggleUpdateForm );
|
||||
} );
|
||||
|
||||
forEach( document.querySelectorAll( '.edd-stripe-cancel-update' ), function( cancelButton ) {
|
||||
cancelButton.addEventListener( 'click', onToggleUpdateForm );
|
||||
} );
|
||||
|
||||
forEach( document.querySelectorAll( '.card-update-form' ), function( updateButton ) {
|
||||
updateButton.addEventListener( 'submit', onUpdatePaymentMethod );
|
||||
} );
|
||||
|
||||
// Delete.
|
||||
forEach( document.querySelectorAll( '.edd-stripe-delete-card' ), function( deleteButton ) {
|
||||
deleteButton.addEventListener( 'click', onDeletePaymentMethod );
|
||||
} );
|
||||
|
||||
// Set Default.
|
||||
forEach( document.querySelectorAll( '.edd-stripe-default-card' ), function( setDefaultButton ) {
|
||||
setDefaultButton.addEventListener( 'click', onSetDefaultPaymentMethod );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a generic Payment Method action (set default, update, delete).
|
||||
*
|
||||
* @param {string} action Payment action.
|
||||
* @param {string} paymentMethodId PaymentMethod ID.
|
||||
* @param {null|Object} data Additional AJAX data.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
function paymentMethodAction( action, paymentMethodId, data = {} ) {
|
||||
var tokenInput = document.getElementById( 'edd-process-stripe-token-' + paymentMethodId );
|
||||
data.timestamp = tokenInput ? tokenInput.dataset.timestamp : '';
|
||||
data.token = tokenInput ? tokenInput.dataset.token : '';
|
||||
|
||||
return apiRequest( action, {
|
||||
payment_method: paymentMethodId,
|
||||
nonce: document.getElementById( 'card_update_nonce_' + paymentMethodId ).value,
|
||||
...data,
|
||||
} )
|
||||
/**
|
||||
* Shows an error when the API request fails.
|
||||
*
|
||||
* @param {Object} response API Request response.
|
||||
*/
|
||||
.fail( function( response ) {
|
||||
handleNotice( paymentMethodId, response );
|
||||
} )
|
||||
/**
|
||||
* Shows a success notice and automatically redirect.
|
||||
*
|
||||
* @param {Object} response API Request response.
|
||||
*/
|
||||
.done( function( response ) {
|
||||
handleNotice( paymentMethodId, response, 'success' );
|
||||
|
||||
// Automatically redirect on success.
|
||||
setTimeout( function() {
|
||||
location.reload();
|
||||
}, 1500 );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
function onToggleUpdateForm( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const source = e.target.dataset.source;
|
||||
|
||||
const form = document.getElementById( source + '-update-form' );
|
||||
const cardActionsEl = document.getElementById( source + '-card-actions' );
|
||||
const isFormVisible = 'block' === form.style.display;
|
||||
|
||||
form.style.display = ! isFormVisible ? 'block' : 'none';
|
||||
cardActionsEl.style.display = ! isFormVisible ? 'none' : 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
function onUpdatePaymentMethod( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.target;
|
||||
const data = {};
|
||||
|
||||
// Gather form data.
|
||||
const updateFields = [
|
||||
'address_city',
|
||||
'address_country',
|
||||
'address_line1',
|
||||
'address_line2',
|
||||
'address_zip',
|
||||
'address_state',
|
||||
'exp_month',
|
||||
'exp_year',
|
||||
];
|
||||
|
||||
updateFields.forEach( function( fieldName ) {
|
||||
const field = form.querySelector( '[name="' + fieldName + '"]' );
|
||||
data[ fieldName ] = fieldValueOrNull( field );
|
||||
} );
|
||||
|
||||
const submitButton = form.querySelector( 'input[type="submit"]' );
|
||||
|
||||
submitButton.disabled = true;
|
||||
submitButton.value = submitButton.dataset.loading;
|
||||
|
||||
paymentMethodAction( 'edds_update_payment_method', e.target.dataset.source, data )
|
||||
.fail( function( response ) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.value = submitButton.dataset.submit;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
function onDeletePaymentMethod( e ) {
|
||||
e.preventDefault();
|
||||
const loading = '<span class="edd-loading-ajax edd-loading"></span>';
|
||||
const linkText = e.target.innerText;
|
||||
e.target.innerHTML = loading;
|
||||
|
||||
paymentMethodAction( 'edds_delete_payment_method', e.target.dataset.source )
|
||||
.fail( function( response ) {
|
||||
e.target.innerText = linkText;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
function onSetDefaultPaymentMethod( e ) {
|
||||
e.preventDefault();
|
||||
const loading = '<span class="edd-loading-ajax edd-loading"></span>';
|
||||
const linkText = e.target.innerText;
|
||||
e.target.innerHTML = loading;
|
||||
|
||||
paymentMethodAction( 'edds_set_payment_method_default', e.target.dataset.source )
|
||||
.fail( function( response ) {
|
||||
e.target.innerText = linkText;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a notice (success or error) for card actions.
|
||||
*
|
||||
* @param {string} paymentMethodId
|
||||
* @param {Object} error Error with message to output.
|
||||
* @param {string} type Notice type.
|
||||
*/
|
||||
export function handleNotice( paymentMethodId, error, type = 'error' ) {
|
||||
// Create the new notice.
|
||||
const notice = generateNotice(
|
||||
( error && error.message ) ? error.message : edd_stripe_vars.generic_error,
|
||||
type
|
||||
);
|
||||
|
||||
// Hide previous notices.
|
||||
forEach( document.querySelectorAll( '.edd-stripe-alert' ), function( alert ) {
|
||||
alert.remove();
|
||||
} );
|
||||
|
||||
const item = document.getElementById( paymentMethodId + '_card_item' );
|
||||
|
||||
// Show new notice.
|
||||
item.insertBefore( notice, item.querySelector( '.card-details' ) );
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
/* global $, edd_stripe_vars */
|
||||
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import {
|
||||
generateNotice,
|
||||
fieldValueOrNull,
|
||||
forEach
|
||||
} from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
// Intents.
|
||||
export * from './intents.js';
|
||||
|
||||
const DEFAULT_ELEMENTS = {
|
||||
'card': '#edd-stripe-card-element',
|
||||
}
|
||||
|
||||
const DEFAULT_SPLIT_ELEMENTS = {
|
||||
'cardNumber': '#edd-stripe-card-element',
|
||||
'cardExpiry': '#edd-stripe-card-exp-element',
|
||||
'cardCvc': '#edd-stripe-card-cvc-element',
|
||||
}
|
||||
|
||||
let ELEMENTS_OPTIONS = { ...edd_stripe_vars.elementsOptions };
|
||||
|
||||
/**
|
||||
* Mounts Elements based on payment form configuration.
|
||||
*
|
||||
* Assigns a `cardElement` object to the global `eddStripe` object
|
||||
* that can be used to collect card data for tokenization.
|
||||
*
|
||||
* Integrations (such as Recurring) should pass a configuration of Element
|
||||
* types and specific HTML IDs to mount based on settings and form markup
|
||||
* to avoid attempting to mount to the same `HTMLElement`.
|
||||
*
|
||||
* @since 2.8.0
|
||||
*
|
||||
* @param {Object} elementsInstance Stripe Elements instance.
|
||||
* @return {Element} The last Stripe Element to be mounted.
|
||||
*/
|
||||
export function createPaymentForm( elementsInstance, elements ) {
|
||||
let mountedEl;
|
||||
|
||||
if ( ! elements ) {
|
||||
elements = ( 'true' === edd_stripe_vars.elementsSplitFields )
|
||||
? DEFAULT_SPLIT_ELEMENTS
|
||||
: DEFAULT_ELEMENTS;
|
||||
}
|
||||
|
||||
forEach( elements, ( selector, element ) => {
|
||||
mountedEl = createAndMountElement( elementsInstance, selector, element );
|
||||
} );
|
||||
|
||||
// Make at least one Element available globally.
|
||||
window.eddStripe.cardElement = mountedEl;
|
||||
|
||||
return mountedEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and returns an object of styles that can be used to change the appearance
|
||||
* of the Stripe Elements iFrame based on existing form styles.
|
||||
*
|
||||
* Styles that can be applied to the current DOM are injected to the page via
|
||||
* a <style> element.
|
||||
*
|
||||
* @link https://stripe.com/docs/stripe-js/reference#the-elements-object
|
||||
*
|
||||
* @since 2.8.0
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function generateElementStyles() {
|
||||
// Try to mimick existing input styles.
|
||||
const cardNameEl = document.querySelector( '.card-name.edd-input' );
|
||||
|
||||
if ( ! cardNameEl ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const inputStyles = window.getComputedStyle( cardNameEl );
|
||||
|
||||
// Inject inline CSS instead of applying to the Element so it can be overwritten.
|
||||
if ( ! document.getElementById( 'edds-stripe-element-styles' ) ) {
|
||||
const styleTag = document.createElement( 'style' );
|
||||
|
||||
styleTag.innerHTML = `
|
||||
.edd-stripe-card-element.StripeElement,
|
||||
.edd-stripe-card-exp-element.StripeElement,
|
||||
.edd-stripe-card-cvc-element.StripeElement {
|
||||
background-color: ${ inputStyles.getPropertyValue( 'background-color' ) };
|
||||
|
||||
${
|
||||
[ 'top', 'right', 'bottom', 'left' ]
|
||||
.map( ( dir ) => (
|
||||
`border-${ dir }-color: ${ inputStyles.getPropertyValue( `border-${ dir }-color` ) };
|
||||
border-${ dir }-width: ${ inputStyles.getPropertyValue( `border-${ dir }-width` ) };
|
||||
border-${ dir }-style: ${ inputStyles.getPropertyValue( `border-${ dir }-style` ) };
|
||||
padding-${ dir }: ${ inputStyles.getPropertyValue( `padding-${ dir }` ) };`
|
||||
) )
|
||||
.join( '' )
|
||||
}
|
||||
${
|
||||
[ 'top-right', 'bottom-right', 'bottom-left', 'top-left' ]
|
||||
.map( ( dir ) => (
|
||||
`border-${ dir }-radius: ${ inputStyles.getPropertyValue( 'border-top-right-radius' ) };`
|
||||
) )
|
||||
.join( '' )
|
||||
}
|
||||
}`
|
||||
// Remove whitespace.
|
||||
.replace( /\s/g, '' );
|
||||
|
||||
styleTag.id = 'edds-stripe-element-styles';
|
||||
|
||||
document.body.appendChild( styleTag );
|
||||
}
|
||||
|
||||
return {
|
||||
base: {
|
||||
color: inputStyles.getPropertyValue( 'color' ),
|
||||
fontFamily: inputStyles.getPropertyValue( 'font-family' ),
|
||||
fontSize: inputStyles.getPropertyValue( 'font-size' ),
|
||||
fontWeight: inputStyles.getPropertyValue( 'font-weight' ),
|
||||
fontSmoothing: inputStyles.getPropertyValue( '-webkit-font-smoothing' ),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts an Elements Card to the DOM and adds event listeners to submission.
|
||||
*
|
||||
* @link https://stripe.com/docs/stripe-js/reference#the-elements-object
|
||||
*
|
||||
* @since 2.8.0
|
||||
*
|
||||
* @param {Elements} elementsInstance Stripe Elements instance.
|
||||
* @param {string} selector Selector to mount Element on.
|
||||
* @return {Element|undefined} Stripe Element.
|
||||
*/
|
||||
function createAndMountElement( elementsInstance, selector, element ) {
|
||||
const el = document.querySelector( selector );
|
||||
|
||||
if ( ! el ) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
ELEMENTS_OPTIONS.style = jQuery.extend(
|
||||
true,
|
||||
{},
|
||||
generateElementStyles(),
|
||||
ELEMENTS_OPTIONS.style
|
||||
);
|
||||
|
||||
// Remove hidePostalCode if not using a combined `card` Element.
|
||||
if ( 'cardNumber' === element && ELEMENTS_OPTIONS.hasOwnProperty( 'hidePostalCode' ) ) {
|
||||
delete ELEMENTS_OPTIONS.hidePostalCode;
|
||||
}
|
||||
|
||||
// Remove unused parameter from options.
|
||||
delete ELEMENTS_OPTIONS.i18n;
|
||||
|
||||
const stripeElement = elementsInstance
|
||||
.create( element, ELEMENTS_OPTIONS );
|
||||
|
||||
stripeElement
|
||||
.addEventListener( 'change', ( event ) => {
|
||||
handleElementError( event, el );
|
||||
handleCardBrandIcon( event );
|
||||
} )
|
||||
.mount( el );
|
||||
|
||||
return stripeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts an Elements Card to the DOM and adds event listeners to submission.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @since 2.8.0 Deprecated
|
||||
*
|
||||
* @deprecated Use createPaymentForm() to mount specific elements.
|
||||
*
|
||||
* @param {Elements} elementsInstance Stripe Elements instance.
|
||||
* @param {string} toMount Selector to mount Element on.
|
||||
* @return {Element} Stripe Element.
|
||||
*/
|
||||
export function mountCardElement( elementsInstance, toMount = '#edd-stripe-card-element' ) {
|
||||
const mountedEl = createPaymentForm( elementsInstance, {
|
||||
'card': toMount,
|
||||
} );
|
||||
|
||||
// Hide split card details fields because any integration that is using this
|
||||
// directly has not properly implemented split fields.
|
||||
const splitFields = document.getElementById( 'edd-card-details-wrap' );
|
||||
|
||||
if ( splitFields ) {
|
||||
splitFields.style.display = 'none';
|
||||
}
|
||||
|
||||
return mountedEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles error output for Elements Card.
|
||||
*
|
||||
* @param {Event} event Change event on the Card Element.
|
||||
* @param {HTMLElement} el HTMLElement the Stripe Element is being mounted on.
|
||||
*/
|
||||
function handleElementError( event, el ) {
|
||||
const newCardContainer = el.closest( '.edd-stripe-new-card' );
|
||||
const errorsContainer = newCardContainer.querySelector( '#edd-stripe-card-errors' );
|
||||
|
||||
// Only show one error at once.
|
||||
errorsContainer.innerHTML = '';
|
||||
|
||||
if ( event.error ) {
|
||||
const { code, message } = event.error;
|
||||
const { elementsOptions: { i18n: { errorMessages } } } = window.edd_stripe_vars;
|
||||
|
||||
const localizedMessage = errorMessages[ code ] ? errorMessages[ code ] : message;
|
||||
|
||||
errorsContainer.appendChild( generateNotice( localizedMessage ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates card brand icon if using a split form.
|
||||
*
|
||||
* @since 2.8.0
|
||||
*
|
||||
* @param {Event} event Change event on the Card Element.
|
||||
*/
|
||||
function handleCardBrandIcon( event ) {
|
||||
const {
|
||||
brand,
|
||||
elementType,
|
||||
} = event;
|
||||
|
||||
if ( 'cardNumber' !== event.elementType ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cardTypeEl = document.querySelector( '.card-type' );
|
||||
|
||||
if ( 'unknown' === brand ) {
|
||||
cardTypeEl.className = 'card-type';
|
||||
} else {
|
||||
cardTypeEl.classList.add( brand );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves (or creates) a PaymentMethod.
|
||||
*
|
||||
* @param {HTMLElement} billingDetailsForm Form to find data from.
|
||||
* @return {Object} PaymentMethod ID and if it previously existed.
|
||||
*/
|
||||
export function getPaymentMethod( billingDetailsForm, cardElement ) {
|
||||
const selectedPaymentMethod = $( 'input[name="edd_stripe_existing_card"]:checked' );
|
||||
|
||||
// An existing PaymentMethod is selected.
|
||||
if ( selectedPaymentMethod.length > 0 && 'new' !== selectedPaymentMethod.val() ) {
|
||||
return Promise.resolve( {
|
||||
id: selectedPaymentMethod.val(),
|
||||
exists: true,
|
||||
} );
|
||||
}
|
||||
|
||||
// Create a PaymentMethod using the Element data.
|
||||
return window.eddStripe
|
||||
.createPaymentMethod(
|
||||
'card',
|
||||
cardElement,
|
||||
{
|
||||
billing_details: getBillingDetails( billingDetailsForm ),
|
||||
}
|
||||
)
|
||||
.then( function( result ) {
|
||||
if ( result.error ) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
return {
|
||||
id: result.paymentMethod.id,
|
||||
exists: false,
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves billing details from the Billing Details sections of a form.
|
||||
*
|
||||
* @param {HTMLElement} form Form to find data from.
|
||||
* @return {Object} Billing details
|
||||
*/
|
||||
export function getBillingDetails( form ) {
|
||||
return {
|
||||
// @todo add Phone
|
||||
// @todo add Email
|
||||
name: fieldValueOrNull( form.querySelector( '.card-name' ) ),
|
||||
address: {
|
||||
line1: fieldValueOrNull( form.querySelector( '.card-address' ) ),
|
||||
line2: fieldValueOrNull( form.querySelector( '.card-address-2' ) ),
|
||||
city: fieldValueOrNull( form.querySelector( '.card-city' ) ),
|
||||
state: fieldValueOrNull( form.querySelector( '.card_state' ) ),
|
||||
postal_code: fieldValueOrNull( form.querySelector( '.card-zip' ) ),
|
||||
country: fieldValueOrNull( form.querySelector( '#billing_country' ) ),
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
/* global jQuery */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { apiRequest } from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
* Retrieve a PaymentIntent.
|
||||
*
|
||||
* @param {string} intentId Intent ID.
|
||||
* @param {string} intentType Intent type. payment_intent or setup_intent.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function retrieve( intentId, intentType = 'payment_intent' ) {
|
||||
const form = $( window.eddStripe.cardElement._parent ).closest( 'form' ),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
return apiRequest( 'edds_get_intent', {
|
||||
intent_id: intentId,
|
||||
intent_type: intentType,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
form_data: form.serialize(),
|
||||
} )
|
||||
// Returns just the PaymentIntent object.
|
||||
.then( function( response ) {
|
||||
return response.intent;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm a PaymentIntent.
|
||||
*
|
||||
* @param {Object} intent Stripe PaymentIntent or SetupIntent.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function confirm( intent ) {
|
||||
const form = $( window.eddStripe.cardElement._parent ).closest( 'form' ),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
return apiRequest( 'edds_confirm_intent', {
|
||||
intent_id: intent.id,
|
||||
intent_type: intent.object,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
form_data: form.serialize(),
|
||||
} )
|
||||
// Returns just the PaymentIntent object for easier reprocessing.
|
||||
.then( function( response ) {
|
||||
return response.intent;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture a PaymentIntent.
|
||||
*
|
||||
* @param {Object} intent Stripe PaymentIntent or SetupIntent.
|
||||
* @param {Object} data Extra data to pass to the intent action.
|
||||
* @param {string} refreshedNonce A refreshed nonce that might be needed if the
|
||||
* user logged in.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function capture( intent, data, refreshedNonce ) {
|
||||
const form = $( window.eddStripe.cardElement._parent ).closest( 'form' );
|
||||
|
||||
if ( 'requires_capture' !== intent.status ) {
|
||||
return Promise.resolve( intent );
|
||||
}
|
||||
|
||||
let formData = form.serialize(),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
// Add the refreshed nonce if available.
|
||||
if ( refreshedNonce ) {
|
||||
formData += `&edd-process-checkout-nonce=${ refreshedNonce }`;
|
||||
}
|
||||
|
||||
return apiRequest( 'edds_capture_intent', {
|
||||
intent_id: intent.id,
|
||||
intent_type: intent.object,
|
||||
form_data: formData,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
...data,
|
||||
} )
|
||||
// Returns just the PaymentIntent object for easier reprocessing.
|
||||
.then( function( response ) {
|
||||
return response.intent;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a PaymentIntent.
|
||||
*
|
||||
* @param {Object} intent Stripe PaymentIntent or SetupIntent.
|
||||
* @param {Object} data PaymentIntent data to update.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function update( intent, data ) {
|
||||
const form = $( window.eddStripe.cardElement._parent ).closest( 'form' ),
|
||||
tokenInput = $( '#edd-process-stripe-token' );
|
||||
|
||||
return apiRequest( 'edds_update_intent', {
|
||||
intent_id: intent.id,
|
||||
intent_type: intent.object,
|
||||
timestamp: tokenInput.length ? tokenInput.data( 'timestamp' ) : '',
|
||||
token: tokenInput.length ? tokenInput.data( 'token' ) : '',
|
||||
form_data: form.serialize(),
|
||||
...data,
|
||||
} )
|
||||
// Returns just the PaymentIntent object for easier reprocessing.
|
||||
.then( function( response ) {
|
||||
return response.intent;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the PaymentIntent requires further action.
|
||||
*
|
||||
* @link https://stripe.com/docs/stripe-js/reference
|
||||
*
|
||||
* @param {Object} intent Stripe PaymentIntent or SetupIntent.
|
||||
* @param {Object} data Extra data to pass to the intent action.
|
||||
*/
|
||||
export async function handle( intent, data ) {
|
||||
// requires_confirmation
|
||||
if ( 'requires_confirmation' === intent.status ) {
|
||||
// Attempt to capture.
|
||||
const confirmedIntent = await confirm( intent );
|
||||
|
||||
// Run through again.
|
||||
return await handle( confirmedIntent );
|
||||
}
|
||||
|
||||
// requires_payment_method
|
||||
// @link https://stripe.com/docs/payments/intents#intent-statuses
|
||||
if (
|
||||
'requires_payment_method' === intent.status ||
|
||||
'requires_source' === intent.status
|
||||
) {
|
||||
// Attempt to update.
|
||||
const updatedIntent = await update( intent, data );
|
||||
|
||||
// Run through again.
|
||||
return await handle( updatedIntent, data );
|
||||
}
|
||||
|
||||
// requires_action
|
||||
// @link https://stripe.com/docs/payments/intents#intent-statuses
|
||||
if (
|
||||
( 'requires_action' === intent.status && 'use_stripe_sdk' === intent.next_action.type ) ||
|
||||
( 'requires_source_action' === intent.status && 'use_stripe_sdk' === intent.next_action.type )
|
||||
) {
|
||||
let cardHandler = 'setup_intent' === intent.object ? 'handleCardSetup' : 'handleCardAction';
|
||||
|
||||
if ( 'automatic' === intent.confirmation_method ) {
|
||||
cardHandler = 'handleCardPayment';
|
||||
}
|
||||
|
||||
return window.eddStripe[ cardHandler ]( intent.client_secret )
|
||||
.then( async ( result ) => {
|
||||
if ( result.error ) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const {
|
||||
setupIntent,
|
||||
paymentIntent,
|
||||
} = result;
|
||||
|
||||
// Run through again.
|
||||
return await handle( setupIntent || paymentIntent );
|
||||
} );
|
||||
}
|
||||
|
||||
// Nothing done, return Intent.
|
||||
return intent;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* global $, edd_scripts, ajaxurl */
|
||||
|
||||
/**
|
||||
* Sends an API request to admin-ajax.php
|
||||
*
|
||||
* @link https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-util.js#L49
|
||||
*
|
||||
* @param {string} action AJAX action to send to admin-ajax.php
|
||||
* @param {Object} data Additional data to send to the action.
|
||||
* @return {Promise} jQuery Promise.
|
||||
*/
|
||||
export function apiRequest( action, data ) {
|
||||
const options = {
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
xhrFields: {
|
||||
withCredentials: true,
|
||||
},
|
||||
url: ( window.edd_scripts && window.edd_scripts.ajaxurl ) || window.ajaxurl,
|
||||
data: {
|
||||
action,
|
||||
...data,
|
||||
},
|
||||
};
|
||||
|
||||
const deferred = $.Deferred( function( deferred ) {
|
||||
// Use with PHP's wp_send_json_success() and wp_send_json_error()
|
||||
deferred.jqXHR = $.ajax( options ).done( function( response ) {
|
||||
// Treat a response of 1 or 'success' as successful for backward compatibility with existing handlers.
|
||||
if ( response === '1' || response === 1 ) {
|
||||
response = { success: true };
|
||||
}
|
||||
|
||||
if ( typeof response === 'object' && typeof response.success !== undefined ) {
|
||||
deferred[ response.success ? 'resolveWith' : 'rejectWith' ]( this, [ response.data ] );
|
||||
} else {
|
||||
deferred.rejectWith( this, [ response ] );
|
||||
}
|
||||
} ).fail( function() {
|
||||
deferred.rejectWith( this, arguments );
|
||||
} );
|
||||
} );
|
||||
|
||||
const promise = deferred.promise();
|
||||
promise.abort = function() {
|
||||
deferred.jqXHR.abort();
|
||||
return this;
|
||||
};
|
||||
|
||||
return promise;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
import { forEach } from 'utils'; // eslint-disable-line @wordpress/dependency-group
|
||||
|
||||
/**
|
||||
* forEach implementation that can handle anything.
|
||||
*/
|
||||
export { default as forEach } from 'lodash.foreach';
|
||||
|
||||
/**
|
||||
* DOM ready.
|
||||
*
|
||||
* Handles multiple callbacks.
|
||||
*
|
||||
* @param {Function} Callback function to run.
|
||||
*/
|
||||
export function domReady() {
|
||||
forEach( arguments, ( callback ) => {
|
||||
document.addEventListener( 'DOMContentLoaded', callback );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all following siblings of an element.
|
||||
*
|
||||
* @param {HTMLElement} el Starting element.
|
||||
* @return {Array} siblings List of sibling elements.
|
||||
*/
|
||||
export function getNextSiblings( el ) {
|
||||
const siblings = [];
|
||||
let sibling = el.nextElementSibling;
|
||||
|
||||
while ( sibling ) {
|
||||
if ( sibling.nodeType === 1 ) {
|
||||
siblings.push( sibling );
|
||||
}
|
||||
|
||||
sibling = sibling.nextElementSibling;
|
||||
}
|
||||
|
||||
return siblings;
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Internal dependencies.
|
||||
*/
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { forEach } from 'utils';
|
||||
|
||||
/**
|
||||
* Checks is a form passes HTML5 validation.
|
||||
*
|
||||
* @param {HTMLElement} form Form to trigger validation on.
|
||||
* @return {Bool} If the form has valid inputs.
|
||||
*/
|
||||
export function hasValidInputs( form ) {
|
||||
let plainInputsValid = true;
|
||||
|
||||
forEach( form.querySelectorAll( 'input' ), function( input ) {
|
||||
if ( input.checkValidity && ! input.checkValidity() ) {
|
||||
plainInputsValid = false;
|
||||
}
|
||||
} );
|
||||
|
||||
return plainInputsValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers HTML5 browser validation.
|
||||
*
|
||||
* @param {HTMLElement} form Form to trigger validation on.
|
||||
*/
|
||||
export function triggerBrowserValidation( form ) {
|
||||
const submit = document.createElement( 'input' );
|
||||
submit.type = 'submit';
|
||||
submit.style.display = 'none';
|
||||
|
||||
form.appendChild( submit );
|
||||
submit.click();
|
||||
submit.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input's value, or null.
|
||||
*
|
||||
* @param {HTMLElement} field Field to retrieve value from.
|
||||
* @return {null|string} Value if the field has a value.
|
||||
*/
|
||||
export function fieldValueOrNull( field ) {
|
||||
if ( ! field ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( '' === field.value ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return field.value;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import './polyfill-includes.js';
|
||||
import './polyfill-closest.js';
|
||||
import './polyfill-object-entries.js';
|
||||
import './polyfill-remove.js';
|
||||
|
||||
export * from './api-request.js';
|
||||
export * from './dom.js';
|
||||
export * from './notice.js';
|
||||
export * from './form.js';
|
@ -0,0 +1,61 @@
|
||||
/* global $, edd_stripe_vars */
|
||||
|
||||
/**
|
||||
* Generates a notice element.
|
||||
*
|
||||
* @param {string} message The notice text.
|
||||
* @param {string} type The type of notice. error or success. Default error.
|
||||
* @return {Element} HTML element containing errors.
|
||||
*/
|
||||
export function generateNotice( message, type = 'error' ) {
|
||||
const notice = document.createElement( 'p' );
|
||||
notice.classList.add( 'edd-alert' );
|
||||
notice.classList.add( 'edd-stripe-alert' );
|
||||
notice.style.clear = 'both';
|
||||
|
||||
if ( 'error' === type ) {
|
||||
notice.classList.add( 'edd-alert-error' );
|
||||
} else {
|
||||
notice.classList.add( 'edd-alert-success' );
|
||||
}
|
||||
|
||||
notice.innerHTML = message || edd_stripe_vars.generic_error;
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a notice.
|
||||
*
|
||||
*
|
||||
* @param {object} args Output arguments.
|
||||
* @param {string} args.errorType The type of notice. error or success
|
||||
* @param {string} args.errorMessasge The notice text.
|
||||
* @param {HTMLElement} args.errorContainer HTML element containing errors.
|
||||
* @param {bool} args.errorContainerReplace If true Appends the notice before
|
||||
* the container.
|
||||
*/
|
||||
export function outputNotice( {
|
||||
errorType,
|
||||
errorMessage,
|
||||
errorContainer,
|
||||
errorContainerReplace = true,
|
||||
} ) {
|
||||
const $errorContainer = $( errorContainer );
|
||||
const notice = generateNotice( errorMessage, errorType );
|
||||
|
||||
if ( true === errorContainerReplace ) {
|
||||
$errorContainer.html( notice );
|
||||
} else {
|
||||
$errorContainer.before( notice );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a notice.
|
||||
*
|
||||
* @param {HTMLElement} errorContainer HTML element containing errors.
|
||||
*/
|
||||
export function clearNotice( errorContainer ) {
|
||||
$( errorContainer ).html( '' );
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/// Polyfill .closest
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||
if ( ! Element.prototype.matches ) {
|
||||
Element.prototype.matches =
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector;
|
||||
}
|
||||
|
||||
if ( ! Element.prototype.closest ) {
|
||||
Element.prototype.closest = function( s ) {
|
||||
let el = this;
|
||||
|
||||
do {
|
||||
if ( Element.prototype.matches.call( el, s ) ) return el;
|
||||
|
||||
el = el.parentElement || el.parentNode;
|
||||
} while ( el !== null && el.nodeType === 1 );
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// Polyfill string.contains
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
|
||||
if ( ! String.prototype.includes ) {
|
||||
String.prototype.includes = function( search, start ) {
|
||||
'use strict';
|
||||
|
||||
if ( typeof start !== 'number' ) {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if ( start + search.length > this.length ) {
|
||||
return false;
|
||||
} else {
|
||||
return this.indexOf( search, start ) !== -1;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/// Polyfill Object.entries
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill
|
||||
if ( ! Object.entries ) {
|
||||
Object.entries = function( obj ) {
|
||||
var ownProps = Object.keys( obj ),
|
||||
i = ownProps.length,
|
||||
resArray = new Array( i ); // preallocate the Array
|
||||
|
||||
while ( i-- ) {
|
||||
resArray[ i ] = [ ownProps[ i ], obj[ ownProps[ i ] ] ];
|
||||
}
|
||||
|
||||
return resArray;
|
||||
};
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/// Polyfill .remove
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
|
||||
( function ( arr ) {
|
||||
arr.forEach( function( item ) {
|
||||
if ( item.hasOwnProperty( 'remove' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty( item, 'remove', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function remove() {
|
||||
this.parentNode.removeChild( this );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} )( [ Element.prototype, CharacterData.prototype, DocumentType.prototype ] );
|
Reference in New Issue
Block a user