installed plugin Easy Digital Downloads
version 3.1.0.3
This commit is contained in:
@ -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