From 79861d538a6477be75f920152aa24be219ef504e Mon Sep 17 00:00:00 2001 From: Remco Tolsma <869674+remcotolsma@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:17:31 +0100 Subject: [PATCH] Simplify Mollie card component. --- .wp-env.json | 1 + js/dist/components.js | 184 -------------------------------------- js/dist/components.js.bak | 122 ------------------------- js/dist/components.js.map | 1 - js/dist/mollie.min.js | 1 + js/src/components.js | 184 -------------------------------------- js/src/mollie.js | 56 ++++++++++++ package.json | 9 +- src/CardField.php | 148 ++++++++++++++++++++++++++++++ src/ComponentsField.php | 102 --------------------- src/Gateway.php | 49 +++++----- 11 files changed, 240 insertions(+), 617 deletions(-) delete mode 100644 js/dist/components.js delete mode 100644 js/dist/components.js.bak delete mode 100644 js/dist/components.js.map create mode 100644 js/dist/mollie.min.js delete mode 100644 js/src/components.js create mode 100644 js/src/mollie.js create mode 100644 src/CardField.php delete mode 100644 src/ComponentsField.php diff --git a/.wp-env.json b/.wp-env.json index e1b7d3c..47d8240 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -7,6 +7,7 @@ "https://downloads.wordpress.org/plugin/query-monitor.zip", "https://downloads.wordpress.org/plugin/log-http-requests.zip", "https://downloads.wordpress.org/plugin/one-time-login.zip", + "https://downloads.wordpress.org/plugin/woocommerce.zip" "https://downloads.wordpress.org/plugin/wp-plugin-dependencies.zip" ], "mappings": { diff --git a/js/dist/components.js b/js/dist/components.js deleted file mode 100644 index e15f6f3..0000000 --- a/js/dist/components.js +++ /dev/null @@ -1,184 +0,0 @@ -/* global Mollie */ -'use strict'; - -/** - * Mollie Components. - * - * @link https://docs.mollie.com/components/overview - */ -( ( $ ) => { - const components = [ - { - id: 'card-holder', - label: 'Card Holder', - component: 'cardHolder' - }, - { - id: 'card-number', - label: 'Card Number', - component: 'cardNumber' - }, - { - id: 'expiry-date', - label: 'Expiry', - component: 'expiryDate' - }, - { - id: 'verification-code', - label: 'CVC', - component: 'verificationCode' - } - ]; - - function initMollieComponents( forms ) { - let $cardTokenElements; - - if ( typeof forms === 'string' ) { - $cardTokenElements = document.querySelectorAll( forms + ' .pronamic_pay_mollie_card_token' ); - } else { - $cardTokenElements = forms.querySelectorAll( '.pronamic_pay_mollie_card_token' ); - } - - $cardTokenElements.forEach( ( $cardTokenElement ) => { - // Create components. - const data = $cardTokenElement.dataset; - - // Check required Mollie profile ID. - if ( ! $cardTokenElement.dataset.mollieProfileId ) { - throw new Error( 'No Mollie profile ID in element dataset. Unable to load Mollie Components.' ); - - return; - } - - // Initialize Mollie object. - const mollie = Mollie( - $cardTokenElement.dataset.mollieProfileId, - { - locale: $cardTokenElement.dataset.mollieLocale ?? null, - testmode: '1' === $cardTokenElement.dataset.mollieTestmode, - } - ); - - let componentsContainer = document.createElement( 'div' ); - componentsContainer.classList.add( 'mollie-components' ); - - $cardTokenElement.append( componentsContainer ); - - components.forEach( ( component ) => { - // Label. - let label = document.createElement( 'label' ); - label.setAttribute( 'for', component.id ); - label.innerText = component.label; - - // Component container. - let field = document.createElement( 'div' ); - field.setAttribute( 'id', component.id ); - - label.append( field ); - - // Error. - let error = document.createElement( 'div' ); - error.setAttribute( 'id', component.id + '-error' ); - error.setAttribute( 'role', 'alert' ); - error.classList.add( 'field-error' ); - - componentsContainer.append( label, error ); - - // Create and mount component. - let mollieComponent = mollie.createComponent( component.component ); - - mollieComponent.mount( '#' + component.id ); - - // Handle errors. - mollieComponent.addEventListener( 'change', ( event ) => { - // Add error. - if ( event.error && event.touched ) { - error.textContent = event.error; - - label.classList.add( 'is-invalid' ); - - return; - } - - // Remove error. - error.textContent = ''; - - label.classList.remove( 'is-invalid' ); - } ); - } ); - - // Create Mollie token on checkout submit. - const form = $cardTokenElement.closest( 'form' ); - - form.addEventListener( 'submit', async ( e ) => { - // Check existing card token input. - let cardTokenInput = form.querySelector( 'input[name="pronamic_pay_mollie_card_token"]' ); - - if ( cardTokenInput ) { - return; - } - - e.preventDefault(); - - // Create token. - const { token, error } = await mollie.createToken(); - - if ( error ) { - throw new Error( error.message || '' ); - } - - // Add token to form. - const tokenInput = document.createElement( 'input' ); - tokenInput.setAttribute( 'type', 'hidden' ); - tokenInput.setAttribute( 'name', 'pronamic_pay_mollie_card_token' ); - tokenInput.setAttribute( 'value', token ); - - form.append( tokenInput ); - - if ( false !== form.dispatchEvent( new Event( 'pronamic_pay_mollie_components_card_token_added', { cancelable: true } ) ) ) { - // Submit form, now containing the hidden card token field. - // form.submit(); — not working with test meta box - form.querySelector( '[name="' + e.submitter.name + '"]' ).click(); - } - } ); - } ); - } - - function setupWooCommerce() { - const checkoutForm = document.querySelector( 'form.woocommerce-checkout' ); - - if ( ! checkoutForm ) { - return; - } - - // Init components on updated checkout. - $( document.body ).on( 'updated_checkout', function( e ) { - initMollieComponents( checkoutForm ); - } ); - - const $form = $( checkoutForm ); - - // Prevent placing order, need to create token first. - $form.on( 'checkout_place_order_pronamic_pay_credit_card', returnFalse ); - - // Re-enable placing order if card token has been added. - checkoutForm.addEventListener( 'pronamic_pay_mollie_components_card_token_added', function( e ) { - e.preventDefault(); - - $form.off( 'checkout_place_order_pronamic_pay_credit_card', returnFalse ); - - $form.submit(); - } ); - } - - function returnFalse() { - return false; - } - - // Init Mollie Components. - document.addEventListener( 'DOMContentLoaded', function( e ) { - initMollieComponents( 'form:not(.woocommerce-checkout)' ); - } ); - - setupWooCommerce(); -} )( jQuery ); diff --git a/js/dist/components.js.bak b/js/dist/components.js.bak deleted file mode 100644 index ad29dcf..0000000 --- a/js/dist/components.js.bak +++ /dev/null @@ -1,122 +0,0 @@ -/* global Mollie */ - -'use strict'; - -/** - * Mollie Components. - * - * @link https://docs.mollie.com/components/overview - */ -($ => { - const componentItems = [{ - id: 'card-number', - label: 'Card Number', - component: 'cardNumber' - }, { - id: 'card-holder', - label: 'Card Holder', - component: 'cardHolder' - }, { - id: 'expiry-date', - label: 'Expiry Date', - component: 'expiryDate' - }, { - id: 'verification-code', - label: 'Verification Code', - component: 'verificationCode' - }]; - function returnFalse() { - return false; - } - function initMollieComponents() { - const $elements = document.querySelectorAll('.pronamic_pay_mollie_card_token'); - $elements.forEach($element => { - const data = $element.dataset; - if (!("mollie-profile-id" in data)) { - throw new Error('No Mollie profile ID in element dataset. Unable to load Mollie Components.'); - return; - } - - // Initialize Mollie object. - const mollie = Mollie(data['mollie-profile-id'], { - locale: data['mollie-locale'] ?? null, - testmode: "mollie-testmode" in data - }); - - // Create components. - componentItems.forEach(item => { - // Field label. - let label = document.createElement('label'); - label.setAttribute('for', item.id); - label.innerText = item.label; - - // Field container. - let field = document.createElement('div'); - field.setAttribute('id', item.id); - - // Field error. - let error = document.createElement('div'); - error.setAttribute('id', item.id + '-error'); - error.setAttribute('role', 'alert'); - error.classList.add('field-error'); - $element.append(label, field, error); - - // Create and mount component. - let component = mollie.createComponent(item.component); - component.mount('#' + item.id); - - // Handling errors. - component.addEventListener('change', event => { - error.textContent = event.error && event.touched ? event.error : ''; - }); - }); - - // Get token on form submit. - let form = $element.closest('form'), - $form = $(form); - - // Prevent placing order, need to create token first. - $form.on('checkout_place_order_pronamic_pay_credit_card', returnFalse); - - // Create Mollie token on checkout submit. - $form.on('submit', async e => { - e.preventDefault(); - - // Check card token input. - let cardTokenInput = form.querySelector('input[name="pronamic_pay_mollie_card_token"]'); - if (cardTokenInput) { - return; - } - - // Create Mollie token. - const { - token, - error - } = await mollie.createToken(); - if (error) { - throw new Error(error.message || ''); - } - - // Add token to the form. - const tokenInput = document.createElement('input'); - tokenInput.setAttribute('type', 'hidden'); - tokenInput.setAttribute('name', 'pronamic_pay_mollie_card_token'); - tokenInput.setAttribute('value', token); - form.append(tokenInput); - - // Re-enable placing order again. - $form.off('checkout_place_order_pronamic_pay_credit_card', returnFalse); - - // Submit form, now containing the hidden card token field. - $form.submit(); - }); - }); - } - - // Init Mollie Components. - document.addEventListener('DOMContentLoaded', initMollieComponents); - - // WooCommerce support. - $(document.body).on('updated_checkout', initMollieComponents); -})(jQuery); -//# sourceMappingURL=components.js.map \ No newline at end of file diff --git a/js/dist/components.js.map b/js/dist/components.js.map deleted file mode 100644 index 0ff570e..0000000 --- a/js/dist/components.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"components.js","names":["$","components","id","label","component","initMollieComponents","forms","$cardTokenElements","document","querySelectorAll","forEach","$cardTokenElement","data","dataset","Error","mollie","Mollie","locale","testmode","componentsContainer","createElement","classList","add","append","setAttribute","innerText","field","error","mollieComponent","createComponent","mount","addEventListener","event","touched","textContent","remove","form","closest","e","cardTokenInput","querySelector","preventDefault","token","createToken","message","tokenInput","dispatchEvent","Event","cancelable","submitter","name","click","setupWooCommerce","checkoutForm","body","on","$form","returnFalse","off","submit","jQuery"],"sources":["../src/components.js"],"sourcesContent":["/* global Mollie */\n'use strict';\n\n/**\n * Mollie Components.\n *\n * @link https://docs.mollie.com/components/overview\n */\n( ( $ ) => {\n\tconst components = [\n\t\t{\n\t\t\tid: 'card-holder',\n\t\t\tlabel: 'Card Holder',\n\t\t\tcomponent: 'cardHolder'\n\t\t},\n\t\t{\n\t\t\tid: 'card-number',\n\t\t\tlabel: 'Card Number',\n\t\t\tcomponent: 'cardNumber'\n\t\t},\n\t\t{\n\t\t\tid: 'expiry-date',\n\t\t\tlabel: 'Expiry',\n\t\t\tcomponent: 'expiryDate'\n\t\t},\n\t\t{\n\t\t\tid: 'verification-code',\n\t\t\tlabel: 'CVC',\n\t\t\tcomponent: 'verificationCode'\n\t\t}\n\t];\n\n\tfunction initMollieComponents( forms ) {\n\t\tlet $cardTokenElements;\n\n\t\tif ( typeof forms === 'string' ) {\n\t\t\t$cardTokenElements = document.querySelectorAll( forms + ' .pronamic_pay_mollie_card_token' );\n\t\t} else {\n\t\t\t$cardTokenElements = forms.querySelectorAll( '.pronamic_pay_mollie_card_token' );\n\t\t}\n\n\t\t$cardTokenElements.forEach( ( $cardTokenElement ) => {\n\t\t\t// Create components.\n\t\t\tconst data = $cardTokenElement.dataset;\n\n\t\t\t// Check required Mollie profile ID.\n\t\t\tif ( ! ( \"mollie-profile-id\" in data ) ) {\n\t\t\t\tthrow new Error( 'No Mollie profile ID in element dataset. Unable to load Mollie Components.' );\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Initialize Mollie object.\n\t\t\tconst mollie = Mollie(\n\t\t\t\tdata['mollie-profile-id'],\n\t\t\t\t{\n\t\t\t\t\tlocale: data['mollie-locale'] ?? null,\n\t\t\t\t\ttestmode: ( \"mollie-testmode\" in data ),\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tlet componentsContainer = document.createElement( 'div' );\n\t\t\tcomponentsContainer.classList.add( 'mollie-components' );\n\n\t\t\t$cardTokenElement.append( componentsContainer );\n\n\t\t\tcomponents.forEach( ( component ) => {\n\t\t\t\t// Label.\n\t\t\t\tlet label = document.createElement( 'label' );\n\t\t\t\tlabel.setAttribute( 'for', component.id );\n\t\t\t\tlabel.innerText = component.label;\n\n\t\t\t\t// Component container.\n\t\t\t\tlet field = document.createElement( 'div' );\n\t\t\t\tfield.setAttribute( 'id', component.id );\n\n\t\t\t\tlabel.append( field );\n\n\t\t\t\t// Error.\n\t\t\t\tlet error = document.createElement( 'div' );\n\t\t\t\terror.setAttribute( 'id', component.id + '-error' );\n\t\t\t\terror.setAttribute( 'role', 'alert' );\n\t\t\t\terror.classList.add( 'field-error' );\n\n\t\t\t\tcomponentsContainer.append( label, error );\n\n\t\t\t\t// Create and mount component.\n\t\t\t\tlet mollieComponent = mollie.createComponent( component.component );\n\n\t\t\t\tmollieComponent.mount( '#' + component.id );\n\n\t\t\t\t// Handle errors.\n\t\t\t\tmollieComponent.addEventListener( 'change', ( event ) => {\n\t\t\t\t\t// Add error.\n\t\t\t\t\tif ( event.error && event.touched ) {\n\t\t\t\t\t\terror.textContent = event.error;\n\n\t\t\t\t\t\tlabel.classList.add( 'is-invalid' );\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove error.\n\t\t\t\t\terror.textContent = '';\n\n\t\t\t\t\tlabel.classList.remove( 'is-invalid' );\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\t// Create Mollie token on checkout submit.\n\t\t\tconst form = $cardTokenElement.closest( 'form' );\n\n\t\t\tform.addEventListener( 'submit', async ( e ) => {\n\t\t\t\t// Check existing card token input.\n\t\t\t\tlet cardTokenInput = form.querySelector( 'input[name=\"pronamic_pay_mollie_card_token\"]' );\n\n\t\t\t\tif ( cardTokenInput ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\te.preventDefault();\n\n\t\t\t\t// Create token.\n\t\t\t\tconst { token, error } = await mollie.createToken();\n\n\t\t\t\tif ( error ) {\n\t\t\t\t\tthrow new Error( error.message || '' );\n\t\t\t\t}\n\n\t\t\t\t// Add token to form.\n\t\t\t\tconst tokenInput = document.createElement( 'input' );\n\t\t\t\ttokenInput.setAttribute( 'type', 'hidden' );\n\t\t\t\ttokenInput.setAttribute( 'name', 'pronamic_pay_mollie_card_token' );\n\t\t\t\ttokenInput.setAttribute( 'value', token );\n\n\t\t\t\tform.append( tokenInput );\n\n\t\t\t\tif ( false !== form.dispatchEvent( new Event( 'pronamic_pay_mollie_components_card_token_added', { cancelable: true } ) ) ) {\n\t\t\t\t\t// Submit form, now containing the hidden card token field.\n\t\t\t\t\t// form.submit(); — not working with test meta box\n\t\t\t\t\tform.querySelector( '[name=\"' + e.submitter.name + '\"]' ).click();\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\tfunction setupWooCommerce() {\n\t\tconst checkoutForm = document.querySelector( 'form.woocommerce-checkout' );\n\n\t\tif ( ! checkoutForm ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Init components on updated checkout.\n\t\t$( document.body ).on( 'updated_checkout', function( e ) {\n\t\t\tinitMollieComponents( checkoutForm );\n\t\t} );\n\n\t\tconst $form = $( checkoutForm );\n\n\t\t// Prevent placing order, need to create token first.\n\t\t$form.on( 'checkout_place_order_pronamic_pay_credit_card', returnFalse );\n\n\t\t// Re-enable placing order if card token has been added.\n\t\tcheckoutForm.addEventListener( 'pronamic_pay_mollie_components_card_token_added', function( e ) {\n\t\t\te.preventDefault();\n\n\t\t\t$form.off( 'checkout_place_order_pronamic_pay_credit_card', returnFalse );\n\n\t\t\t$form.submit();\n\t\t} );\n\t}\n\n\tfunction returnFalse() {\n\t\treturn false;\n\t}\n\n\t// Init Mollie Components.\n\tdocument.addEventListener( 'DOMContentLoaded', function( e ) {\n\t\tinitMollieComponents( 'form:not(.woocommerce-checkout)' );\n\t} );\n\n\tsetupWooCommerce();\n} )( jQuery );\n"],"mappings":"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"} \ No newline at end of file diff --git a/js/dist/mollie.min.js b/js/dist/mollie.min.js new file mode 100644 index 0000000..5d75b3d --- /dev/null +++ b/js/dist/mollie.min.js @@ -0,0 +1 @@ +!function(){function e(e){const n=Mollie(e.profileId,e.options),t=document.getElementById(e.elementId);if(!t)throw new Error("No data token element.");if(!t.form)throw new Error("Data token element not in form.");const o=t.form;o.addEventListener("submit",(async function e(i){i.preventDefault();const{token:r}=await n.createToken();r&&(t.value=r),o.removeEventListener("submit",e),o.requestSubmit(i.submitter),o.addEventListener("submit",e)}));n.createComponent("card").mount(e.mount)}window.pronamicPayMollieFields=window.pronamicPayMollieFields||[],window.pronamicPayMollieFields.push=function(){Array.from(arguments).forEach((function(n){e(n)}))},window.pronamicPayMollieFields.filter((function(n){return e(n),!1}))}(); \ No newline at end of file diff --git a/js/src/components.js b/js/src/components.js deleted file mode 100644 index 0021892..0000000 --- a/js/src/components.js +++ /dev/null @@ -1,184 +0,0 @@ -/* global Mollie */ -'use strict'; - -/** - * Mollie Components. - * - * @link https://docs.mollie.com/components/overview - */ -( ( $ ) => { - const components = [ - { - id: 'card-holder', - label: 'Card Holder', - component: 'cardHolder' - }, - { - id: 'card-number', - label: 'Card Number', - component: 'cardNumber' - }, - { - id: 'expiry-date', - label: 'Expiry', - component: 'expiryDate' - }, - { - id: 'verification-code', - label: 'CVC', - component: 'verificationCode' - } - ]; - - function initMollieComponents( forms ) { - let $cardTokenElements; - - if ( typeof forms === 'string' ) { - $cardTokenElements = document.querySelectorAll( forms + ' .pronamic_pay_mollie_card_token' ); - } else { - $cardTokenElements = forms.querySelectorAll( '.pronamic_pay_mollie_card_token' ); - } - - $cardTokenElements.forEach( ( $cardTokenElement ) => { - // Create components. - const data = $cardTokenElement.dataset; - - // Check required Mollie profile ID. - if ( ! $cardTokenElement.dataset.mollieProfileId ) { - throw new Error( 'No Mollie profile ID in element dataset. Unable to load Mollie Components.' ); - - return; - } - - // Initialize Mollie object. - const mollie = Mollie( - $cardTokenElement.dataset.mollieProfileId, - { - locale: $cardTokenElement.dataset.mollieLocale ?? null, - testmode: '1' === $cardTokenElement.dataset.mollieTestmode, - } - ); - - let componentsContainer = document.createElement( 'div' ); - componentsContainer.classList.add( 'mollie-components' ); - - $cardTokenElement.append( componentsContainer ); - - components.forEach( ( component ) => { - // Label. - let label = document.createElement( 'label' ); - label.setAttribute( 'for', component.id ); - label.innerText = component.label; - - // Component container. - let field = document.createElement( 'div' ); - field.setAttribute( 'id', component.id ); - - label.append( field ); - - // Error. - let error = document.createElement( 'div' ); - error.setAttribute( 'id', component.id + '-error' ); - error.setAttribute( 'role', 'alert' ); - error.classList.add( 'field-error' ); - - componentsContainer.append( label, error ); - - // Create and mount component. - let mollieComponent = mollie.createComponent( component.component ); - - mollieComponent.mount( '#' + component.id ); - - // Handle errors. - mollieComponent.addEventListener( 'change', ( event ) => { - // Add error. - if ( event.error && event.touched ) { - error.textContent = event.error; - - label.classList.add( 'is-invalid' ); - - return; - } - - // Remove error. - error.textContent = ''; - - label.classList.remove( 'is-invalid' ); - } ); - } ); - - // Create Mollie token on checkout submit. - const form = $cardTokenElement.closest( 'form' ); - - form.addEventListener( 'submit', async ( e ) => { - // Check existing card token input. - let cardTokenInput = form.querySelector( 'input[name="pronamic_pay_mollie_card_token"]' ); - - if ( cardTokenInput ) { - return; - } - - e.preventDefault(); - - // Create token. - const { token, error } = await mollie.createToken(); - - if ( error ) { - throw new Error( error.message || '' ); - } - - // Add token to form. - const tokenInput = document.createElement( 'input' ); - tokenInput.setAttribute( 'type', 'hidden' ); - tokenInput.setAttribute( 'name', 'pronamic_pay_mollie_card_token' ); - tokenInput.setAttribute( 'value', token ); - - form.append( tokenInput ); - - if ( false !== form.dispatchEvent( new Event( 'pronamic_pay_mollie_components_card_token_added', { cancelable: true } ) ) ) { - // Submit form, now containing the hidden card token field. - // form.submit(); — not working with test meta box - form.querySelector( '[name="' + e.submitter.name + '"]' ).click(); - } - } ); - } ); - } - - function setupWooCommerce() { - const checkoutForm = document.querySelector( 'form.woocommerce-checkout' ); - - if ( ! checkoutForm ) { - return; - } - - // Init components on updated checkout. - $( document.body ).on( 'updated_checkout', function( e ) { - initMollieComponents( checkoutForm ); - } ); - - const $form = $( checkoutForm ); - - // Prevent placing order, need to create token first. - $form.on( 'checkout_place_order_pronamic_pay_credit_card', returnFalse ); - - // Re-enable placing order if card token has been added. - checkoutForm.addEventListener( 'pronamic_pay_mollie_components_card_token_added', function( e ) { - e.preventDefault(); - - $form.off( 'checkout_place_order_pronamic_pay_credit_card', returnFalse ); - - $form.submit(); - } ); - } - - function returnFalse() { - return false; - } - - // Init Mollie Components. - document.addEventListener( 'DOMContentLoaded', function( e ) { - initMollieComponents( 'form:not(.woocommerce-checkout)' ); - } ); - - setupWooCommerce(); -} )( jQuery ); diff --git a/js/src/mollie.js b/js/src/mollie.js new file mode 100644 index 0000000..d214fa5 --- /dev/null +++ b/js/src/mollie.js @@ -0,0 +1,56 @@ +/* global Mollie */ +( function () { + function init( data ) { + const mollie = Mollie( data.profileId, data.options ); + + const tokenElement = document.getElementById( data.elementId ); + + if ( ! tokenElement ) { + throw new Error( 'No data token element.' ); + } + + if ( ! tokenElement.form ) { + throw new Error( 'Data token element not in form.' ); + } + + const form = tokenElement.form; + + async function createToken( e ) { + e.preventDefault(); + + const { token } = await mollie.createToken(); + + if ( token ) { + tokenElement.value = token; + } + + form.removeEventListener( 'submit', createToken ); + + form.requestSubmit( e.submitter ); + + form.addEventListener( 'submit', createToken ); + } + + form.addEventListener( 'submit', createToken ); + + const cardComponent = mollie.createComponent( 'card' ); + + cardComponent.mount( data.mount ); + } + + window.pronamicPayMollieFields = window.pronamicPayMollieFields || []; + + window.pronamicPayMollieFields.push = function () { + const args = Array.from( arguments ); + + args.forEach( function ( data ) { + init( data ); + } ); + }; + + window.pronamicPayMollieFields.filter( function ( item ) { + init( item ); + + return false; + } ); +} )(); diff --git a/package.json b/package.json index ceb721e..ebbfc7c 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,16 @@ "homepage": "http://www.wp-pay.org/gateways/mollie/", "devDependencies": { "@wordpress/env": "^8.13.0", - "npm-run-all": "^4.1.5" + "@wordpress/prettier-config": "^3.4.0", + "@wordpress/scripts": "^26.18.0", + "npm-run-all": "^4.1.5", + "prettier": "npm:wp-prettier@^3.0.3", + "terser": "^5.24.0" }, + "prettier": "@wordpress/prettier-config", "scripts": { + "lint-js": "wp-scripts lint-js ./js/src", + "js-minify": "terser js/src/mollie.js --compress --mangle --output js/dist/mollie.min.js", "wp-env-setup": "npm-run-all wp-env-setup-*", "wp-env-setup-mollie": "wp-env run cli wp config set MOLLIE_API_KEY $MOLLIE_API_KEY", "wp-env-setup-buckaroo-website-key": "wp-env run cli wp config set BUCKAROO_WEBSITE_KEY $BUCKAROO_WEBSITE_KEY", diff --git a/src/CardField.php b/src/CardField.php new file mode 100644 index 0000000..649b2a1 --- /dev/null +++ b/src/CardField.php @@ -0,0 +1,148 @@ + + * @copyright 2005-2022 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\Pay\Mollie + */ + +namespace Pronamic\WordPress\Pay\Gateways\Mollie; + +use Pronamic\WordPress\Html\Element; +use Pronamic\WordPress\Pay\Fields\Field; + +/** + * Mollie card field class + */ +class CardField extends Field { + /** + * Gatweay. + * + * @var Gateway + */ + private $gateway; + + /** + * Construct card field. + * + * @param string $id ID. + * @param Gateway $gateway Gateway. + */ + public function __construct( $id, Gateway $gateway ) { + parent::__construct( $id ); + + $this->gateway = $gateway; + } + + /** + * Setup field. + */ + public function setup(): void { + \wp_register_script( + 'mollie.js', + 'https://js.mollie.com/v1/mollie.js', + [], + // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL. + null, + false + ); + + $file = '../css/components.css'; + + \wp_register_style( + 'pronamic-pay-mollie-components', + \plugins_url( $file, __FILE__ ), + [], + \hash_file( 'crc32b', __DIR__ . '/' . $file ), + ); + + $file = '../js/dist/mollie.min.js'; + + \wp_register_script( + 'pronamic-pay-mollie', + \plugins_url( $file, __FILE__ ), + [ 'mollie.js' ], + \hash_file( 'crc32b', __DIR__ . '/' . $file ), + true + ); + } + + /** + * Get element. + * + * @return Element|null + */ + protected function get_element() { + \wp_enqueue_script( 'pronamic-pay-mollie' ); + + \wp_enqueue_style( 'pronamic-pay-mollie' ); + + $element = new Element( 'div' ); + + $element->children[] = new Element( + 'div', + [ + 'id' => 'card', + ] + ); + + $element->children[] = new Element( + 'input', + [ + 'id' => $this->get_id(), + 'name' => $this->get_id(), + 'type' => 'hidden', + ] + ); + + return $element; + } + + /** + * Ouput. + * + * @return void + */ + public function output() { + parent::output(); + + try { + $profile_id = $this->gateway->get_profile_id(); + } catch ( \Exception $e ) { + return; + } + + $locale_transformer = new LocaleTransformer(); + + $data = [ + 'elementId' => $this->get_id(), + 'profileId' => $profile_id, + 'options' => [ + 'locale' => $locale_transformer->transform_wp_to_mollie( \get_locale() ), + 'testmode' => 'test' === $this->gateway->get_mode(), + ], + 'mount' => '#card', + ]; + + ?> + + - * @copyright 2005-2022 Pronamic - * @license GPL-3.0-or-later - * @package Pronamic\WordPress\Pay\Mollie - */ - -namespace Pronamic\WordPress\Pay\Gateways\Mollie; - -use Pronamic\WordPress\Html\Element; -use Pronamic\WordPress\Pay\Fields\Field; - -/** - * HTML field class - */ -class ComponentsField extends Field { - /** - * Mollie profile ID. - */ - private ?string $profile_id; - - /** - * Setup field. - */ - public function setup(): void { - // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL. - \wp_register_script( - 'pronamic-pay-mollie', - 'https://js.mollie.com/v1/mollie.js', - [], - null, - false - ); - - $file = '../css/components.css'; - - \wp_register_style( - 'pronamic-pay-mollie-components', - \plugins_url( $file, __FILE__ ), - [], - \hash_file( 'crc32b', __DIR__ . '/' . $file ), - ); - - $file = '../js/dist/components.js'; - - \wp_register_script( - 'pronamic-pay-mollie-components', - \plugins_url( $file, __FILE__ ), - [ 'pronamic-pay-mollie' ], - \hash_file( 'crc32b', __DIR__ . '/' . $file ), - true - ); - } - - /** - * Set Mollie profile ID. - * - * @param string $profile_id Mollie profile ID. - */ - public function set_profile_id( string $profile_id ): void { - $this->profile_id = $profile_id; - } - - /** - * Get element. - * - * @return Element|null - */ - protected function get_element() { - \wp_enqueue_script( 'pronamic-pay-mollie-components' ); - - \wp_enqueue_style( 'pronamic-pay-mollie-components' ); - - $locale_transformer = new LocaleTransformer(); - - $element = new Element( - 'div', - [ - 'class' => $this->get_id(), - 'data-mollie-profile-id' => $this->profile_id, - 'data-mollie-locale' => $locale_transformer->transform_wp_to_mollie( \get_locale() ), - 'data-mollie-testmode' => true, - ] - ); - - return $element; - } - - /** - * Serialize to JSON. - */ - public function jsonSerialize() : array { - $data = parent::jsonSerialize(); - - $data['type'] = 'html'; - - return $data; - } -} diff --git a/src/Gateway.php b/src/Gateway.php index 8390bc7..6fae26a 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -127,28 +127,8 @@ function () { $field_consumer_iban->set_required( true ); $field_consumer_iban->meta_key = 'consumer_bank_details_iban'; - $field_mollie_components = new ComponentsField( 'pronamic_pay_mollie_card_token' ); - $field_mollie_components->meta_key = 'mollie_card_token'; - - $cache_key = 'pronamic_pay_mollie_profile_id_' . \md5( (string) \wp_json_encode( $config ) ); - - $profile_id = \get_transient( $cache_key ); - - if ( false === $profile_id ) { - try { - $current_profile = $this->client->get_current_profile(); - - $profile = Profile::from_object( $current_profile ); - - $profile_id = $profile->get_id(); - } catch ( \Exception $e ) { - // No problem. - } - - \set_transient( $cache_key, $profile_id, \DAY_IN_SECONDS ); - } - - $field_mollie_components->set_profile_id( (string) $profile_id ); + $field_mollie_card = new CardField( 'pronamic_pay_mollie_card_token', $this ); + $field_mollie_card->meta_key = 'mollie_card_token'; // Apple Pay. $payment_method_apple_pay = new PaymentMethod( PaymentMethods::APPLE_PAY ); @@ -177,7 +157,7 @@ function () { // Payment method credit card. $payment_method_credit_card = new PaymentMethod( PaymentMethods::CREDIT_CARD ); $payment_method_credit_card->add_support( 'recurring' ); - $payment_method_credit_card->add_field( $field_mollie_components ); + $payment_method_credit_card->add_field( $field_mollie_card ); $this->register_payment_method( $payment_method_credit_card ); @@ -265,6 +245,29 @@ function () { $this->register_payment_method( $payment_method_sofort ); } + /** + * Get profile ID. + * + * @return string + */ + public function get_profile_id() { + $cache_key = 'pronamic_pay_mollie_profile_id_' . \md5( (string) \wp_json_encode( $this->config ) ); + + $profile_id = \get_transient( $cache_key ); + + if ( false === $profile_id ) { + $current_profile = $this->client->get_current_profile(); + + $profile = Profile::from_object( $current_profile ); + + $profile_id = $profile->get_id(); + + \set_transient( $cache_key, $profile_id, \DAY_IN_SECONDS ); + } + + return $profile_id; + } + /** * Get payment methods. *