diff --git a/.github/workflows/sylius.yaml b/.github/workflows/sylius.yaml index 15e307f9..33dc6c31 100644 --- a/.github/workflows/sylius.yaml +++ b/.github/workflows/sylius.yaml @@ -81,15 +81,6 @@ jobs: - name: 'Install Sylius-Standard and Plugin' run: 'make install -e SYLIUS_VERSION=${{ matrix.sylius }} SYMFONY_VERSION=${{ matrix.symfony }}' - - - name: 'Install certificates' - run: 'symfony server:ca:install' - - - name: 'Run Chrome headless' - run: 'google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server=''direct://'' --proxy-bypass-list=''*'' https://127.0.0.1 > /dev/null 2>&1 &' - - - name: 'Run webserver' - run: 'symfony server:start --port=8080 --daemon' id: end-of-setup-sylius - name: 'Doctrine Schema Validate - Run' @@ -98,14 +89,6 @@ jobs: name: 'Run PHPUnit' run: 'make phpunit' if: 'always() && steps.end-of-setup-sylius.outcome == ''success''' -# - -# name: 'Configure Behat' -# run: 'make behat-configure' -# if: 'always() && steps.end-of-setup-sylius.outcome == ''success''' -# - -# name: 'Run behat' -# run: 'vendor/bin/behat --strict --no-interaction -f progress || vendor/bin/behat --strict -vvv --no-interaction --rerun' -# if: 'always() && steps.end-of-setup-sylius.outcome == ''success''' - uses: actions/upload-artifact@v4 if: failure() diff --git a/.gitignore b/.gitignore index 19975f9a..cde5dab0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,10 @@ /tests/TestApplication/.env.local /tests/TestApplication/.env.*.local /var/ + + +Audit.md +CLAUDE.md +.DS_Store +.claude +.review diff --git a/README.md b/README.md index 5d2f4eea..610a38d8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ -[](https://github.com/payplug/SyliusPayPlugPlugin/blob/master/LICENSE) - -[](https://packagist.org/packages/payplug/payplug-sylius) -[](https://packagist.org/packages/payplug/payplug-sylius) +[](https://github.com/payplug/SyliusPayPlugPlugin/blob/master/LICENSE) +[](https://github.com/payplug/SyliusPayPlugPlugin/actions/workflows/analysis.yaml) +[](https://github.com/payplug/SyliusPayPlugPlugin/actions/workflows/sylius.yaml) +[](https://packagist.org/packages/payplug/sylius-payplug-plugin) +[](https://packagist.org/packages/payplug/sylius-payplug-plugin)
@@ -26,150 +31,145 @@ In local environment, the plugin will not work properly because you will not be ## Compatibility -| | Version | -| :--- |:----------------------| -| PHP | 7.4, 8.0, 8.1 | -| Sylius | 1.9, 1.10, 1.11, 1.12 | +| | Version | +|:-------|:--------| +| PHP | ^8.2 | +| Sylius | ^2.0 | -## Installation -1. Require the **payplug/sylius-payplug-plugin** : +### With Symfony Flex - ```bash - composer config extra.symfony.allow-contrib true - composer require payplug/sylius-payplug-plugin - ``` +#### 1. Allow contrib recipes and require the plugin -2. Apply migrations to your database: +```bash +composer config extra.symfony.allow-contrib true +composer require payplug/sylius-payplug-plugin +``` - ```shell - bin/console doctrine:migrations:migrate - ``` +#### 2. Install the Flex recipe + +```bash +composer recipes:install payplug/sylius-payplug-plugin --force +``` -3. Copy templates that are overridden by Sylius into `templates/bundles/SyliusAdminBundle` - - ```shell - mkdir -p templates/bundles/SyliusAdminBundle/ - cp -R vendor/payplug/sylius-payplug-plugin/templates/SyliusAdminBundle/* templates/bundles/SyliusAdminBundle/ - ``` +This automatically registers the bundle, copies configuration files, and sets up assets (on Sylius 2.1+). -4. Add Payplug to refundable payment method for Sylius Refund Plugin in `config/services.yaml` +#### 3. Apply migrations to your database - ```yaml - parameters: - locale: fr_FR - sylius_refund.supported_gateways: - - payplug - - payplug_oney - - payplug_bancontact - - payplug_apple_pay - - payplug_american_express - ``` +```shell +bin/console doctrine:migrations:migrate +``` -5. Add Payplug routes in `config/routes.yaml` +#### 4. Add Payplug to refundable payment methods for Sylius Refund Plugin in `config/services.yaml` - ```yaml - sylius_payplug: - resource: "@PayPlugSyliusPayPlugPlugin/config/routing.yaml" - ``` +```yaml +parameters: + locale: fr_FR + sylius_refund.supported_gateways: + - payplug + - payplug_oney + - payplug_bancontact + - payplug_apple_pay + - payplug_american_express +``` -8. Add Traits for Customer and PaymentMethod entities +#### 5. Add Traits for Customer and PaymentMethod entities * App\Entity\Customer\Customer - ```php - Payment methods`, then click on `Create` and choose "**Payplug**". @@ -199,30 +199,6 @@ Run the below command to see what Symfony services are shared with this plugin: $ bin/console debug:container payplug_sylius_payplug_plugin ``` -### Template overriding - -This plugin override some sylius templates. -If you plan override them also, you should retrieve them in your application. - -Copy Sylius templates overridden in plugin to your templates directory (e.g templates/bundles/) - -```shell -mkdir -p templates/bundles/SyliusAdminBundle/ -mkdir -p templates/bundles/SyliusShopBundle/ -mkdir -p templates/bundles/SyliusUiBundle/ -cp -R vendor/payplug/sylius-payplug-plugin/templates/SyliusAdminBundle/* templates/bundles/SyliusAdminBundle/ -cp -R vendor/payplug/sylius-payplug-plugin/templates/SyliusShopBundle/* templates/bundles/SyliusShopBundle/ -cp -R vendor/payplug/sylius-payplug-plugin/templates/SyliusUiBundle/* templates/bundles/SyliusUiBundle/ -``` - -You also need to edit your twig config to add your path to avoid our configuration to be prepended : -```yaml -twig: - paths: - '%kernel.project_dir%/templates/bundles/SyliusAdminBundle': SyliusAdmin - '%kernel.project_dir%/templates/bundles/SyliusShopBundle': SyliusShop - '%kernel.project_dir%/templates/bundles/SyliusUiBundle': SyliusUi -``` ## Development See [How to contribute](CONTRIBUTING.md). diff --git a/assets/shop/controllers/apple-pay_controller.js b/assets/shop/controllers/apple-pay_controller.js index e9d99adc..fe977eb6 100644 --- a/assets/shop/controllers/apple-pay_controller.js +++ b/assets/shop/controllers/apple-pay_controller.js @@ -1,166 +1,181 @@ import { Controller } from '@hotwired/stimulus'; -import $ from 'jquery'; /* stimulusFetch: 'lazy' */ export default class extends Controller { - connect() { - this.applePayHandler(); + static values = { + paymentInputId: String, + settings: Object, + notice: String, } - showApplePayButton() { - const applePayButton = $(document).find("apple-pay-button"); - if (applePayButton.length) { - applePayButton.addClass('enabled'); + + connect() { + this.applePayButton = this.element.querySelector('apple-pay-button'); + this.onApplePayButtonClick = this.onApplePayButtonClick.bind(this); + + if (this.applePayButton) { + this.applePayButton.addEventListener('click', this.onApplePayButtonClick); } + + this.retryCount = 0; + this.initApplePay(); } - hideApplePayButton() { - const applePayButton = $(document).find("apple-pay-button.enabled"); - if (applePayButton.length) { - applePayButton.removeClass('enabled'); + + initApplePay() { + if (typeof window.ApplePaySession !== 'undefined') { + this.checkSupport(); + return; + } + + if (this.retryCount < 20) { // Try for 2 seconds + this.retryCount++; + setTimeout(() => this.initApplePay(), 100); + } else { + this.disablePaymentMethod(); } } - applePayHandler() { - const applePayChoice = $(".payment-item .checkbox-applepay input:radio"); - if (applePayChoice) { - if (applePayChoice.is(':checked')) { - this.disableNextStepButton(); - this.showApplePayButton(); + + checkSupport() { + try { + const isSupported = window.ApplePaySession.canMakePayments(); + + if (isSupported && this.applePayButton) { + this.applePayButton.classList.add('enabled'); } else { - this.enableNextStepButton(); - this.hideApplePayButton(); + this.disablePaymentMethod(); } + } catch (e) { + this.disablePaymentMethod(); } - $(".payment-item .checkbox input:radio").on('change', this.onPaymentMethodChoice); - $(document).find("apple-pay-button").on('click', this.onApplePayButtonClick); } - onPaymentMethodChoice(event) { - const isApplePay = $(event.currentTarget).closest('.checkbox-applepay').length; - if (isApplePay) { - this.showApplePayButton(); - this.disableNextStepButton(); - } else { - this.hideApplePayButton(); - this.enableNextStepButton(); + + disablePaymentMethod() { + if (!this.paymentInputIdValue) return; + + const input = document.getElementById(this.paymentInputIdValue); + if (!input) return; + + const container = input.closest('.card, .item, .form-check, .field'); + if (container) { + container.style.opacity = '0.5'; + container.style.pointerEvents = 'none'; + container.classList.add('apple-pay-ineligible'); + + // Add a small info message if not already present + if (!container.querySelector('.apple-pay-notice')) { + const notice = document.createElement('div'); + notice.className = 'apple-pay-notice small text-muted mt-2'; + notice.style.padding = '0 1rem 1rem'; + notice.innerText = this.noticeValue || 'Apple Pay is not available on this browser or device.'; + container.appendChild(notice); + } + + if (input.checked) { + input.checked = false; + input.dispatchEvent(new Event('change', { bubbles: true })); + } + } + + input.disabled = true; + + if (this.element) { + this.element.style.display = 'none'; } } - onApplePayButtonClick(event) { - const applePayButton = $(event.currentTarget); - if (applePaySessionRequestSettings === undefined) { + async onApplePayButtonClick(event) { + const applePayButton = event.currentTarget; + + if (!this.settingsValue) { console.error('Invalid Apple Pay settings!'); - return false; + return; } - // Create ApplePaySession - const session = new ApplePaySession(3, applePaySessionRequestSettings); + if (typeof window.ApplePaySession === 'undefined') { + return; + } - session.onvalidatemerchant = async event => { - $.ajax({ - url: applePayButton.data('validate-merchant-route'), - method: 'POST', - cache: false, - data: {}, - success: (authorization) => { - let result = authorization.merchant_session; - console.log(result); + // Prepare settings + const settings = { ...this.settingsValue }; + if (settings.applePayDomain) { + settings.applicationData = btoa(JSON.stringify({ + 'apple_pay_domain': settings.applePayDomain + })); + delete settings.applePayDomain; + } + + try { + const version = window.ApplePaySession.supportsVersion(14) ? 14 : 3; + const session = new window.ApplePaySession(version, settings); + + session.onvalidatemerchant = async (event) => { + try { + const response = await fetch(applePayButton.dataset.validateMerchantRoute, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + const authorization = await response.json(); if (authorization.success === true) { - console.log(authorization.merchant_session); - session.completeMerchantValidation(result); + session.completeMerchantValidation(authorization.merchant_session); } else { session.abort(); } - }, - error: (XHR, status, error) => { - console.log(XHR, status, error); + } catch (error) { + console.error(error); session.abort(); window.location.reload(); - }, - }); - }; - - session.onpaymentauthorized = event => { - $.ajax({ - url: applePayButton.data('payment-authorized-route'), - method: 'POST', - cache: false, - data: { - token: event.payment.token - }, - success: (authorization) => { - try { - var apple_pay_Session_status = ApplePaySession.STATUS_SUCCESS; - - console.log(authorization); - console.log(authorization.data.responseToApple.status); - if (authorization.data.responseToApple.status != 1) { - apple_pay_Session_status = ApplePaySession.STATUS_FAILURE; - } - - const result = { - "status": apple_pay_Session_status - }; - - console.log(apple_pay_Session_status); - console.log(result); - - session.completePayment(result); - - console.log(authorization.data.returnUrl); - window.location.href = authorization.data.returnUrl; - } catch (err) { - console.error(err); - window.location.reload(); + } + }; + + session.onpaymentauthorized = async (event) => { + try { + const response = await fetch(applePayButton.dataset.paymentAuthorizedRoute, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: event.payment.token, + }), + }); + const authorization = await response.json(); + + let applePaySessionStatus = window.ApplePaySession.STATUS_SUCCESS; + + if (authorization.data.responseToApple.status !== 1) { + applePaySessionStatus = window.ApplePaySession.STATUS_FAILURE; } - }, - error: (XHR, status, error) => { - console.log(XHR, status, error); - session.abort(); + + session.completePayment({ "status": applePaySessionStatus }); + window.location.href = authorization.data.returnUrl; + } catch (error) { + console.error(error); window.location.reload(); - }, - }); - }; - - session.oncancel = event => { - console.log('Cancelling Apple Pay session!'); - - $.ajax({ - url: applePayButton.data('session-cancel-route'), - cache: false, - method: 'POST', - data: {}, - success: (authorization) => { - console.log('Cancelled!'); - console.log(authorization.data.returnUrl); + } + }; + + session.oncancel = async (event) => { + try { + const response = await fetch(applePayButton.dataset.sessionCancelRoute, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + const authorization = await response.json(); window.location.href = authorization.data.returnUrl; - }, - error: (XHR, status, error) => { - console.log(XHR, status, error); + } catch (error) { + console.error(error); window.location.reload(); - }, - }); - }; + } + }; - session.begin(); - } - disableNextStepButton() { - const nextStepButton = $('form[name*="checkout_select_payment"] [data-test-next-step]'); - nextStepButton.replaceWith( - $("", { - id: 'next-step', - class: 'btn btn-primary btn-icon', - html: nextStepButton.html() - }) - ); - } - enableNextStepButton() { - const nextStepButton = $('form[name*="checkout_select_payment"] [data-test-next-step]'); - nextStepButton.replaceWith( - $("", { - type: 'submit', - id: 'next-step', - class: 'btn btn-primary btn-icon', - html: nextStepButton.html() - }) - ); + session.begin(); + } catch (e) { + console.error('Failed to create Apple Pay session:', e); + } } } diff --git a/assets/shop/controllers/checkout-select-payment_controller.js b/assets/shop/controllers/checkout-select-payment_controller.js index e2a21c74..ead033d4 100644 --- a/assets/shop/controllers/checkout-select-payment_controller.js +++ b/assets/shop/controllers/checkout-select-payment_controller.js @@ -4,46 +4,126 @@ import $ from 'jquery'; /* stimulusFetch: 'lazy' */ export default class extends Controller { static targets = ['trigger']; - form= null; + form = null; connect() { + this.form = this.element.closest('form') || document.querySelector('form[name*="checkout_select_payment"]'); + this.findNextStepButton(); + + this.handleStateChange = this.handleStateChange.bind(this); + this.element.addEventListener('payment-method-state-change', this.handleStateChange); + this.toggleGateway(); - this.form = document.querySelector('form[name*="checkout_select_payment"]'); - this.form.addEventListener('submit', (event) => { - this.handleForm(); - }); + if (this.form) { + this.form.addEventListener('submit', (event) => { + this.handleForm(); + }); + } + } + + disconnect() { + this.element.removeEventListener('payment-method-state-change', this.handleStateChange); + } + + handleStateChange(event) { + this.updateNextStepButtonVisibility(event.target); + } + + findNextStepButton() { + if (!this.form) return; + this.nextStepButton = this.form.querySelector('button[type="submit"], button:not([type])'); } + toggleGateway() { - let checkedPaymentMethodInput; + const $inputs = $(this.element).find('input[id*="checkout_select_payment_payments"]'); + const checkedInput = $inputs.filter(':checked')[0]; - $(this.triggerTargets).each((k, v) => { - const paymentMethodInputId = $(v).data('payment-input-id'); - checkedPaymentMethodInput = $(`#${paymentMethodInputId}:checked`); - if (checkedPaymentMethodInput.length) { - return false; - } - }) + // Hide all initially + this.triggerTargets.forEach(target => $(target).hide()); - if (checkedPaymentMethodInput?.length) { - $(`.payment-method-choice[data-payment-input-id="${$(checkedPaymentMethodInput).attr('id')}"]`).show(); + if (checkedInput) { + const currentTarget = this.triggerTargets.find(target => target.dataset.paymentInputId === checkedInput.id); + if (currentTarget) { + $(currentTarget).show(); + this.updateNextStepButtonVisibility(currentTarget); + } else { + this.toggleNextStepButton(true); + } } - const $inputs = $('input[id*="checkout_select_payment_payments"]'); $inputs.on('change', (event) => { - const clickedPaymentMethodId = $(event.currentTarget).attr('id'); - $('.payment-method-choice').slideUp(); // Hide others - $(`.payment-method-choice[data-payment-input-id="${clickedPaymentMethodId}"]`).slideDown(); // Show current + const clickedPaymentMethodId = event.currentTarget.id; + + // Hide all choices + this.triggerTargets.forEach(target => $(target).slideUp()); + + const currentTarget = this.triggerTargets.find(target => target.dataset.paymentInputId === clickedPaymentMethodId); + if (currentTarget) { + $(currentTarget).slideDown(() => { + this.updateNextStepButtonVisibility(currentTarget); + }); + } else { + this.toggleNextStepButton(true); + } }); } + + updateNextStepButtonVisibility(container) { + let targetContainer = container; + + if (!targetContainer || !targetContainer.dataset.paymentInputId) { + // Fallback to currently checked input + const checkedInput = this.element.querySelector('input[id*="checkout_select_payment_payments"]:checked'); + if (checkedInput) { + targetContainer = this.triggerTargets.find(target => target.dataset.paymentInputId === checkedInput.id); + } + } + + if (!targetContainer) { + this.toggleNextStepButton(true); + return; + } + + const handlesSubmit = targetContainer.dataset.paymentInlineSubmit === 'true' || + targetContainer.querySelector('[data-payment-inline-submit="true"]') !== null; + + this.toggleNextStepButton(!handlesSubmit); + } + + toggleNextStepButton(show) { + if (!this.nextStepButton) { + this.findNextStepButton(); + } + if (!this.nextStepButton) return; + + if (show) { + this.nextStepButton.classList.remove('disabled'); + this.nextStepButton.disabled = false; + } else { + this.nextStepButton.classList.add('disabled'); + this.nextStepButton.disabled = true; + } + } + + enableNextStepButton() { + this.toggleNextStepButton(true); + } + + disableNextStepButton() { + this.toggleNextStepButton(false); + } + handleForm() { if ($('.checkbox-oney :radio:checked').length) { - $('.checkbox-payplug').closest('.payment-item').find('.payment-choice__input:checked').prop('checked', false); + $('.checkbox-payplug').closest('.oney-payment-choice__item, .payplug-payment-choice__item, .form-check, .payment-item').find('.payment-choice__input:checked').prop('checked', false); } else if ($('.checkbox-payplug :radio:checked').length) { - $('.checkbox-oney').closest('.payment-item').find('.payment-choice__input:checked').prop('checked', false); + $('.checkbox-oney').closest('.oney-payment-choice__item, .payplug-payment-choice__item, .form-check, .payment-item').find('.payment-choice__input:checked').prop('checked', false); } - $('input#payplug_choice_card_other').attr('disabled', true); - this.form.submit(); + const otherCardOther = document.querySelector('input#payplug_choice_card_other'); + if (otherCardOther) { + otherCardOther.disabled = true; + } } } diff --git a/assets/shop/controllers/integrated-payment_controller.js b/assets/shop/controllers/integrated-payment_controller.js index 4af78c21..5c85c16e 100644 --- a/assets/shop/controllers/integrated-payment_controller.js +++ b/assets/shop/controllers/integrated-payment_controller.js @@ -3,6 +3,7 @@ import WebFont from 'webfontloader'; /* stimulusFetch: 'lazy' */ export default class extends Controller { + static targets = ['container'] static values = { code: String, factoryName: String, @@ -55,23 +56,32 @@ export default class extends Controller { // Verify if we have required data // TODO: Pass as param - if (payplug_integrated_payment_params === undefined) { + if (typeof payplug_integrated_payment_params === 'undefined') { return; } + this.form = this.element.closest('form'); + if (payplug_integrated_payment_params.has_saved_cards) { - document.querySelectorAll('.payment-choice__input, .payment-item input[type=radio]:not([name=schemeOptions])').forEach((element) => { + const otherCardRadio = this.element.querySelector('#payplug_choice_card_other'); + const payplugRadio = document.querySelector(`[id*="checkout_select_payment_payments"][value="${payplug_integrated_payment_params.payment_method_code}"]`); + + // Initial check + if ( + (otherCardRadio && otherCardRadio.checked && payplugRadio && payplugRadio.checked) || + (otherCardRadio && otherCardRadio.checked && !payplugRadio && document.querySelector('.payplug-payment-choice__input:checked')) + ) { + this.openFields(); + } + + this.element.querySelectorAll('.payment-choice__input, [id*="checkout_select_payment_payments"]').forEach((element) => { element.addEventListener('change', (e) => { - if ( - 'payplug_choice_card_other' === e.currentTarget.id && - e.currentTarget.checked || - e.target.value === payplug_integrated_payment_params.payment_method_code && - document.querySelector('#payplug_choice_card_other').checked - ) { + const isOtherCardChecked = this.element.querySelector('#payplug_choice_card_other')?.checked; + const isPayplugSelected = document.querySelector(`[id*="checkout_select_payment_payments"][value="${payplug_integrated_payment_params.payment_method_code}"]`)?.checked; + + if (isOtherCardChecked && isPayplugSelected) { this.openFields(); - return; } - this.closeFields(); }) }) return; @@ -82,18 +92,39 @@ export default class extends Controller { this.openFields(); } - const selectPaymentMethodsField= this.getPaymentMethodSelectors(); + const selectPaymentMethodsField = this.getPaymentMethodSelectors(); selectPaymentMethodsField.forEach((element) => { - // On payment method select, open or close card fields + // On payment method select, open fields if payplug selected element.addEventListener('change', (e) => { if (payplug_integrated_payment_params.payment_method_code === e.currentTarget.value && e.currentTarget.checked) { this.openFields(); - return; } - this.closeFields(); }) }); } + + handleShow(event) { + if (this.hasContainerTarget) { + import('jquery').then(({ default: $ }) => { + $(this.containerTarget).slideDown(); + }); + this.openFields(); + this.containerTarget.dataset.paymentInlineSubmit = "true"; + this.element.dispatchEvent(new CustomEvent('payment-method-state-change', { bubbles: true })); + } + } + + handleHide(event) { + if (this.hasContainerTarget) { + import('jquery').then(({ default: $ }) => { + $(this.containerTarget).slideUp(); + }); + this.closeFields(); + this.containerTarget.dataset.paymentInlineSubmit = "false"; + this.element.dispatchEvent(new CustomEvent('payment-method-state-change', { bubbles: true })); + } + } + getPaymentMethodSelectors({ methodCode, checked } = {}) { const baseSelector = '[id*=checkout_select_payment_payments]'; @@ -105,45 +136,47 @@ export default class extends Controller { } return document.querySelectorAll(baseSelector); } + openFields() { - document.querySelector('.payplugIntegratedPayment').classList.add('payplugIntegratedPayment--loaded'); - document.querySelector('button[type=submit]').classList.add('disabled'); + this.containerTarget.classList.add('payplugIntegratedPayment--loaded'); if (null === this.options.api) { this.load(); } } + closeFields() { - document.querySelector('.payplugIntegratedPayment').classList.remove('payplugIntegratedPayment--loaded'); - document.querySelector('button[type=submit]').classList.remove('disabled'); + this.containerTarget.classList.remove('payplugIntegratedPayment--loaded'); } + load() { - this.options.api = new Payplug.IntegratedPayment(payplug_integrated_payment_params.is_test_mode); + this.options.api = new window.Payplug.IntegratedPayment(payplug_integrated_payment_params.is_test_mode); + this.options.api.setDisplayMode3ds(window.Payplug.DisplayMode3ds.LIGHTBOX); - this.options.api.setDisplayMode3ds(Payplug.DisplayMode3ds.LIGHTBOX); + const container = this.hasContainerTarget ? this.containerTarget : this.element; this.options.form.cardHolder = this.options.api.cardHolder( - document.querySelector('.cardHolder-input-container'), + container.querySelector('.cardHolder-input-container'), { default: this.options.inputStyle.default, placeholder: payplug_integrated_payment_params.cardholder } ); this.options.form.pan = this.options.api.cardNumber( - document.querySelector('.pan-input-container'), + container.querySelector('.pan-input-container'), { default: this.options.inputStyle.default, placeholder: payplug_integrated_payment_params.pan } ); this.options.form.cvv = this.options.api.cvv( - document.querySelector('.cvv-input-container'), + container.querySelector('.cvv-input-container'), { default: this.options.inputStyle.default, placeholder: payplug_integrated_payment_params.cvv } ); this.options.form.exp = this.options.api.expiration( - document.querySelector('.exp-input-container'), + container.querySelector('.exp-input-container'), { default: this.options.inputStyle.default, placeholder: payplug_integrated_payment_params.exp @@ -154,20 +187,24 @@ export default class extends Controller { this.fieldValidation(); } bindEvents() { - document.querySelector('#paid').addEventListener('click', (event) => { - event.preventDefault(); + const container = this.hasContainerTarget ? this.containerTarget : this.element; + const paidButton = container.querySelector('#paid'); + if (paidButton) { + paidButton.addEventListener('click', (event) => { + event.preventDefault(); + this.options.api.validateForm(); + }); + } - this.options.api.validateForm(); - }); this.options.api.onValidateForm(async ({ isFormValid }) => { if (isFormValid) { this.toggleLoader(); - const saveCardElement = document.querySelector('#savecard'); + const saveCardElement = this.element.querySelector('#savecard'); if (null !== saveCardElement) { this.options.save_card = saveCardElement.checked; } - const chosenScheme = document.querySelector('input.schemeOptions:checked'); - this.options.scheme = Payplug.Scheme.AUTO; + const chosenScheme = this.element.querySelector('input.schemeOptions:checked'); + this.options.scheme = window.Payplug.Scheme.AUTO; if (null !== chosenScheme) { this.options.scheme = chosenScheme.value; } @@ -186,28 +223,29 @@ export default class extends Controller { return; } document.querySelector('input[name=payplug_integrated_payment_token]').value = event.token; - document.querySelector('form[name*="checkout_select_payment"]').submit(); + this.form.submit(); }); } fieldValidation () { + const container = this.hasContainerTarget ? this.containerTarget : this.element; Object.keys(this.options.form).forEach((key) => { const field = this.options.form[key]; field.onChange((err) => { if (err.error) { - document.querySelector(`.payplugIntegratedPayment__error--${key}`).classList.remove('payplugIntegratedPayment__error--hide'); - document.querySelector(`.${key}-input-container`).classList.add('payplugIntegratedPayment__container--invalid'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).classList.remove('payplugIntegratedPayment__error--hide'); + container.querySelector(`.${key}-input-container`).classList.add('payplugIntegratedPayment__container--invalid'); if (err.error.name === "FIELD_EMPTY") { - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.remove('payplugIntegratedPayment__error--hide'); - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.add('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.remove('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.add('payplugIntegratedPayment__error--hide'); } else { - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.remove('payplugIntegratedPayment__error--hide'); - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.add('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.remove('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.add('payplugIntegratedPayment__error--hide'); } } else { - document.querySelector(`.payplugIntegratedPayment__error--${key}`).classList.add('payplugIntegratedPayment__error--hide'); - document.querySelector(`.${key}-input-container`).classList.remove('payplugIntegratedPayment__container--invalid'); - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.add('payplugIntegratedPayment__error--hide'); - document.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.add('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).classList.add('payplugIntegratedPayment__error--hide'); + container.querySelector(`.${key}-input-container`).classList.remove('payplugIntegratedPayment__container--invalid'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".invalidField").classList.add('payplugIntegratedPayment__error--hide'); + container.querySelector(`.payplugIntegratedPayment__error--${key}`).querySelector(".emptyField").classList.add('payplugIntegratedPayment__error--hide'); this.options.fieldsValid[key] = true; this.options.fieldsEmpty[key] = false; } @@ -215,6 +253,7 @@ export default class extends Controller { }) } toggleLoader() { - document.querySelector('.payplugIntegratedPayment').querySelector('.sylius-shop-loader').classList.toggle('d-none'); + const container = this.hasContainerTarget ? this.containerTarget : this.element; + container.querySelector('.sylius-shop-loader').classList.toggle('d-none'); } } diff --git a/assets/shop/dist/payment/index.css b/assets/shop/dist/payment/index.css index bb6bfa52..95e0bf4b 100644 --- a/assets/shop/dist/payment/index.css +++ b/assets/shop/dist/payment/index.css @@ -4,10 +4,6 @@ display: flex !important } -.oney-payment-choice, .payplug-payment-choice { - display: none -} - .oney-payment-choice__container, .payplug-payment-choice__container { margin: 1rem 0; display: flex @@ -103,11 +99,11 @@ padding: 0 !important } -.payment-item .oney-logo[src*=without-fees] { +.oney-logo[src*=without-fees] { max-width: 200px } -.payment-item apple-pay-button { +apple-pay-button { --apple-pay-button-width: 222px; --apple-pay-button-height: 40px; --apple-pay-button-border-radius: 4px; @@ -115,29 +111,12 @@ --apple-pay-button-box-sizing: border-box; min-width: 140px; max-width: 100%; - transition: background-color .3s ease-in-out; - display: none -} - -.payment-item apple-pay-button.enabled { - display: block } -.payment-item label { +label { cursor: pointer } -.payment-item .bancontact-method label, .payment-item .apple-pay-method label, .payment-item .american-express-method label { - position: relative -} - -.payment-item .bancontact-method label img, .payment-item .apple-pay-method label img, .payment-item .american-express-method label img { - margin-left: .5em; - position: absolute; - top: -12px; - left: 100% -} - .payplug-payment-choice__container { flex-direction: column } @@ -174,16 +153,17 @@ content: ""; background-image: var(--logo); display: inline-block; - width: 60px; - height: 25px; + width: 45px; + height: 40px; + margin-top: -6px; + margin-left: 10px; background-repeat: no-repeat; - background-position: 0 -11px; - background-size: 60px 45px; - float: right + background-position: center; + background-size: contain; + float: right; } - -.bancontact-label::after { - margin-left: 5px +.payment-label-with-image.oney-label::after { + width: 130px; } @media screen and (max-width: 991px) { diff --git a/assets/shop/dist/payment/integrated.css b/assets/shop/dist/payment/integrated.css index 9a2805f7..2bf34d51 100644 --- a/assets/shop/dist/payment/integrated.css +++ b/assets/shop/dist/payment/integrated.css @@ -1 +1,301 @@ -.payplugIntegratedPayment{justify-self:center;display:none}.payplugIntegratedPayment *{font-family:Poppins,Arial,sans-serif!important}.payplugIntegratedPayment--loaded{width:100%;max-width:400px;flex-wrap:wrap;justify-content:space-between;margin-top:20px;margin-bottom:0;display:flex;position:relative}.payplugIntegratedPayment__select{height:36px;width:100%;border:1px solid #ccc;border-radius:5px;margin:0 0 10px;padding:0 8px}.payplugIntegratedPayment__container{width:100%;margin:0 0 10px;padding:0;display:flex;position:relative}.payplugIntegratedPayment__container--cardHolder,.payplugIntegratedPayment__container--pan,.payplugIntegratedPayment__container--exp,.payplugIntegratedPayment__container--cvv{height:40px;cursor:text;border:1px solid #d5d6d8;border-radius:2px;padding:0 16px 0 50px;line-height:40px}.payplugIntegratedPayment__container--cardHolder:before,.payplugIntegratedPayment__container--pan:before,.payplugIntegratedPayment__container--exp:before,.payplugIntegratedPayment__container--cvv:before{content:"";width:24px;height:24px;background:#95999e 50%/100% no-repeat;position:absolute;top:20%;left:16px}.payplugIntegratedPayment__container--cardHolder:focus,.payplugIntegratedPayment__container--pan:focus,.payplugIntegratedPayment__container--exp:focus,.payplugIntegratedPayment__container--cvv:focus{border-color:#2b343d}.payplugIntegratedPayment__container--cardHolder--invalid,.payplugIntegratedPayment__container--pan--invalid,.payplugIntegratedPayment__container--exp--invalid,.payplugIntegratedPayment__container--cvv--invalid{border-color:#e91932}.payplugIntegratedPayment__container--cardHolder:before{-webkit-mask-image:url(account.039342ad.svg);mask-image:url(account.039342ad.svg)}.payplugIntegratedPayment__container--pan:before{-webkit-mask-image:url(card.0d2bd9bc.svg);mask-image:url(card.0d2bd9bc.svg)}.payplugIntegratedPayment__container--exp:before{-webkit-mask-image:url(calendar.3c23bb16.svg);mask-image:url(calendar.3c23bb16.svg)}.payplugIntegratedPayment__container--cvv:before{-webkit-mask-image:url(lock.fe8a73cd.svg);mask-image:url(lock.fe8a73cd.svg)}.payplugIntegratedPayment__container--exp,.payplugIntegratedPayment__container--cvv{max-width:calc(50% - 2px);display:inline-block}.payplugIntegratedPayment__container--scheme{text-transform:uppercase;height:22px;justify-content:space-between;align-items:center;margin:10px 0;font-size:14px;font-weight:700}.payplugIntegratedPayment__container--saveCard{height:auto;align-items:center;padding:10px 0 0;display:flex}.payplugIntegratedPayment__container--saveCard input{display:none}.payplugIntegratedPayment__container--saveCard input:checked+label span:before{opacity:1}.payplugIntegratedPayment__container--saveCard label{cursor:pointer;color:#918f8f;margin:0!important;font-size:12px!important}.payplugIntegratedPayment__container--saveCard label span{cursor:pointer;height:16px;-o-transition:border .4s;width:16px;border:1px solid #d5d6d8;border-radius:2px;margin:0 10px -3px 0;transition:border .4s;display:inline-block;position:relative}.payplugIntegratedPayment__container--saveCard label span:before{content:"";height:5px;opacity:0;width:10px;border-top:none;border-bottom:2.5px solid #2b343d;border-left:2.5px solid #2b343d;border-right:none;border-radius:1px;transition:opacity .4s;display:block;position:absolute;top:50%;left:50%;transform:translate(-50%,-55%)rotate(-48deg)}.payplugIntegratedPayment__container--saveCard label:hover{color:#2b343d;transition:all .1s}.payplugIntegratedPayment__container--saveCard label:hover span{border-color:#2b343d;transition:all .1s}.payplugIntegratedPayment__container--transaction{align-items:center;margin-top:10px}.payplugIntegratedPayment__container--transaction .transaction-label{vertical-align:super;margin-left:5px;font-size:12px}.payplugIntegratedPayment__container img.lock-icon{width:18px;float:left!important}.payplugIntegratedPayment__container img.payplug-logo{width:80px;height:auto;vertical-align:text-top;margin-left:6px;display:inline-block;float:inherit!important}.payplugIntegratedPayment__container--privacy-policy{text-align:center;display:inline-block}.payplugIntegratedPayment__container--privacy-policy a{color:#918f8f;font-size:14px}.payplugIntegratedPayment__schemes{width:115px;flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex}.payplugIntegratedPayment__schemes label{display:table-cell}.payplugIntegratedPayment__scheme{margin:0}.payplugIntegratedPayment__scheme span{cursor:pointer;width:33px;height:22px;background:50%/100% no-repeat;display:block}.payplugIntegratedPayment__scheme span:before{width:100%;height:100%;content:"";opacity:0;background:50%/100% no-repeat;display:block}.payplugIntegratedPayment__scheme input{display:none}.payplugIntegratedPayment__scheme input:checked+span:before{opacity:1}.payplugIntegratedPayment__scheme--visa span{background-image:url(visa-dark.87c34e0f.svg)}.payplugIntegratedPayment__scheme--visa span:before{background-image:url(visa.d11a46f6.svg)}.payplugIntegratedPayment__scheme--mastercard span{background-image:url(mastercard-dark.8977e440.svg)}.payplugIntegratedPayment__scheme--mastercard span:before{background-image:url(mastercard.7dd4ce0b.svg)}.payplugIntegratedPayment__scheme--cb span{background-image:url(./cb-dark.888aec45.svg)}.payplugIntegratedPayment__scheme--cb span:before{background-image:url(./cb.ccd964e9.svg)}.payplugIntegratedPayment__error{color:#e91932;width:100%;margin:-10px 0 10px;padding-left:4px;font-size:12px;line-height:18px}.payplugIntegratedPayment__error--cardHolder{margin:-10px 0 0}.payplugIntegratedPayment__error--cvv{justify-self:flex-end;margin:-10px 0 10px auto}.payplugIntegratedPayment__error--exp,.payplugIntegratedPayment__error--cvv{width:100%;max-width:49%}.payplugIntegratedPayment__error--hide{display:none} \ No newline at end of file +.payplugIntegratedPayment { + justify-self: center; + display: none +} + +.payplugIntegratedPayment * { + font-family: Poppins, Arial, sans-serif !important +} + +.payplugIntegratedPayment--loaded { + width: 100%; + max-width: 400px; + flex-wrap: wrap; + justify-content: space-between; + margin: 20px auto 0; + display: flex; + position: relative +} + +.payplugIntegratedPayment__select { + height: 36px; + width: 100%; + border: 1px solid #ccc; + border-radius: 5px; + margin: 0 0 10px; + padding: 0 8px +} + +.payplugIntegratedPayment__container { + width: 100%; + margin: 0 0 10px; + padding: 0; + display: flex; + position: relative +} + +.payplugIntegratedPayment__container--cardHolder, +.payplugIntegratedPayment__container--pan, +.payplugIntegratedPayment__container--exp, +.payplugIntegratedPayment__container--cvv { + height: 40px; + cursor: text; + border: 1px solid #d5d6d8; + border-radius: 2px; + padding: 0 16px 0 50px; + line-height: 40px +} + +.payplugIntegratedPayment__container--cardHolder:before, +.payplugIntegratedPayment__container--pan:before, +.payplugIntegratedPayment__container--exp:before, +.payplugIntegratedPayment__container--cvv:before { + content: ""; + width: 24px; + height: 24px; + background: #95999e 50%/100% no-repeat; + position: absolute; + top: 20%; + left: 16px +} + +.payplugIntegratedPayment__container--cardHolder:focus, +.payplugIntegratedPayment__container--pan:focus, +.payplugIntegratedPayment__container--exp:focus, +.payplugIntegratedPayment__container--cvv:focus { + border-color: #2b343d +} + +.payplugIntegratedPayment__container--cardHolder--invalid, +.payplugIntegratedPayment__container--pan--invalid, +.payplugIntegratedPayment__container--exp--invalid, +.payplugIntegratedPayment__container--cvv--invalid { + border-color: #e91932 +} + +.payplugIntegratedPayment__container--cardHolder:before { + -webkit-mask-image: url(account.039342ad.svg); + mask-image: url(account.039342ad.svg) +} + +.payplugIntegratedPayment__container--pan:before { + -webkit-mask-image: url(card.0d2bd9bc.svg); + mask-image: url(card.0d2bd9bc.svg) +} + +.payplugIntegratedPayment__container--exp:before { + -webkit-mask-image: url(calendar.3c23bb16.svg); + mask-image: url(calendar.3c23bb16.svg) +} + +.payplugIntegratedPayment__container--cvv:before { + -webkit-mask-image: url(lock.fe8a73cd.svg); + mask-image: url(lock.fe8a73cd.svg) +} + +.payplugIntegratedPayment__container--exp, +.payplugIntegratedPayment__container--cvv { + max-width: calc(50% - 2px); + display: inline-block +} + +.payplugIntegratedPayment__container--scheme { + text-transform: uppercase; + height: 22px; + justify-content: space-between; + align-items: center; + margin: 10px 0; + font-size: 14px; + font-weight: 700 +} + +.payplugIntegratedPayment__container--saveCard { + height: auto; + align-items: center; + padding: 10px 0 0; + display: flex +} + +.payplugIntegratedPayment__container--saveCard input { + display: none +} + +.payplugIntegratedPayment__container--saveCard input:checked+label span:before { + opacity: 1 +} + +.payplugIntegratedPayment__container--saveCard label { + cursor: pointer; + color: #918f8f; + margin: 0 !important; + font-size: 12px !important +} + +.payplugIntegratedPayment__container--saveCard label span { + cursor: pointer; + height: 16px; + -o-transition: border .4s; + width: 16px; + border: 1px solid #d5d6d8; + border-radius: 2px; + margin: 0 10px -3px 0; + transition: border .4s; + display: inline-block; + position: relative +} + +.payplugIntegratedPayment__container--saveCard label span:before { + content: ""; + height: 5px; + opacity: 0; + width: 10px; + border-top: none; + border-bottom: 2.5px solid #2b343d; + border-left: 2.5px solid #2b343d; + border-right: none; + border-radius: 1px; + transition: opacity .4s; + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -55%)rotate(-48deg) +} + +.payplugIntegratedPayment__container--saveCard label:hover { + color: #2b343d; + transition: all .1s +} + +.payplugIntegratedPayment__container--saveCard label:hover span { + border-color: #2b343d; + transition: all .1s +} + +.payplugIntegratedPayment__container--transaction { + align-items: center; + margin-top: 10px +} + +.payplugIntegratedPayment__container--transaction .transaction-label { + vertical-align: super; + margin-left: 5px; + font-size: 12px +} + +.payplugIntegratedPayment__container img.lock-icon { + width: 18px; + float: left !important +} + +.payplugIntegratedPayment__container img.payplug-logo { + width: 80px; + height: auto; + vertical-align: text-top; + margin-left: 6px; + display: inline-block; + float: inherit !important +} + +.payplugIntegratedPayment__container--privacy-policy { + text-align: center; + display: inline-block +} + +.payplugIntegratedPayment__container--privacy-policy a { + color: #918f8f; + font-size: 14px +} + +.payplugIntegratedPayment__schemes { + width: 115px; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + display: flex +} + +.payplugIntegratedPayment__schemes label { + display: table-cell +} + +.payplugIntegratedPayment__scheme { + margin: 0 +} + +.payplugIntegratedPayment__scheme span { + cursor: pointer; + width: 33px; + height: 22px; + background: 50%/100% no-repeat; + display: block +} + +.payplugIntegratedPayment__scheme span:before { + width: 100%; + height: 100%; + content: ""; + opacity: 0; + background: 50%/100% no-repeat; + display: block +} + +.payplugIntegratedPayment__scheme input { + display: none +} + +.payplugIntegratedPayment__scheme input:checked+span:before { + opacity: 1 +} + +.payplugIntegratedPayment__scheme--visa span { + background-image: url(visa-dark.87c34e0f.svg) +} + +.payplugIntegratedPayment__scheme--visa span:before { + background-image: url(visa.d11a46f6.svg) +} + +.payplugIntegratedPayment__scheme--mastercard span { + background-image: url(mastercard-dark.8977e440.svg) +} + +.payplugIntegratedPayment__scheme--mastercard span:before { + background-image: url(mastercard.7dd4ce0b.svg) +} + +.payplugIntegratedPayment__scheme--cb span { + background-image: url(./cb-dark.888aec45.svg) +} + +.payplugIntegratedPayment__scheme--cb span:before { + background-image: url(./cb.ccd964e9.svg) +} + +.payplugIntegratedPayment__error { + color: #e91932; + width: 100%; + margin: -10px 0 10px; + padding-left: 4px; + font-size: 12px; + line-height: 18px +} + +.payplugIntegratedPayment__error--cardHolder { + margin: -10px 0 0 +} + +.payplugIntegratedPayment__error--cvv { + justify-self: flex-end; + margin: -10px 0 10px auto +} + +.payplugIntegratedPayment__error--exp, +.payplugIntegratedPayment__error--cvv { + width: 100%; + max-width: 49% +} + +.payplugIntegratedPayment__error--hide { + display: none +} diff --git a/composer.json b/composer.json index 079ccdb9..f7d338e1 100755 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "phpstan/phpstan-doctrine": "2.0.1", "phpstan/phpstan-strict-rules": "2.0.1", "phpstan/phpstan-webmozart-assert": "2.0.0", - "phpunit/phpunit": "9.6.22", + "phpunit/phpunit": "^9.6", "rector/rector": "2.0.4", "sylius-labs/coding-standard": "4.4.0", "sylius/test-application": "^2.1.0@alpha", diff --git a/config/services.yaml b/config/services.yaml index a58fc643..8b5dac42 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -20,6 +20,7 @@ services: PayPlug\SyliusPayPlugPlugin\Controller\OrderController: parent: sylius.controller.order + autowire: true PayPlug\SyliusPayPlugPlugin\Provider\OneySimulation\OneySimulationDataProviderInterface: class: PayPlug\SyliusPayPlugPlugin\Provider\OneySimulation\OneySimulationDataProvider diff --git a/public/assets/american-express/logo.svg b/public/assets/american-express/logo.svg index 5a587766..b6dfc5b8 100644 --- a/public/assets/american-express/logo.svg +++ b/public/assets/american-express/logo.svg @@ -1,25 +1 @@ - - - + \ No newline at end of file diff --git a/public/assets/apple-pay/logo.svg b/public/assets/apple-pay/logo.svg index 1240e7d4..5a86310e 100644 --- a/public/assets/apple-pay/logo.svg +++ b/public/assets/apple-pay/logo.svg @@ -1,5 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/bancontact/logo.svg b/public/assets/bancontact/logo.svg index 99d4989c..1ab4d0f9 100644 --- a/public/assets/bancontact/logo.svg +++ b/public/assets/bancontact/logo.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/ruleset/phpstan-baseline.neon b/ruleset/phpstan-baseline.neon index 2ed4e7ab..1d16fed3 100644 --- a/ruleset/phpstan-baseline.neon +++ b/ruleset/phpstan-baseline.neon @@ -1156,12 +1156,6 @@ parameters: count: 1 path: ../src/Provider/AbstractSupportedRefundPaymentMethodsProvider.php - - - message: '#^Cannot cast mixed to int\.$#' - identifier: cast.int - count: 1 - path: ../src/Provider/ApplePayOrderProvider.php - - message: '#^Cannot access offset ''max_amounts'' on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php index 86d0f65a..603ad6aa 100644 --- a/src/Action/Admin/Auth/UnifiedAuthenticationController.php +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -18,6 +18,7 @@ use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Cache\CacheInterface; /** * This controller is used to authenticate the user with PayPlug @@ -37,6 +38,7 @@ public function __construct( private EntityManagerInterface $entityManager, private PaymentMethodValidator $paymentMethodValidator, private LoggerInterface $logger, + private CacheInterface $cache, ) { } @@ -114,6 +116,11 @@ public function oauthCallback(Request $request): Response $this->cleanSession($request); $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.admin.oauth_callback_success'); + // Clean previous cached client config + $cacheKeyLive = sprintf('payplug_%s_api_key_live', $gatewayConfig->getFactoryName()); + $cacheKeyTest = sprintf('payplug_%s_api_key_test', $gatewayConfig->getFactoryName()); + $this->cache->delete($cacheKeyLive); + $this->cache->delete($cacheKeyTest); // Ensure that the payment method is well configured $this->paymentMethodValidator->process($paymentMethod); diff --git a/src/Action/Admin/CompleteRefundPaymentAction.php b/src/Action/Admin/CompleteRefundPaymentAction.php deleted file mode 100644 index 548d0029..00000000 --- a/src/Action/Admin/CompleteRefundPaymentAction.php +++ /dev/null @@ -1,73 +0,0 @@ -refundPaymentRepository->find($id); - - /** @var OrderInterface $order */ - $order = $this->orderRepository->findOneByNumber($orderNumber); - - try { - $this->messageBus->dispatch(new RefundPaymentGenerated( - $refundPayment->getId(), - $refundPayment->getOrder()->getNumber() ?? '', - $refundPayment->getAmount(), - $refundPayment->getCurrencyCode(), - $refundPayment->getPaymentMethod()->getId(), - $this->relatedPaymentIdProvider->getForRefundPayment($refundPayment), - )); - - if (self::COMPLETED_STATE !== $refundPayment->getState()) { - $this->refundPaymentCompletedStateApplier->apply($refundPayment); - } - $this->requestStack->getSession()->getFlashBag()->add('success', 'sylius_refund.refund_payment_completed'); - } catch (Throwable) { - $this->requestStack->getSession()->getFlashBag()->add('error', $this->translator->trans('payplug_sylius_payplug_plugin.ui.impossible_to_refund_this_payment')); - } - - return new RedirectResponse($this->router->generate( - 'sylius_admin_order_show', - ['id' => $order->getId()], - )); - } -} diff --git a/src/Action/Admin/RefundUnitsAction.php b/src/Action/Admin/RefundUnitsAction.php deleted file mode 100644 index 33d8e933..00000000 --- a/src/Action/Admin/RefundUnitsAction.php +++ /dev/null @@ -1,60 +0,0 @@ -commandBus->dispatch($this->commandCreator->fromRequest($request)); - - $this->requestStack->getSession()->getFlashBag()->add('success', 'sylius_refund.units_successfully_refunded'); - } catch (InvalidRefundAmount $exception) { - $this->requestStack->getSession()->getFlashBag()->add('error', $exception->getMessage()); - - $this->logger->error($exception->getMessage()); - } catch (HandlerFailedException $exception) { - /** @var Exception $previousException */ - $previousException = $exception->getPrevious(); - - $this->requestStack->getSession()->getFlashBag()->add('error', $previousException->getMessage()); - - $this->logger->error($previousException->getMessage()); - } - - return new RedirectResponse($this->router->generate( - 'sylius_refund_order_refunds_list', - ['orderNumber' => $request->attributes->get('orderNumber')], - )); - } -} diff --git a/src/Action/CaptureAction.php b/src/Action/CaptureAction.php index 1fbb8125..8030246c 100644 --- a/src/Action/CaptureAction.php +++ b/src/Action/CaptureAction.php @@ -42,6 +42,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; use Webmozart\Assert\Assert; +/** @deprecated */ #[AutoconfigureTag( name: 'payum.action', attributes: [ diff --git a/src/Action/ConvertPaymentAction.php b/src/Action/ConvertPaymentAction.php index 4b00ebe3..663b9b1a 100644 --- a/src/Action/ConvertPaymentAction.php +++ b/src/Action/ConvertPaymentAction.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; +/** @deprecated */ #[AutoconfigureTag( name: 'payum.action', attributes: [ diff --git a/src/Action/NotifyAction.php b/src/Action/NotifyAction.php index 3f5fc36a..e90dac54 100644 --- a/src/Action/NotifyAction.php +++ b/src/Action/NotifyAction.php @@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use Symfony\Component\DependencyInjection\Attribute\Autowire; +/** @deprecated */ #[AutoconfigureTag( name: 'payum.action', attributes: [ diff --git a/src/Action/StatusAction.php b/src/Action/StatusAction.php index 6e356921..135af4a6 100644 --- a/src/Action/StatusAction.php +++ b/src/Action/StatusAction.php @@ -28,6 +28,7 @@ use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use Symfony\Component\HttpFoundation\RequestStack; +/** @deprecated */ #[AutoconfigureTag( name: 'payum.action', attributes: [ diff --git a/src/ApiClient/PayPlugApiClientFactory.php b/src/ApiClient/PayPlugApiClientFactory.php index 67c09ca7..4d12c900 100644 --- a/src/ApiClient/PayPlugApiClientFactory.php +++ b/src/ApiClient/PayPlugApiClientFactory.php @@ -5,6 +5,7 @@ namespace PayPlug\SyliusPayPlugPlugin\ApiClient; use Payplug\Authentication; +use PayPlug\SyliusPayPlugPlugin\Exception\GatewayConfigurationException; use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Component\Payment\Model\PaymentMethodInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; @@ -52,7 +53,7 @@ private function getTokenForGatewayConfig(GatewayConfigInterface $gatewayConfig) $clientConfig = $config['test_client']; } if (!\is_array($clientConfig)) { - throw new \LogicException('No client config found for ' . $gatewayConfig->getFactoryName() . '. Please renew your credentials in the PayPlug plugin configuration.'); + throw new GatewayConfigurationException('No client config found for ' . $gatewayConfig->getFactoryName() . '. Please renew your credentials in the PayPlug plugin configuration.'); } $cacheKey = sprintf('payplug_%s_api_key_%s', $gatewayConfig->getFactoryName(), $config['live'] === true ? 'live' : 'test'); @@ -61,12 +62,12 @@ private function getTokenForGatewayConfig(GatewayConfigInterface $gatewayConfig) return $this->cache->get($cacheKey, function (ItemInterface $item) use ($clientConfig) { $response = Authentication::generateJWT($clientConfig['client_id'] ?? '', $clientConfig['client_secret'] ?? ''); if ([] === $response || !is_array($response['httpResponse'])) { - throw new \LogicException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); + throw new GatewayConfigurationException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); } $accessToken = $response['httpResponse']['access_token']; if (!is_string($accessToken)) { - throw new \LogicException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); + throw new GatewayConfigurationException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); } $expiresIn = $response['httpResponse']['expires_in']; if (!is_int($expiresIn)) { diff --git a/src/Checker/ApplePayChecker.php b/src/Checker/ApplePayChecker.php deleted file mode 100644 index f3e681b1..00000000 --- a/src/Checker/ApplePayChecker.php +++ /dev/null @@ -1,42 +0,0 @@ -requestStack->getMainRequest(); - if (!$mainRequest instanceof Request) { - return false; - } - - $userAgent = $mainRequest->headers->get('User-Agent'); - - if (!is_string($userAgent)) { - return false; - } - - $browsers = ['Opera', 'Edg', 'Chrome', 'Firefox', 'MSIE', 'Trident']; - - foreach ($browsers as $browser) { - if (str_contains($userAgent, $browser)) { - return false; - } - } - - preg_match('/iPhone|Android|iPad|iPod|webOS|Mac/', $userAgent, $matches); - $os = current($matches); - - return \is_string($os); - } -} diff --git a/src/Checker/ApplePayCheckerInterface.php b/src/Checker/ApplePayCheckerInterface.php deleted file mode 100644 index 531d576b..00000000 --- a/src/Checker/ApplePayCheckerInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -paymentRequestProvider->provide($notifyPaymentRequest); /** @var PaymentInterface $payment */ $payment = $paymentRequest->getPayment(); - if ($payment->getState() !== PaymentInterface::STATE_COMPLETED) { - // If the payment is already completed, we do not need to notify again - $this->stateMachine->apply( - $paymentRequest, - PaymentRequestTransitions::GRAPH, - PaymentRequestTransitions::TRANSITION_COMPLETE, - ); - - return; - } try { - // Payload contains what's send by payplug, no need to retrieve it from PayPlug - // @phpstan-ignore-next-line - cannot access offset content / http_request on mixed - $payplugPayment = Payment::fromAttributes(json_decode($paymentRequest->getPayload()['http_request']['content'] ?? '{}', true, 512, \JSON_THROW_ON_ERROR)); + $payload = $paymentRequest->getPayload(); + $content = $payload['http_request']['content'] ?? null; // @phpstan-ignore-line + if (!is_string($content) || '' === $content) { + throw new \LogicException('Invalid PayPlug notification payload.'); + } + + $method = $payment->getMethod(); + if (null === $method) { + throw new \LogicException('Payment method is not set for the payment.'); + } + + $client = $this->apiClientFactory->createForPaymentMethod($method); + $resource = $client->treat($content); + + if ($resource instanceof Payment && $payment->getState() === PaymentInterface::STATE_COMPLETED) { + // If the payment is already completed, we do not need to update it again + $this->stateMachine->apply( + $paymentRequest, + PaymentRequestTransitions::GRAPH, + PaymentRequestTransitions::TRANSITION_COMPLETE, + ); + + return; + } + $details = new \ArrayObject($payment->getDetails()); - $this->paymentNotificationHandler->treat($payment, $payplugPayment, $details); + $this->paymentNotificationHandler->treat($payment, $resource, $details); + $this->refundNotificationHandler->treat($payment, $resource, $details); $payment->setDetails($details->getArrayCopy()); - $this->updatePaymentState($payment); + if ($resource instanceof Payment) { + $this->updatePaymentState($payment); + } $this->stateMachine->apply( $paymentRequest, @@ -70,7 +89,7 @@ public function __invoke(NotifyPaymentRequest $notifyPaymentRequest): void private function updatePaymentState(PaymentInterface $payment): void { match ($payment->getDetails()['status'] ?? '') { - PayPlugApiClientInterface::STATUS_ABORTED, PayPlugApiClientInterface::STATUS_CANCELED => $this->stateMachine + PayPlugApiClientInterface::STATUS_ABORTED, PayPlugApiClientInterface::STATUS_CANCELED, PayPlugApiClientInterface::STATUS_CANCELED_BY_ONEY => $this->stateMachine ->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CANCEL), PayPlugApiClientInterface::STATUS_AUTHORIZED => $this->stateMachine ->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_AUTHORIZE), diff --git a/src/Command/Handler/StatusPaymentRequestHandler.php b/src/Command/Handler/StatusPaymentRequestHandler.php index 4757f2ae..1f2c47fe 100644 --- a/src/Command/Handler/StatusPaymentRequestHandler.php +++ b/src/Command/Handler/StatusPaymentRequestHandler.php @@ -8,6 +8,7 @@ use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientInterface; use PayPlug\SyliusPayPlugPlugin\Command\StatusPaymentRequest; use PayPlug\SyliusPayPlugPlugin\Handler\PaymentNotificationHandler; +use Psr\Log\LoggerInterface; use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\PaymentBundle\Provider\PaymentRequestProviderInterface; use Sylius\Component\Payment\Model\PaymentInterface; @@ -24,6 +25,7 @@ public function __construct( private StateMachineInterface $stateMachine, private PayPlugApiClientFactoryInterface $apiClientFactory, private PaymentNotificationHandler $paymentNotificationHandler, + private LoggerInterface $logger, ) {} public function __invoke(StatusPaymentRequest $statusPaymentRequest): void @@ -43,15 +45,26 @@ public function __invoke(StatusPaymentRequest $statusPaymentRequest): void // We don't have a forced status, so we retrieve the payment status from PayPlug $client = $this->apiClientFactory->createForPaymentMethod($method); - // @phpstan-ignore-next-line - getDetails() return mixed - $payplugPayment = $client->retrieve($payment->getDetails()['payment_id'] ?? throw new \LogicException('No PayPlug payment ID found in payment details.')); + /** @var null|string $payplugPaymentId */ + $payplugPaymentId = $payment->getDetails()['payment_id'] ?? null; + if (null === $payplugPaymentId) { + $this->logger->warning('No PayPlug payment ID found in payment details.', ['payment_id' => $payment->getId(), 'order_id' => $payment->getOrder()?->getId()]); + $payment->setDetails(['status' => PayPlugApiClientInterface::FAILED]); + $this->updatePaymentState($payment); + return; + } + + $payplugPayment = $client->retrieve($payplugPaymentId); $paymentRequest->setResponseData((array) $payplugPayment); $details = new \ArrayObject($payment->getDetails()); $this->paymentNotificationHandler->treat($payment, $payplugPayment, $details); $payment->setDetails($details->getArrayCopy()); - $this->updatePaymentState($payment); + if ($payment->getState() !== PaymentInterface::STATE_COMPLETED) { + // If is already completed, do not try to update it again (updated by notification) + $this->updatePaymentState($payment); + } // Mark the PaymentRequest as completed $this->stateMachine->apply( @@ -85,7 +98,7 @@ private function handleForcedStatus( private function updatePaymentState(PaymentInterface $payment): void { match ($payment->getDetails()['status'] ?? '') { - PayPlugApiClientInterface::STATUS_ABORTED, PayPlugApiClientInterface::STATUS_CANCELED => $this->stateMachine + PayPlugApiClientInterface::STATUS_ABORTED, PayPlugApiClientInterface::STATUS_CANCELED, PayPlugApiClientInterface::STATUS_CANCELED_BY_ONEY => $this->stateMachine ->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CANCEL), PayPlugApiClientInterface::STATUS_AUTHORIZED => $this->stateMachine ->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_AUTHORIZE), diff --git a/src/Controller/IpnAction.php b/src/Controller/IpnAction.php index 96fe35d6..ee7ca7c5 100644 --- a/src/Controller/IpnAction.php +++ b/src/Controller/IpnAction.php @@ -17,9 +17,9 @@ use PayPlug\SyliusPayPlugPlugin\Handler\RefundNotificationHandler; use PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepositoryInterface; use Payum\Core\Bridge\Spl\ArrayObject; -use Payum\Core\Model\GatewayConfigInterface; use Psr\Log\LoggerInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -27,6 +27,7 @@ use Symfony\Component\Routing\Attribute\Route; use Webmozart\Assert\Assert; +/** @deprecated */ #[AsController] class IpnAction { @@ -60,6 +61,11 @@ public function __invoke(Request $request): JsonResponse } $payment = $this->paymentRepository->findOneByPayPlugPaymentId($details['id']); + + if (null === $payment) { + return new JsonResponse(null, Response::HTTP_UNAUTHORIZED); + } + $paymentMethod = $payment->getMethod(); Assert::isInstanceOf($paymentMethod, PaymentMethodInterface::class); diff --git a/src/Controller/OneClickAction.php b/src/Controller/OneClickAction.php index 6ce2764a..166fc383 100644 --- a/src/Controller/OneClickAction.php +++ b/src/Controller/OneClickAction.php @@ -10,7 +10,7 @@ use Payum\Core\ApiAwareInterface; use Payum\Core\GatewayAwareInterface; use Payum\Core\GatewayAwareTrait; -use Payum\Core\Model\GatewayConfigInterface; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Payum\Core\Payum; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -23,7 +23,7 @@ use Symfony\Component\Routing\Attribute\Route; /** - * TODO : !!! Check if this controller is still needed + * @deprecated - Used by payum */ #[AsController] class OneClickAction extends AbstractController implements GatewayAwareInterface, ApiAwareInterface @@ -51,9 +51,13 @@ public function __invoke(Request $request): Response /** @var GatewayConfigInterface $paymentGateway */ $paymentGateway = $paymentMethod->getGatewayConfig(); + $gatewayName = $paymentGateway->getGatewayName(); + if (null === $gatewayName) { + throw new \InvalidArgumentException('Payment gateway name cannot be null'); + } $captureToken = $this->payum->getTokenFactory()->createCaptureToken( - $paymentGateway->getGatewayName(), + $gatewayName, $payment, 'sylius_shop_order_thank_you', [], diff --git a/src/Controller/OrderController.php b/src/Controller/OrderController.php index 8cb7afe1..7c70250e 100644 --- a/src/Controller/OrderController.php +++ b/src/Controller/OrderController.php @@ -18,6 +18,7 @@ use Sylius\Component\Resource\Exception\UpdateHandlingException; use Sylius\Component\Resource\ResourceActions; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -32,33 +33,26 @@ final class OrderController extends BaseOrderController { private const APPLE_ERROR_RESPONSE_CODE = 0; - private const APPLE_SUCCESS_RESPONSE_CODE = 1; - #[Required] // @phpstan-ignore-next-line - Symfony write this attribute - private StateMachineInterface $stateMachineAbstraction; + #[Required] + public StateMachineInterface $stateMachineAbstraction; - #[Required] // @phpstan-ignore-next-line - Symfony write this attribute - private ApplePayPaymentProvider $applePayPaymentProvider; + #[Required] + public ApplePayPaymentProvider $applePayPaymentProvider; - #[Required] // @phpstan-ignore-next-line - Symfony write this attribute - private LockFactory $lockFactory; + #[Required] + public LockFactory $lockFactory; - #[Required] // @phpstan-ignore-next-line - Symfony write this attribute - private LoggerInterface $logger; + #[Required] + public LoggerInterface $logger; #[Route( - path: '/payplug/apple-pay/prepare/{orderId}', + path: '/payplug/apple-pay/prepare/{id}', name: 'payplug_shop_checkout_apple_prepare', options: [ '_sylius' => [ 'flash' => false, - 'repository' => [ - 'method' => 'find', - 'arguments' => [ - 'expr:service("PayPlug\\SyliusPayPlugPlugin\\Provider\\ApplePayOrderProvider").getCurrentCart()', - ], - ], ], ], methods: ['GET', 'POST'], @@ -101,7 +95,6 @@ public function initiateApplePaySessionAction(Request $request): Response } $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource); - $initializeEventResponse = $initializeEvent->getResponse(); if ($initializeEventResponse instanceof \Symfony\Component\HttpFoundation\Response) { @@ -114,6 +107,7 @@ public function initiateApplePaySessionAction(Request $request): Response $payment = $this->applePayPaymentProvider->provide($request, $resource); + $this->logger->info('[Payplug] ApplePay payment', ['payment' => $payment, 'details' => $payment->getDetails()]); $this->manager->flush(); return new JsonResponse([ @@ -132,6 +126,7 @@ public function initiateApplePaySessionAction(Request $request): Response } catch (\Exception) { try { $this->applePayPaymentProvider->cancel($resource); + $this->manager->flush(); } catch (\Throwable $throwable) { $this->logger->error('Could not cancel ApplePay payment', [ 'order_id' => $resource->getId(), @@ -143,7 +138,7 @@ public function initiateApplePaySessionAction(Request $request): Response $request->getSession()->getFlashBag()->add('error', 'sylius.payment.cancelled'); $dataResponse = []; - $redirect = $this->redirectToRoute('sylius_shop_checkout_select_payment'); + $redirect = $this->getApplePayNextRoute($resource); $dataResponse['returnUrl'] = $redirect->getTargetUrl(); $dataResponse['responseToApple'] = ['status' => self::APPLE_ERROR_RESPONSE_CODE]; @@ -157,17 +152,11 @@ public function initiateApplePaySessionAction(Request $request): Response } #[Route( - path: '/payplug/apple-pay/complete/{orderId}', + path: '/payplug/apple-pay/complete/{id}', name: 'payplug_shop_checkout_apple_confirm', options: [ '_sylius' => [ 'flash' => false, - 'repository' => [ - 'method' => 'find', - 'arguments' => [ - 'expr:service("PayPlug\\SyliusPayPlugPlugin\\Provider\\ApplePayOrderProvider").getCurrentCart()', - ], - ], ], ], methods: ['GET', 'POST'], @@ -175,60 +164,51 @@ public function initiateApplePaySessionAction(Request $request): Response public function confirmApplePayPaymentAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); - $this->isGrantedOr403($configuration, ResourceActions::UPDATE); - /** @var OrderInterface $resource */ - $resource = $this->findOr404($configuration); - + /** @var OrderInterface $order */ + $order = $this->findOr404($configuration); /** @var ResourceControllerEvent $event */ - $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource); - + $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $order); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } - if ($event->isStopped()) { $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } - return new JsonResponse([], Response::HTTP_BAD_REQUEST); } try { - $lastPayment = $this->applePayPaymentProvider->patch($request, $resource); - + $lastPayment = $this->applePayPaymentProvider->patch($request, $order); if (PaymentInterface::STATE_COMPLETED !== $lastPayment->getState()) { throw new PaymentNotCompletedException(); } } catch (\Exception | PaymentNotCompletedException $exception) { - try { - $this->applePayPaymentProvider->cancel($resource); - } catch (\Throwable $throwable) { - $this->logger->error('Could not cancel ApplePay payment', [ - 'order_id' => $resource->getId(), - 'code' => $throwable->getCode(), - 'message' => $throwable->getMessage(), - 'trace' => $throwable->getTraceAsString(), - ]); - } - $request->getSession()->getFlashBag()->add('error', 'sylius.payment.cancelled'); - $redirect = $this->redirectToRoute('sylius_shop_checkout_select_payment'); + $redirect = $this->getApplePayNextRoute($order); + $dataResponse = []; $dataResponse['returnUrl'] = $redirect->getTargetUrl(); $dataResponse['responseToApple'] = ['status' => self::APPLE_ERROR_RESPONSE_CODE]; - $dataResponse['errors'] = 'Payment not created'; + $dataResponse['errors'] = 'Payment not completed'; $dataResponse['message'] = $exception->getMessage(); - return new JsonResponse($dataResponse, Response::HTTP_BAD_REQUEST); + $this->logger->error('Could not complete ApplePay payment', ['exception' => $exception, 'data_response' => $dataResponse]); + + $response = [ + 'success' => false, + 'data' => $dataResponse, + ]; + + return new JsonResponse($response, Response::HTTP_BAD_REQUEST); } $this->manager->flush(); - $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource); + $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $order); $postEventResponse = $postEvent->getResponse(); @@ -236,17 +216,6 @@ public function confirmApplePayPaymentAction(Request $request): Response return $postEventResponse; } - $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource); - - $initializeEventResponse = $initializeEvent->getResponse(); - - if ($initializeEventResponse instanceof \Symfony\Component\HttpFoundation\Response) { - return $initializeEventResponse; - } - - $order = $lastPayment->getOrder(); - Assert::isInstanceOf($order, OrderInterface::class); - if ($this->stateMachineAbstraction->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_COMPLETE)) { $this->stateMachineAbstraction->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_COMPLETE); } @@ -268,17 +237,11 @@ public function confirmApplePayPaymentAction(Request $request): Response } #[Route( - path: '/payplug/apple-pay/cancel/{orderId}', + path: '/payplug/apple-pay/cancel/{id}', name: 'payplug_shop_checkout_apple_cancel', options: [ '_sylius' => [ 'flash' => false, - 'repository' => [ - 'method' => 'find', - 'arguments' => [ - 'expr:service("PayPlug\\SyliusPayPlugPlugin\\Provider\\ApplePayOrderProvider").getCurrentCart()', - ], - ], ], ], methods: ['GET', 'POST'], @@ -291,7 +254,6 @@ public function cancelApplePaySessionAction(Request $request): Response /** @var OrderInterface $resource */ $resource = $this->findOr404($configuration); - $lock = $this->lockFactory->createLock('apple_pay_cancel' . $resource->getId()); $lock->acquire(true); @@ -332,6 +294,7 @@ public function cancelApplePaySessionAction(Request $request): Response try { $this->applePayPaymentProvider->cancel($resource); + $this->manager->flush(); } catch (\Throwable $throwable) { $this->logger->error('Could not cancel ApplePay payment', [ 'order_id' => $resource->getId(), @@ -349,14 +312,7 @@ public function cancelApplePaySessionAction(Request $request): Response $request->getSession()->getFlashBag()->add('error', 'sylius.payment.cancelled'); $dataResponse = []; - $redirect = $this->redirectToRoute('sylius_shop_checkout_select_payment', ['_locale' => $resource->getLocaleCode()]); - - if (OrderCheckoutStates::STATE_COMPLETED === $resource->getCheckoutState()) { - $redirect = $this->redirectToRoute('sylius_shop_order_show', [ - 'tokenValue' => $resource->getTokenValue(), - '_locale' => $resource->getLocaleCode(), - ]); - } + $redirect = $this->getApplePayNextRoute($resource); $dataResponse['returnUrl'] = $redirect->getTargetUrl(); $dataResponse['responseToApple'] = ['status' => self::APPLE_SUCCESS_RESPONSE_CODE]; @@ -390,14 +346,7 @@ public function cancelApplePaySessionAction(Request $request): Response $dataResponse = []; - $redirect = $this->redirectToRoute('sylius_shop_checkout_select_payment'); - - if (OrderInterface::STATE_NEW === $resource->getState()) { - $redirect = $this->redirectToRoute('sylius_shop_order_show', [ - 'tokenValue' => $resource->getTokenValue(), - '_locale' => $resource->getLocaleCode(), - ]); - } + $redirect = $this->getApplePayNextRoute($resource); $dataResponse['returnUrl'] = $redirect->getTargetUrl(); $dataResponse['responseToApple'] = ['status' => self::APPLE_ERROR_RESPONSE_CODE]; @@ -410,4 +359,27 @@ public function cancelApplePaySessionAction(Request $request): Response return new JsonResponse($response, Response::HTTP_OK); } } + + private function getApplePayNextRoute(OrderInterface $resource): RedirectResponse + { + $redirect = $this->redirectToRoute('sylius_shop_checkout_select_payment', ['_locale' => $resource->getLocaleCode()]); + + if (OrderCheckoutStates::STATE_COMPLETED === $resource->getCheckoutState()) { + $redirect = $this->redirectToRoute('sylius_shop_order_show', [ + 'tokenValue' => $resource->getTokenValue(), + '_locale' => $resource->getLocaleCode(), + ]); + } + + if (OrderInterface::STATE_NEW === $resource->getState()) { + $redirect = $this->redirectToRoute('sylius_shop_order_show', [ + 'tokenValue' => $resource->getTokenValue(), + '_locale' => $resource->getLocaleCode(), + ]); + } + + $this->logger->debug('[Payplug] Redirect', ['redirect' => $redirect, 'targetUrl' => $redirect->getTargetUrl()]); + + return $redirect; + } } diff --git a/src/Creator/PayPlugPaymentDataCreator.php b/src/Creator/PayPlugPaymentDataCreator.php index 940fe558..e9e32322 100644 --- a/src/Creator/PayPlugPaymentDataCreator.php +++ b/src/Creator/PayPlugPaymentDataCreator.php @@ -54,7 +54,8 @@ public function create( /** @var CustomerInterface $customer */ $customer = $order->getCustomer(); - $details = new ArrayObject($payment->getDetails()); + /** @var ArrayObject
diff --git a/templates/shop/select_payment/_apple_pay.html.twig b/templates/shop/select_payment/_apple_pay.html.twig
index 3ae26542..fd8a56d2 100644
--- a/templates/shop/select_payment/_apple_pay.html.twig
+++ b/templates/shop/select_payment/_apple_pay.html.twig
@@ -1,55 +1,39 @@
{% set form = hookable_metadata.context.form %}
{% set method = hookable_metadata.context.method %}
{% set order = hookable_metadata.context.order %}
-{% set factoryName = method.gatewayConfig.factoryName %}
-{% set code = method.code %}
-{% set applePayFactoryName = constant('PayPlug\\SyliusPayPlugPlugin\\Gateway\\ApplePayGatewayFactory::FACTORY_NAME') %}
-{% set checkboxClass = 'checkbox-applepay' %}
+{% set applePaySettings = {
+ 'countryCode': sylius.channel.defaultLocale.code|length == 2 ? sylius.channel.defaultLocale.code : sylius.channel.defaultLocale.code|slice(3, 2),
+ 'currencyCode': sylius.channel.baseCurrency.code,
+ 'merchantCapabilities': ['supports3DS'],
+ 'supportedNetworks': ['cartesBancaires', 'visa', 'masterCard'],
+ 'total': {
+ 'label': sylius.channel.name,
+ 'type': 'final',
+ 'amount': (order is not null ? order.total / 100 : 0)
+ },
+ 'applePayDomain': sylius.channel.hostname
+} %}