From 616c80a87012aa8806edce62860d4dcfe59489d0 Mon Sep 17 00:00:00 2001 From: Vitalij Mik Date: Tue, 22 Oct 2024 11:42:13 +0200 Subject: [PATCH] MOL-245: PPE (#705) * MOL-245: Add PayPal Express * NTR: Squashed commit of the following: commit 95aa056782238f17a797b2fb4599b29e4b40cf73 Author: Vitalij Mik Date: Wed Jan 24 10:02:14 2024 +0100 NTR: test newer shopware version (#696) Co-authored-by: Vitalij Mik commit aab2afc732af848ba6c1052d0f79724f1b8d6426 Author: Vitalij Mik Date: Wed Jan 24 10:01:22 2024 +0100 NTR: change sw version release (#697) Co-authored-by: Vitalij Mik commit 2cee88b7dd3968afd382a13161d007ac9e29c10f Author: Vitalij Mik Date: Fri Jan 19 13:15:08 2024 +0100 NTR: prepare hotfix 4.4.1 (#694) Co-authored-by: Vitalij Mik commit 549addbfda33e355baa217881b078324691ea64a Author: Vitalij Mik Date: Thu Jan 18 13:47:44 2024 +0100 NTR: change min sw release version (#693) Co-authored-by: Vitalij Mik commit 01f09cf92687ec4a514af41305ed6a7c0e3fa356 Author: Vitalij Mik Date: Thu Jan 18 13:37:00 2024 +0100 NTR: Fix release version for production composer.json (#692) Co-authored-by: Vitalij Mik commit 4c75daf3a8edb3647999f6b891e68e4a273bbb61 Author: Vitalij Mik Date: Thu Jan 18 12:40:44 2024 +0100 NTR: new plugin version (#691) * NTR: new plugin version * NTR: Composer update * NTR: update composer --------- Co-authored-by: Vitalij Mik commit 08f42c54782f99d9834b76140900e2a7fe51aba6 Author: Vitalij Mik Date: Thu Jan 18 10:46:31 2024 +0100 NTR: revert shopware version (#690) Co-authored-by: Vitalij Mik commit a341cf12e4d906bdac1bf6e84144493ffbea0fc8 Author: Vitalij Mik Date: Thu Jan 18 09:12:28 2024 +0100 NTR: update shopware version (#689) Co-authored-by: Vitalij Mik commit fd8746e2568591dbf90d08eeb8c80ebf3a06f97b Author: Vitalij Mik Date: Wed Jan 17 16:13:37 2024 +0100 NTR: Fix saved credit card payment (#688) Co-authored-by: Vitalij Mik commit de772ee1940e639b9914e39d79bc9522bf2224b4 Author: Vitalij Mik Date: Wed Jan 17 08:52:26 2024 +0100 NTR: Update shopware version to 6.5.7.4 (#687) Co-authored-by: Vitalij Mik commit 5d763dddc14fd63e0e53d497d110c45f1035bfb5 Author: Vitalij Mik Date: Tue Jan 16 15:24:22 2024 +0100 MOL-1196: add shipping method for tracking url (#686) Co-authored-by: Vitalij Mik commit 190047478d635d7dea0e942099d2cb7aa84f8428 Author: Vitalij Mik Date: Tue Jan 16 11:21:25 2024 +0100 MOL-1196: fix automatic shipping and add error logs (#685) * MOL-1196: fix automatic shipping and add error logs * MOL-1196: remove comment --------- Co-authored-by: Vitalij Mik commit 32b0be3f47ae801c8bf9cf80a0c8b83fe6b7ddad Author: Vitalij Mik Date: Mon Jan 15 13:46:33 2024 +0100 MOL-1280: Fix retry route and add cypress tests (#683) * MOL-1280: Fix retry route and add cypress tests * MOL-1280: Update cypress ID * MOL-1280: cleanup cypress test --------- Co-authored-by: Vitalij Mik commit 9de44f902ef0596e16cb6f9541ca9965c3334ab6 Author: Vitalij Mik Date: Mon Jan 15 08:59:14 2024 +0100 NTR: PISHPS-204: store bank data in order custom fields (#664) Co-authored-by: Vitalij Mik commit cbcc33ca5969989e5299a3777679c95f825a8745 Author: Vitalij Mik Date: Fri Jan 12 13:54:42 2024 +0100 MOL-1280: Fix subscription metadata after retry (#682) Co-authored-by: Vitalij Mik commit 48281db551ef4a605b6e5dc317548fd6942f724c Author: Vitalij Mik Date: Thu Jan 11 15:21:13 2024 +0100 MOL-1255: Fix cypress tests (#681) Co-authored-by: Vitalij Mik commit 04c71dad576cc259550aa162d45d7d44f353c1da Author: Vitalij Mik Date: Thu Jan 11 09:50:58 2024 +0100 MOL-1287: Fix custom fields for payment methods (#680) Co-authored-by: Vitalij Mik commit 1491af387135c53d367e08c3d4af4ec758f03859 Author: Vitalij Mik Date: Wed Jan 10 10:35:50 2024 +0100 MOL-1287: do not overwrite exisisting technical name (#678) * MOL-1287: do not overwrite exisisting technical name * MOL-1287: technical name in 6.5.7 * MOL-1287: cs and stan fix --------- Co-authored-by: Vitalij Mik commit 37d176b63ec89032333fd7179897d0eb0a5e814b Author: Vitalij Mik Date: Tue Jan 9 11:49:11 2024 +0100 MOL-1294: force label to be empty string on null (#677) * MOL-1294: force label to be empty string on null * MOL-1294: update test --------- Co-authored-by: Vitalij Mik commit f411812bcdb237a975cc81e5dea5ad0f90008103 Author: Christian Date: Fri Jan 5 10:13:43 2024 +0100 MOL-1287: add technical name to payment methods (#675) commit 250a3d386469569e8cb153f8b721266076907397 Author: Christian Date: Fri Dec 22 12:34:53 2023 +0100 MOL-1206: create plugin config for log file retention days (#674) commit 5511d15b0dab22fe8a8624e711ed898265cb767d Author: Christian Date: Thu Dec 21 17:46:06 2023 +0100 MOL-1225: add batch shipments to administration (#665) commit 2bc7a18f72705cce42001fb84d4282c95ef339fb Author: Vitalij Mik Date: Wed Dec 20 12:13:35 2023 +0100 MOL-1285: use stock manager config (#669) Co-authored-by: Vitalij Mik commit 8b464a8b09d702bd10a4b018c56f0981582bc6fe Author: Christian Date: Wed Dec 20 11:34:20 2023 +0100 NTR: add one click payments to headless config (#672) commit c7622fadbaaf8a405d774b08ebd77f85f76c66de Author: Christian Date: Wed Dec 20 11:30:08 2023 +0100 NTR: add missing oneClick payments to swagger docs (#671) commit bb26750f6066ede36d55d37251f48f6ee142db63 Author: Christian Dangl Date: Thu Dec 14 17:17:15 2023 +0100 NTR: allow shopware-cli downgrades commit 0a614b2b5ccc5536e072a0c87cfbedb2211418ef Author: Christian Dangl Date: Thu Dec 14 17:08:20 2023 +0100 NTR: fix problem in automated shopware-cli installer commit 3906e26f814f4a614d4e951b2dd2baae517252e4 Author: Vitalij Mik Date: Thu Dec 14 16:35:44 2023 +0100 NTR: Set fixed version of shopware cli (#668) Co-authored-by: Vitalij Mik commit 22a6e8a8930962c3b34bd69d8522549b558231ad Author: Vitalij Mik Date: Thu Dec 14 09:35:57 2023 +0100 NTR: Fix make run (#666) * NTR: Fix make run * NTR: Fix build * NTR: update --------- Co-authored-by: Vitalij Mik commit ab42644d765646a01c4e0f39ee790ff73ecd46bf Author: Christian Date: Mon Dec 4 15:48:29 2023 +0100 MOL-1260: use technical state machine name for log entry on reopen (#663) commit f52ae300187e91aa6bfba04b3b1fcda744b998d5 Author: Vitalij Mik Date: Mon Dec 4 15:48:06 2023 +0100 MOL-1196: Tracking (#607) * MOL-1196: Tracking * MOL-1196: fix pipeline * MOL-1196: extract to factory * MOL-1196: check tracking code lenght * MOL-1196: update comment --------- Co-authored-by: Vitalij Mik commit c08c340565b1d37a3824f820ba99033923697f36 Author: Christian Dangl Date: Fri Dec 1 10:50:31 2023 +0100 NTR: fix typo in plugin config commit cc97b68f2d8bb1be13fac1640faeda965e7e54ed Author: Christian Dangl Date: Fri Dec 1 09:45:49 2023 +0100 NTR: add Shopware 6.5.7.3 to pipeline commit 54836bb72c0c5dd34ab1b2f75f668bd79f3b39a5 Author: Christian Date: Wed Nov 29 09:38:20 2023 +0100 MOL-1276: add store api config route for frontend components (#660) commit faa72b61abe9c92214dd87ecc983831ef5e1ac27 Author: Christian Date: Wed Nov 29 09:38:00 2023 +0100 MOL-1277: fix wrong syntax in OrderLifeTimeLimitDetectorService.js (#661) commit a420fa73a56445045bca31bb735ad8da659a3d09 Author: Christian Date: Wed Nov 29 09:37:47 2023 +0100 MOL-1278: fix ideal issuer reset leads to wrong url (#662) * MOL-245: refactor code * MOL-245: reuse addresses * MOL-245: refactor buttons * MOL-245: PPE * MOL-245: refactor custom fields keys * MOL-245: refactor custom fields * MOL-245: refactor * MOL-245: use quantity + fix 2nd attempt payment * MOL-245: fix csrf * MOL-245: fix customfields for first customer * MOL-245: fix config * MOL-245: add cypress ui tests * MOL-245: setup a timer * MOL-245: added release tag * NTR: Squashed commit of the following: commit 4b0471e3a98377af6bc39a0f1dc3231bc1dbdf87 Author: Vitalij Mik Date: Tue Apr 23 13:36:38 2024 +0200 NTR: fix tests commit 23341f7bfe2253b367635e70afa5fb00ae3e7860 Author: Vitalij Mik Date: Tue Apr 23 11:49:29 2024 +0200 NTR: drop shopware 6.4.0.0 commit 05d4ce6bf09401dd9d0846a00826111516cf64a4 Author: Vitalij Mik Date: Tue Apr 23 09:18:33 2024 +0200 NTR: fix exception commit a8427fe9b57e0c39742b9a08b9e6733fd53cec59 Author: Vitalij Mik Date: Tue Apr 23 09:04:14 2024 +0200 NTR: update waiting timer commit 1c32404f7d5dade3c01d9af763222b745b9377d9 Author: Vitalij Mik Date: Mon Apr 22 15:28:39 2024 +0200 NTR: fix #733 commit e4c8022b8950a3ac4b6bf7940e45b687bce8d383 Author: Vitalij Mik Date: Mon Apr 22 15:12:33 2024 +0200 NTR: fix subscription symbol commit 24dda2b13f3177e4ac3a7ffc0dcd562cdcd1c86d Author: Vitalij Mik Date: Mon Apr 22 14:44:30 2024 +0200 NTR: fix shipment cypress commit 4295f717f7ba8e4b313d8d48921af9dc48d7bd3f Author: Vitalij Mik Date: Mon Apr 22 14:25:40 2024 +0200 NTR: fix shipment in 6.4 commit 24c33d0bf43e6033dbcfbbed7ad33e3386002ddd Author: Vitalij Mik Date: Mon Apr 22 14:10:11 2024 +0200 NTR: fix stan commit e1359f6dbc8b568a34a50b82fe30222f52db2ff6 Author: Vitalij Mik Date: Mon Apr 22 13:54:29 2024 +0200 NTR: fix checkout for 6.4 commit e0d6e8093443ed609dde51286b1e1d8b0f50d246 Author: Vitalij Mik Date: Mon Apr 22 11:37:18 2024 +0200 NTR: fix refund cypress tests commit a641bdd0dfbcd8d72d92dbffee1ece2c11b35db7 Author: Vitalij Mik Date: Tue Apr 16 16:06:27 2024 +0200 NTR: revert dependencies commit c891cd96cf80d9b53005ecad853097ac318268cf Author: Vitalij Mik Date: Tue Apr 16 15:34:59 2024 +0200 NTR: change dependencies commit 23c3cb8c854a19577810d492c8b63794fa3691c4 Author: Vitalij Mik Date: Tue Apr 16 14:50:43 2024 +0200 NTR: update composer depdencies commit 675d13759c929518431df9ac8396215801ae7719 Author: Vitalij Mik Date: Tue Apr 16 13:54:15 2024 +0200 NTR: updates for 6.6 (#721) * NTR: set routing to xml * NTR: update routes * NTR: delete 6.5 specific controller * NTR: cleanup * NTR: fallback einbauen * NTR: fix routing for 6.5 * NTR: revert fixtures * NTR: fix routes scopes * NTR: fix routescope * NTR: phpstan fix * NTR: fix some tests for 6.6 * NTR: fix twig and exceptions * NTR: fix stan * NTR: fix smart contact form * NTR: fix phpstan errors * NTR: fix smart contact form * NTR: revert cart service * NTR: Csfix * NTR: add 6.6 to pipline * NTR: fix cart service * NTR: csfixer * NTR: fix add product * NTR: fix controller * NTR: admin updates * NTR: Update linter * NTR: fix refund manager * NTR: delete route annotations * NTR: add 6.6 to pipeline * NTR: fix subscription * NTR: csfix * NTR: fix mgration for sw 6.4 * NTR: update new sw version in pipeline checks * NTR: test apple pay buttons * NTR: bumb sw version * NTR: update php version * NTR: fix preview * NTR: eslint fix * NTR: eslint * NTR: eslint --------- Co-authored-by: Vitalij Mik commit 4b1986bce52f656670ab45822e6f39b2aad21d9a Author: Thilo <40621547+ThLind@users.noreply.github.com> Date: Tue Apr 16 13:02:41 2024 +0200 Mol 1299/refund tax at net orders (#734) * MOL-1299: Refund Tax at Net Orders * MOL-1299: Correct VAT Promotion Calculation --------- Co-authored-by: Thilo Lindner commit c29bf21208edd80df7f9ad7216e152de0527cbc5 Author: Vitalij Mik Date: Tue Apr 16 10:14:32 2024 +0200 NTR: revert commit 2e8c35a64f0ec202c32e4d4160011084edbf3712 Author: Vitalij Mik Date: Tue Apr 16 10:06:53 2024 +0200 NTR: update php version commit 8fc3a2bbb085c4fe070a959d2013a5afe45f62ba Author: Vitalij Mik Date: Tue Apr 16 08:58:55 2024 +0200 NTR: test new sw version commit ff38ec971d831c8e06c0869e4e3dd4be36c7ad33 Author: Vitalij Mik Date: Tue Mar 26 09:22:27 2024 +0100 NTR: set shopware version constraint commit 59664228017e8cda98da853f000f4521737f1c43 Author: Vitalij Mik Date: Mon Mar 25 16:08:44 2024 +0100 NTR: Revert storeapi key commit 10d2dede78d85a6c6964ec4c0c6e1251a7d85eac Author: Vitalij Mik Date: Mon Mar 25 11:34:27 2024 +0100 NTR: prepare 4..6.0 release (#732) Co-authored-by: Vitalij Mik commit 584f2346ed3460c68e27b97c016e6a4d56d908a6 Author: Vitalij Mik Date: Fri Mar 22 13:56:28 2024 +0100 PISHPS-243: create guest account over route (#730) * NTR: PISHPS-243: create guest account over route * NTR: CSfixer * NTR: fix 6.4.20.2 --------- Co-authored-by: Vitalij Mik commit c2fdc22b86f4872755fc67db046a7734ec090b9b Author: Vitalij Mik Date: Fri Mar 22 09:05:19 2024 +0100 PISHPS-263: allow set apple pay as default payment method (#728) * NTR: PISHPS-263: allow set apple pay as default payment method * NTR: delete cypress tests for account --------- Co-authored-by: Vitalij Mik commit edbe0dde5e7ccf4825d0e4cd59c389ce02b63542 Author: Vitalij Mik Date: Thu Mar 21 13:17:48 2024 +0100 NTR: PISHPS-264: use SW logic for shipping methods in Apple Pay (#731) Co-authored-by: Vitalij Mik commit c13df34a0b19a8e8e4c5877aea607c870dc80c27 Author: Vitalij Mik Date: Thu Mar 21 13:17:13 2024 +0100 NTR: PISHPS-247: Klarna One payment (#727) Co-authored-by: Vitalij Mik commit 1734730e454b622210048e7e464fa8fe167dab07 Author: Vitalij Mik Date: Wed Mar 20 08:24:42 2024 +0100 NTR: fix payment in order edit (#729) Co-authored-by: Vitalij Mik commit 190312956939ae38704df683ac6cc23d4fd106d3 Author: Vitalij Mik Date: Tue Mar 19 08:33:14 2024 +0100 NTR: add the default system language (#726) Co-authored-by: Vitalij Mik commit 77be51e0e1e193b8fca00086831caea4700e25f9 Author: Vitalij Mik Date: Tue Mar 19 08:32:52 2024 +0100 NTR: PISHPS-256: rebranding ideal (#725) Co-authored-by: Vitalij Mik commit 70fe37e90ccacd1c97748110e21300c1cb16ccc5 Author: Vitalij Mik Date: Mon Mar 18 15:17:08 2024 +0100 NTR: update mollie lbirary (#723) Co-authored-by: Vitalij Mik commit 7a18c81988ede00872d14bde55e336946dfa2384 Author: Vitalij Mik Date: Thu Mar 7 15:25:11 2024 +0100 NTR: test SW 6.5.8.7 (#720) Co-authored-by: Vitalij Mik commit cfb4830d671a3377ada05235e2e47a99343f7c2c Author: Vitalij Mik Date: Thu Feb 29 14:44:18 2024 +0100 NTR: Fix #704 (#718) * NTR: Fix #704 * NTR: check subscription with custom routes * NTR: fix translation problem --------- Co-authored-by: Vitalij Mik commit 89dd84e47379d9bb7148de4233db67b1a12da83a Author: Aleksej Wert <55035661+GuentherHade@users.noreply.github.com> Date: Thu Feb 29 14:32:17 2024 +0100 NTR: show mollie payment js if needed (#712) * NTR: show mollie payment js if needed * NTR: added comments * NTR: Fixes commit b2eff45fe713d60415554f7d1157e282c11041e2 Author: Vitalij Mik Date: Wed Feb 28 07:59:35 2024 +0100 NTR: search for active payment methods only (#717) Co-authored-by: Vitalij Mik commit a13da65c087f643f57a1e91f0e1117771213807c Author: Florian Bender Date: Tue Feb 27 13:43:33 2024 +0100 NTR: Check for `OrderAware` interface (#716) commit f06f96dd4ae6236dec7fb4c3a172b6064e226bfd Author: Vitalij Mik Date: Wed Feb 21 11:04:21 2024 +0100 NTR: new sw version (#713) Co-authored-by: Vitalij Mik commit e584842aed28f74950474ea89d7f59df3424171d Author: Vitalij Mik Date: Mon Feb 19 10:56:56 2024 +0100 NTR: new release 4.5.0 (#710) Co-authored-by: Vitalij Mik commit bf0055d39a7385778a203e517d9c0ea92dede389 Author: Vitalij Mik Date: Fri Feb 16 13:39:31 2024 +0100 NTR: add testreail id to blik (#709) Co-authored-by: Vitalij Mik commit 20538c43835adc1fbcce34bf59b214e5519ee8af Author: Vitalij Mik Date: Fri Feb 16 12:34:41 2024 +0100 MOL-1297: Fix payment method cache in production (#706) * MOL-1297: Fix payment method cache in production * MOL-1297: refactor * MOL-1297: codestyle fix --------- Co-authored-by: Vitalij Mik commit 4ecd79d6ab055d400702fca8b1e5e3ef0e9843d0 Author: Vitalij Mik Date: Fri Feb 16 12:34:28 2024 +0100 NTR: PISHPS-242: do not call save twice (#708) * NTR: PISHPS-242: do not call save twice * NTR: PISHPS-242: fix refund manager in 6.5 --------- Co-authored-by: Vitalij Mik commit 6189790af7bdcdbb7e98204897417781442845c4 Author: Uwe Thiess Date: Fri Feb 16 11:28:55 2024 +0100 Update register.html.twig (#700) Insert hidden field to let shopware know we want to create an account. commit 1c582fa8f5e7335e8579700fb9b94175ce2472c2 Author: Vitalij Mik Date: Thu Feb 15 13:13:50 2024 +0100 NTR: test new shopware version (#707) Co-authored-by: Vitalij Mik * NTR: update PPE * NTR: fix guest account * NTR: update composer * NTR: fix ppe tests * NTR: update * NTR: Merge master into PPE * NTR: Cs fix * NTR: fix subscriber * NTR: do not check paypal redirect, api key is not set in pipeline * NTR: fixed config * NTR: update mollie api to 2.73 * NTR: CS Fix * NTR: Fix cypress * NTR: switch to method Details * NTR: add onfig over env * NTR: fix tests * NTR: fix tests * NTR: added more logs in PPE * NTR: fix settings * NTR: use express buttons as class selector * NTR: remove old klarna from tests * NTR: Cs fix * NTR: display privacy note checkbox * NTR: refactored JS * NTR: accepted data protection serverside check * NTR: update backup cart * NTR: Fix ppe cypress tests * NTR: refactor apple pay --------- Co-authored-by: Christian Dangl Co-authored-by: Vitalij Mik --- .github/actions/run-e2e/action.yml | 1 + .github/workflows/ci_pipe.yml | 2 +- .github/workflows/nightly_pipe.yml | 2 +- makefile | 71 +++-- .../ApplePayDirect/ApplePayDirect.php | 31 +-- .../PaypalExpress/PayPalExpress.php | 205 ++++++++++++++ .../ShipmentManager/ShipmentManager.php | 2 +- .../Api/Order/OrderControllerBase.php | 3 +- .../PaypalExpressControllerBase.php | 247 ++++++++++++++++ src/Facade/MolliePaymentDoPay.php | 12 +- src/Handler/Method/CreditCardPayment.php | 8 +- src/Handler/Method/PayPalExpressPayment.php | 41 +++ src/Handler/Method/PosPayment.php | 4 +- .../Migration1725347559MollieTags.php | 2 +- .../CustomerAddressRepository.php | 23 ++ .../CustomerAddressRepositoryInterface.php | 30 ++ .../PaymentMethod/PaymentMethodRepository.php | 22 ++ .../view/sw-order-detail-general/index.js | 4 +- .../core/creditcard-mandate.plugin.js | 2 +- .../plugins/apple-pay-direct.plugin.js | 222 +++------------ .../apple-pay-payment-method.plugin.js | 2 +- .../plugins/bancomat-plugin.js | 2 +- .../creditcard-mandate-manage.plugin.js | 2 +- .../plugins/mollie-express-actions.plugin.js | 114 ++++++++ .../plugins/paypal-express.plugin.js | 65 +++++ .../plugins/pos-terminal.plugin.js | 2 +- .../repository/BuyButtonRepository.js | 17 ++ .../repository/BuyElementRepository.js | 21 ++ .../repository/ExpressButtonsRepository.js | 16 ++ .../repository/PrivacyNoteElement.js | 86 ++++++ .../services/ApplePaySessionFactory.js | 4 +- .../services/ExpressAddToCart.js | 25 ++ src/Resources/app/storefront/src/register.js | 8 +- .../app/storefront/src/scss/base.scss | 1 + .../scss/component/paypal-express-button.scss | 73 +++++ .../app/storefront/src/scss/display.scss | 3 + src/Resources/config/config.xml | 220 ++++++++++++--- .../routes/storefront/paypal_express.xml | 21 ++ src/Resources/config/services.xml | 15 + src/Resources/config/services/components.xml | 9 + src/Resources/config/services/controller.xml | 13 + src/Resources/config/services/handlers.xml | 7 + src/Resources/config/services/payment.xml | 11 + .../config/services/repositories.xml | 5 + src/Resources/config/services/subscriber.xml | 6 + src/Resources/public/static/ppe-black.png | Bin 0 -> 6708 bytes src/Resources/public/static/ppe-primary.png | Bin 0 -> 6454 bytes src/Resources/public/static/ppe-white.png | Bin 0 -> 6894 bytes .../component/apple-pay-direct-button.twig | 7 +- .../express-privacy-notice.html.twig | 9 + .../component/paypal-express-button.twig | 40 +++ src/Resources/views/mollie/head.html.twig | 14 +- .../buy-widget/buy-widget-form.html.twig | 64 +++-- .../checkout/offcanvas-cart.html.twig | 21 +- .../component/product/card/action.html.twig | 40 ++- .../paypal-express-button-csrf.twig | 1 + .../page/checkout/address/index.html.twig | 20 ++ .../page/checkout/cart/index.html.twig | 20 +- .../page/checkout/confirm/index.html.twig | 12 +- .../product-detail/buy-widget-form.html.twig | 51 ++-- src/Service/Cart/CartBackupService.php | 16 +- .../Cart/Voucher/VoucherCartCollector.php | 3 +- src/Service/CartService.php | 6 + src/Service/CartServiceInterface.php | 7 + src/Service/CustomFieldsInterface.php | 10 + src/Service/CustomerService.php | 263 ++++++++++++++++-- src/Service/CustomerServiceInterface.php | 15 +- src/Service/Order/UpdateOrderLineItems.php | 3 +- src/Service/PayPalExpressConfig.php | 50 ++++ .../Remover/PayPalExpressPaymentRemover.php | 59 ++++ src/Service/PaymentMethodService.php | 58 ++-- .../Refund/CompositionMigrationService.php | 5 +- src/Service/Router/RoutingBuilder.php | 21 ++ src/Service/SettingsService.php | 44 ++- .../UpdateOrderTransactionCustomFields.php | 2 +- src/Setting/MollieSettingStruct.php | 67 +++++ src/Struct/Address/AddressStruct.php | 145 ++++++++++ src/Struct/CustomerStruct.php | 3 +- src/Struct/LineItem/LineItemAttributes.php | 5 +- src/Struct/Order/OrderAttributes.php | 29 +- .../OrderLineItemEntityAttributes.php | 9 +- .../OrderTransactionAttributes.php | 22 +- src/Subscriber/CartConvertedSubscriber.php | 36 +++ src/Subscriber/PaypalExpressSubscriber.php | 66 +++++ src/Traits/Storefront/RedirectTrait.php | 5 + .../e2e/storefront/cancel/cancel-item.cy.js | 2 +- .../storefront/checkout/checkout-states.cy.js | 2 +- .../checkout/checkout-success.cy.js | 10 +- .../payment-methods/creditcard.cy.js | 4 +- .../payment-methods/paypal-express.cy.js | 190 +++++++++++++ .../e2e/storefront/shipment/shipment.cy.js | 2 +- .../subscriptions/subscription.cy.js | 8 +- .../actions/admin/ShopConfigurationAction.js | 13 +- .../actions/storefront/products/PDPAction.js | 12 +- .../storefront/checkout/CartRepository.js | 7 + .../checkout/OffCanvasRepository.js | 7 + .../storefront/checkout/RegisterRepository.js | 11 + .../storefront/products/ListingRepository.js | 7 + .../storefront/products/PDPRepository.js | 9 + tests/PHPUnit/Fakes/FakeCartService.php | 6 + tests/PHPUnit/Fakes/FakeCustomerService.php | 6 +- tests/PHPUnit/Service/CustomerServiceTest.php | 3 + .../Service/PaymentMethodServiceTest.php | 10 +- vendor_manual/makefile | 5 +- .../mollie-api-php/.php-cs-fixer.dist.php | 37 --- vendor_manual/mollie/mollie-api-php/README.md | 4 +- .../mollie/mollie-api-php/composer.json | 2 +- .../mollie-api-php/phpstan-baseline.neon | 5 - .../mollie/mollie-api-php/phpstan.neon | 17 -- .../mollie/mollie-api-php/phpunit.xml | 15 - .../mollie/mollie-api-php/scoper.inc.php | 13 - .../src/Endpoints/EndpointAbstract.php | 2 +- .../src/Endpoints/MandateEndpoint.php | 4 +- .../src/Endpoints/MethodIssuerEndpoint.php | 85 ++++++ .../src/Endpoints/OnboardingEndpoint.php | 4 +- .../src/Endpoints/OrderRefundEndpoint.php | 24 ++ .../src/Endpoints/PaymentLinkEndpoint.php | 35 ++- .../Endpoints/PaymentLinkPaymentEndpoint.php | 93 +++++++ .../src/Endpoints/PaymentRefundEndpoint.php | 8 +- .../src/Endpoints/SessionEndpoint.php | 139 +++++++++ .../Endpoints/SubscriptionPaymentEndpoint.php | 79 ++++++ .../mollie-api-php/src/MollieApiClient.php | 118 +++++--- .../mollie-api-php/src/Resources/Capture.php | 16 ++ .../mollie-api-php/src/Resources/Customer.php | 28 +- .../src/Resources/HasPresetOptions.php | 38 +++ .../mollie-api-php/src/Resources/Order.php | 44 +-- .../mollie-api-php/src/Resources/Payment.php | 55 ++-- .../src/Resources/PaymentLink.php | 77 +++++ .../mollie-api-php/src/Resources/Session.php | 178 ++++++++++++ .../src/Resources/SessionCollection.php | 22 ++ .../src/Types/PaymentMethod.php | 43 ++- .../src/Types/SessionStatus.php | 26 ++ 132 files changed, 3718 insertions(+), 737 deletions(-) create mode 100644 src/Components/PaypalExpress/PayPalExpress.php create mode 100644 src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php create mode 100644 src/Handler/Method/PayPalExpressPayment.php create mode 100644 src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js create mode 100644 src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js create mode 100644 src/Resources/app/storefront/src/scss/component/paypal-express-button.scss create mode 100644 src/Resources/config/routes/storefront/paypal_express.xml create mode 100644 src/Resources/public/static/ppe-black.png create mode 100644 src/Resources/public/static/ppe-primary.png create mode 100644 src/Resources/public/static/ppe-white.png create mode 100644 src/Resources/views/mollie/component/express-privacy-notice.html.twig create mode 100644 src/Resources/views/mollie/component/paypal-express-button.twig create mode 100644 src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig create mode 100644 src/Resources/views/storefront/page/checkout/address/index.html.twig create mode 100644 src/Service/PayPalExpressConfig.php create mode 100644 src/Service/Payment/Remover/PayPalExpressPaymentRemover.php create mode 100644 src/Struct/Address/AddressStruct.php create mode 100644 src/Subscriber/CartConvertedSubscriber.php create mode 100644 src/Subscriber/PaypalExpressSubscriber.php create mode 100644 tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js create mode 100644 tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js delete mode 100644 vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php delete mode 100644 vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon delete mode 100644 vendor_manual/mollie/mollie-api-php/phpstan.neon delete mode 100644 vendor_manual/mollie/mollie-api-php/phpunit.xml delete mode 100644 vendor_manual/mollie/mollie-api-php/scoper.inc.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Resources/Session.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php create mode 100644 vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php diff --git a/.github/actions/run-e2e/action.yml b/.github/actions/run-e2e/action.yml index 2d33ab682..974d3fee0 100644 --- a/.github/actions/run-e2e/action.yml +++ b/.github/actions/run-e2e/action.yml @@ -97,6 +97,7 @@ runs: docker exec shop bash -c 'sed -i "s/APP_ENV=dev/APP_ENV=prod/g" /var/www/html/.env' || true; # we have to enable cypress mode in our shop, this helps us to create subscriptions without webhooks docker exec shop bash -c "echo "MOLLIE_CYPRESS_MODE=1" >> /var/www/html/.env" || true; + docker exec shop bash -c "echo "MOLLIE_PAYPAL_EXPRESS_BETA=1" >> /var/www/html/.env" || true; # -------------------------------------------------------------------------------------------------------------------------------------- diff --git a/.github/workflows/ci_pipe.yml b/.github/workflows/ci_pipe.yml index f4bcdbeb0..1d0d117f1 100644 --- a/.github/workflows/ci_pipe.yml +++ b/.github/workflows/ci_pipe.yml @@ -373,7 +373,7 @@ jobs: MOLLIE_APIKEY_TEST: ${{ secrets.MOLLIE_APIKEY_TEST }} # ------------------------------------------- RUN_CYPRESS: true - TESTRAIL: true + TESTRAIL: false TESTRAIL_DOMAIN: ${{ secrets.TESTRAIL_DOMAIN }} TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }} diff --git a/.github/workflows/nightly_pipe.yml b/.github/workflows/nightly_pipe.yml index 24b7cab5c..1f000f4c2 100644 --- a/.github/workflows/nightly_pipe.yml +++ b/.github/workflows/nightly_pipe.yml @@ -417,7 +417,7 @@ jobs: MOLLIE_APIKEY_TEST: ${{ secrets.MOLLIE_APIKEY_TEST }} # ------------------------------------------- RUN_CYPRESS: true - TESTRAIL: true + TESTRAIL: false TESTRAIL_DOMAIN: ${{ secrets.TESTRAIL_DOMAIN }} TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }} diff --git a/makefile b/makefile index 855ffd852..e2debaeff 100644 --- a/makefile +++ b/makefile @@ -12,11 +12,18 @@ NODE_VERSION:=$(shell node -v) help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + @printf "\033[33mInstallation:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##1 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##1 "}; {printf "\033[33m - %-30s\033[0m %s\n", $$1, $$2}' + @echo "---------------------------------------------------------------------------------------------------------" + @printf "\033[36mDevelopment:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##2 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##2 "}; {printf "\033[36m - %-30s\033[0m %s\n", $$1, $$2}' + @echo "---------------------------------------------------------------------------------------------------------" + @printf "\033[35mDevOps:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##3 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##3 "}; {printf "\033[35m - %-30s\033[0m %s\n", $$1, $$2}' # ------------------------------------------------------------------------------------------------------------ -prod: ## Installs all production dependencies +prod: ##1 Installs all production dependencies # do not switch to production composer PROD, otherwise it would # also install shopware in here -> we just need it for the release composer.json file # so just switch to our dev dependency variant @@ -27,7 +34,7 @@ prod: ## Installs all production dependencies cd src/Resources/app/administration && npm install --omit=dev cd src/Resources/app/storefront && npm install --omit=dev -dev: ## Installs all dev dependencies +dev: ##1 Installs all dev dependencies php switch-composer.php dev @composer validate # we have to run update in dev mode, because dev dependencies are not compatible with newer php version. should be updated when support for 6.4 is dropped @@ -36,10 +43,10 @@ dev: ## Installs all dev dependencies cd src/Resources/app/storefront && npm install curl -1sLf 'https://dl.cloudsmith.io/public/friendsofshopware/stable/setup.deb.sh' | sudo -E bash && sudo apt install -y --allow-downgrades shopware-cli=0.3.18 -install: ## [deprecated] Installs all production dependencies. Please use "make prod" now. +install: ##1 [deprecated] Installs all production dependencies. Please use "make prod" now. @make prod -B -clean: ## Cleans all dependencies and files +clean: ##1 Cleans all dependencies and files rm -rf vendor/* # ------------------------------------------------------ rm -rf .reports | true @@ -52,7 +59,7 @@ clean: ## Cleans all dependencies and files rm -rf ./src/Resources/public/administration rm -rf ./src/Resources/public/mollie-payments.js -build: ## Installs the plugin, and builds the artifacts using the Shopware build commands. +build: ##3 Installs the plugin, and builds the artifacts using the Shopware build commands. # ----------------------------------------------------- # CUSTOM WEBPACK php switch-composer.php dev @@ -68,83 +75,93 @@ build: ## Installs the plugin, and builds the artifacts using the Shopware build cd ../../.. && php bin/console --no-debug assets:install cd ../../.. && php bin/console --no-debug cache:clear -fixtures: ## Installs all available testing fixtures of the Mollie plugin +fixtures: ##3 Installs all available testing fixtures of the Mollie plugin cd ../../.. && php bin/console --no-debug cache:clear cd ../../.. && php bin/console --no-debug fixture:load:group mollie # ------------------------------------------------------------------------------------------------------------ -phpcheck: ## Starts the PHP syntax checks +phpcheck: ##2 Starts the PHP syntax checks @find . -name '*.php' -not -path "./vendor/*" -not -path "./tests/*" | xargs -n 1 -P4 php -l -phpmin: ## Starts the PHP compatibility checks +phpmin: ##2 Starts the PHP compatibility checks @php vendor/bin/phpcs -p --standard=PHPCompatibility --extensions=php --runtime-set testVersion 7.4 ./src -csfix: ## Starts the PHP CS Fixer +csfix: ##2 Starts the PHP CS Fixer @PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer fix --config=./.php_cs.php --dry-run -stan: ## Starts the PHPStan Analyser +stan: ##2 Starts the PHPStan Analyser @php vendor/bin/phpstan analyse -c ./.phpstan.neon -phpunit: ## Starts all PHPUnit Tests +phpunit: ##2 Starts all PHPUnit Tests @XDEBUG_MODE=coverage php vendor/bin/phpunit --configuration=phpunit.xml --coverage-html ./.reports/phpunit/coverage -infection: ## Starts all Infection/Mutation tests +infection: ##2 Starts all Infection/Mutation tests @XDEBUG_MODE=coverage php vendor/bin/infection --configuration=./.infection.json --log-verbosity=all --debug -insights: ## Starts the PHPInsights Analyser +insights: ##2 Starts the PHPInsights Analyser @php vendor/bin/phpinsights analyse --no-interaction -jest: ## Starts all Jest tests +jest: ##2 Starts all Jest tests cd ./src/Resources/app/administration && ./node_modules/.bin/jest --config=.jest.config.js --coverage cd ./src/Resources/app/storefront && ./node_modules/.bin/jest --config=.jest.config.js --coverage -stryker: ## Starts the Stryker Jest Mutation Tests +stryker: ##2 Starts the Stryker Jest Mutation Tests cd ./src/Resources/app/administration && ./node_modules/.bin/stryker run .stryker.conf.json @# Storefront has no tests at the moment @# cd ./src/Resources/app/storefront && ./node_modules/.bin/stryker run .stryker.conf.json -eslint: ## Starts the ESLinter +eslint: ##2 Starts the ESLinter +ifndef mode cd ./src/Resources/app/administration && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src cd ./src/Resources/app/storefront && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src +endif +ifeq ($(mode), no-dry-run) + cd ./src/Resources/app/administration && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src --fix + cd ./src/Resources/app/storefront && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src --fix +endif + + + -stylelint: ## Starts the Stylelinter + +stylelint: ##2 Starts the Stylelinter cd ./src/Resources/app/administration && ./node_modules/.bin/stylelint --allow-empty-input ./src/**/*.scss cd ./src/Resources/app/storefront && ./node_modules/.bin/stylelint --allow-empty-input ./src/**/*.scss -configcheck: ## Tests and verifies the plugin configuration file +configcheck: ##2 Tests and verifies the plugin configuration file cd ./tests/Custom && php verify-plugin-config.php # ------------------------------------------------------------------------------------------------------------ -snippetcheck: ## Tests and verifies all plugin snippets +snippetcheck: ##2 Tests and verifies all plugin snippets php vendor/bin/phpunuhi validate --configuration=./.phpunuhi.xml --report-format=junit --report-output=./.phpunuhi/junit.xml -snippetexport: ## Exports all snippets +snippetexport: ##2 Exports all snippets php vendor/bin/phpunuhi export --configuration=./.phpunuhi.xml --dir=./.phpunuhi -snippetimport: ## Imports the provided snippet set [set=xyz file=xz.csv] +snippetimport: ##2 Imports the provided snippet set [set=xyz file=xz.csv] php vendor/bin/phpunuhi import --configuration=./.phpunuhi.xml --set=$(set) --file=$(file) --intent=1 # ------------------------------------------------------------------------------------------------------------ -pr: ## Prepares everything for a Pull Request +pr: ##2 Prepares everything for a Pull Request @PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer fix --config=./.php_cs.php @make phpcheck -B @make phpmin -B @make stan -B @make phpunit -B - @make infection -B @make jest -B - @make stryker -B - @make eslint -B + @make eslint mode=no-dry-run -B @make stylelint -B @make configcheck -B @make snippetcheck -B + @make stryker -B + @make infection -B # ------------------------------------------------------------------------------------------------- -release: ## Builds a PROD version and creates a ZIP file in plugins/.build. +release: ##3 Builds a PROD version and creates a ZIP file in plugins/.build. ifneq (,$(findstring v12,$(NODE_VERSION))) $(warning Attention, reqruires Node v14 or higher to build a release!) @exit 1 diff --git a/src/Components/ApplePayDirect/ApplePayDirect.php b/src/Components/ApplePayDirect/ApplePayDirect.php index 764639abc..e703de143 100644 --- a/src/Components/ApplePayDirect/ApplePayDirect.php +++ b/src/Components/ApplePayDirect/ApplePayDirect.php @@ -22,6 +22,7 @@ use Kiener\MolliePayments\Service\OrderService; use Kiener\MolliePayments\Service\SettingsService; use Kiener\MolliePayments\Service\ShopService; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Mollie\Api\Exceptions\ApiException; use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection; @@ -251,7 +252,7 @@ public function addProduct(string $productId, int $quantity, SalesChannelContext { # if we already have a backup cart, then do NOT backup again. # because this could backup our temp. apple pay cart - if (!$this->cartBackupService->isBackupExisting($context)) { + if (! $this->cartBackupService->isBackupExisting($context)) { $this->cartBackupService->backupCart($context); } @@ -348,7 +349,7 @@ public function restoreCart(SalesChannelContext $context): void * @throws \Exception * @return SalesChannelContext */ - public function prepareCustomer(string $firstname, string $lastname, string $email, string $street, string $zipcode, string $city, string $countryCode, string $phone, string $paymentToken, int $acceptedDataProtection, SalesChannelContext $context): SalesChannelContext + public function prepareCustomer(string $firstname, string $lastname, string $email, string $street, string $zipcode, string $city, string $countryCode, string $phone, string $paymentToken, ?int $acceptedDataProtection, SalesChannelContext $context): SalesChannelContext { if (empty($paymentToken)) { throw new \Exception('PaymentToken not found!'); @@ -359,28 +360,24 @@ public function prepareCustomer(string $firstname, string $lastname, string $ema # if we are not logged in, # then we have to create a new guest customer for our express order - if (!$this->customerService->isCustomerLoggedIn($context)) { - $customer = $this->customerService->createApplePayDirectCustomerIfNotExists( - $firstname, - $lastname, - $email, - $phone, - $street, - $zipcode, - $city, - $countryCode, - $acceptedDataProtection, - $context + if (! $this->customerService->isCustomerLoggedIn($context)) { + $address = new AddressStruct($firstname, $lastname, $email, $street, '', $zipcode, $city, $countryCode, $phone); + + $customer = $this->customerService->createGuestAccount( + $address, + $applePayID, + $context, + $acceptedDataProtection ); - if (!$customer instanceof CustomerEntity) { + if (! $customer instanceof CustomerEntity) { throw new \Exception('Error when creating customer!'); } # now start the login of our customer. # Our SalesChannelContext will be correctly updated after our # forward to the finish-payment page. - $this->customerService->customerLogin($customer, $context); + $this->customerService->loginCustomer($customer, $context); } # also (always) update our payment method to use Apple Pay for our cart @@ -455,7 +452,7 @@ public function createPayment(OrderEntity $order, string $shopwareReturnUrl, str $transactions = $order->getTransactions(); $transaction = $transactions->last(); - if (!$transaction instanceof OrderTransactionEntity) { + if (! $transaction instanceof OrderTransactionEntity) { throw new \Exception('Created Apple Pay Direct order has not OrderTransaction!'); } diff --git a/src/Components/PaypalExpress/PayPalExpress.php b/src/Components/PaypalExpress/PayPalExpress.php new file mode 100644 index 000000000..05d2f5744 --- /dev/null +++ b/src/Components/PaypalExpress/PayPalExpress.php @@ -0,0 +1,205 @@ +repoPaymentMethods = $repoPaymentMethods; + $this->mollieApiFactory = $mollieApiFactory; + $this->priceBuilder = $priceBuilder; + $this->urlBuilder = $urlBuilder; + $this->customerService = $customerService; + $this->cartService = $cartService; + } + + + /** + * @param SalesChannelContext $context + * @return bool + */ + public function isPaypalExpressEnabled(SalesChannelContext $context): bool + { + try { + $methodID = $this->getActivePaypalExpressID($context); + + return (! empty($methodID)); + } catch (\Exception $ex) { + return false; + } + } + + /** + * @param SalesChannelContext $context + * @throws \Exception + * @return string + */ + public function getActivePaypalExpressID(SalesChannelContext $context): string + { + return $this->repoPaymentMethods->getActivePaypalExpressID($context->getContext()); + } + + + /** + * @param Cart $cart + * @param SalesChannelContext $context + * @throws \Mollie\Api\Exceptions\ApiException + * @return Session + */ + public function startSession(Cart $cart, SalesChannelContext $context): Session + { + $mollie = $this->mollieApiFactory->getLiveClient($context->getSalesChannelId()); + + $params = [ + 'method' => 'paypal', + 'methodDetails' => [ + 'checkoutFlow' => 'express', + ], + 'amount' => $this->priceBuilder->build( + $cart->getPrice()->getTotalPrice(), + $context->getCurrency()->getIsoCode() + ), + 'redirectUrl' => $this->urlBuilder->buildPaypalExpressRedirectUrl(), + 'cancelUrl' => $this->urlBuilder->buildPaypalExpressCancelUrl(), + ]; + + return $mollie->sessions->create($params); + } + + public function loadSession(string $sessionId, SalesChannelContext $context): Session + { + $mollie = $this->mollieApiFactory->getLiveClient($context->getSalesChannelId()); + /** + * if we load the session from mollie api, we dont get the shipping address at first time. usually it takes several seconds until the data from paypal is transfered to mollie + * so we try to load the session at least 5 times with increased waiting time. + */ + for ($i = 0; $i < self::SESSION_MAX_RETRY; $i++) { + $sleepTimer = self::SESSION_BASE_TIMEOUT * ($i+1); + usleep($sleepTimer); + $session = $mollie->sessions->get($sessionId); + if ($session->methodDetails !== null && $session->methodDetails->shippingAddress !== null) { + break; + } + } + + return $session; + } + + + + /** + * @param AddressStruct $shippingAddress + * @param SalesChannelContext $context + * @param null|AddressStruct $billingAddress + * @throws \Exception + * @return SalesChannelContext + */ + public function prepareCustomer(AddressStruct $shippingAddress, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): SalesChannelContext + { + $updateShippingAddress = true; + $paypalExpressId = $this->getActivePaypalExpressID($context); + + $customer = $context->getCustomer(); + + # if we are not logged in, + # then we have to create a new guest customer for our express order + # check here for instance because of phpstan + if ($customer === null) { + + # find existing customer by email + $customer = $this->customerService->findCustomerByEmail($shippingAddress->getEmail(), $context); + + + if ($customer === null) { + $updateShippingAddress = false; + $customer = $this->customerService->createGuestAccount( + $shippingAddress, + $paypalExpressId, + $context, + $acceptedDataProtection, + $billingAddress + ); + } + + + if (! $customer instanceof CustomerEntity) { + throw new \Exception('Error when creating customer!'); + } + + # now start the login of our customer. + # Our SalesChannelContext will be correctly updated after our + # forward to the finish-payment page. + $this->customerService->loginCustomer($customer, $context); + } + + # if we have an existing customer, we want reuse his shipping address instead of creating new one + if ($updateShippingAddress) { + $this->customerService->reuseOrCreateAddresses($customer, $shippingAddress, $context->getContext(), $billingAddress); + } + + + # also (always) update our payment method to use Apple Pay for our cart + return $this->cartService->updatePaymentMethod($context, $paypalExpressId); + } +} diff --git a/src/Components/ShipmentManager/ShipmentManager.php b/src/Components/ShipmentManager/ShipmentManager.php index 50964c074..f1660c913 100644 --- a/src/Components/ShipmentManager/ShipmentManager.php +++ b/src/Components/ShipmentManager/ShipmentManager.php @@ -348,7 +348,7 @@ private function findMatchingLineItems(OrderEntity $order, string $itemIdentifie // Check itemIdentifier against the mollie order_line_id custom field $customFields = $lineItem->getCustomFields() ?? []; - $mollieOrderLineId = $customFields[CustomFieldsInterface::MOLLIE_KEY]['order_line_id'] ?? null; + $mollieOrderLineId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY] ?? null; if (!is_null($mollieOrderLineId) && $mollieOrderLineId === $itemIdentifier) { return true; } diff --git a/src/Controller/Api/Order/OrderControllerBase.php b/src/Controller/Api/Order/OrderControllerBase.php index e16a7d1cc..7ae33ffc0 100644 --- a/src/Controller/Api/Order/OrderControllerBase.php +++ b/src/Controller/Api/Order/OrderControllerBase.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Controller\Api\Order; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\MollieApi\Order; use Kiener\MolliePayments\Service\OrderService; use Shopware\Core\Framework\Context; @@ -56,7 +57,7 @@ private function paymentUrlResponse(string $orderId, Context $context): JsonResp $customFields = $order->getCustomFields(); - $mollieOrderId = ($customFields !== null && isset($customFields['mollie_payments']['order_id'])) ? $customFields['mollie_payments']['order_id'] : null; + $mollieOrderId = ($customFields !== null && isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY])) ? $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY] : null; if (is_null($mollieOrderId)) { return $this->json([], 404); diff --git a/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php b/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php new file mode 100644 index 000000000..3a5209c1c --- /dev/null +++ b/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php @@ -0,0 +1,247 @@ +paypalExpress = $paypalExpress; + $this->cartService = $cartService; + $this->router = $router; + $this->logger = $logger; + $this->settingsService = $settingsService; + } + + /** + * @param Request $request + * @param SalesChannelContext $context + * @throws ApiException + * @return Response + */ + public function startCheckout(Request $request, SalesChannelContext $context): Response + { + $redirectUrl = $this->getCheckoutCartPage($this->router); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + + if ($settings->isPaypalExpressEnabled() === false) { + $this->logger->error('Paypal Express is disabled'); + return new RedirectResponse($redirectUrl); + } + + $cart = $this->cartService->getCalculatedMainCart($context); + + + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + $oldSessionId = null; + + if ($cartExtension instanceof ArrayStruct) { + $oldSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? null; + } + + if ($oldSessionId !== null) { + $session = $this->paypalExpress->loadSession($oldSessionId, $context); + } else { + $session = $this->paypalExpress->startSession($cart, $context); + + $cartExtension = [ + CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY => $session->id + ]; + + if ($settings->isRequireDataProtectionCheckbox()) { + $cartExtension[CustomFieldsInterface::ACCEPTED_DATA_PROTECTION] = (bool)$request->get(CustomFieldsInterface::ACCEPTED_DATA_PROTECTION, false); + } + + $cart->addExtension(CustomFieldsInterface::MOLLIE_KEY, new ArrayStruct($cartExtension)); + + $this->cartService->persistCart($cart, $context); + } + + $sessionRedirect = $session->getRedirectUrl(); + if ($sessionRedirect !== null) { + $redirectUrl = $sessionRedirect; + } + + return new RedirectResponse($redirectUrl); + } + + /** + * @param SalesChannelContext $context + * @return Response + */ + public function finishCheckout(SalesChannelContext $context): Response + { + $returnUrl = $this->getCheckoutCartPage($this->router); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + + if ($settings->isPaypalExpressEnabled() === false) { + $this->logger->error('Paypal Express is disabled'); + return new RedirectResponse($returnUrl); + } + + $cart = $this->cartService->getCalculatedMainCart($context); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + + $payPalExpressSessionId = null; + $acceptedDataProtection = null; + + if ($cartExtension instanceof ArrayStruct) { + $payPalExpressSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? null; + if ($settings->isRequireDataProtectionCheckbox()) { + $acceptedDataProtection = $cartExtension[CustomFieldsInterface::ACCEPTED_DATA_PROTECTION] ?? false; + } + } + + + + + + if ($payPalExpressSessionId === null) { + $this->logger->error('Failed to finish checkout, session not exists'); + + return new RedirectResponse($returnUrl); + } + + try { + $payPalExpressSession = $this->paypalExpress->loadSession($payPalExpressSessionId, $context); + } catch (\Throwable $e) { + $this->logger->critical('Failed to load session from mollie', [ + 'message' => $e->getMessage(), + 'sessionId' => $payPalExpressSessionId + ]); + return new RedirectResponse($returnUrl); + } + + + $methodDetails = $payPalExpressSession->methodDetails; + + + if ($methodDetails->shippingAddress === null) { + $this->logger->error('Failed to finish checkout, got methodDetails without shipping address', [ + 'sessionId' => $payPalExpressSession->id, + 'status' => $payPalExpressSession->status + ]); + + return new RedirectResponse($returnUrl); + } + if ($methodDetails->billingAddress === null) { + $this->logger->error('Failed to finish checkout, got methodDetails without billing address', [ + 'sessionId' => $payPalExpressSession->id, + 'status' => $payPalExpressSession->status + ]); + + return new RedirectResponse($returnUrl); + } + + $billingAddress = null; + + $shippingAddress = $methodDetails->shippingAddress; + $shippingAddress->phone = ''; + if ($methodDetails->billingAddress->streetAdditional !== null) { + $shippingAddress->streetAdditional = $methodDetails->billingAddress->streetAdditional; + } + if ($methodDetails->billingAddress->phone !== null) { + $shippingAddress->phone = $methodDetails->billingAddress->phone; + } + if ($methodDetails->billingAddress->email !== null) { + $shippingAddress->email = $methodDetails->billingAddress->email; + } + if ($methodDetails->billingAddress->streetAndNumber !== null) { + try { + $billingAddress = AddressStruct::createFromApiResponse($methodDetails->billingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create billing address', [ + 'message' => $e->getMessage(), + 'shippingAddress' => $shippingAddress, + 'billingAddress' => $methodDetails->billingAddress + ]); + return new RedirectResponse($returnUrl); + } + } + + try { + $shippingAddress = AddressStruct::createFromApiResponse($shippingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create shipping address', [ + 'message' => $e->getMessage(), + 'shippingAddress' => $shippingAddress, + 'billingAddress' => $methodDetails->billingAddress + ]); + return new RedirectResponse($returnUrl); + } + + + try { + # we have to update the cart extension before a new user is created and logged in, otherwise the extension is not saved + $cartExtension = new ArrayStruct([ + CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID => $payPalExpressSession->authenticationId + ]); + $cart->addExtension(CustomFieldsInterface::MOLLIE_KEY, $cartExtension); + + $this->cartService->updateCart($cart); + + $this->cartService->persistCart($cart, $context); + + + # create new account or find existing and login + $this->paypalExpress->prepareCustomer($shippingAddress, $context, $acceptedDataProtection, $billingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create customer or cart', [ + 'message' => $e->getMessage(), + ]); + return new RedirectResponse($returnUrl); + } + + + $returnUrl = $this->getCheckoutConfirmPage($this->router); + return new RedirectResponse($returnUrl); + } +} diff --git a/src/Facade/MolliePaymentDoPay.php b/src/Facade/MolliePaymentDoPay.php index 3547ee2df..f356a0240 100644 --- a/src/Facade/MolliePaymentDoPay.php +++ b/src/Facade/MolliePaymentDoPay.php @@ -9,10 +9,11 @@ use Kiener\MolliePayments\Exception\MollieOrderExpiredException; use Kiener\MolliePayments\Exception\PaymentUrlException; use Kiener\MolliePayments\Handler\Method\CreditCardPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PosPayment; use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\Mollie\MolliePaymentStatus; use Kiener\MolliePayments\Service\MollieApi\Builder\MollieOrderBuilder; use Kiener\MolliePayments\Service\MollieApi\Order; @@ -276,6 +277,11 @@ public function startMolliePayment(string $paymentMethod, AsyncPaymentTransactio if ($paymentHandler instanceof CreditCardPayment) { $checkoutURL = $transactionStruct->getReturnUrl(); } + + # paypal express does not have a redirect since we were already on paypal site before + if ($paymentHandler instanceof PayPalExpressPayment) { + $checkoutURL = $transactionStruct->getReturnUrl(); + } } return new MolliePaymentPrepareData((string)$checkoutURL, (string)$molliePaymentData->getId()); @@ -301,8 +307,8 @@ public function createCustomerAtMollie(OrderEntity $order, SalesChannelContext $ $customFields = $customer->getCustomFields(); - if (isset($customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS])) { - $mollieData = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS]; + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY])) { + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; $oneClickShouldSaveCard = (isset($mollieData[CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL])) ? (bool)$mollieData[CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] : false; $oneClickIsReused = (isset($mollieData[CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID])) ? (bool)$mollieData[CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] : false; diff --git a/src/Handler/Method/CreditCardPayment.php b/src/Handler/Method/CreditCardPayment.php index a2fe5f852..4bcb6109b 100644 --- a/src/Handler/Method/CreditCardPayment.php +++ b/src/Handler/Method/CreditCardPayment.php @@ -4,7 +4,7 @@ use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Types\PaymentMethod; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -49,13 +49,13 @@ public function __construct( public function processPaymentMethodSpecificParameters(array $orderData, OrderEntity $orderEntity, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array { $customFields = $customer->getCustomFields() ?? []; - $cardToken = $customFields['mollie_payments']['credit_card_token'] ?? ''; + $cardToken = $customFields[CustomFieldsInterface::MOLLIE_KEY]['credit_card_token'] ?? ''; if (!empty($cardToken)) { $orderData['payment']['cardToken'] = $cardToken; $this->customerService->setCardToken($customer, '', $salesChannelContext); - $isSaveCardToken = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] ?? false; + $isSaveCardToken = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] ?? false; # change payment sequenceType to first if this is a single-click payment if ($this->enableSingleClickPayment && $isSaveCardToken) { $orderData['payment']['sequenceType'] = PaymentHandler::PAYMENT_SEQUENCE_TYPE_FIRST; @@ -70,7 +70,7 @@ public function processPaymentMethodSpecificParameters(array $orderData, OrderEn return $orderData; } - $mandateId = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] ?? ''; + $mandateId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] ?? ''; if (empty($mandateId)) { return $orderData; } diff --git a/src/Handler/Method/PayPalExpressPayment.php b/src/Handler/Method/PayPalExpressPayment.php new file mode 100644 index 000000000..a114fffa7 --- /dev/null +++ b/src/Handler/Method/PayPalExpressPayment.php @@ -0,0 +1,41 @@ + $orderData + * @param OrderEntity $orderEntity + * @param SalesChannelContext $salesChannelContext + * @param CustomerEntity $customer + * @return array + */ + public function processPaymentMethodSpecificParameters(array $orderData, OrderEntity $orderEntity, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array + { + $orderData['authenticationId'] = $orderEntity->getCustomFields()[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? null; + return $orderData; + } +} diff --git a/src/Handler/Method/PosPayment.php b/src/Handler/Method/PosPayment.php index 1340d9be9..dea7a20e4 100644 --- a/src/Handler/Method/PosPayment.php +++ b/src/Handler/Method/PosPayment.php @@ -4,7 +4,7 @@ use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Types\PaymentMethod; use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Checkout\Order\OrderEntity; @@ -37,7 +37,7 @@ public function processPaymentMethodSpecificParameters(array $orderData, OrderEn { $customFields = $customer->getCustomFields() ?? []; - $this->selectedTerminalId = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] ?? ''; + $this->selectedTerminalId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] ?? ''; return $orderData; } diff --git a/src/Migration/Migration1725347559MollieTags.php b/src/Migration/Migration1725347559MollieTags.php index 28cabbe4f..275ba01d7 100644 --- a/src/Migration/Migration1725347559MollieTags.php +++ b/src/Migration/Migration1725347559MollieTags.php @@ -53,7 +53,7 @@ private function createTag( } $query = <<customerAddressRepository = $customerAddressRepository; } + + public function upsert(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->upsert($data, $context); + } + + public function create(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->create($data, $context); + } + + public function search(Criteria $criteria, Context $context): EntitySearchResult + { + return $this->customerAddressRepository->search($criteria, $context); + } + + public function update(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->update($data, $context); + } + /** * @param array $ids * @param Context $context diff --git a/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php b/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php index d7ce9c077..d3380ea4f 100644 --- a/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php +++ b/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php @@ -5,9 +5,39 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult; interface CustomerAddressRepositoryInterface { + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function upsert(array $data, Context $context): EntityWrittenContainerEvent; + + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function create(array $data, Context $context): EntityWrittenContainerEvent; + + /** + * @param Criteria $criteria + * @param Context $context + * @return EntitySearchResult + */ + public function search(Criteria $criteria, Context $context): EntitySearchResult; + + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function update(array $data, Context $context): EntityWrittenContainerEvent; + /** * @param array $ids * @param Context $context diff --git a/src/Repository/PaymentMethod/PaymentMethodRepository.php b/src/Repository/PaymentMethod/PaymentMethodRepository.php index 4eed03162..a6a313dea 100644 --- a/src/Repository/PaymentMethod/PaymentMethodRepository.php +++ b/src/Repository/PaymentMethod/PaymentMethodRepository.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Repository\PaymentMethod; use Kiener\MolliePayments\Handler\Method\ApplePayPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; @@ -77,4 +78,25 @@ public function getActiveApplePayID(Context $context): string return (string)$paymentMethods[0]; } + + /** + * @param Context $context + * @throws \Exception + * @return string + */ + public function getActivePaypalExpressID(Context $context): string + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('handlerIdentifier', PayPalExpressPayment::class)); + $criteria->addFilter(new EqualsFilter('active', true)); + + /** @var array $paymentMethods */ + $paymentMethods = $this->repoPaymentMethods->searchIds($criteria, $context)->getIds(); + + if (count($paymentMethods) <= 0) { + throw new \Exception('Payment Method PayPal Express not found in system'); + } + + return (string)$paymentMethods[0]; + } } diff --git a/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js b/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js index 2d77a129e..413006563 100644 --- a/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js +++ b/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js @@ -228,12 +228,12 @@ Component.override('sw-order-detail-general', { * */ copyPaymentUrlToClipboard() { - let fallback = async function(e) { + const fallback = async function(e) { await navigator.clipboard.writeText(e) }; // eslint-disable-next-line no-undef - let clipboard = typeof Shopware.Utils.dom.copyToClipboard === 'function' ? Shopware.Utils.dom.copyToClipboard : fallback; + const clipboard = typeof Shopware.Utils.dom.copyToClipboard === 'function' ? Shopware.Utils.dom.copyToClipboard : fallback; // eslint-disable-next-line no-undef clipboard(this.molliePaymentUrl); this.molliePaymentUrlCopied = true; diff --git a/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js b/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js index f9942cb00..b8064f136 100644 --- a/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; /** * This plugin manage the credit card mandate of the customer diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js index 53f974b58..d0666e18c 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js @@ -1,229 +1,81 @@ -import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; -import ApplePaySessionFactory from "../services/ApplePaySessionFactory"; +import Plugin from '../Plugin'; +import ApplePaySessionFactory from '../services/ApplePaySessionFactory'; +import ExpressButtonsRepository from '../repository/ExpressButtonsRepository'; +import BuyElementRepository from '../repository/BuyElementRepository'; +import {MOLLIE_BIND_EXPRESS_EVENTS} from './mollie-express-actions.plugin'; export default class MollieApplePayDirect extends Plugin { - /** - * - * @type {number} - */ - APPLE_PAY_VERSION = 3; - /** * */ init() { - const me = this; - me.client = new HttpClient(); - - // register our off-canvas listener - // we need to re-init all apple pay button - // once the offcanvas is loaded (lazy) into the DOM - - const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get("instances"); + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); if (pluginOffCanvasInstances.length > 0) { - const pluginOffCanvas = pluginOffCanvasInstances[0]; - pluginOffCanvas.$emitter.subscribe('offCanvasOpened', me.initCurrentPage.bind(me)); - } - - - const submitForm = document.querySelector('#productDetailPageBuyProductForm'); - - if (submitForm !== null) { - this.checkSubmitButton(submitForm); - submitForm.addEventListener('change', (event) => { - this.checkSubmitButton(event.target.closest('form#productDetailPageBuyProductForm')); - this.initCurrentPage(); + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); }); } - // now update our current page - this.initCurrentPage(); - + this.bindEvents(); } - - /** - * - */ - initCurrentPage() { - - const me = this; - - // we might have wrapping containers - // that also need to be hidden -> they might have different margins or other things - const applePayContainers = document.querySelectorAll('.js-apple-pay-container'); - // of course, also grab our real buttons - const applePayButtons = document.querySelectorAll('.js-apple-pay'); - + bindEvents() { if (!window.ApplePaySession || !window.ApplePaySession.canMakePayments()) { - // hide our wrapping Apple Pay containers - // to avoid any wrong margins being displayed - - if (applePayContainers) { - applePayContainers.forEach(function (container) { - container.style.display = 'none'; - container.classList.add('d-none'); - }); - } return; } + const expressButtonsRepository = new ExpressButtonsRepository(); + const expressButtons = expressButtonsRepository.findAll('.js-apple-pay'); + const applePayContainers = document.querySelectorAll('.js-apple-pay-container'); - - if (applePayButtons.length <= 0) { + if (expressButtons.length === 0 && applePayContainers.length === 0) { return; } - // we start by fetching the shop url from the data attribute. - // we need this as prefix for our ajax calls, so that we always - // call the correct sales channel and its controllers. - - const shopUrl = me.getShopUrl(applePayButtons[0]); - - - // verify if apple pay is even allowed - // in our current sales channel - me.client.get( - shopUrl + '/mollie/apple-pay/available', - data => { - if (data.available === undefined || data.available === false) { - return; - } - - applePayContainers.forEach(function (container) { - container.classList.remove('d-none'); - }); - - applePayButtons.forEach(function (button) { - - if (button.hasAttribute('disabled')) { - button.classList.add('d-none'); - button.removeEventListener('click', me.onButtonClick); - return; - } - // Remove display none - button.classList.remove('d-none'); - // remove previous handlers (just in case) - button.removeEventListener('click', me.onButtonClick); - // add click event handlers - button.addEventListener('click', me.onButtonClick); - }); - } - ); - } + document.dispatchEvent(new CustomEvent(MOLLIE_BIND_EXPRESS_EVENTS, {detail: expressButtons})); - checkSubmitButton(form) { - const buyButton = form.querySelector('.btn-buy'); + applePayContainers.forEach((container) => { + container.classList.remove('d-none'); + }) - if (buyButton === null) { - return; - } + expressButtons.forEach((button) => { + button.classList.remove('d-none'); + button.addEventListener('click', this.onExpressCheckout) + }); + } - const applePayButton = form.querySelector('.js-apple-pay'); + onExpressCheckout(event) { + const clickedButton = event.target; - if (applePayButton === null) { + if (!clickedButton.classList.contains('processed')) { return; } - if (applePayButton.hasAttribute('disabled')) { - applePayButton.removeAttribute('disabled'); - } - if (buyButton.hasAttribute('disabled')) { - applePayButton.setAttribute('disabled', 'disabled'); - } - - } - - /** - * - * @param event - */ - onButtonClick(event) { - - event.preventDefault(); - const button = event.target; - const form = button.parentNode; - // get sales channel base URL - // so that our shop slug is correctly - let shopSlug = button.getAttribute('data-shop-url'); + const buyElementRepository = new BuyElementRepository(); + const buyElement = buyElementRepository.find(clickedButton); - // remove trailing slash if existing - if (shopSlug.substr(-1) === '/') { - shopSlug = shopSlug.substr(0, shopSlug.length - 1); - } - - const countryCode = form.querySelector('input[name="countryCode"]').value; - const currency = form.querySelector('input[name="currency"]').value; - const mode = form.querySelector('input[name="mode"]').value; - const withPhone = parseInt(form.querySelector('input[name="withPhone"]').value); - const dataProtection = form.querySelector('input[name="acceptedDataProtection"]'); - form.classList.remove('was-validated'); + const countryCode = buyElement.querySelector('input[name="countryCode"]').value; + const currency = buyElement.querySelector('input[name="currency"]').value; + const mode = buyElement.querySelector('input[name="mode"]').value; + const withPhone = parseInt(buyElement.querySelector('input[name="withPhone"]').value); + const dataProtection = buyElement.querySelector('input[name="acceptedDataProtection"]'); + const isProductMode = mode === 'productMode'; - if (dataProtection !== null) { - const dataProtectionValue = dataProtection.checked ? 1: 0; - form.classList.add('was-validated'); + let shopSlug = clickedButton.getAttribute('data-shop-url'); - dataProtection.classList.remove('is-invalid'); - if (dataProtectionValue === 0) { - dataProtection.classList.add('is-invalid'); - return; - } + if (shopSlug.slice(-1) === '/') { + shopSlug = shopSlug.slice(0, -1); } - // this helps us to figure out if we are in - // "product" mode to purchase a single product, or in "cart" mode - // to just purchase the current cart with Apple Pay Direct. - const isProductMode = (mode === 'productMode'); - - if (isProductMode) { - - let productForm = document.querySelector('#productDetailPageBuyProductForm'); - if (productForm === null) { - productForm = button.closest('.product-box').querySelector('form'); - } - - - const formData = new FormData(productForm); - formData.delete("redirectTo"); - formData.append("isExpressCheckout", "1"); - - - fetch(productForm.action, { - method: productForm.method, - body: formData, - }); - - - } const applePaySessionFactory = new ApplePaySessionFactory(); const session = applePaySessionFactory.create(isProductMode, countryCode, currency, withPhone, shopSlug, dataProtection); session.begin(); } - - /** - * - * @param button - * @returns string - */ - getShopUrl(button) { - // get sales channel base URL - // so that our shop slug is correctly - let shopSlug = button.getAttribute('data-shop-url'); - - // remove trailing slash if existing - if (shopSlug.substr(-1) === '/') { - shopSlug = shopSlug.substr(0, shopSlug.length - 1); - } - - return shopSlug; - } - } diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js index 4b0a4cbd3..ca6674e1c 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MollieApplePayPaymentMethod extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js index eee0d5b46..4d0d5a005 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js @@ -1,4 +1,4 @@ -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MollieBancomatPlugin extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js index 0903a86c0..10cd4e9fb 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; /** * This plugin manage the credit card mandate of customer diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js new file mode 100644 index 000000000..a835a63b6 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js @@ -0,0 +1,114 @@ +import Plugin from '../Plugin'; +import {PrivacyNoteElement} from '../repository/PrivacyNoteElement'; +import BuyButtonRepository from '../repository/BuyButtonRepository'; +import ExpressAddToCart from '../services/ExpressAddToCart'; + +export const MOLLIE_BIND_EXPRESS_EVENTS = 'BindExpressEvents'; + +export class MollieExpressActions extends Plugin { + + + init() { + + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); + if (pluginOffCanvasInstances.length > 0) { + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); + }); + } + + this.bindEvents(); + + } + + bindEvents() { + + const privacyNote = new PrivacyNoteElement(); + privacyNote.observeButtons(); + + document.addEventListener(MOLLIE_BIND_EXPRESS_EVENTS, (event) => { + const expressButtons = event.detail; + + if (expressButtons.length === 0) { + return; + } + + + + const buyButtonRepository = new BuyButtonRepository(); + + expressButtons.forEach((button) => { + + + button.classList.remove('d-none'); + button.addEventListener('click', this.onButtonClick) + + const buyButton = buyButtonRepository.find(button); + if (!(buyButton instanceof HTMLButtonElement)) { + return; + } + + if (buyButton.hasAttribute('disabled')) { + button.classList.add('d-none'); + button.removeEventListener('click', this.onButtonClick) + } + + const buyButtonForm = buyButton.closest('form'); + if (!(buyButtonForm instanceof HTMLFormElement)) { + return; + } + + buyButtonForm.addEventListener('change', () => { + + button.classList.remove('d-none'); + button.addEventListener('click', this.onButtonClick) + + if (buyButton.hasAttribute('disabled')) { + + button.classList.add('d-none'); + button.removeEventListener('click', this.onButtonClick) + } + + }) + + }); + + }); + } + + onButtonClick(event) { + + let target = event.target; + if (!(target instanceof HTMLButtonElement)) { + target = target.closest('button'); + } + + if (target.classList.contains('processed')) { + return; + } + + + + const privacyNote = new PrivacyNoteElement(); + + const privacyNoteElement = privacyNote.find(target); + + if (privacyNoteElement instanceof HTMLDivElement) { + privacyNoteElement.classList.add('was-validated'); + const isValid = privacyNote.validate(privacyNoteElement); + if (isValid === false) { + return; + } + } + + + const expressAddToCart = new ExpressAddToCart(); + + expressAddToCart.addItemToCart(target); + + target.classList.add('processed'); + const mollieEvent = new event.constructor(event.type, event); + target.dispatchEvent(mollieEvent); + target.classList.remove('processed'); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js new file mode 100644 index 000000000..a0c4533a8 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js @@ -0,0 +1,65 @@ +import Plugin from '@shopware-storefront-sdk/plugin-system/plugin.class'; +import ExpressButtonsRepository from '../repository/ExpressButtonsRepository'; +import {PrivacyNoteElement} from '../repository/PrivacyNoteElement'; +import {MOLLIE_BIND_EXPRESS_EVENTS} from './mollie-express-actions.plugin'; + +export default class PayPalExpressPlugin extends Plugin { + + init() { + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); + if (pluginOffCanvasInstances.length > 0) { + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); + }); + } + + this.bindEvents(); + + } + + bindEvents() { + const expressButtonsRepository = new ExpressButtonsRepository(); + + const expressButtons = expressButtonsRepository.findAll('.mollie-paypal-button'); + + if (expressButtons.length === 0) { + return; + } + + document.dispatchEvent(new CustomEvent(MOLLIE_BIND_EXPRESS_EVENTS, {detail: expressButtons})); + + expressButtons.forEach((button) => { + button.addEventListener('click', this.onExpressCheckout) + }); + + } + + onExpressCheckout(event) { + + const clickedButton = event.target; + if (!clickedButton.classList.contains('processed')) { + return; + } + + + const submitUrl = clickedButton.getAttribute('data-form-action'); + + const form = document.createElement('form'); + form.setAttribute('action', submitUrl); + form.setAttribute('method', 'POST'); + + const privacyNoteElement = new PrivacyNoteElement(); + const privacyNote = privacyNoteElement.find(clickedButton); + if (privacyNote instanceof HTMLDivElement) { + const checkbox = privacyNoteElement.getCheckbox(privacyNote); + const checkboxValue = checkbox.checked ? 'on' : ''; + form.setAttribute('acceptedDataProtection', checkboxValue); + } + + document.body.insertAdjacentElement('beforeend', form); + + form.submit(); + } + + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js index 610f1dd7c..fb7015452 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient' -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MolliePosTerminalPlugin extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js new file mode 100644 index 000000000..1f903611c --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js @@ -0,0 +1,17 @@ +import BuyElementRepository from './BuyElementRepository'; + +export default class BuyButtonRepository { + constructor() { + this.buyElementRepository = new BuyElementRepository(); + } + + find(button) { + const buyElementContainer = this.buyElementRepository.find(button); + if (buyElementContainer === null) { + return null; + } + return buyElementContainer.querySelector('.btn-buy'); + } + + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js new file mode 100644 index 000000000..ddb4ca1d0 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js @@ -0,0 +1,21 @@ +export default class BuyElementRepository { + find(target) { + let buyElementContainer = target.closest('.product-action'); + + if (buyElementContainer === null) { + buyElementContainer = target.closest('.product-detail-form-container'); + } + if(buyElementContainer === null){ + buyElementContainer = target.closest('.offcanvas-cart-actions'); + } + + if(buyElementContainer === null){ + buyElementContainer = target.closest('.checkout-aside-container'); + } + if(buyElementContainer === null){ + buyElementContainer = target.closest('.checkout-main'); + } + + return buyElementContainer; + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js new file mode 100644 index 000000000..3550335bd --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js @@ -0,0 +1,16 @@ +export default class ExpressButtonsRepository { + + constructor(target = null) { + this.target = target; + if(this.target === null){ + this.target = document; + } + } + findAll(additionalSelector = null) { + let selector = '.mollie-express-button'; + if(additionalSelector !== null){ + selector += additionalSelector; + } + return this.target.querySelectorAll(selector); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js b/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js new file mode 100644 index 000000000..1c30fc24f --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js @@ -0,0 +1,86 @@ +import BuyElementRepository from './BuyElementRepository'; +import ExpressButtonsRepository from './ExpressButtonsRepository'; + +export const TOGGLE_PRIVACY_NOTE_EVENT = 'TogglePrivacyNote'; + +export class PrivacyNoteElement { + constructor() { + this.buyElementRepository = new BuyElementRepository(); + } + + find(button) { + const buyElementContainer = this.buyElementRepository.find(button); + if (buyElementContainer === null) { + return null; + } + return buyElementContainer.querySelector('.mollie-privacy-note'); + } + + getCheckbox(privacyNote) { + return privacyNote.querySelector('input[name="acceptedDataProtection"]'); + } + + validate(privacyNote) { + + const dataProtection = this.getCheckbox(privacyNote); + + const dataProtectionValue = dataProtection.checked ? 1 : 0; + dataProtection.classList.remove('is-invalid'); + + if (dataProtectionValue === 0) { + dataProtection.classList.add('is-invalid'); + return false; + } + return true; + } + + observeButtons() { + + const privacyNotes = document.querySelectorAll('.mollie-privacy-note:not(.observed)'); + const buyElementRepository = new BuyElementRepository(); + + privacyNotes.forEach((privacyNote) => { + + privacyNote.classList.add('observed'); + + const buyElement = buyElementRepository.find(privacyNote); + const expressButtonsRepository = new ExpressButtonsRepository(buyElement); + const expressButtons = expressButtonsRepository.findAll(':not(.d-none)'); + + + if(expressButtons.length === 0){ + privacyNote.classList.add('d-none'); + } + + + expressButtons.forEach((expressButton) => { + + const observer = new MutationObserver((mutations) => { + let visibleExpressButtons = expressButtons.length; + privacyNote.classList.remove('d-none'); + + mutations.forEach((mutation) => { + + if(mutation.target.classList.contains('d-none')){ + visibleExpressButtons--; + } + + }); + + + if(visibleExpressButtons <= 0){ + privacyNote.classList.add('d-none'); + } + + }); + + observer.observe(expressButton, {attributes: true, attributeFilter: ['class']}); + }) + + + }) + + + } + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js b/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js index 54974e0c3..3fa9a1958 100644 --- a/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js +++ b/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js @@ -1,4 +1,4 @@ -import HttpClient from "./HttpClient"; +import HttpClient from './HttpClient'; export default class ApplePaySessionFactory { /** @@ -19,7 +19,7 @@ export default class ApplePaySessionFactory { * @param shopSlug * @param withPhone * @param dataProtection - * @returns {ApplePaySession} + * @returns ApplePaySession */ create(isProductMode, country, currency, withPhone, shopSlug, dataProtection) { diff --git a/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js b/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js new file mode 100644 index 000000000..64e0188a5 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js @@ -0,0 +1,25 @@ +import BuyButtonRepository from '../repository/BuyButtonRepository'; + +export default class ExpressAddToCart { + addItemToCart(button) { + const buyButtonRepository = new BuyButtonRepository(); + const buyButton = buyButtonRepository.find(button); + if (!(buyButton instanceof HTMLButtonElement)) { + return; + + } + const buyButtonForm = buyButton.closest('form'); + if (!(buyButtonForm instanceof HTMLFormElement)) { + return; + } + + const formData = new FormData(buyButtonForm); + formData.delete('redirectTo'); + formData.append('isExpressCheckout', '1'); + + fetch(buyButtonForm.action, { + method: buyButtonForm.method, + body: formData, + }).finally(() =>{}); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/register.js b/src/Resources/app/storefront/src/register.js index bfd76dc6a..fd23720dd 100644 --- a/src/Resources/app/storefront/src/register.js +++ b/src/Resources/app/storefront/src/register.js @@ -4,10 +4,12 @@ import MollieApplePayDirect from './mollie-payments/plugins/apple-pay-direct.plu import MollieApplePayPaymentMethod from './mollie-payments/plugins/apple-pay-payment-method.plugin'; import MollieCreditCardMandateManage from './mollie-payments/plugins/creditcard-mandate-manage.plugin'; import MolliePosTerminalPlugin from './mollie-payments/plugins/pos-terminal.plugin'; +import PayPalExpressPlugin from './mollie-payments/plugins/paypal-express.plugin'; import MollieBancomatPlugin from './mollie-payments/plugins/bancomat-plugin'; +import {MollieExpressActions} from './mollie-payments/plugins/mollie-express-actions.plugin'; -export default class MolliRegistration { +export default class MollieRegistration { /** * @@ -19,9 +21,13 @@ export default class MolliRegistration { // global plugins // ----------------------------------------------------------------------------- // hide apple pay direct buttons across the whole shop, if not available + pluginManager.register('MollieExpressActions', MollieExpressActions); pluginManager.register('MollieApplePayDirect', MollieApplePayDirect); + // fix quantity select on PDP Page + pluginManager.register('PayPalExpressPlugin',PayPalExpressPlugin); + // hiding the standard Apple Pay method in the checkout and account area // ----------------------------------------------------------------------------- pluginManager.register('MollieApplePayPaymentMethod', MollieApplePayPaymentMethod, '[data-mollie-template-applepay-account]'); diff --git a/src/Resources/app/storefront/src/scss/base.scss b/src/Resources/app/storefront/src/scss/base.scss index ffe1075c6..a01e8887a 100644 --- a/src/Resources/app/storefront/src/scss/base.scss +++ b/src/Resources/app/storefront/src/scss/base.scss @@ -4,4 +4,5 @@ @import "./account/payment-selection"; @import "./account/subscriptions"; @import "./component/credit-card-mandate"; +@import "./component/paypal-express-button"; @import "./display"; diff --git a/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss b/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss new file mode 100644 index 000000000..c741377ea --- /dev/null +++ b/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss @@ -0,0 +1,73 @@ +.mollie-paypal-button { + display: flex; + align-items: center; + justify-content: center; + + font-size: 16px; + font-weight: bold; + text-decoration: none; + text-align: center; + cursor: pointer; + transition: background-color 0.3s ease; + width: 100%; + padding: 7px; +} + +.paypal-button-pill { + border-radius: 30px; +} + +.paypal-button-rect { + border-radius: 0; +} + +.paypal-theme-gold { + color: #000000; + background-color: #FFC439FF; + border: 1px solid #fcc85b; +} + +.paypal-theme-gold:hover { + background-color: #fcc85b; +} + +.paypal-theme-blue { + color: #ffffff; + background-color: #009CDEFF; + border: 1px solid #09adf3; +} + +.paypal-theme-blue:hover { + background-color: #09adf3; +} + +.paypal-theme-silver { + color: #000000; + background-color: #EEEEEEFF; + border: 1px solid #f6f6f6; +} + +.paypal-theme-silver:hover { + background-color: #f6f6f6; +} + +.paypal-theme-white { + color: #000000; + background-color: #ffffff; + border: 1px solid #f3f3f3; +} + +.paypal-theme-white:hover { + background-color: #f3f3f3; +} + +.paypal-theme-black { + color: #ffffff; + background-color: #000000; + border: 1px solid #555555; +} + +.paypal-theme-black:hover { + background-color: #333333; +} + diff --git a/src/Resources/app/storefront/src/scss/display.scss b/src/Resources/app/storefront/src/scss/display.scss index 390a283e2..e7a7a056d 100644 --- a/src/Resources/app/storefront/src/scss/display.scss +++ b/src/Resources/app/storefront/src/scss/display.scss @@ -1,4 +1,5 @@ /* stylelint-disable selector-id-pattern, declaration-no-important */ +.mollie-express-button.d-none, .js-apple-pay .d-none, .js-apple-pay-container .d-none, .mollie-pos-terminals .d-none, @@ -7,5 +8,7 @@ #mollieCreditCardMandateDeleteSuccess .d-none, .mollie-ideal-issuer .d-none { display: none !important; + margin: 0 !important; + padding: 0 !important; } /* stylelint-enable selector-id-pattern, declaration-no-important */ diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 5a18e5823..98f9f9c39 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -77,15 +77,61 @@ Aktiviert das Standardverhalten von Shopware für fehlerhafte Zahlungen. Wenn nicht aktiv, kümmert sich das Mollie Plugin um einen erneuten Versuch der Zahlung und leitet den Käufer auf die externe Mollie Zahlungsauswahl. Gebruik de standaard Shopware instelling voor mislukte betalingen. Indien uitgeschakeld, zal Mollie automatisch een manier aanbieden om de betaling opnieuw te proberen door de user om te leiden naar de Mollie betalingspagina. + + createCustomersAtMollie + + + + false + Automatically have customers being created inside your Mollie Dashboard to see all payments of a specific customer. + Erstellt automatisch Kunden im Mollie Dashboard. Dadurch hat man einen zusätzlichen Überblick über alle Zahlungen dieses Kunden innerhalb von Mollie. + Automatisch klanten laten aanmaken in het Mollie Dashboard om alle betalingen van een specifieke klant te zien. + + + useMolliePaymentMethodLimits + false + + + + Automatically hides payment methods in the checkout based on the availability rules for payment methods. Only active payment methods from your mollie dashboard will be shown. If the payment method has a cart limit, currency restriction or billing address restriction, it will be hidden during checkout. + Blendet automatisch Zahlungsart im Checkout basierend auf Verfügbarkeitsregeln von Mollie. Es werden nur die aktiven Zahlungsarten aus dem Mollie Dashboard angezeigt. Wenn die Zahlungsart eine Einschränkung auf den Warenkorbwert, Währung oder Rechnungsadresse hat, wird diese auch ausgeblendet. + Automatische betalingsmethode wordt verborgen tijdens het afrekenen op basis van beschikbaarheidsregels van Mollie. Alleen actieve betalingsmethoden uit het Mollie-dashboard worden weergegeven. Als de betalingsmethode beperkingen heeft op de winkelwagenwaarde, valuta of factuuradres, wordt deze ook verborgen. + + + formatOrderNumber + + + + If set, this format will be used before the order number in the Mollie dashboard. This will also be passed on to PayPal as invoice number. + Wenn gesetzt, wird dieses Format vor der Bestellnummer im Mollie-Dashboard verwendet. Dieser Wert wird auch an PayPal als Rechnungsnummber übergeben + Indien ingesteld, wordt dit formaat vóór het bestelnummer in het Mollie-dashboard gebruikt. Dit wordt ook als factuurnummer doorgegeven aan PayPal. + + + molliePluginConfigSectionPaymentsFormat + + + molliePluginConfigSectionPayments + + + + Credit card + Kreditkarte + Credit card enableCreditCardComponents true - Show credit card input fields directly within your Shopware shop. If disabled, Mollie will show the forms on the Mollie payment page. - Zeigt Kreditkartenfelder direkt im Shopware shop an. Wenn nicht aktiviert, dann zeigt Mollie diese Felder direkt auf der externen Mollie Zahlungsseite. - Toon credit card invoervelden direct in jouw Shopware shop. Indien uitgeschakeld, zal Mollie de formulieren tonen op de Mollie betaalpagina. + Show credit card input fields directly within your Shopware shop. If disabled, Mollie will show + the forms on the Mollie payment page. + + Zeigt Kreditkartenfelder direkt im Shopware shop an. Wenn nicht aktiviert, dann zeigt + Mollie diese Felder direkt auf der externen Mollie Zahlungsseite. + + Toon credit card invoervelden direct in jouw Shopware shop. Indien uitgeschakeld, zal + Mollie de formulieren tonen op de Mollie betaalpagina. + oneClickPaymentsEnabled @@ -93,9 +139,15 @@ false - Customers can choose to save their credit card data for repeating orders. Sensitive data will only be stored on the Mollie servers and not in Shopware. - Kunden haben die Möglichkeit, ihre Kreditkartendaten für erneute Bestellungen zu speichern. Sensible Daten werden nur auf den Mollie-Servern und nicht in Shopware gespeichert. - Klanten kunnen ervoor kiezen hun creditcardgegevens op te slaan voor toekomstige bestellingen. Gevoelige gegevens worden alleen opgeslagen op de servers van Mollie en niet in Shopware. + Customers can choose to save their credit card data for repeating orders. Sensitive data will only + be stored on the Mollie servers and not in Shopware. + + Kunden haben die Möglichkeit, ihre Kreditkartendaten für erneute Bestellungen zu + speichern. Sensible Daten werden nur auf den Mollie-Servern und nicht in Shopware gespeichert. + + Klanten kunnen ervoor kiezen hun creditcardgegevens op te slaan voor toekomstige + bestellingen. Gevoelige gegevens worden alleen opgeslagen op de servers van Mollie en niet in Shopware. + oneClickPaymentsCompactView @@ -103,10 +155,122 @@ false - If enabled, the stored credit cards will not be displayed as visual cards on the checkout page. - Wenn aktiviert, werden die gespeicherten Kreditkarten nicht als visuelle Karten auf der Checkout-Seite angezeigt. - Indien ingeschakeld, worden de opgeslagen creditcards niet weergegeven als visuele kaarten op de afrekenpagina. + If enabled, the stored credit cards will not be displayed as visual cards on the checkout page. + + Wenn aktiviert, werden die gespeicherten Kreditkarten nicht als visuelle Karten auf + der Checkout-Seite angezeigt. + + Indien ingeschakeld, worden de opgeslagen creditcards niet weergegeven als visuele + kaarten op de afrekenpagina. + + + + + + Apple Pay + Apple Pay + Apple Pay enableApplePayDirect @@ -173,43 +337,9 @@ en een alternatieve domein voor uw Apple Pay Direct integratie wilt opgeven. - - - createCustomersAtMollie - - - - false - Automatically have customers being created inside your Mollie Dashboard to see all payments of a specific customer. - Erstellt automatisch Kunden im Mollie Dashboard. Dadurch hat man einen zusätzlichen Überblick über alle Zahlungen dieses Kunden innerhalb von Mollie. - Automatisch klanten laten aanmaken in het Mollie Dashboard om alle betalingen van een specifieke klant te zien. - - - useMolliePaymentMethodLimits - false - - - - Automatically hides payment methods in the checkout based on the availability rules for payment methods. Only active payment methods from your mollie dashboard will be shown. If the payment method has a cart limit, currency restriction or billing address restriction, it will be hidden during checkout. - Blendet automatisch Zahlungsart im Checkout basierend auf Verfügbarkeitsregeln von Mollie. Es werden nur die aktiven Zahlungsarten aus dem Mollie Dashboard angezeigt. Wenn die Zahlungsart eine Einschränkung auf den Warenkorbwert, Währung oder Rechnungsadresse hat, wird diese auch ausgeblendet. - Automatische betalingsmethode wordt verborgen tijdens het afrekenen op basis van beschikbaarheidsregels van Mollie. Alleen actieve betalingsmethoden uit het Mollie-dashboard worden weergegeven. Als de betalingsmethode beperkingen heeft op de winkelwagenwaarde, valuta of factuuradres, wordt deze ook verborgen. - - - formatOrderNumber - - - - If set, this format will be used before the order number in the Mollie dashboard. This will also be passed on to PayPal as invoice number. - Wenn gesetzt, wird dieses Format vor der Bestellnummer im Mollie-Dashboard verwendet. Dieser Wert wird auch an PayPal als Rechnungsnummber übergeben - Indien ingesteld, wordt dit formaat vóór het bestelnummer in het Mollie-dashboard gebruikt. Dit wordt ook als factuurnummer doorgegeven aan PayPal. - - - molliePluginConfigSectionPaymentsFormat - - - molliePluginConfigSectionPayments - + + Order Management Auftragsverwaltung diff --git a/src/Resources/config/routes/storefront/paypal_express.xml b/src/Resources/config/routes/storefront/paypal_express.xml new file mode 100644 index 000000000..415e18085 --- /dev/null +++ b/src/Resources/config/routes/storefront/paypal_express.xml @@ -0,0 +1,21 @@ + + + + + Kiener\MolliePayments\Controller\Storefront\PaypalExpress\PaypalExpressControllerBase::startCheckout + storefront + true + + + + + Kiener\MolliePayments\Controller\Storefront\PaypalExpress\PaypalExpressControllerBase::finishCheckout + storefront + true + + + + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 6c27fb0e3..4ad3856d2 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -69,6 +69,7 @@ + @@ -101,15 +102,25 @@ + + + %env(default::MOLLIE_PAYPAL_EXPRESS_BETA)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_STYLE)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_SHAPE)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_RESTRICTIONS)% + + + + %env(default::MOLLIE_SHOP_DOMAIN)% %env(default::MOLLIE_DEV_MODE)% %env(default::MOLLIE_CYPRESS_MODE)% @@ -224,6 +235,10 @@ %kernel.shopware_version% + + + + diff --git a/src/Resources/config/services/components.xml b/src/Resources/config/services/components.xml index 568cd9a1b..fc5db6262 100644 --- a/src/Resources/config/services/components.xml +++ b/src/Resources/config/services/components.xml @@ -44,6 +44,15 @@ + + + + + + + + + diff --git a/src/Resources/config/services/controller.xml b/src/Resources/config/services/controller.xml index 5d6123c75..f305c0d04 100644 --- a/src/Resources/config/services/controller.xml +++ b/src/Resources/config/services/controller.xml @@ -216,6 +216,19 @@ + + + + + + + + + + + + + diff --git a/src/Resources/config/services/handlers.xml b/src/Resources/config/services/handlers.xml index ca9cd19a4..fbf911565 100644 --- a/src/Resources/config/services/handlers.xml +++ b/src/Resources/config/services/handlers.xml @@ -128,6 +128,13 @@ + + + + + + + diff --git a/src/Resources/config/services/payment.xml b/src/Resources/config/services/payment.xml index 7233d5e38..24c841615 100644 --- a/src/Resources/config/services/payment.xml +++ b/src/Resources/config/services/payment.xml @@ -56,5 +56,16 @@ + + + + + + + + + + + diff --git a/src/Resources/config/services/repositories.xml b/src/Resources/config/services/repositories.xml index 0edfc3c9a..3ec784071 100644 --- a/src/Resources/config/services/repositories.xml +++ b/src/Resources/config/services/repositories.xml @@ -35,6 +35,11 @@ + + + + + diff --git a/src/Resources/config/services/subscriber.xml b/src/Resources/config/services/subscriber.xml index 641937bc9..60475b33c 100644 --- a/src/Resources/config/services/subscriber.xml +++ b/src/Resources/config/services/subscriber.xml @@ -53,6 +53,12 @@ + + + + + + diff --git a/src/Resources/public/static/ppe-black.png b/src/Resources/public/static/ppe-black.png new file mode 100644 index 0000000000000000000000000000000000000000..9297e892208fde2e852903d27a292faf02f67024 GIT binary patch literal 6708 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 KZNAOs9sdI%(`Qcr literal 0 HcmV?d00001 diff --git a/src/Resources/public/static/ppe-primary.png b/src/Resources/public/static/ppe-primary.png new file mode 100644 index 0000000000000000000000000000000000000000..780c84f6c563dc322932985cd46e363b90b7fe5e GIT binary patch literal 6454 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 WL$bF#Zdm+xhnwa3s`T8)C<&(Q!V`t|P(V6CTPC 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 literal 0 HcmV?d00001 diff --git a/src/Resources/views/mollie/component/apple-pay-direct-button.twig b/src/Resources/views/mollie/component/apple-pay-direct-button.twig index 8b54cdaa7..e0e0f4e3f 100644 --- a/src/Resources/views/mollie/component/apple-pay-direct-button.twig +++ b/src/Resources/views/mollie/component/apple-pay-direct-button.twig @@ -33,12 +33,7 @@ {% block mollie_apple_pay_direct_button %} - - {% if mollie_express_required_data_protection %} - {% sw_include '@Storefront/storefront/component/privacy-notice.html.twig' %} - {% endif %} - - + +{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/mollie/head.html.twig b/src/Resources/views/mollie/head.html.twig index 28d382a6c..1f9ecd873 100644 --- a/src/Resources/views/mollie/head.html.twig +++ b/src/Resources/views/mollie/head.html.twig @@ -6,7 +6,8 @@ 'frontend.checkout.cart.page' : 'cart', 'frontend.navigation.page' : 'plp', 'frontend.account.edit-order.page' : '', - 'frontend.detail.page' : 'pdp' + 'frontend.detail.page' : 'pdp', + 'frontend.checkout.register.page' : 'register' } %} {% set currentRoute = app.request.attributes.get('_route') %} @@ -17,12 +18,17 @@ {% set includeJsInHeader = true %} {% endif %} + {% set restrictions = mollie_applepaydirect_restrictions | merge(mollie_paypalexpress_restrictions) %} + {# requirement check for apple pay direct #} - {% if mollie_applepaydirect_enabled == true or mollie_applepay_enabled == true %} - {% if currentRoute in onlyShowHere|keys and onlyShowHere[currentRoute] not in mollie_applepaydirect_restrictions %} + {% if mollie_applepaydirect_enabled == true or mollie_applepay_enabled == true or mollie_paypalexpress_enabled == true %} + + {% if currentRoute in onlyShowHere|keys and onlyShowHere[currentRoute] not in restrictions %} {% set includeJsInHeader = true %} {% endif %} - {% if 'offcanvas' not in mollie_applepaydirect_restrictions %} + + + {% if 'offcanvas' not in mollie_applepaydirect_restrictions or 'offcanvas' not in mollie_paypalexpress_restrictions %} {% set includeJsInHeader = true %} {% endif %} {% endif %} diff --git a/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig b/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig index 9b66aa5a1..fcb32f45e 100644 --- a/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig +++ b/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig @@ -1,10 +1,12 @@ {% sw_extends '@Storefront/storefront/component/buy-widget/buy-widget-form.html.twig' %} -{% block buy_widget_buy_button %} +{% block buy_widget_buy_container %} {% if mollie_subscriptions_enabled and product.translated.customFields.mollie_payments_product_subscription_enabled %}
-
@@ -16,31 +18,51 @@ {% block buy_widget_buy_form_inner %} {{ parent() }} - {% block page_product_detail_buy_container_apple_direct %} - {# this is for Shopware < 6.4 #} - {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} - {# this is for Shopware >= 6.4 #} - {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} - - {% set productPrice = 0 %} - - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + + + {# this is for Shopware < 6.4 #} + {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} + {# this is for Shopware >= 6.4 #} + {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} + + {% set productPrice = 0 %} + + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} - {% if mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} - {% block page_product_detail_buy_container_apple_direct_component %} -
+ {% set applePayVisible = mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('pdp' not in mollie_paypalexpress_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + + {% block page_product_detail_buy_container_apple_direct %} + + {% if applePayVisible %} + {% block page_product_detail_buy_container_apple_direct_component %} +
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' with {cols: 'col-8'} %}
+ {% endblock %} + {% endif %} + {% endblock %} + {% block page_product_detail_buy_container_paypal_express %} + {% if paypalExpressVisible %} + {% block page_product_detail_buy_container_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-8'} %} +
{% endblock %} {% endif %} {% endblock %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig b/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig index 61882e92b..26d87a73d 100644 --- a/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig +++ b/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig @@ -4,13 +4,30 @@ {% block component_offcanvas_cart_actions_checkout %} {{ parent() }} {% if page.cart.lineItems|length > 0 %} - {% if mollie_applepaydirect_enabled and ('offcanvas' not in mollie_applepaydirect_restrictions) %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('offcanvas' not in mollie_applepaydirect_restrictions) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('offcanvas' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + {% if applePayVisible %} {% block component_offcanvas_cart_actions_checkout_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + + {% if paypalExpressVisible %} + {% block component_offcanvas_cart_actions_checkout_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} + {% endif %} {% endblock %} diff --git a/src/Resources/views/storefront/component/product/card/action.html.twig b/src/Resources/views/storefront/component/product/card/action.html.twig index 4605626e4..c31334fd7 100644 --- a/src/Resources/views/storefront/component/product/card/action.html.twig +++ b/src/Resources/views/storefront/component/product/card/action.html.twig @@ -5,33 +5,47 @@ {{ parent() }} - {% block component_product_box_action_buy_apple_direct %} - - {% set productPrice = 0 %} + {% set productPrice = 0 %} - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('plp' not in mollie_applepaydirect_restrictions) and productPrice > 0 %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('plp' not in mollie_paypalexpress_restrictions) and productPrice > 0 %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} - {% if mollie_applepaydirect_enabled and ('plp' not in mollie_applepaydirect_restrictions) and productPrice > 0 %} + {% block component_product_box_action_buy_apple_direct %} + + {% if applePayVisible %} {% block component_product_box_action_buy_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + {% endblock %} + {% block component_product_box_action_buy_paypal_express %} + {% if paypalExpressVisible %} + {% block component_product_box_action_buy_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} {% endblock %} {% endblock %} - {% block page_product_detail_product_buy_button %} diff --git a/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig b/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig new file mode 100644 index 000000000..80142ac9f --- /dev/null +++ b/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig @@ -0,0 +1 @@ +{{ sw_csrf('frontend.mollie.paypal-express.start') }} diff --git a/src/Resources/views/storefront/page/checkout/address/index.html.twig b/src/Resources/views/storefront/page/checkout/address/index.html.twig new file mode 100644 index 000000000..3ab87e2ec --- /dev/null +++ b/src/Resources/views/storefront/page/checkout/address/index.html.twig @@ -0,0 +1,20 @@ +{% sw_extends '@Storefront/storefront/page/checkout/address/index.html.twig' %} + +{% block page_checkout_address_login_toggle %} + + {{ parent() }} + + + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('register' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:paypalExpressVisible} %} + + {% if paypalExpressVisible %} + {% block page_checkout_address_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-5'} %} +
+ {% endblock %} + {% endif %} +{% endblock %} diff --git a/src/Resources/views/storefront/page/checkout/cart/index.html.twig b/src/Resources/views/storefront/page/checkout/cart/index.html.twig index 536dffa58..35883d56e 100644 --- a/src/Resources/views/storefront/page/checkout/cart/index.html.twig +++ b/src/Resources/views/storefront/page/checkout/cart/index.html.twig @@ -21,12 +21,28 @@ {% block page_checkout_aside_actions %} {{ parent() }} - {% if mollie_applepaydirect_enabled and ('cart' not in mollie_applepaydirect_restrictions) %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('cart' not in mollie_applepaydirect_restrictions) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('cart' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + {% if applePayVisible %} {% block page_checkout_aside_actions_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + + {% if paypalExpressVisible %} + {% block page_checkout_aside_actions_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} {% endblock %} diff --git a/src/Resources/views/storefront/page/checkout/confirm/index.html.twig b/src/Resources/views/storefront/page/checkout/confirm/index.html.twig index 556efe76f..e5bbc8775 100644 --- a/src/Resources/views/storefront/page/checkout/confirm/index.html.twig +++ b/src/Resources/views/storefront/page/checkout/confirm/index.html.twig @@ -3,13 +3,15 @@ {% block page_checkout_main_content %} {% if mollie_applepay_enabled %} -
+
{% endif %} + {% if page.enable_credit_card_components == true %} {% endif %} diff --git a/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig b/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig index 14dc36b18..5993c5c5f 100755 --- a/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig +++ b/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig @@ -2,34 +2,51 @@ {% block page_product_detail_buy_form_inner %} {{ parent() }} - {% block page_product_detail_buy_container_apple_direct %} - {# this is for Shopware < 6.4 #} - {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} - {# this is for Shopware >= 6.4 #} - {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} - {% set productPrice = 0 %} + {# this is for Shopware < 6.4 #} + {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} + {# this is for Shopware >= 6.4 #} + {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} + + {% set productPrice = 0 %} - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('pdp' not in mollie_paypalexpress_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} - {% if mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% block page_product_detail_buy_container_apple_direct %} + {% if applePayVisible %} {% block page_product_detail_buy_container_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' with {cols: 'col-8'} %}
{% endblock %} {% endif %} {% endblock %} + {% block page_product_detail_buy_container_paypal_express %} + {% if paypalExpressVisible %} + {% block page_product_detail_buy_container_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-8'} %} +
+ {% endblock %} + {% endif %} + {% endblock %} + {% endblock %} @@ -38,7 +55,9 @@ {% block page_product_detail_buy_button %} {% if mollie_subscriptions_enabled and page.product.translated.customFields.mollie_payments_product_subscription_enabled %} - {% else %} diff --git a/src/Service/Cart/CartBackupService.php b/src/Service/Cart/CartBackupService.php index 282bf4294..4fd04951d 100644 --- a/src/Service/Cart/CartBackupService.php +++ b/src/Service/Cart/CartBackupService.php @@ -12,7 +12,7 @@ class CartBackupService /** * */ - private const BACKUP_TOKEN = 'mollie_backup'; + private const BACKUP_TOKEN = 'mollie_backup_%s'; /** * @var CartService @@ -35,11 +35,14 @@ public function __construct(CartService $cartService) */ public function isBackupExisting(SalesChannelContext $context): bool { - $backupCart = $this->cartService->getCart(self::BACKUP_TOKEN, $context); - + $backupCart = $this->cartService->getCart($this->getToken($context), $context); return ($backupCart->getLineItems()->count() > 0); } + private function getToken(SalesChannelContext $context):string + { + return sprintf(self::BACKUP_TOKEN, $context->getToken()); + } /** * @param SalesChannelContext $context */ @@ -47,11 +50,8 @@ public function backupCart(SalesChannelContext $context): void { $originalCart = $this->cartService->getCart($context->getToken(), $context); - # additional language shops do not have a name, so make sure it has a string cast - $salesChannelName = (string)$context->getSalesChannel()->getName(); - # create new cart with our backup token - $newCart = $this->cartService->createNew(self::BACKUP_TOKEN); + $newCart = $this->cartService->createNew($this->getToken($context)); # assign our items to the backup # this is the only thing we really need to backup at this stage. @@ -89,7 +89,7 @@ public function restoreCart(SalesChannelContext $context): Cart */ public function clearBackup(SalesChannelContext $context): void { - $backupCart = $this->cartService->getCart(self::BACKUP_TOKEN, $context); + $backupCart = $this->cartService->getCart($this->getToken($context), $context); # removing does not really work # but we can set the item count to 0, which means "not existing" for us diff --git a/src/Service/Cart/Voucher/VoucherCartCollector.php b/src/Service/Cart/Voucher/VoucherCartCollector.php index af4ea6222..5a0f0b35a 100644 --- a/src/Service/Cart/Voucher/VoucherCartCollector.php +++ b/src/Service/Cart/Voucher/VoucherCartCollector.php @@ -4,6 +4,7 @@ use Kiener\MolliePayments\Handler\Method\VoucherPayment; use Kiener\MolliePayments\Repository\PaymentMethod\PaymentMethodRepositoryInterface; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\LineItem\LineItemAttributes; use Kiener\MolliePayments\Struct\Voucher\VoucherType; use Shopware\Core\Checkout\Cart\Cart; @@ -88,7 +89,7 @@ public function collect(CartDataCollection $data, Cart $original, SalesChannelCo $attributes->setVoucherType($voucherType); $customFields = $item->getPayload()['customFields']; - $customFields['mollie_payments'] = $attributes->toArray(); + $customFields[CustomFieldsInterface::MOLLIE_KEY] = $attributes->toArray(); $item->setPayloadValue('customFields', $customFields); } diff --git a/src/Service/CartService.php b/src/Service/CartService.php index 853da875a..32e76d5c0 100644 --- a/src/Service/CartService.php +++ b/src/Service/CartService.php @@ -96,6 +96,7 @@ public function updateCart(Cart $cart): void $this->swCartService->setCart($cart); } + /** * @param Cart $cart * @return float @@ -186,4 +187,9 @@ public function clearFakeAddressIfExists(SalesChannelContext $context): void $this->shippingAddressFaker->deleteFakeShippingAddress($customer, $context->getContext()); } + + public function persistCart(Cart $cart, SalesChannelContext $context):Cart + { + return $this->swCartService->recalculate($cart, $context); + } } diff --git a/src/Service/CartServiceInterface.php b/src/Service/CartServiceInterface.php index 2c49d0844..a83e42373 100644 --- a/src/Service/CartServiceInterface.php +++ b/src/Service/CartServiceInterface.php @@ -52,4 +52,11 @@ public function updateShippingMethod(SalesChannelContext $context, string $shipp * @return SalesChannelContext */ public function updatePaymentMethod(SalesChannelContext $context, string $paymentMethodID): SalesChannelContext; + + /** + * @param Cart $cart + * @param SalesChannelContext $context + * @return Cart + */ + public function persistCart(Cart $cart, SalesChannelContext $context):Cart; } diff --git a/src/Service/CustomFieldsInterface.php b/src/Service/CustomFieldsInterface.php index dc5126ced..a068deecb 100644 --- a/src/Service/CustomFieldsInterface.php +++ b/src/Service/CustomFieldsInterface.php @@ -23,4 +23,14 @@ interface CustomFieldsInterface * */ public const DELIVERY_SHIPPED = 'is_shipped'; + + public const PAYMENT_KEY = 'payment_id'; + + public const THIRD_PARTY_PAYMENT_KEY = 'third_party_payment_id'; + + public const PAYPAL_EXPRESS_SESSION_ID_KEY = 'mollie_ppe_session_id'; + + public const PAYPAL_EXPRESS_AUTHENTICATE_ID = 'mollie_ppe_auth_id'; + + public const ACCEPTED_DATA_PROTECTION = 'acceptedDataProtection'; } diff --git a/src/Service/CustomerService.php b/src/Service/CustomerService.php index 1d954b371..313e3b353 100644 --- a/src/Service/CustomerService.php +++ b/src/Service/CustomerService.php @@ -8,11 +8,14 @@ use Kiener\MolliePayments\Exception\CustomerCouldNotBeFoundException; use Kiener\MolliePayments\Repository\Country\CountryRepositoryInterface; use Kiener\MolliePayments\Repository\Customer\CustomerRepositoryInterface; +use Kiener\MolliePayments\Repository\CustomerAddress\CustomerAddressRepositoryInterface; use Kiener\MolliePayments\Repository\Salutation\SalutationRepositoryInterface; use Kiener\MolliePayments\Service\MollieApi\Customer; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressDefinition; use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity; use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Checkout\Customer\Event\CustomerBeforeLoginEvent; @@ -22,8 +25,11 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; +use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister; @@ -38,6 +44,7 @@ class CustomerService implements CustomerServiceInterface public const CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL = 'shouldSaveCardDetail'; public const CUSTOM_FIELDS_KEY_PREFERRED_IDEAL_ISSUER = 'preferred_ideal_issuer'; public const CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL = 'preferred_pos_terminal'; + public const CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID = 'ppe_address_id'; /** * @var CountryRepositoryInterface @@ -82,6 +89,9 @@ class CustomerService implements CustomerServiceInterface */ private $container; + /** @var CustomerAddressRepositoryInterface */ + private $customerAddressRepository; + /** * @param CountryRepositoryInterface $countryRepository @@ -96,17 +106,18 @@ class CustomerService implements CustomerServiceInterface * @param ConfigService $configService */ public function __construct( - CountryRepositoryInterface $countryRepository, - CustomerRepositoryInterface $customerRepository, - Customer $customerApiService, - EventDispatcherInterface $eventDispatcher, - LoggerInterface $logger, - SalesChannelContextPersister $salesChannelContextPersister, - SalutationRepositoryInterface $salutationRepository, - SettingsService $settingsService, - string $shopwareVersion, - ConfigService $configService, - ContainerInterface $container //we have to inject the container, because in SW 6.4.20.2 we have circular injection for the register route + CountryRepositoryInterface $countryRepository, + CustomerRepositoryInterface $customerRepository, + CustomerAddressRepositoryInterface $customerAddressRepository, + Customer $customerApiService, + EventDispatcherInterface $eventDispatcher, + LoggerInterface $logger, + SalesChannelContextPersister $salesChannelContextPersister, + SalutationRepositoryInterface $salutationRepository, + SettingsService $settingsService, + string $shopwareVersion, + ConfigService $configService, + ContainerInterface $container //we have to inject the container, because in SW 6.4.20.2 we have circular injection for the register route ) { $this->countryRepository = $countryRepository; $this->customerRepository = $customerRepository; @@ -118,6 +129,7 @@ public function __construct( $this->settingsService = $settingsService; $this->shopwareVersion = $shopwareVersion; $this->configService = $configService; + $this->customerAddressRepository = $customerAddressRepository; $this->container = $container; } @@ -129,7 +141,7 @@ public function __construct( * * @return null|string */ - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string { /** @var null|string $newToken */ $newToken = null; @@ -217,9 +229,9 @@ public function setCardToken(CustomerEntity $customer, string $cardToken, SalesC } // Store the card token in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_CREDIT_CARD_TOKEN] = $cardToken; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_CREDIT_CARD_TOKEN] = $cardToken; // Store shouldSaveCardDetail in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] = $shouldSaveCardDetail; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] = $shouldSaveCardDetail; $this->logger->debug("Setting Credit Card Token", [ 'customerId' => $customer->getId(), @@ -248,7 +260,7 @@ public function setMandateId(CustomerEntity $customer, string $mandateId, Contex $customFields = $customer->getCustomFields() ?? []; // Store the mandate id in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_MANDATE_ID] = $mandateId; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_MANDATE_ID] = $mandateId; $this->logger->debug("Setting Credit Card Mandate Id", [ 'customerId' => $customer->getId(), @@ -292,7 +304,7 @@ public function setPosTerminal(CustomerEntity $customer, string $terminalId, Con $customFields = []; } - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] = $terminalId; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] = $terminalId; return $this->customerRepository->update([[ 'id' => $customer->getId(), @@ -346,8 +358,8 @@ public function getCustomer(string $customerId, Context $context): ?CustomerEnti $customer = null; try { - $criteria = new Criteria(); - $criteria->addFilter(new EqualsFilter('id', $customerId)); + $criteria = new Criteria([$customerId]); + $criteria->addAssociations([ 'defaultShippingAddress.country', 'defaultBillingAddress.country', @@ -385,7 +397,7 @@ public function getCustomerStruct(string $customerId, Context $context): Custome if (isset($customFields[self::CUSTOM_FIELDS_KEY_MOLLIE_CUSTOMER_ID])) { $struct->setLegacyCustomerId($customFields[self::CUSTOM_FIELDS_KEY_MOLLIE_CUSTOMER_ID]); } - $molliePaymentsCustomFields = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS] ?? []; + $molliePaymentsCustomFields = $customFields[CustomFieldsInterface::MOLLIE_KEY] ?? []; if (! is_array($molliePaymentsCustomFields)) { $this->logger->warning('Customer customFields for MolliePayments are invalid. Array is expected', [ 'currentCustomFields' => $molliePaymentsCustomFields @@ -436,23 +448,17 @@ public function getAddressArray($address, CustomerEntity $customer): array * @param SalesChannelContext $context * @return null|CustomerEntity */ - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity + public function createApplePayDirectCustomer(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, SalesChannelContext $context): ?CustomerEntity { $countryId = $this->getCountryId($countryISO2, $context->getContext()); $salutationId = $this->getSalutationId($context->getContext()); - $customer = $this->findCustomerByEmail($email, $context); - if ($customer instanceof CustomerEntity) { - return $customer; - } - $data = new RequestDataBag(); $data->set('salutationId', $salutationId); $data->set('guest', true); $data->set('firstName', $firstname); $data->set('lastName', $lastname); $data->set('email', $email); - $data->set('acceptedDataProtection', $acceptedDataProtection); $billingAddress = new RequestDataBag(); $billingAddress->set('street', $street); @@ -471,9 +477,9 @@ public function createApplePayDirectCustomerIfNotExists(string $firstname, strin $errors = []; /** we have to store the errors in an array because getErrors returns a generator */ foreach ($e->getErrors() as $error) { - $errors[] = $error; + $errors[]=$error; } - $this->logger->error($e->getMessage(), ['errors' => $errors]); + $this->logger->error($e->getMessage(), ['errors'=>$errors]); return null; } } @@ -597,7 +603,8 @@ public function createMollieCustomer(string $customerId, string $salesChannelId, ); } - private function findCustomerByEmail(string $email, SalesChannelContext $context): ?CustomerEntity + + public function findCustomerByEmail(string $email, SalesChannelContext $context): ?CustomerEntity { $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('email', $email)); @@ -630,4 +637,202 @@ private function findCustomerByEmail(string $email, SalesChannelContext $context } return $foundCustomer; } + + public function reuseOrCreateAddresses(CustomerEntity $customer, AddressStruct $shippingAddress, Context $context, ?AddressStruct $billingAddress = null): EntityWrittenContainerEvent + { + $mollieAddressIds = [$shippingAddress->getMollieAddressId()]; + if ($billingAddress !== null) { + $mollieAddressIds[] = $billingAddress->getMollieAddressId(); + } + $criteria = new Criteria(); + $criteria->addFilter(new AndFilter([ + new EqualsFilter('customerId', $customer->getId()), + new EqualsAnyFilter('customFields.' . CustomFieldsInterface::MOLLIE_KEY . '.' . self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID, $mollieAddressIds) + ])); + + $customerAddressSearchResult = $this->customerAddressRepository->search($criteria, $context); + + // if we dont find any address for customer we create new once + if ($customerAddressSearchResult->getTotal() === 0) { + $shippingAddressId = Uuid::randomHex(); + $billingAddressId = $shippingAddressId; + + $addresses = [ + $this->createShopwareAddressArray($shippingAddressId, $customer->getId(), $customer->getSalutationId(), $shippingAddress, $context) + ]; + if ($billingAddress !== null) { + $billingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($billingAddressId, $customer->getId(), $customer->getSalutationId(), $billingAddress, $context); + } + + $customer = [ + 'id' => $customer->getId(), + 'defaultBillingAddressId' => $shippingAddressId, + 'defaultShippingAddressId' => $billingAddressId, + 'addresses' => $addresses + ]; + + return $this->customerRepository->upsert([$customer], $context); + } + + + $defaultShippingAddressId = null; + $defaultBillingAddressId = null; + + + /** @var CustomerAddressEntity $customerAddress */ + foreach ($customerAddressSearchResult->getElements() as $customerAddress) { + $addressCustomFields = $customerAddress->getCustomFields(); + + if ($addressCustomFields === null) { + continue; + } + + // skip addresses without custom fields, those are configured by the customer in backend + $mollieAddressId = $addressCustomFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID] ?? null; + if ($mollieAddressId === null) { + continue; + } + // try to find default shipping and billing address and store them for later + if ($mollieAddressId === $shippingAddress->getMollieAddressId()) { + $defaultShippingAddressId = $customerAddress->getId(); + } + + if ($billingAddress !== null && $mollieAddressId === $billingAddress->getMollieAddressId()) { + $defaultBillingAddressId = $customerAddress->getId(); + } + } + + //customer have addresses, might be from old PPE orders, might be from shopware, lets find them and select them + $addresses = []; + + // we havent found a default adress, create a new one + if ($defaultShippingAddressId === null) { + $defaultShippingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($defaultShippingAddressId, $customer->getId(), $customer->getSalutationId(), $shippingAddress, $context); + } + + //we have a billing address but we didnt found them in saved addresses, create new one + if ($billingAddress !== null && $defaultBillingAddressId === null) { + $defaultBillingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($defaultBillingAddressId, $customer->getId(), $customer->getSalutationId(), $billingAddress, $context); + } + + //we dont have a billing adress, we use the shipping adress as billing + if ($billingAddress === null && $defaultBillingAddressId === null) { + $defaultBillingAddressId = $defaultShippingAddressId; + } + $customer = [ + 'id' => $customer->getId(), + 'defaultBillingAddressId' => $defaultBillingAddressId, + 'defaultShippingAddressId' => $defaultBillingAddressId, + ]; + + if (count($addresses) > 0) { + $customer['addresses'] = $addresses; + } + return $this->customerRepository->upsert([$customer], $context); + } + + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity + { + $countryId = $this->getCountryId($shippingAddress->getCountryCode(), $context->getContext()); + $salutationId = $this->getSalutationId($context->getContext()); + + $data = new RequestDataBag(); + $data->set('salutationId', $salutationId); + $data->set('guest', true); + $data->set('firstName', $shippingAddress->getFirstName()); + $data->set('lastName', $shippingAddress->getLastName()); + $data->set('email', $shippingAddress->getEmail()); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + if ($settings->isRequireDataProtectionCheckbox()) { + $data->set('acceptedDataProtection', (bool)$acceptedDataProtection); + } + + + + $shippingAddressData = new RequestDataBag(); + $shippingAddressData->set('firstName', $shippingAddress->getFirstName()); + $shippingAddressData->set('lastName', $shippingAddress->getLastName()); + $shippingAddressData->set('street', $shippingAddress->getStreet()); + $shippingAddressData->set('additionalAddressLine1', $shippingAddress->getStreetAdditional()); + $shippingAddressData->set('zipcode', $shippingAddress->getZipCode()); + $shippingAddressData->set('city', $shippingAddress->getCity()); + $shippingAddressData->set('countryId', $countryId); + $customFields = new RequestDataBag(); + $customFields->set(CustomerAddressDefinition::ENTITY_NAME, [ + CustomFieldsInterface::MOLLIE_KEY => [self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $shippingAddress->getMollieAddressId()] + ]); + $shippingAddressData->set('customFields', $customFields); + $data->set('shippingAddress', $shippingAddressData); + $data->set('billingAddress', $shippingAddressData); + + if ($billingAddress !== null) { + $countryId = $this->getCountryId($billingAddress->getCountryCode(), $context->getContext()); + + $billingAddressData = new RequestDataBag(); + $billingAddressData->set('street', $billingAddress->getStreet()); + $billingAddressData->set('additionalAddressLine1', $billingAddress->getStreetAdditional()); + $billingAddressData->set('zipcode', $billingAddress->getZipCode()); + $billingAddressData->set('city', $billingAddress->getCity()); + $billingAddressData->set('countryId', $countryId); + $customFields = new RequestDataBag(); + $customFields->set(CustomerAddressDefinition::ENTITY_NAME, [ + CustomFieldsInterface::MOLLIE_KEY => [self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $billingAddress->getMollieAddressId()] + ]); + $billingAddressData->set('customFields', $customFields); + + $data->set('billingAddress', $shippingAddressData); + } + + try { + $abstractRegisterRoute = $this->container->get(RegisterRoute::class); + $response = $abstractRegisterRoute->register($data, $context, false); + return $response->getCustomer(); + } catch (ConstraintViolationException $e) { + $errors = []; + /** we have to store the errors in an array because getErrors returns a generator */ + foreach ($e->getErrors() as $error) { + $errors[] = $error; + } + $this->logger->error($e->getMessage(), ['errors' => $errors]); + return null; + } + } + + + /** + * @param string $addressId + * @param string $customerId + * @param null|string $salutationId + * @param AddressStruct $address + * @param Context $context + * @return array + */ + private function createShopwareAddressArray(string $addressId, string $customerId, ?string $salutationId, AddressStruct $address, Context $context): array + { + $addressArray = [ + 'id' => $addressId, + 'customerId' => $customerId, + 'countryId' => $this->getCountryId($address->getCountryCode(), $context), + 'firstName' => $address->getFirstName(), + 'lastName' => $address->getLastName(), + 'street' => $address->getStreet(), + 'additionalAddressLine1' => $address->getStreetAdditional(), + 'zipcode' => $address->getZipCode(), + 'city' => $address->getCity(), + 'phoneNumber' => '', + 'customFields' => [ + CustomFieldsInterface::MOLLIE_KEY => [ + self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $address->getMollieAddressId() + ] + ] + ]; + if ($salutationId !== null) { + $addressArray['salutationId'] = $salutationId; + } + return $addressArray; + } } diff --git a/src/Service/CustomerServiceInterface.php b/src/Service/CustomerServiceInterface.php index d450a9112..b131baa24 100644 --- a/src/Service/CustomerServiceInterface.php +++ b/src/Service/CustomerServiceInterface.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Service; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity; use Shopware\Core\Checkout\Customer\CustomerEntity; @@ -13,9 +14,12 @@ interface CustomerServiceInterface { - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string; + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string; + public function isCustomerLoggedIn(SalesChannelContext $context): bool; + public function setCardToken(CustomerEntity $customer, string $cardToken, SalesChannelContext $context, bool $shouldSaveCardDetail = false): EntityWrittenContainerEvent; + public function setMandateId(CustomerEntity $customer, string $cardToken, Context $context): EntityWrittenContainerEvent; /** @@ -26,8 +30,11 @@ public function setMandateId(CustomerEntity $customer, string $cardToken, Contex */ public function saveCustomerCustomFields(string $customerID, array $customFields, Context $context): EntityWrittenContainerEvent; public function getMollieCustomerId(string $customerId, string $salesChannelId, Context $context): string; + public function setMollieCustomerId(string $customerId, string $mollieCustomerId, string $profileId, bool $testMode, Context $context): void; + public function getCustomer(string $customerId, Context $context): ?CustomerEntity; + public function getCustomerStruct(string $customerId, Context $context): CustomerStruct; /** @@ -36,8 +43,12 @@ public function getCustomerStruct(string $customerId, Context $context): Custome * @return array */ public function getAddressArray($address, CustomerEntity $customer): array; - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity; + + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity; + public function getCountryId(string $countryCode, Context $context): ?string; + public function getSalutationId(Context $context): ?string; + public function createMollieCustomer(string $customerId, string $salesChannelId, Context $context): void; } diff --git a/src/Service/Order/UpdateOrderLineItems.php b/src/Service/Order/UpdateOrderLineItems.php index 2ce73f5c6..fab2aba21 100644 --- a/src/Service/Order/UpdateOrderLineItems.php +++ b/src/Service/Order/UpdateOrderLineItems.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Service\Order; use Kiener\MolliePayments\Repository\OrderLineItem\OrderLineItemRepositoryInterface; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Resources\Order; use Mollie\Api\Resources\OrderLine; use Mollie\Api\Types\OrderLineType; @@ -57,7 +58,7 @@ public function updateOrderLineItems(array $orderLines, OrderLineItemCollection $updateLines[] = [ 'id' => $shopwareLine->getId(), 'customFields' => [ - 'mollie_payments' => $originalCustomFields + CustomFieldsInterface::MOLLIE_KEY => $originalCustomFields ], ]; } diff --git a/src/Service/PayPalExpressConfig.php b/src/Service/PayPalExpressConfig.php new file mode 100644 index 000000000..c675c44fa --- /dev/null +++ b/src/Service/PayPalExpressConfig.php @@ -0,0 +1,50 @@ +enabled = $enabled ?? 0; + $this->buttonStyle = $buttonStyle ?? 1; + $this->buttonShape = $buttonShape ?? 1; + $restrictions = $restrictions ?? ''; + + if (strlen($restrictions) > 0) { + $this->restrictions = explode(' ', $restrictions); + } + } + + public function isEnabled(): bool + { + return $this->enabled === 1; + } + + /** + * @param array $structData + * @return array + */ + public function assign(array $structData): array + { + $structData['paypalExpressEnabled'] = $this->isEnabled(); + $structData['paypalExpressButtonStyle'] = $structData['paypalExpressButtonStyle'] ?? $this->buttonStyle; + $structData['paypalExpressButtonShape'] = $structData['paypalExpressButtonShape'] ?? $this->buttonShape; + $structData['paypalExpressRestrictions'] = array_unique(array_merge($structData['paypalExpressRestrictions'] ?? [], $this->restrictions)); + return $structData; + } +} diff --git a/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php b/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php new file mode 100644 index 000000000..c6f0f8988 --- /dev/null +++ b/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php @@ -0,0 +1,59 @@ +isAllowedRoute()) { + return $originalData; + } + $showPPEOnly = false; + + if ($this->isOrderRoute()) { + $order = $this->getOrder($context->getContext()); + $showPPEOnly = (bool)($order->getCustomFields()[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? false); + } + if ($this->isCartRoute()) { + $cart = $this->getCart($context); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + if ($cartExtension instanceof ArrayStruct) { + $ppeSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? ''; + $ppeAuthId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? ''; + $showPPEOnly = mb_strlen($ppeSessionId) > 0 || mb_strlen($ppeAuthId); + } + } + + foreach ($originalData->getPaymentMethods() as $key => $paymentMethod) { + $attributes = new PaymentMethodAttributes($paymentMethod); + $isPayPalExpress = $attributes->getMollieIdentifier() === PayPalExpressPayment::PAYMENT_METHOD_NAME; + + if ($showPPEOnly === true && $isPayPalExpress === false) { + $originalData->getPaymentMethods()->remove($key); + continue; + } + + if ($showPPEOnly === false && $isPayPalExpress === true) { + $originalData->getPaymentMethods()->remove($key); + } + } + + + return $originalData; + } +} diff --git a/src/Service/PaymentMethodService.php b/src/Service/PaymentMethodService.php index 7dcc15078..47de70700 100644 --- a/src/Service/PaymentMethodService.php +++ b/src/Service/PaymentMethodService.php @@ -14,6 +14,7 @@ use Kiener\MolliePayments\Handler\Method\CreditCardPayment; use Kiener\MolliePayments\Handler\Method\EpsPayment; use Kiener\MolliePayments\Handler\Method\GiftCardPayment; +use Kiener\MolliePayments\Handler\Method\GiroPayPayment; use Kiener\MolliePayments\Handler\Method\iDealPayment; use Kiener\MolliePayments\Handler\Method\In3Payment; use Kiener\MolliePayments\Handler\Method\IngHomePayPayment; @@ -24,6 +25,7 @@ use Kiener\MolliePayments\Handler\Method\KlarnaSliceItPayment; use Kiener\MolliePayments\Handler\Method\MyBankPayment; use Kiener\MolliePayments\Handler\Method\PayconiqPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PayPalPayment; use Kiener\MolliePayments\Handler\Method\PaySafeCardPayment; use Kiener\MolliePayments\Handler\Method\PosPayment; @@ -89,6 +91,7 @@ class PaymentMethodService * @var VersionCompare */ private $versionCompare; + private PayPalExpressConfig $payPalExpressConfig; /** @@ -99,7 +102,7 @@ class PaymentMethodService * @param PluginIdProvider $pluginIdProvider * @param HttpClientInterface $httpClient */ - public function __construct(string $shopwareVersion, MediaService $mediaService, MediaRepositoryInterface $mediaRepository, PaymentMethodRepositoryInterface $paymentRepository, PluginIdProvider $pluginIdProvider, HttpClientInterface $httpClient) + public function __construct(string $shopwareVersion, MediaService $mediaService, MediaRepositoryInterface $mediaRepository, PaymentMethodRepositoryInterface $paymentRepository, PluginIdProvider $pluginIdProvider, HttpClientInterface $httpClient, PayPalExpressConfig $payPalExpressConfig) { $this->mediaService = $mediaService; $this->mediaRepository = $mediaRepository; @@ -108,6 +111,7 @@ public function __construct(string $shopwareVersion, MediaService $mediaService, $this->httpClient = $httpClient; $this->versionCompare = new VersionCompare($shopwareVersion); + $this->payPalExpressConfig = $payPalExpressConfig; } @@ -120,7 +124,14 @@ public function installAndActivatePaymentMethods(Context $context): void # we still need the min the database # but always disable them :) $this->disablePaymentMethod(IngHomePayPayment::class, $context); - + $this->disablePaymentMethod(GiroPayPayment::class, $context); + $this->disablePaymentMethod(KlarnaPayLaterPayment::class, $context); + $this->disablePaymentMethod(KlarnaPayNowPayment::class, $context); + $this->disablePaymentMethod(KlarnaSliceItPayment::class, $context); + $this->disablePaymentMethod(SofortPayment::class, $context); + if (! $this->payPalExpressConfig->isEnabled()) { + $this->disablePaymentMethod(PayPalExpressPayment::class, $context); + } // Get installable payment methods $installablePaymentMethods = $this->getInstallablePaymentMethods(); @@ -200,7 +211,8 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void if ($this->versionCompare->gte('6.5.7.0')) { # we do a string cast here, since getTechnicalName will be not nullable in the future - $technicalName = (string)$existingPaymentMethod->getTechnicalName(); /** @phpstan-ignore-line */ + /** @phpstan-ignore-next-line */ + $technicalName = (string)$existingPaymentMethod->getTechnicalName(); } } else { # let's create a full parameter list of everything @@ -213,8 +225,8 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void 'description' => '', 'mediaId' => $mediaId, 'afterOrderEnabled' => true, - 'translations'=>[ - Defaults::LANGUAGE_SYSTEM=>[ + 'translations' => [ + Defaults::LANGUAGE_SYSTEM => [ 'name' => $paymentMethod['description'] ] ] @@ -222,7 +234,7 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void } if (mb_strlen($technicalName) === 0) { - $technicalName = self::TECHNICAL_NAME_PREFIX . $paymentMethod['name']; + $technicalName = self::TECHNICAL_NAME_PREFIX . $paymentMethod['name']; } # custom field name is required to be specific, because we use it in the template to display components @@ -257,13 +269,13 @@ public function getInstalledPaymentMethodHandlers(array $installableHandlers, Co $paymentMethods = $this->paymentRepository->search($paymentCriteria, $context); - if (!$paymentMethods->count()) { + if (! $paymentMethods->count()) { return $installableHandlers; } /** @var PaymentMethodEntity $paymentMethod */ foreach ($paymentMethods->getEntities() as $paymentMethod) { - if (!in_array($paymentMethod->getHandlerIdentifier(), $installableHandlers, true)) { + if (! in_array($paymentMethod->getHandlerIdentifier(), $installableHandlers, true)) { continue; } @@ -282,10 +294,10 @@ public function getInstalledPaymentMethodHandlers(array $installableHandlers, Co */ public function activatePaymentMethods(array $paymentMethods, array $installedHandlers, Context $context): void { - if (!empty($paymentMethods)) { + if (! empty($paymentMethods)) { foreach ($paymentMethods as $paymentMethod) { if ( - !isset($paymentMethod['handler']) || + ! isset($paymentMethod['handler']) || in_array($paymentMethod['handler'], $installedHandlers, true) ) { continue; @@ -421,7 +433,7 @@ private function getPaymentMethod($handlerIdentifier, Context $context): ?Paymen */ public function getPaymentHandlers(): array { - return [ + $paymentHandlers = [ ApplePayPayment::class, BanContactPayment::class, BankTransferPayment::class, @@ -432,14 +444,14 @@ public function getPaymentHandlers(): array GiftCardPayment::class, iDealPayment::class, KbcPayment::class, - KlarnaPayLaterPayment::class, - KlarnaPayNowPayment::class, - KlarnaSliceItPayment::class, + // KlarnaPayLaterPayment::class, + // KlarnaPayNowPayment::class, + // KlarnaSliceItPayment::class, KlarnaOnePayment::class, PayPalPayment::class, PaySafeCardPayment::class, Przelewy24Payment::class, - SofortPayment::class, + // SofortPayment::class, VoucherPayment::class, In3Payment::class, PosPayment::class, @@ -455,6 +467,12 @@ public function getPaymentHandlers(): array // IngHomePayPayment::class, // not allowed anymore // DirectDebitPayment::class, // only allowed when updating subsriptions, aka => not allowed anymore ]; + + if ($this->payPalExpressConfig->isEnabled()) { + $paymentHandlers[] = PayPalExpressPayment::class; + } + + return $paymentHandlers; } /** @@ -467,8 +485,14 @@ public function getPaymentHandlers(): array */ private function getMediaId(array $paymentMethod, Context $context): ?string { + $name = $paymentMethod['name']; + + if ($name === PayPalExpressPayment::PAYMENT_METHOD_NAME) { + $name = PayPalPayment::PAYMENT_METHOD_NAME; + } + /** @var string $fileName */ - $fileName = $paymentMethod['name'] . '-icon'; + $fileName = $name . '-icon'; $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('fileName', $fileName)); @@ -517,7 +541,7 @@ public function isPaidApplePayTransaction(OrderTransactionEntity $transaction, O $paymentMethodId = $transaction->getPaymentMethodId(); $paymentMethod = $transaction->getPaymentMethod(); - if (!$paymentMethod instanceof PaymentMethodEntity) { + if (! $paymentMethod instanceof PaymentMethodEntity) { $criteria = new Criteria([$paymentMethodId]); $paymentMethod = $this->paymentRepository->search($criteria, Context::createDefaultContext())->first(); } diff --git a/src/Service/Refund/CompositionMigrationService.php b/src/Service/Refund/CompositionMigrationService.php index 5706cc2b8..82f176bb0 100644 --- a/src/Service/Refund/CompositionMigrationService.php +++ b/src/Service/Refund/CompositionMigrationService.php @@ -8,6 +8,7 @@ use Kiener\MolliePayments\Components\RefundManager\DAL\Refund\RefundEntity; use Kiener\MolliePayments\Components\RefundManager\DAL\RefundItem\RefundItemCollection; use Kiener\MolliePayments\Components\RefundManager\DAL\RefundItem\RefundItemEntity; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\MollieApi\Fixer\RoundingDifferenceFixer; use Mollie\Api\Resources\Refund; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; @@ -159,10 +160,10 @@ private function filterByMollieId(OrderLineItemCollection $lineItems, string $mo if ($customFields === null) { continue; } - if (!isset($customFields['mollie_payments']['order_line_id'])) { + if (!isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY])) { continue; } - if ($customFields['mollie_payments']['order_line_id'] === $mollieLineId) { + if ($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY] === $mollieLineId) { $foundItem = $lineItem; break; } diff --git a/src/Service/Router/RoutingBuilder.php b/src/Service/Router/RoutingBuilder.php index 010ed56f1..d30ab7e36 100644 --- a/src/Service/Router/RoutingBuilder.php +++ b/src/Service/Router/RoutingBuilder.php @@ -192,6 +192,27 @@ public function buildSubscriptionPaymentUpdatedReturnUrl(string $subscriptionId) return $this->applyCustomDomain((string)$webhookUrl); } + /** + * @return string + */ + public function buildPaypalExpressRedirectUrl(): string + { + $confirmPage = $this->router->generate('frontend.mollie.paypal-express.finish', [], $this->router::ABSOLUTE_URL); + + return $confirmPage; + } + + /** + * @return string + */ + public function buildPaypalExpressCancelUrl(): string + { + $confirmPage = $this->router->generate('frontend.checkout.confirm.page', [], $this->router::ABSOLUTE_URL); + + return $confirmPage; + } + + /** * @param string $url * @return string diff --git a/src/Service/SettingsService.php b/src/Service/SettingsService.php index 424b645af..99c1c826e 100644 --- a/src/Service/SettingsService.php +++ b/src/Service/SettingsService.php @@ -18,7 +18,7 @@ class SettingsService implements PluginSettingsServiceInterface private const PHONE_NUMBER_FIELD = 'showPhoneNumberField'; - private const REQUIRE_DATA_PROTECTION ='requireDataProtectionCheckbox'; + private const REQUIRE_DATA_PROTECTION = 'requireDataProtectionCheckbox'; private const PAYMENT_FINALIZE_TRANSACTION_TIME = 'paymentFinalizeTransactionTime'; const LIVE_API_KEY = 'liveApiKey'; @@ -51,7 +51,12 @@ class SettingsService implements PluginSettingsServiceInterface * @var string */ private $envCypressMode; + private PayPalExpressConfig $payPalExpressConfig; + /** + * @var array + */ + private array $cachedStructs = []; /** * @param SystemConfigService $systemConfigService @@ -60,7 +65,7 @@ class SettingsService implements PluginSettingsServiceInterface * @param ?string $envDevMode * @param ?string $envCypressMode */ - public function __construct(SystemConfigService $systemConfigService, SalesChannelRepositoryInterface $repoSalesChannels, ?string $envShopDomain, ?string $envDevMode, ?string $envCypressMode) + public function __construct(SystemConfigService $systemConfigService, SalesChannelRepositoryInterface $repoSalesChannels, PayPalExpressConfig $payPalExpressConfig, ?string $envShopDomain, ?string $envDevMode, ?string $envCypressMode) { $this->systemConfigService = $systemConfigService; $this->repoSalesChannels = $repoSalesChannels; @@ -68,6 +73,7 @@ public function __construct(SystemConfigService $systemConfigService, SalesChann $this->envShopDomain = (string)$envShopDomain; $this->envDevMode = (string)$envDevMode; $this->envCypressMode = (string)$envCypressMode; + $this->payPalExpressConfig = $payPalExpressConfig; } /** @@ -78,11 +84,16 @@ public function __construct(SystemConfigService $systemConfigService, SalesChann */ public function getSettings(?string $salesChannelId = null): MollieSettingStruct { + $cacheKey = $salesChannelId ?? 'all'; + + if (isset($this->cachedStructs[$cacheKey])) { + return $this->cachedStructs[$cacheKey]; + } $structData = []; /** @var array $systemConfigData */ $systemConfigData = $this->systemConfigService->get(self::SYSTEM_CONFIG_DOMAIN, $salesChannelId); - if (count($systemConfigData) !== 0) { + if (is_array($systemConfigData) && count($systemConfigData) > 0) { foreach ($systemConfigData as $key => $value) { if (stripos($key, self::SYSTEM_CONFIG_DOMAIN) !== false) { $structData[substr($key, strlen(self::SYSTEM_CONFIG_DOMAIN))] = $value; @@ -94,18 +105,31 @@ public function getSettings(?string $salesChannelId = null): MollieSettingStruct /** @var array $coreSettings */ $coreSettings = $this->systemConfigService->get(self::SYSTEM_CORE_LOGIN_REGISTRATION_CONFIG_DOMAIN, $salesChannelId); + if (is_array($coreSettings) && count($coreSettings) > 0) { + $structData[self::PHONE_NUMBER_FIELD_REQUIRED] = $coreSettings[self::PHONE_NUMBER_FIELD_REQUIRED] ?? false; + $structData[self::PHONE_NUMBER_FIELD] = $coreSettings[self::PHONE_NUMBER_FIELD] ?? false; + $structData[self::REQUIRE_DATA_PROTECTION] = $coreSettings[self::REQUIRE_DATA_PROTECTION] ?? false; + } - $structData[self::PHONE_NUMBER_FIELD_REQUIRED] = $coreSettings[self::PHONE_NUMBER_FIELD_REQUIRED] ?? false; - - $structData[self::PHONE_NUMBER_FIELD] = $coreSettings[self::PHONE_NUMBER_FIELD] ?? false; - - $structData[self::REQUIRE_DATA_PROTECTION] = $coreSettings[self::REQUIRE_DATA_PROTECTION] ?? false; /** @var array $cartSettings */ $cartSettings = $this->systemConfigService->get(self::SYSTEM_CORE_CART_CONFIG_DOMAIN, $salesChannelId); - $structData[self::PAYMENT_FINALIZE_TRANSACTION_TIME] = $cartSettings[self::PAYMENT_FINALIZE_TRANSACTION_TIME] ?? 1800; + if (is_array($cartSettings) && count($cartSettings) > 0) { + $structData[self::PAYMENT_FINALIZE_TRANSACTION_TIME] = $cartSettings[self::PAYMENT_FINALIZE_TRANSACTION_TIME] ?? 1800; + } + + + /** + * TODO: remove this when we move to config + */ + if ($this->payPalExpressConfig->isEnabled()) { + $structData = $this->payPalExpressConfig->assign($structData); + } + + + $this->cachedStructs[$cacheKey] = (new MollieSettingStruct())->assign($structData); - return (new MollieSettingStruct())->assign($structData); + return $this->cachedStructs[$cacheKey]; } /** diff --git a/src/Service/UpdateOrderTransactionCustomFields.php b/src/Service/UpdateOrderTransactionCustomFields.php index 761372fd5..236721882 100644 --- a/src/Service/UpdateOrderTransactionCustomFields.php +++ b/src/Service/UpdateOrderTransactionCustomFields.php @@ -33,7 +33,7 @@ public function updateOrderTransaction(string $shopwareOrderTransactionId, Order $data = [ 'id' => $shopwareOrderTransactionId, 'customFields' => [ - 'mollie_payments' => $struct->toArray(), + CustomFieldsInterface::MOLLIE_KEY => $struct->toArray(), ] ]; diff --git a/src/Setting/MollieSettingStruct.php b/src/Setting/MollieSettingStruct.php index 2d4d44d04..c84d99cdd 100644 --- a/src/Setting/MollieSettingStruct.php +++ b/src/Setting/MollieSettingStruct.php @@ -290,6 +290,26 @@ class MollieSettingStruct extends Struct */ protected $automaticOrderExpire = false; + /** + * @var bool + */ + protected bool $paypalExpressEnabled = false; + + /** + * @var int + */ + protected $paypalExpressButtonStyle = 1; + + /** + * @var int + */ + protected $paypalExpressButtonShape = 1; + + /** + * @var array + */ + protected $paypalExpressRestrictions = []; + /** * @return string */ @@ -1088,4 +1108,51 @@ public function setAutomaticOrderExpire(bool $automaticOrderExpire): void { $this->automaticOrderExpire = $automaticOrderExpire; } + + public function isPaypalExpressEnabled(): bool + { + return $this->paypalExpressEnabled; + } + + public function setPaypalExpressEnabled(bool $paypalExpressEnabled): void + { + $this->paypalExpressEnabled = $paypalExpressEnabled; + } + + public function getPaypalExpressButtonStyle(): int + { + return $this->paypalExpressButtonStyle; + } + + public function setPaypalExpressButtonStyle(int $paypalExpressButtonStyle): void + { + $this->paypalExpressButtonStyle = $paypalExpressButtonStyle; + } + + public function getPaypalExpressButtonShape(): int + { + return $this->paypalExpressButtonShape; + } + + public function setPaypalExpressButtonShape(int $paypalExpressButtonShape): void + { + $this->paypalExpressButtonShape = $paypalExpressButtonShape; + } + + /** + * @return string[] + */ + public function getPaypalExpressRestrictions(): array + { + return $this->paypalExpressRestrictions; + } + + /** + * @param array $paypalExpressRestrictions + * @return void + */ + public function setPaypalExpressRestrictions(array $paypalExpressRestrictions): void + { + $this->paypalExpressRestrictions = $paypalExpressRestrictions; + } } diff --git a/src/Struct/Address/AddressStruct.php b/src/Struct/Address/AddressStruct.php new file mode 100644 index 000000000..15e983a61 --- /dev/null +++ b/src/Struct/Address/AddressStruct.php @@ -0,0 +1,145 @@ +firstName = $firstName; + $this->lastName = $lastName; + $this->street = $street; + $this->streetAdditional = $streetAdditional; + $this->zipCode = $zipCode; + $this->city = $city; + $this->countryCode = $countryCode; + $this->email = $email; + $this->phone = $phone; + } + + /** + * @param \stdClass $address + * @return self + */ + public static function createFromApiResponse(\stdClass $address) + { + $streetAdditional = ''; + if (property_exists($address, 'streetAdditional')) { + $streetAdditional = $address->streetAdditional; + } + if (property_exists($address, 'familyName')) { + $nameParts = explode(' ', $address->familyName); + $address->familyName = array_pop($nameParts); + $address->givenName = implode(' ', $nameParts); + } + + return new AddressStruct($address->givenName, $address->familyName, $address->email, $address->streetAndNumber, $streetAdditional, $address->postalCode, $address->city, $address->country, (string)$address->phone); + } + + public function getFirstName(): string + { + return $this->firstName; + } + + public function getLastName(): string + { + return $this->lastName; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getStreet(): string + { + return $this->street; + } + + public function getStreetAdditional(): string + { + return $this->streetAdditional; + } + + public function getZipCode(): string + { + return $this->zipCode; + } + + public function getCity(): string + { + return $this->city; + } + + public function getCountryCode(): string + { + return $this->countryCode; + } + + public function getPhone(): string + { + return $this->phone; + } + + + public function getMollieAddressId(): string + { + return md5(implode('-', [$this->firstName, $this->lastName, $this->email, $this->street, $this->streetAdditional, $this->zipCode, $this->city, $this->countryCode])); + } +} diff --git a/src/Struct/CustomerStruct.php b/src/Struct/CustomerStruct.php index 5837b918c..d7c84495a 100644 --- a/src/Struct/CustomerStruct.php +++ b/src/Struct/CustomerStruct.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Shopware\Core\Framework\Struct\Struct; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; @@ -143,7 +144,7 @@ public function toCustomFieldsArray(): array $fullCustomField = [ - 'mollie_payments' => $mollieData + CustomFieldsInterface::MOLLIE_KEY => $mollieData ]; # now either reset our old customer ID diff --git a/src/Struct/LineItem/LineItemAttributes.php b/src/Struct/LineItem/LineItemAttributes.php index efb4d1521..06968ef24 100644 --- a/src/Struct/LineItem/LineItemAttributes.php +++ b/src/Struct/LineItem/LineItemAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\LineItem; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Shopware\Core\Checkout\Cart\LineItem\LineItem; class LineItemAttributes @@ -232,9 +233,9 @@ private function getCustomFieldValue(LineItem $lineItem, string $keyName): strin # and load, but we migrate to the new one # check if we have customFields if ($foundValue === '') { - if ($customFields !== null && array_key_exists('mollie_payments', $customFields)) { + if ($customFields !== null && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/Order/OrderAttributes.php b/src/Struct/Order/OrderAttributes.php index eca8bad18..dbec6cfb1 100644 --- a/src/Struct/Order/OrderAttributes.php +++ b/src/Struct/Order/OrderAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\Order; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\OrderLineItemEntity\OrderLineItemEntityAttributes; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; use Shopware\Core\Checkout\Order\OrderEntity; @@ -104,6 +105,11 @@ class OrderAttributes */ private $bankBic; + /** + * @var string + */ + private $payPalExpressAuthenticateId; + /** * @var string */ @@ -134,6 +140,7 @@ public function __construct(OrderEntity $order) $this->bankBic = $this->getCustomFieldValue($order, 'bankBic'); $this->timezone = $this->getCustomFieldValue($order, 'timezone'); $this->bancomatPayPhoneNumber = $this->getCustomFieldValue($order, 'bancomatPayPhoneNumber'); + $this->payPalExpressAuthenticateId = $this->getCustomFieldValue($order, CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID); } /** @@ -450,6 +457,18 @@ public function setBankTransferDetails(?stdClass $details) } } + public function getPayPalExpressAuthenticateId(): string + { + return $this->payPalExpressAuthenticateId; + } + + public function setPayPalExpressAuthenticateId(string $payPalExpressAuthenticateId): void + { + $this->payPalExpressAuthenticateId = $payPalExpressAuthenticateId; + } + + + /** * @return string */ @@ -555,8 +574,12 @@ public function toArray(): array if ($this->bancomatPayPhoneNumber !== '') { $mollieData['bancomatPayPhoneNumber'] = $this->bancomatPayPhoneNumber; } + if ((string)$this->payPalExpressAuthenticateId !== '') { + $mollieData[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] = $this->payPalExpressAuthenticateId; + } + return [ - 'mollie_payments' => $mollieData, + CustomFieldsInterface::MOLLIE_KEY => $mollieData, ]; } @@ -602,9 +625,9 @@ private function getCustomFieldValue(OrderEntity $order, string $keyName): strin $customFields = $order->getCustomFields(); # check if we have a mollie entry - if ($customFields !== null && array_key_exists('mollie_payments', $customFields)) { + if ($customFields !== null && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php b/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php index 669edbb7f..2975ea838 100644 --- a/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php +++ b/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\OrderLineItemEntity; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\Voucher\VoucherType; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity; @@ -205,9 +206,9 @@ private function getCustomFieldValue(OrderLineItemEntity $lineItem, string $keyN # old structure # check if we have a mollie entry - if ($foundValue === '' && array_key_exists('mollie_payments', $customFields)) { + if ($foundValue === '' && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } @@ -230,9 +231,9 @@ private function getCustomFieldValue(OrderLineItemEntity $lineItem, string $keyN # old structure # check if we have a mollie entry - if ($foundValue === '' && array_key_exists('mollie_payments', $customFields)) { + if ($foundValue === '' && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/OrderTransaction/OrderTransactionAttributes.php b/src/Struct/OrderTransaction/OrderTransactionAttributes.php index 1e10617e9..ad1909462 100644 --- a/src/Struct/OrderTransaction/OrderTransactionAttributes.php +++ b/src/Struct/OrderTransaction/OrderTransactionAttributes.php @@ -2,6 +2,8 @@ namespace Kiener\MolliePayments\Struct\OrderTransaction; +use Kiener\MolliePayments\Service\CustomFieldsInterface; + class OrderTransactionAttributes { /** @@ -88,9 +90,9 @@ public function toArray(): array } return [ - 'order_id' => $this->mollieOrderId, - 'payment_id' => $this->molliePaymentId, - 'third_party_payment_id' => $this->thirdPartyPaymentId, + CustomFieldsInterface::ORDER_KEY => $this->mollieOrderId, + CustomFieldsInterface::PAYMENT_KEY => $this->molliePaymentId, + CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY => $this->thirdPartyPaymentId, ]; } @@ -99,20 +101,20 @@ public function toArray(): array */ private function setCustomFields(array $customFields): void { - if (!isset($customFields['mollie_payments'])) { + if (!isset($customFields[CustomFieldsInterface::MOLLIE_KEY])) { return; } - if (isset($customFields['mollie_payments']['order_id'])) { - $this->setMollieOrderId((string)$customFields['mollie_payments']['order_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY])) { + $this->setMollieOrderId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY]); } - if (isset($customFields['mollie_payments']['payment_id'])) { - $this->setMolliePaymentId((string)$customFields['mollie_payments']['payment_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYMENT_KEY])) { + $this->setMolliePaymentId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYMENT_KEY]); } - if (isset($customFields['mollie_payments']['third_party_payment_id'])) { - $this->setThirdPartyPaymentId((string)$customFields['mollie_payments']['third_party_payment_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY])) { + $this->setThirdPartyPaymentId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY]); } } } diff --git a/src/Subscriber/CartConvertedSubscriber.php b/src/Subscriber/CartConvertedSubscriber.php new file mode 100644 index 000000000..0e3013d49 --- /dev/null +++ b/src/Subscriber/CartConvertedSubscriber.php @@ -0,0 +1,36 @@ + 'savePayPalExpressData' + ]; + } + + public function savePayPalExpressData(CartConvertedEvent $event): void + { + $cart = $event->getCart(); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + if (! ($cartExtension instanceof ArrayStruct)) { + return; + } + $paypalExpressAuthenticateId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? null; + if ($paypalExpressAuthenticateId === null) { + return; + } + + $convertedCart = $event->getConvertedCart(); + $convertedCart['customFields'][CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] = $paypalExpressAuthenticateId; + $event->setConvertedCart($convertedCart); + } +} diff --git a/src/Subscriber/PaypalExpressSubscriber.php b/src/Subscriber/PaypalExpressSubscriber.php new file mode 100644 index 000000000..6004a4d49 --- /dev/null +++ b/src/Subscriber/PaypalExpressSubscriber.php @@ -0,0 +1,66 @@ +settingsService = $settingsService; + $this->paypal = $paypal; + } + + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + StorefrontRenderEvent::class => 'onStorefrontRender', + ]; + } + + /** + * @param StorefrontRenderEvent $event + * @throws \Exception + * @return void + */ + public function onStorefrontRender(StorefrontRenderEvent $event): void + { + $settings = $this->settingsService->getSettings($event->getSalesChannelContext()->getSalesChannel()->getId()); + + $paymentEnabled = $this->paypal->isPaypalExpressEnabled($event->getSalesChannelContext()) && $settings->isPaypalExpressEnabled(); + + $event->setParameter('mollie_paypalexpress_enabled', $paymentEnabled); + + $style = $settings->getPaypalExpressButtonStyle(); + $shape = $settings->getPaypalExpressButtonShape(); + + $restrictions = $settings->getPaypalExpressRestrictions(); + + $event->setParameter('mollie_paypalexpress_style', $style); + $event->setParameter('mollie_paypalexpress_shape', $shape); + $event->setParameter('mollie_paypalexpress_restrictions', $restrictions); + } +} diff --git a/src/Traits/Storefront/RedirectTrait.php b/src/Traits/Storefront/RedirectTrait.php index 3d0a487be..47b72d62a 100644 --- a/src/Traits/Storefront/RedirectTrait.php +++ b/src/Traits/Storefront/RedirectTrait.php @@ -50,4 +50,9 @@ public function getEditOrderPage(string $orderId, RouterInterface $router): stri $router::ABSOLUTE_URL ); } + + public function getCheckoutCartPage(RouterInterface $router):string + { + return $router->generate('frontend.checkout.cart.page', [], $router::ABSOLUTE_URL); + } } diff --git a/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js b/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js index 36a61e6c1..c02e41fd9 100644 --- a/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js @@ -43,7 +43,7 @@ context("Cancel Authorized items", () => { context(devices.getDescription(device), () => { it('C3259233: Cancel items from order', () => { - createOrderAndOpenAdmin('Pay now'); + createOrderAndOpenAdmin('Klarna'); orderDetailsRepository.getLineItemActionsButton(1).should('be.visible').click({force: true}); diff --git a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js index 4eeee1da2..308060f4a 100644 --- a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js @@ -88,7 +88,7 @@ context("Order Status Mapping Tests", () => { it('C4024: Test Status Authorized', () => { scenarioDummyBasket.execute(); - paymentAction.switchPaymentMethod('Pay later'); + paymentAction.switchPaymentMethod('Klarna'); shopware.prepareDomainChange(); checkout.placeOrderOnConfirm(); diff --git a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js index cf640d97f..52f2ac686 100644 --- a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js @@ -45,9 +45,7 @@ const payments = [ {caseId: 'C4101', key: 'credit-card', name: 'Card', sanity: false}, {caseId: 'C4111', key: 'paypal', name: 'PayPal', sanity: true}, {caseId: 'C466903', key: 'billie', name: 'Billie', sanity: false}, - {caseId: 'C4114', key: 'klarnapaynow', name: 'Pay now', sanity: false}, - {caseId: 'C4115', key: 'klarnapaylater', name: 'Pay later', sanity: false}, - {caseId: 'C4117', key: 'klarnasliceit', name: 'Slice it', sanity: false}, + {caseId: '', key: 'klarna', name: 'Klarna', sanity: false}, {caseId: 'C4118', key: 'ideal', name: 'iDEAL', sanity: false}, {caseId: 'C4120', key: 'eps', name: 'eps', sanity: false}, {caseId: 'C4123', key: 'mistercash', name: 'Bancontact', sanity: false}, @@ -126,11 +124,7 @@ context("Checkout Tests", () => { mollieSandbox.initSandboxCookie(); - if (payment.key === 'klarnapaylater' || payment.key === 'klarnapaynow' || payment.key === 'klarnasliceit') { - - molliePayment.selectAuthorized(); - - } else if (payment.key === 'billie') { + if (payment.key === 'billie' || payment.key === 'klarna') { molliePayment.selectAuthorized(); diff --git a/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js b/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js index c97249dde..69ee003ff 100644 --- a/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js @@ -265,7 +265,7 @@ describe('Status Tests', () => { devices.setDevice(devices.getFirstDevice()); // turn off credit card components // to speed up a few things - configAction.setupPlugin(false, false, false, false); + configAction.setupPlugin(false, false, false, false,[]); }) @@ -306,7 +306,7 @@ describe('Administration Tests', () => { before(function () { devices.setDevice(devices.getFirstDevice()); - configAction.setupPlugin(false, false, false, false); + configAction.setupPlugin(false, false, false, false,[]); }) beforeEach(() => { diff --git a/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js b/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js new file mode 100644 index 000000000..6e2ea8634 --- /dev/null +++ b/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js @@ -0,0 +1,190 @@ +import TopMenuAction from "Actions/storefront/navigation/TopMenuAction"; +import ListingAction from "Actions/storefront/products/ListingAction"; +import Devices from "Services/utils/Devices"; +import ShopConfigurationAction from "Actions/admin/ShopConfigurationAction"; +import PDPRepository from "Repositories/storefront/products/PDPRepository"; +import PDPAction from "Actions/storefront/products/PDPAction"; +import OffCanvasRepository from "Repositories/storefront/checkout/OffCanvasRepository"; +import CheckoutAction from "Actions/storefront/checkout/CheckoutAction"; +import CartRepository from "Repositories/storefront/checkout/CartRepository"; +import ListingRepository from "Repositories/storefront/products/ListingRepository"; +import RegisterRepository from "Repositories/storefront/checkout/RegisterRepository"; + + +const devices = new Devices(); +const configAction = new ShopConfigurationAction(); +const topMenu = new TopMenuAction(); +const listing = new ListingAction(); +const pdp = new PDPAction(); +const checkout = new CheckoutAction(); + +const repoPDP = new PDPRepository(); +const repoListing = new ListingRepository(); +const repoOffcanvas = new OffCanvasRepository(); +const repoCart = new CartRepository(); +const registerRepo = new RegisterRepository(); + + +describe('Paypal Express - UI Tests', () => { + + + before(function () { + devices.setDevice(devices.getFirstDevice()); + }) + + beforeEach(function () { + devices.setDevice(devices.getFirstDevice()); + }) + + describe('PDP', () => { + + it('Paypal Express button is visible @core', () => { + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + + const button = repoPDP.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['pdp']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + + repoPDP.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Listing', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + + const button = repoListing.getPayPalExpressButton().first(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['plp']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + + repoListing.getPayPalExpressButton().should('not.exist'); + }) + }) + + + describe('Offcanvas', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + const button = repoOffcanvas.getPayPalExpressButton(); + button.should('be.visible'); + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['offcanvas']) + + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + repoOffcanvas.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Cart', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCartInOffCanvas(); + + const button = repoCart.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['cart']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCartInOffCanvas(); + + repoCart.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Register Page', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCheckoutInOffCanvas(); + + const button = registerRepo.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['register']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCheckoutInOffCanvas(); + + registerRepo.getPayPalExpressButton().should('not.exist'); + }) + + }) + +}); \ No newline at end of file diff --git a/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js b/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js index a7e30d72e..9a760ea5d 100644 --- a/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js @@ -266,7 +266,7 @@ function createOrderAndOpenAdmin(itemCount, itemQty) { const scenarioDummyBasket = new DummyBasketScenario(itemQty, itemCount); scenarioDummyBasket.execute(); - paymentAction.switchPaymentMethod('Pay later'); + paymentAction.switchPaymentMethod('Klarna'); shopware.prepareDomainChange(); checkout.placeOrderOnConfirm(); diff --git a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js index e9da18259..0d8534331 100644 --- a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js @@ -225,7 +225,7 @@ describe('Subscription', () => { it('C4067: Subscription Indicator on PDP can be turned ON @core', () => { configAction.updateProducts('', true, 3, 'weeks'); - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); cy.wait(2000); cy.visit('/'); @@ -241,7 +241,7 @@ describe('Subscription', () => { it('C4068: Subscription Indicator on PDP can be turned OFF @core', () => { configAction.updateProducts('', true, 3, 'weeks'); - configAction.setupPlugin(true, false, false, false); + configAction.setupPlugin(true, false, false, false,[]); cy.wait(2000); cy.visit('/'); @@ -333,7 +333,7 @@ describe('Subscription', () => { function purchaseSubscriptionAndGoToPayment(){ - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); configAction.updateProducts('', true, 3, 'weeks'); dummyUserScenario.execute(); @@ -451,7 +451,7 @@ function assertAvailablePaymentMethods() { } function prepareSubscriptionAndOpenAdminDetails() { - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); configAction.updateProducts('', true, 3, 'weeks'); const dummyScenario = new DummyBasketScenario(1) diff --git a/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js b/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js index f3231b82a..134381037 100644 --- a/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js +++ b/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js @@ -18,6 +18,7 @@ export default class ShopConfigurationAction { * @param mollieFailureMode * @param creditCardComponents * @param applePayDirect + * @param paypalExpressRestrictions */ setupShop(mollieFailureMode, creditCardComponents, applePayDirect) { @@ -27,7 +28,7 @@ export default class ShopConfigurationAction { this.prepareShippingMethods(); - this.setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, false); + this.setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, false, []); this._clearCache(); } @@ -39,8 +40,9 @@ export default class ShopConfigurationAction { * @param creditCardComponents * @param applePayDirect * @param subscriptionIndicator + * @param paypalExpressRestrictions */ - setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator) { + setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions) { // assign all payment methods to // all available sales channels @@ -52,7 +54,7 @@ export default class ShopConfigurationAction { channels.forEach(channel => { this._configureSalesChannel(channel.id); - this._configureMolliePlugin(channel.id, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator); + this._configureMolliePlugin(channel.id, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions); }); }); } @@ -148,9 +150,10 @@ export default class ShopConfigurationAction { * @param creditCardComponents * @param applePayDirect * @param subscriptionIndicator + * @param paypalExpressRestrictions * @private */ - _configureMolliePlugin(channelId, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator) { + _configureMolliePlugin(channelId, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions) { const data = {}; const config = { @@ -173,6 +176,8 @@ export default class ShopConfigurationAction { "MolliePayments.config.subscriptionsShowIndicator": subscriptionIndicator, "MolliePayments.config.subscriptionsAllowPauseResume": true, "MolliePayments.config.subscriptionsAllowSkip": true, + // --------------------------------------------------------------- + "MolliePayments.config.paypalExpressRestrictions": paypalExpressRestrictions }; data[null] = config; // also add for "All Sales Channels" otherwise things in admin wouldnt work diff --git a/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js b/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js index 491bf2a2e..c70c14a61 100644 --- a/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js +++ b/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js @@ -2,19 +2,21 @@ import PDPRepository from 'Repositories/storefront/products/PDPRepository'; import Shopware from "Services/shopware/Shopware"; const shopware = new Shopware(); - +const repo = new PDPRepository(); export default class PDPAction { - /** * * @param quantity */ addToCart(quantity) { - const repo = new PDPRepository(); + this.setQuantity(quantity); + repo.getAddToCartButton().click(); + } + setQuantity(quantity){ if (shopware.isVersionGreaterEqual('6.5')) { const repetitions = quantity - 1; // its already 1 initially @@ -26,9 +28,5 @@ export default class PDPAction { } else { repo.getQuantityDropdown().select(quantity + ""); } - - - repo.getAddToCartButton().click(); } - } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js index 97b01098c..858a93052 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js @@ -8,4 +8,11 @@ export default class CartRepository { return cy.get('.mollie-apple-pay-direct-cart > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-cart button[name="paypal-express"]'); + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js index d5f93b694..ceaf9abb5 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js @@ -24,4 +24,11 @@ export default class OffCanvasRepository { return cy.get('.mollie-apple-pay-direct-offcanvas > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-offcanvas button[name="paypal-express"]'); + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js new file mode 100644 index 000000000..33ee9707a --- /dev/null +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js @@ -0,0 +1,11 @@ +export default class RegisterRepository { + + + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-register button[name="paypal-express"]'); + } +} \ No newline at end of file diff --git a/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js b/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js index f033d40c5..60c6e05a5 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js @@ -25,4 +25,11 @@ export default class ListingRepository { return cy.get('.mollie-apple-pay-direct-listing > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-plp button[name="paypal-express"]') + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js b/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js index 4ceecdb18..8601f74a0 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js @@ -44,4 +44,13 @@ export default class PDPRepository { return cy.get('.mollie-apple-pay-direct-pdp > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-pdp button[name="paypal-express"]'); + } + + } diff --git a/tests/PHPUnit/Fakes/FakeCartService.php b/tests/PHPUnit/Fakes/FakeCartService.php index 88abf000c..662e1dad8 100644 --- a/tests/PHPUnit/Fakes/FakeCartService.php +++ b/tests/PHPUnit/Fakes/FakeCartService.php @@ -73,4 +73,10 @@ public function updatePaymentMethod(SalesChannelContext $context, string $paymen { // TODO: Implement updatePaymentMethod() method. } + + public function persistCart(Cart $cart, SalesChannelContext $context): Cart + { + // TODO: Implement persistCart() method. + } + } diff --git a/tests/PHPUnit/Fakes/FakeCustomerService.php b/tests/PHPUnit/Fakes/FakeCustomerService.php index a4232a72f..f17d01b6f 100644 --- a/tests/PHPUnit/Fakes/FakeCustomerService.php +++ b/tests/PHPUnit/Fakes/FakeCustomerService.php @@ -5,6 +5,7 @@ use Exception; use Kiener\MolliePayments\Service\CustomerServiceInterface; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Kiener\MolliePayments\Struct\Mandate\MandateCollection; use Shopware\Core\Checkout\Customer\CustomerEntity; @@ -28,7 +29,7 @@ public function __construct(bool $throwException = false) $this->throwException = $throwException; } - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string { return null; } @@ -83,8 +84,7 @@ public function getAddressArray($address, CustomerEntity $customer): array return []; } - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2,int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity - { + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context,?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity{ return null; } diff --git a/tests/PHPUnit/Service/CustomerServiceTest.php b/tests/PHPUnit/Service/CustomerServiceTest.php index 5f62b13a9..d9a304c77 100644 --- a/tests/PHPUnit/Service/CustomerServiceTest.php +++ b/tests/PHPUnit/Service/CustomerServiceTest.php @@ -4,6 +4,7 @@ use Kiener\MolliePayments\Repository\Country\CountryRepository; use Kiener\MolliePayments\Repository\Customer\CustomerRepositoryInterface; +use Kiener\MolliePayments\Repository\CustomerAddress\CustomerAddressRepositoryInterface; use Kiener\MolliePayments\Repository\Salutation\SalutationRepository; use Kiener\MolliePayments\Service\ConfigService; use Kiener\MolliePayments\Service\CustomerService; @@ -42,11 +43,13 @@ class CustomerServiceTest extends TestCase public function setUp(): void { $this->customerRepository = new FakeCustomerRepository(new CustomerDefinition()); + $this->settingsService = $this->createMock(SettingsService::class); $this->customerService = new CustomerService( $this->createMock(CountryRepository::class), $this->customerRepository, + $this->createMock(CustomerAddressRepositoryInterface::class), $this->createMock(Customer::class), $this->createMock(EventDispatcherInterface::class), new NullLogger(), diff --git a/tests/PHPUnit/Service/PaymentMethodServiceTest.php b/tests/PHPUnit/Service/PaymentMethodServiceTest.php index 2f8a32973..3d32a2720 100644 --- a/tests/PHPUnit/Service/PaymentMethodServiceTest.php +++ b/tests/PHPUnit/Service/PaymentMethodServiceTest.php @@ -22,6 +22,7 @@ use Kiener\MolliePayments\Handler\Method\KlarnaPayNowPayment; use Kiener\MolliePayments\Handler\Method\KlarnaSliceItPayment; use Kiener\MolliePayments\Handler\Method\MyBankPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PayconiqPayment; use Kiener\MolliePayments\Handler\Method\PayPalPayment; use Kiener\MolliePayments\Handler\Method\PaySafeCardPayment; @@ -36,6 +37,7 @@ use Kiener\MolliePayments\Repository\Media\MediaRepositoryInterface; use Kiener\MolliePayments\Repository\PaymentMethod\PaymentMethodRepositoryInterface; use Kiener\MolliePayments\Service\PaymentMethodService; +use Kiener\MolliePayments\Service\PayPalExpressConfig; use MolliePayments\Tests\Fakes\FakeHttpClient; use MolliePayments\Tests\Fakes\Repositories\FakeMediaRepository; use MolliePayments\Tests\Fakes\Repositories\FakePaymentMethodRepository; @@ -90,7 +92,8 @@ protected function setUp(): void $this->mediaRepository, $this->paymentMethodRepository, $this->createMock(PluginIdProvider::class), - new FakeHttpClient() + new FakeHttpClient(), + new PayPalExpressConfig(1), ); } @@ -124,14 +127,10 @@ public function testSupportedMethods(): void GiftCardPayment::class, iDealPayment::class, KbcPayment::class, - KlarnaPayLaterPayment::class, - KlarnaPayNowPayment::class, - KlarnaSliceItPayment::class, KlarnaOnePayment::class, PayPalPayment::class, PaySafeCardPayment::class, Przelewy24Payment::class, - SofortPayment::class, VoucherPayment::class, In3Payment::class, PosPayment::class, @@ -144,6 +143,7 @@ public function testSupportedMethods(): void PayconiqPayment::class, RivertyPayment::class, SatispayPayment::class, + PayPalExpressPayment::class, ]; $handlers = $this->paymentMethodService->getPaymentHandlers(); diff --git a/vendor_manual/makefile b/vendor_manual/makefile index 4e1d8209e..880e6a673 100644 --- a/vendor_manual/makefile +++ b/vendor_manual/makefile @@ -1,5 +1,6 @@ -MOLLIE_PHP_VERSION:=2.61.0 +#MOLLIE_PHP_VERSION:=v2.61.0 +MOLLIE_PHP_VERSION:=feature/add-sessions help: @@ -9,7 +10,7 @@ help: install: ## Installs all production dependencies rm -rf mollie - git clone -b v$(MOLLIE_PHP_VERSION) https://github.com/mollie/mollie-api-php.git mollie/mollie-api-php + git clone -b $(MOLLIE_PHP_VERSION) https://github.com/mollie/mollie-api-php.git mollie/mollie-api-php rm -rf mollie/mollie-api-php/.git rm -rf mollie/mollie-api-php/.github rm -rf mollie/mollie-api-php/.gitattributes diff --git a/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php b/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php deleted file mode 100644 index f79cca01d..000000000 --- a/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php +++ /dev/null @@ -1,37 +0,0 @@ -in([ - __DIR__ . '/src', - __DIR__ . '/examples', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->notPath('bootstrap/*') - ->notPath('storage/*') - ->notPath('vendor') - ->ignoreDotFiles(true) - ->ignoreVCS(true); - -return (new PhpCsFixer\Config()) - ->setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sort_algorithm' => 'alpha'], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline' => true, - 'phpdoc_scalar' => true, - 'unary_operator_spaces' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_var_without_name' => true, - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'single_trait_insert_per_statement' => true, - ]) - ->setFinder($finder); diff --git a/vendor_manual/mollie/mollie-api-php/README.md b/vendor_manual/mollie/mollie-api-php/README.md index 8f9cb7d71..faa1275e3 100644 --- a/vendor_manual/mollie/mollie-api-php/README.md +++ b/vendor_manual/mollie/mollie-api-php/README.md @@ -18,7 +18,7 @@ To use the Mollie API client, the following things are required: + Get yourself a free [Mollie account](https://www.mollie.com/signup). No sign up costs. + Now you're ready to use the Mollie API client in test mode. + Follow [a few steps](https://www.mollie.com/dashboard/?modal=onboarding) to enable payment methods in live mode, and let us handle the rest. -+ PHP >= 7.0 ++ PHP >= 7.2 + Up-to-date OpenSSL (or other SSL/TLS toolkit) For leveraging [Mollie Connect](https://docs.mollie.com/oauth/overview) (advanced use cases only), we recommend also installing our [OAuth2 client](https://github.com/mollie/oauth2-mollie-php). @@ -32,7 +32,7 @@ The easiest way to install the Mollie API client is by using [Composer](http://g composer require mollie/mollie-api-php ``` -To work with the most recent API version, ensure that you are using a version of this API client that is equal to or greater than 2.0.0. If you prefer to continue using the v1 API, make sure your client version is below 2.0.0. For guidance on transitioning from v1 to v2, please refer to the [migration notes](https://docs.mollie.com/migrating-v1-to-v2). +To work with the most recent API version, ensure that you are using a version of this API client that is equal to or greater than 2.0.0. If you prefer to continue using the v1 API, make sure your client version is below 2.0.0. For guidance on transitioning from v1 to v2, please refer to the [migration notes](https://docs.mollie.com/docs/migrating-from-v1-to-v2). ### Manual Installation ### If you're not familiar with using composer we've added a ZIP file to the releases containing the API client and all the packages normally installed by composer. diff --git a/vendor_manual/mollie/mollie-api-php/composer.json b/vendor_manual/mollie/mollie-api-php/composer.json index 47040d24b..04def5417 100644 --- a/vendor_manual/mollie/mollie-api-php/composer.json +++ b/vendor_manual/mollie/mollie-api-php/composer.json @@ -57,7 +57,7 @@ "eloquent/liberator": "^2.0||^3.0", "friendsofphp/php-cs-fixer": "^3.0", "guzzlehttp/guzzle": "^6.3 || ^7.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^8.5 || ^9.5" }, "suggest": { diff --git a/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon b/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon deleted file mode 100644 index 56cabd08a..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Variable \\$mollie might not be defined\\.$#" - path: examples/* diff --git a/vendor_manual/mollie/mollie-api-php/phpstan.neon b/vendor_manual/mollie/mollie-api-php/phpstan.neon deleted file mode 100644 index ecd56da76..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpstan.neon +++ /dev/null @@ -1,17 +0,0 @@ -parameters: - level: 5 - paths: - - %currentWorkingDirectory%/src - - %currentWorkingDirectory%/tests - - %currentWorkingDirectory%/examples - excludePaths: - - %currentWorkingDirectory%/vendor - ignoreErrors: - - '#Access to an undefined property Eloquent\\Liberator\\LiberatorProxyInterface::\$request#' - - '#Access to protected property Mollie\\Api\\MollieApiClient::\$apiKey#' - - '#Access to protected property Mollie\\Api\\MollieApiClient::\$httpClient#' - - '#Call to an undefined method Mollie\\Api\\HttpAdapter\\MollieHttpAdapterInterface::enableDebugging\(\)#' - - '#Call to an undefined method Mollie\\Api\\HttpAdapter\\MollieHttpAdapterInterface::disableDebugging\(\)#' - treatPhpDocTypesAsCertain: false -includes: - - phpstan-baseline.neon diff --git a/vendor_manual/mollie/mollie-api-php/phpunit.xml b/vendor_manual/mollie/mollie-api-php/phpunit.xml deleted file mode 100644 index eb7a8f95d..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpunit.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - src/ - - - - - - - - tests/ - - diff --git a/vendor_manual/mollie/mollie-api-php/scoper.inc.php b/vendor_manual/mollie/mollie-api-php/scoper.inc.php deleted file mode 100644 index 88317a713..000000000 --- a/vendor_manual/mollie/mollie-api-php/scoper.inc.php +++ /dev/null @@ -1,13 +0,0 @@ - [], // Finder[] - 'patchers' => [], // callable[] - 'whitelist' => [ - 'Mollie\\Api\\*', - ], -]; \ No newline at end of file diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php index fe87e9787..7c34c0a2d 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php @@ -131,7 +131,7 @@ protected function rest_read($id, array $filters) } /** - * Sends a DELETE request to a single Molle API object. + * Sends a DELETE request to a single Mollie API object. * * @param string $id * @param array $body diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php index d6dee89f3..7e77c167a 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php @@ -122,8 +122,8 @@ public function iteratorFor(Customer $customer, ?string $from = null, ?int $limi /** * @param string $customerId - * @param null $from - * @param null $limit + * @param string|null $from + * @param int|null $limit * @param array $parameters * * @return \Mollie\Api\Resources\MandateCollection diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php new file mode 100644 index 000000000..2cb661b91 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php @@ -0,0 +1,85 @@ +profileId = $profileId; + $this->methodId = $methodId; + $this->issuerId = $issuerId; + + $response = $this->rest_create([], []); + + $this->resetResourceIds(); + + return $response; + } + + public function disable(string $profileId, string $methodId, string $issuerId) + { + $this->profileId = $profileId; + $this->methodId = $methodId; + + return $this->rest_delete($issuerId); + } + + protected function resetResourceIds() + { + $this->profileId = null; + $this->methodId = null; + $this->issuerId = null; + } + + /** + * @return string + * @throws ApiException + */ + public function getResourcePath() + { + if (! $this->profileId) { + throw new ApiException("No profileId provided."); + } + + if (! $this->methodId) { + throw new ApiException("No methodId provided."); + } + + $path = "profiles/{$this->profileId}/methods/{$this->methodId}/issuers"; + + if ($this->issuerId) { + $path .= "/$this->issuerId"; + } + + return $path; + } + + /** + * Get the object that is used by this API endpoint. Every API endpoint uses one type of object. + * + * @return Issuer + */ + protected function getResourceObject() + { + return new Issuer($this->client); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php index 52ba56958..c8cb9a364 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php @@ -40,13 +40,13 @@ public function get() } /** + * @deprecated 2023-05-01 For an alternative, see https://docs.mollie.com/reference/create-client-link . * Submit data that will be prefilled in the merchant’s onboarding. * Please note that the data you submit will only be processed when the onboarding status is needs-data. * * Information that the merchant has entered in their dashboard will not be overwritten. * - * Will throw a ApiException if the resource cannot be found. - * + * Will throw an ApiException if the resource cannot be found. * @throws ApiException */ public function submit(array $parameters = []) diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php index d50c9e3b4..ac4edf3dc 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php @@ -66,4 +66,28 @@ public function createForId($orderId, array $data, array $filters = []) return parent::rest_create($data, $filters); } + + /** + * @param $orderId + * @param array $parameters + * @return RefundCollection + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function pageForId($orderId, array $parameters = []) + { + $this->parentId = $orderId; + + return parent::rest_list(null, null, $parameters); + } + + /** + * @param \Mollie\Api\Resources\Order $order + * @param array $parameters + * @return \Mollie\Api\Resources\RefundCollection + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function pageFor(Order $order, array $parameters = []) + { + return $this->pageForId($order->id, $parameters); + } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php index f27d09ddf..9a19e0dcb 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php @@ -4,7 +4,6 @@ use Mollie\Api\Exceptions\ApiException; use Mollie\Api\Resources\LazyCollection; -use Mollie\Api\Resources\Payment; use Mollie\Api\Resources\PaymentLink; use Mollie\Api\Resources\PaymentLinkCollection; @@ -17,6 +16,40 @@ class PaymentLinkEndpoint extends CollectionEndpointAbstract */ public const RESOURCE_ID_PREFIX = 'pl_'; + /** + * Update a Payment Link. + * + * @param string $paymentLinkId + * @param array $data + * @return PaymentLink + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update(string $paymentLinkId, array $data) + { + if (empty($paymentLinkId) || strpos($paymentLinkId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid payment ID: '{$paymentLinkId}'. A Payment Link ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return $this->rest_update($paymentLinkId, $data); + } + + /** + * Delete a Payment Link. + * + * @param string $paymentLinkId + * @param array $data + * @return void + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function delete(string $paymentLinkId, array $data = []) + { + if (empty($paymentLinkId) || strpos($paymentLinkId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid payment ID: '{$paymentLinkId}'. A Payment Link ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + $this->rest_delete($paymentLinkId, $data); + } + /** * @return PaymentLink */ diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php new file mode 100644 index 000000000..d118b3666 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php @@ -0,0 +1,93 @@ +client, $count, $_links); + } + + /** + * @inheritDoc + */ + protected function getResourceObject() + { + return new Payment($this->client); + } + + public function pageForId(string $paymentLinkId, string $from = null, int $limit = null, array $filters = []) + { + $this->parentId = $paymentLinkId; + + return $this->rest_list($from, $limit, $filters); + } + + public function pageFor(PaymentLink $paymentLink, string $from = null, int $limit = null, array $filters = []) + { + return $this->pageForId($paymentLink->id, $from, $limit, $filters); + } + + /** + * Create an iterator for iterating over payments associated with the provided Payment Link id, retrieved from Mollie. + * + * @param string $paymentLinkId + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). + * + * @return LazyCollection + */ + public function iteratorForId( + string $paymentLinkId, + ?string $from = null, + ?int $limit = null, + array $parameters = [], + bool $iterateBackwards = false + ): LazyCollection { + $this->parentId = $paymentLinkId; + + return $this->rest_iterator($from, $limit, $parameters, $iterateBackwards); + } + + /** + * Create an iterator for iterating over payments associated with the provided Payment Link object, retrieved from Mollie. + * + * @param PaymentLink $paymentLink + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). + * + * @return LazyCollection + */ + public function iteratorFor( + PaymentLink $paymentLink, + ?string $from = null, + ?int $limit = null, + array $parameters = [], + bool $iterateBackwards = false + ): LazyCollection { + return $this->iteratorForId( + $paymentLink->id, + $from, + $limit, + $parameters, + $iterateBackwards + ); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php index 68524ec8c..6110cdb57 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php @@ -78,8 +78,8 @@ public function listFor(Payment $payment, array $parameters = []) * Create an iterator for iterating over refunds for the given payment, retrieved from Mollie. * * @param Payment $payment - * @param string $from The first resource ID you want to include in your list. - * @param int $limit + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit * @param array $parameters * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). * @@ -108,8 +108,8 @@ public function listForId($paymentId, array $parameters = []) * Create an iterator for iterating over refunds for the given payment id, retrieved from Mollie. * * @param string $paymentId - * @param string $from The first resource ID you want to include in your list. - * @param int $limit + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit * @param array $parameters * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). * diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php new file mode 100644 index 000000000..8f2d72ee8 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php @@ -0,0 +1,139 @@ +client); + } + + /** + * Get the collection object that is used by this API endpoint. Every API + * endpoint uses one type of collection object. + * + * @param int $count + * @param \stdClass $_links + * + * @return SessionCollection + */ + protected function getResourceCollectionObject($count, $_links) + { + return new SessionCollection($this->client, $count, $_links); + } + + /** + * Creates a session in Mollie. + * + * @param array $data An array containing details on the session. + * @param array $filters + * + * @return Session + * @throws ApiException + */ + public function create(array $data = [], array $filters = []) + { + return $this->rest_create($data, $filters); + } + + /** + * Update a specific Session resource + * + * Will throw a ApiException if the resource id is invalid or the resource cannot be found. + * + * @param string $resourceId + * + * @param array $data + * @return Session + * @throws ApiException + */ + public function update($resourceId, array $data = []) + { + if (empty($resourceId) || strpos($resourceId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid session ID: '{$resourceId}'. A session ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return parent::rest_update($resourceId, $data); + } + + /** + * Retrieve a single session from Mollie. + * + * Will throw a ApiException if the resource id is invalid or the resource cannot + * be found. + * + * @param array $parameters + * @return Session + * @throws ApiException + */ + public function get($resourceId, array $parameters = []) + { + if (empty($resourceId) || strpos($resourceId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid session ID: '{$resourceId}'. A session ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return parent::rest_read($resourceId, $parameters); + } + + /** + * Cancel the given Session. + * + * @param string $resourceId + * @param array $parameters + * @return Session + * @throws ApiException + */ + public function cancel($resourceId, $parameters = []) + { + return $this->rest_delete($resourceId, $parameters); + } + + /** + * Retrieves a collection of Sessions from Mollie. + * + * @param string $from The first resource ID you want to include in your list. + * @param int $limit + * @param array $parameters + * + * @return SessionCollection + * @throws ApiException + */ + public function page(?string $from = null, ?int $limit = null, array $parameters = []) + { + return $this->rest_list($from, $limit, $parameters); + } + + /** + * Create an iterator for iterating over sessions retrieved from Mollie. + * + * @param string $from The first resource ID you want to include in your list. + * @param int $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse resource iteration (default is false). + * + * @return LazyCollection + */ + public function iterator(?string $from = null, ?int $limit = null, array $parameters = [], bool $iterateBackwards = false): LazyCollection + { + return $this->rest_iterator($from, $limit, $parameters, $iterateBackwards); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php new file mode 100644 index 000000000..b65e0682a --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php @@ -0,0 +1,79 @@ +customerId = $customerId; + $this->subscriptionId = $subscriptionId; + + return $this->rest_list($from, $limit, $parameters); + } + + /** + * Get the object that is used by this API endpoint. Every API endpoint uses one type of object. + * + * @return Payment + */ + protected function getResourceObject() + { + return new Payment($this->client); + } + + /** + * Get the collection object that is used by this API endpoint. Every API endpoint uses one type of collection object. + * + * @param int $count + * @param \stdClass $_links + * + * @return PaymentCollection + */ + protected function getResourceCollectionObject($count, $_links) + { + return new PaymentCollection($this->client, $count, $_links); + } + + public function getResourcePath() + { + if (is_null($this->customerId)) { + throw new ApiException('No customerId provided.'); + } + + if (is_null($this->subscriptionId)) { + throw new ApiException('No subscriptionId provided.'); + } + + return "customers/{$this->customerId}/subscriptions/{$this->subscriptionId}/payments"; + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php b/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php index a7a1a35e7..2b62ea320 100644 --- a/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php +++ b/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php @@ -13,6 +13,7 @@ use Mollie\Api\Endpoints\InvoiceEndpoint; use Mollie\Api\Endpoints\MandateEndpoint; use Mollie\Api\Endpoints\MethodEndpoint; +use Mollie\Api\Endpoints\MethodIssuerEndpoint; use Mollie\Api\Endpoints\OnboardingEndpoint; use Mollie\Api\Endpoints\OrderEndpoint; use Mollie\Api\Endpoints\OrderLineEndpoint; @@ -24,12 +25,14 @@ use Mollie\Api\Endpoints\PaymentChargebackEndpoint; use Mollie\Api\Endpoints\PaymentEndpoint; use Mollie\Api\Endpoints\PaymentLinkEndpoint; +use Mollie\Api\Endpoints\PaymentLinkPaymentEndpoint; use Mollie\Api\Endpoints\PaymentRefundEndpoint; use Mollie\Api\Endpoints\PaymentRouteEndpoint; use Mollie\Api\Endpoints\PermissionEndpoint; use Mollie\Api\Endpoints\ProfileEndpoint; use Mollie\Api\Endpoints\ProfileMethodEndpoint; use Mollie\Api\Endpoints\RefundEndpoint; +use Mollie\Api\Endpoints\SessionEndpoint; use Mollie\Api\Endpoints\SettlementCaptureEndpoint; use Mollie\Api\Endpoints\SettlementChargebackEndpoint; use Mollie\Api\Endpoints\SettlementPaymentEndpoint; @@ -37,6 +40,7 @@ use Mollie\Api\Endpoints\SettlementsEndpoint; use Mollie\Api\Endpoints\ShipmentEndpoint; use Mollie\Api\Endpoints\SubscriptionEndpoint; +use Mollie\Api\Endpoints\SubscriptionPaymentEndpoint; use Mollie\Api\Endpoints\TerminalEndpoint; use Mollie\Api\Endpoints\WalletEndpoint; use Mollie\Api\Exceptions\ApiException; @@ -50,7 +54,7 @@ class MollieApiClient /** * Version of our client. */ - public const CLIENT_VERSION = "2.65.0"; + public const CLIENT_VERSION = "2.73.0"; /** * Endpoint of the remote API. @@ -99,6 +103,11 @@ class MollieApiClient */ public $profileMethods; + /** + * @var \Mollie\Api\Endpoints\MethodIssuerEndpoint + */ + public $methodIssuers; + /** * RESTful Customers resource. * @@ -155,6 +164,13 @@ class MollieApiClient */ public $subscriptions; + /** + * RESTful Subscription Payments resource. + * + * @var SubscriptionPaymentEndpoint + */ + public $subscriptionPayments; + /** * RESTful Mandate resource. * @@ -291,6 +307,13 @@ class MollieApiClient */ public $orderRefunds; + /** + * RESTful Payment Link Payment resource. + * + * @var PaymentLinkPaymentEndpoint + */ + public $paymentLinkPayments; + /** * Manages Payment Links requests * @@ -319,6 +342,27 @@ class MollieApiClient */ public $wallets; + /** + * RESTful Client resource. + * + * @var ClientEndpoint + */ + public $clients; + + /** + * RESTful Client resource. + * + * @var ClientLinkEndpoint + */ + public $clientLinks; + + /** + * RESTful Session resource. + * + * @var SessionEndpoint + */ + public $sessions; + /** * @var string */ @@ -349,20 +393,6 @@ class MollieApiClient */ protected $versionStrings = []; - /** - * RESTful Client resource. - * - * @var ClientEndpoint - */ - public $clients; - - /** - * RESTful Client resource. - * - * @var ClientLinkEndpoint - */ - public $clientLinks; - /** * @param \GuzzleHttp\ClientInterface|\Mollie\Api\HttpAdapter\MollieHttpAdapterInterface|null $httpClient * @param \Mollie\Api\HttpAdapter\MollieHttpAdapterPickerInterface|null $httpAdapterPicker, @@ -384,43 +414,47 @@ public function __construct($httpClient = null, $httpAdapterPicker = null, $idem public function initializeEndpoints() { - $this->payments = new PaymentEndpoint($this); - $this->methods = new MethodEndpoint($this); - $this->profileMethods = new ProfileMethodEndpoint($this); - $this->customers = new CustomerEndpoint($this); - $this->settlements = new SettlementsEndpoint($this); - $this->settlementCaptures = new SettlementCaptureEndpoint($this); - $this->settlementChargebacks = new SettlementChargebackEndpoint($this); - $this->settlementPayments = new SettlementPaymentEndpoint($this); - $this->settlementRefunds = new SettlementRefundEndpoint($this); - $this->subscriptions = new SubscriptionEndpoint($this); - $this->customerPayments = new CustomerPaymentsEndpoint($this); - $this->mandates = new MandateEndpoint($this); - $this->balances = new BalanceEndpoint($this); - $this->balanceTransactions = new BalanceTransactionEndpoint($this); $this->balanceReports = new BalanceReportEndpoint($this); + $this->balanceTransactions = new BalanceTransactionEndpoint($this); + $this->balances = new BalanceEndpoint($this); + $this->chargebacks = new ChargebackEndpoint($this); + $this->clientLinks = new ClientLinkEndpoint($this); + $this->clients = new ClientEndpoint($this); + $this->customerPayments = new CustomerPaymentsEndpoint($this); + $this->customers = new CustomerEndpoint($this); $this->invoices = new InvoiceEndpoint($this); - $this->permissions = new PermissionEndpoint($this); - $this->profiles = new ProfileEndpoint($this); + $this->mandates = new MandateEndpoint($this); + $this->methods = new MethodEndpoint($this); + $this->methodIssuers = new MethodIssuerEndpoint($this); $this->onboarding = new OnboardingEndpoint($this); - $this->organizations = new OrganizationEndpoint($this); - $this->orders = new OrderEndpoint($this); $this->orderLines = new OrderLineEndpoint($this); $this->orderPayments = new OrderPaymentEndpoint($this); $this->orderRefunds = new OrderRefundEndpoint($this); - $this->shipments = new ShipmentEndpoint($this); - $this->refunds = new RefundEndpoint($this); - $this->paymentRefunds = new PaymentRefundEndpoint($this); + $this->orders = new OrderEndpoint($this); + $this->organizationPartners = new OrganizationPartnerEndpoint($this); + $this->organizations = new OrganizationEndpoint($this); $this->paymentCaptures = new PaymentCaptureEndpoint($this); - $this->paymentRoutes = new PaymentRouteEndpoint($this); - $this->chargebacks = new ChargebackEndpoint($this); $this->paymentChargebacks = new PaymentChargebackEndpoint($this); - $this->wallets = new WalletEndpoint($this); + $this->paymentLinkPayments = new PaymentLinkPaymentEndpoint($this); $this->paymentLinks = new PaymentLinkEndpoint($this); + $this->paymentRefunds = new PaymentRefundEndpoint($this); + $this->paymentRoutes = new PaymentRouteEndpoint($this); + $this->payments = new PaymentEndpoint($this); + $this->permissions = new PermissionEndpoint($this); + $this->profileMethods = new ProfileMethodEndpoint($this); + $this->profiles = new ProfileEndpoint($this); + $this->refunds = new RefundEndpoint($this); + $this->settlementCaptures = new SettlementCaptureEndpoint($this); + $this->settlementChargebacks = new SettlementChargebackEndpoint($this); + $this->settlementPayments = new SettlementPaymentEndpoint($this); + $this->settlementRefunds = new SettlementRefundEndpoint($this); + $this->settlements = new SettlementsEndpoint($this); + $this->sessions = new SessionEndpoint($this); + $this->shipments = new ShipmentEndpoint($this); + $this->subscriptionPayments = new SubscriptionPaymentEndpoint($this); + $this->subscriptions = new SubscriptionEndpoint($this); $this->terminals = new TerminalEndpoint($this); - $this->organizationPartners = new OrganizationPartnerEndpoint($this); - $this->clients = new ClientEndpoint($this); - $this->clientLinks = new ClientLinkEndpoint($this); + $this->wallets = new WalletEndpoint($this); } protected function initializeVersionStrings() diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php index 2fc7c4e5e..89cd86209 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php @@ -24,6 +24,13 @@ class Capture extends BaseResource */ public $mode; + /** + * Status of the capture. + * + * @var string + */ + public $status; + /** * Amount object containing the value and currency * @@ -59,6 +66,15 @@ class Capture extends BaseResource */ public $settlementId; + /** + * Provide any data you like, for example a string or a JSON object. The data will be saved alongside the capture. + * Whenever you fetch the capture, the metadata will be included. + * You can use up to approximately 1kB on this field. + * + * @var \stdClass|mixed|null + */ + public $metadata; + /** * @var string */ diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php index 98fc7443d..bc44daaf2 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php @@ -6,6 +6,8 @@ class Customer extends BaseResource { + use HasPresetOptions; + /** * Id of the customer. * @@ -221,30 +223,4 @@ public function hasValidMandateForMethod($method) return false; } - - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php b/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php new file mode 100644 index 000000000..8a0e24e21 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php @@ -0,0 +1,38 @@ +client->usesOAuth()) { + $options["testmode"] = $this->mode === "test" ? true : false; + } + + return $options; + } + + /** + * Apply the preset options. + * + * @param array $options + * @return array + */ + protected function withPresetOptions(array $options) + { + return array_merge($this->getPresetOptions(), $options); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php index 349a2c3df..f579c2aae 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php @@ -3,11 +3,12 @@ namespace Mollie\Api\Resources; use Mollie\Api\Exceptions\ApiException; -use Mollie\Api\MollieApiClient; use Mollie\Api\Types\OrderStatus; class Order extends BaseResource { + use HasPresetOptions; + /** * Id of the order. * @@ -81,7 +82,7 @@ class Order extends BaseResource public $orderNumber; /** - * The person and the address the order is billed to. + * The person and the address the order is shipped to. * * @var \stdClass */ @@ -474,18 +475,7 @@ public function refundAll(array $data = []) */ public function refunds() { - if (! isset($this->_links->refunds->href)) { - return new RefundCollection($this->client, 0, null); - } - - $result = $this->client->performHttpCallToFullUrl(MollieApiClient::HTTP_GET, $this->_links->refunds->href); - - return ResourceFactory::createCursorResourceCollection( - $this->client, - $result->_embedded->refunds, - Refund::class, - $result->_links - ); + return $this->client->orderRefunds->pageFor($this); } /** @@ -541,30 +531,4 @@ public function payments() Payment::class ); } - - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php index e56861f19..e912341eb 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php @@ -9,6 +9,8 @@ class Payment extends BaseResource { + use HasPresetOptions; + /** * Id of the payment (on the Mollie platform). * @@ -144,6 +146,7 @@ class Payment extends BaseResource * * @example "user@mollie.com" * @var string|null + * @deprecated 2024-06-01 The billingEmail field is deprecated. Use the "billingAddress" field instead. */ public $billingEmail; @@ -207,6 +210,27 @@ class Payment extends BaseResource */ public $orderId; + /** + * The lines contain the actual items the customer bought. + * + * @var array|object[]|null + */ + public $lines; + + /** + * The person and the address the order is billed to. + * + * @var \stdClass|null + */ + public $billingAddress; + + /** + * The person and the address the order is shipped to. + * + * @var \stdClass|null + */ + public $shippingAddress; + /** * The settlement ID this payment belongs to. * @@ -729,37 +753,14 @@ public function update() "dueDate" => $this->dueDate, ]; - $result = $this->client->payments->update($this->id, $body); + $result = $this->client->payments->update( + $this->id, + $this->withPresetOptions($body) + ); return ResourceFactory::createFromApiResult($result, new Payment($this->client)); } - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } - /** * The total amount that is already captured for this payment. Only available * when this payment supports captures. diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php b/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php index cd64ed7d9..bef2c9e30 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php @@ -124,4 +124,81 @@ public function getCheckoutUrl() return $this->_links->paymentLink->href; } + + /** + * Persist the current local Payment Link state to the Mollie API. + * + * @return mixed|\Mollie\Api\Resources\BaseResource + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update() + { + $body = $this->withPresetOptions([ + 'description' => $this->description, + 'archived' => $this->archived, + ]); + + $result = $this->client->paymentLinks->update($this->id, $body); + + return ResourceFactory::createFromApiResult($result, new PaymentLink($this->client)); + } + + /** + * Archive this Payment Link. + * + * @return \Mollie\Api\Resources\PaymentLink + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function archive() + { + $data = $this->withPresetOptions([ + 'archived' => true, + ]); + + return $this->client->paymentLinks->update($this->id, $data); + } + + /** + * Retrieve a paginated list of payments associated with this payment link. + * + * @param string|null $from + * @param int|null $limit + * @param array $filters + * @return mixed|\Mollie\Api\Resources\BaseCollection + */ + public function payments(string $from = null, int $limit = null, array $filters = []) + { + return $this->client->paymentLinkPayments->pageFor( + $this, + $from, + $limit, + $this->withPresetOptions($filters) + ); + } + + /** + * When accessed by oAuth we want to pass the testmode by default + * + * @return array + */ + private function getPresetOptions() + { + $options = []; + if ($this->client->usesOAuth()) { + $options["testmode"] = $this->mode === "test"; + } + + return $options; + } + + /** + * Apply the preset options. + * + * @param array $options + * @return array + */ + private function withPresetOptions(array $options) + { + return array_merge($this->getPresetOptions(), $options); + } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php new file mode 100644 index 000000000..842559b73 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php @@ -0,0 +1,178 @@ +status === SessionStatus::STATUS_CREATED; + } + + public function isReadyForProcessing() + { + return $this->status === SessionStatus::STATUS_READY_FOR_PROCESSING; + } + + public function isCompleted() + { + return $this->status === SessionStatus::STATUS_COMPLETED; + } + + public function hasFailed() + { + return $this->status === SessionStatus::STATUS_FAILED; + } + + /** + * Saves the session's updatable properties. + * + * @return \Mollie\Api\Resources\Session + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update() + { + $body = [ + 'billingAddress' => $this->billingAddress, + 'shippingAddress' => $this->shippingAddress, + ]; + + $result = $this->client->sessions->update($this->id, $this->withPresetOptions($body)); + + return ResourceFactory::createFromApiResult($result, new Session($this->client)); + } + + /** + * Cancels this session. + * + * @return Session + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function cancel() + { + return $this->client->sessions->cancel($this->id, $this->getPresetOptions()); + } + + /** + * @return string|null + */ + public function getRedirectUrl() + { + if (empty($this->_links->redirect)) { + return null; + } + + return $this->_links->redirect->href; + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php b/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php new file mode 100644 index 000000000..b1414c36b --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php @@ -0,0 +1,22 @@ +client); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php b/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php index 512804288..3dba80fa2 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php +++ b/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php @@ -14,6 +14,11 @@ class PaymentMethod */ public const APPLEPAY = "applepay"; + /** + * @link https://www.mollie.com/en/payments/bacs + */ + public const BACS = "bacs"; + /** * @link https://www.mollie.com/en/payments/bancomatpay */ @@ -70,10 +75,16 @@ class PaymentMethod public const GIFTCARD = "giftcard"; /** + * @deprecated * @link https://www.mollie.com/en/payments/giropay */ public const GIROPAY = "giropay"; + /** + * @link https://www.mollie.com/en/payments/in3 + */ + public const IN3 = "in3"; + /** * @link https://www.mollie.com/en/payments/ideal */ @@ -116,6 +127,11 @@ class PaymentMethod */ public const MYBANK = "mybank"; + /** + * @link https://www.mollie.com/en/payments/payconiq + */ + public const PAYCONIQ = "payconiq"; + /** * @link https://www.mollie.com/en/payments/paypal */ @@ -127,9 +143,9 @@ class PaymentMethod public const PAYSAFECARD = "paysafecard"; /** - * @link https://www.mollie.com/en/payments/przelewy24 + * @link https://www.mollie.com/en/payments/pay-by-bank */ - public const PRZELEWY24 = 'przelewy24'; + public const PAYBYBANK = "paybybank"; /** * @deprecated @@ -137,20 +153,35 @@ class PaymentMethod */ public const PODIUMCADEAUKAART = "podiumcadeaukaart"; + /** + * @link https://docs.mollie.com/point-of-sale/overview + */ + public const POINT_OF_SALE = "pointofsale"; + + /** + * @link https://www.mollie.com/en/payments/przelewy24 + */ + public const PRZELEWY24 = 'przelewy24'; + + /** + * @link https://www.mollie.com/en/payments/satispay + */ + public const SATISPAY = "satispay"; + /** * @link https://www.mollie.com/en/payments/sofort */ public const SOFORT = "sofort"; /** - * @link https://www.mollie.com/en/payments/in3 + * @link https://www.mollie.com/en/payments/riverty */ - public const IN3 = "in3"; + public const RIVERTY = "riverty"; /** - * @link https://docs.mollie.com/point-of-sale/overview + * @link https://www.mollie.com/en/payments/trustly */ - public const POINT_OF_SALE = "pointofsale"; + public const TRUSTLY = "trustly"; /** * @link https://www.mollie.com/en/payments/twint diff --git a/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php b/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php new file mode 100644 index 000000000..8d19b7798 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php @@ -0,0 +1,26 @@ +