diff --git a/Block/Customer/DirectDebitRenderer.php b/Block/Customer/DirectDebitRenderer.php new file mode 100644 index 0000000..d06aacd --- /dev/null +++ b/Block/Customer/DirectDebitRenderer.php @@ -0,0 +1,84 @@ +config = $config; + } + + /** + * Can render specified token + */ + public function canRender(PaymentTokenInterface $token): bool + { + return $token->getPaymentMethodCode() === Config::METHOD_DIRECT_DEBIT; + } + + /** + * Masked IBAN (e.g. DE************1234) + */ + public function getMaskedIban(): string + { + return $this->getTokenDetails()['maskedIban'] ?? (string)__('Unknown IBAN'); + } + + /** + * Account holder name + */ + public function getAccountHolder(): string + { + return $this->getTokenDetails()['accountHolder'] ?? (string)__('Unknown Holder'); + } + + /** + * Icon URL for SEPA (if you expose one in config). + */ + public function getIconUrl() + { + return ''; + } + + /** + * Icon height + */ + public function getIconHeight() + { + return ''; + } + + /** + * Icon width + */ + public function getIconWidth() + { + return ''; + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5a8d3..422b08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.0.1](https://github.com/unzerdev/magento2/compare/4.0.0..4.0.1) +### Changed +* Invoice B2B component on checkout page +### Added +* Support for Wero payment method +* Support for Unzer Direct Debit recurring payments + ## [4.0.0](https://github.com/unzerdev/magento2/compare/3.2.9..4.0.0) ### Changed * Migration from Unzer UI Component V1 to Unzer UI Component V2 diff --git a/Controller/Adminhtml/Order/Invoice/Cancel.php b/Controller/Adminhtml/Order/Invoice/Cancel.php new file mode 100644 index 0000000..a3d80c8 --- /dev/null +++ b/Controller/Adminhtml/Order/Invoice/Cancel.php @@ -0,0 +1,40 @@ +getInvoice(); + if (!$invoice) { + $resultForward = $this->resultForwardFactory->create(); + return $resultForward->forward('noroute'); + } + + try { + $invoice->cancel(); + $invoice->getOrder()->setIsInProcess(true); + $this->_objectManager->create( + \Magento\Framework\DB\Transaction::class + )->addObject( + $invoice + )->addObject( + $invoice->getOrder() + )->save(); + $this->messageManager->addSuccessMessage(__('You canceled the invoice.')); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage(__('Invoice canceling error')); + } + + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('sales/*/view', ['invoice_id' => $invoice->getId()]); + return $resultRedirect; + } +} diff --git a/Helper/Order.php b/Helper/Order.php index 19f03de..7fae7a4 100644 --- a/Helper/Order.php +++ b/Helper/Order.php @@ -346,7 +346,7 @@ public function createCustomerFromQuote(Quote $quote, string $email, bool $creat $customer->setPhone($billingAddress->getTelephone()); $customer->setBirthDate($quote->getCustomer()->getDob()); - $customerId = (string) $quote->getCustomerId() . '_' . $email; + $customerId = (string) $quote->getCustomerId(); if(!$quote->getCustomerIsGuest()) { $customer->setCustomerId($customerId); @@ -431,7 +431,7 @@ public function createCustomerFromOrder( } $customer->setEmail($email); - $customerId = (string) $order->getCustomerId() . '_' . $email; + $customerId = (string) $order->getCustomerId(); if(!$order->getCustomerIsGuest()) { $customer->setCustomerId($customerId); diff --git a/Helper/Payment.php b/Helper/Payment.php index 6a59a2c..ff1550a 100644 --- a/Helper/Payment.php +++ b/Helper/Payment.php @@ -39,6 +39,7 @@ class Payment { public const STATUS_READY_TO_CAPTURE = 'unzer_ready_to_capture'; + private const METHOD_PREPAYMENT = 'unzer_prepayment'; /** * @var InvoiceRepositoryInterface @@ -361,7 +362,13 @@ private function processPartlyState(OrderInterface $order, PaymentResource $paym $this->transactionSynchronizer->applyCancellationOnMagento($order, $payment); $this->transactionSynchronizer->applyCaptureOnMagento($order, $payment); - $this->setOrderState($order, Order::STATE_PROCESSING); + if($order->getPayment()->getMethod() === self::METHOD_PREPAYMENT) { + $this->setOrderState($order, Order::STATE_PENDING_PAYMENT); + + return; + } + + $this->setOrderState($order); } /** diff --git a/Model/Command/TransactionSynchronizer.php b/Model/Command/TransactionSynchronizer.php index d67893d..7c61335 100644 --- a/Model/Command/TransactionSynchronizer.php +++ b/Model/Command/TransactionSynchronizer.php @@ -2,7 +2,10 @@ namespace Unzer\PAPI\Model\Command; +use Exception; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderPaymentRepositoryInterface; use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\Order\Payment as OrderPayment; @@ -64,6 +67,7 @@ public function applyCaptureOnMagento(OrderInterface $order, UnzerPayment $unzer $payment->setTransactionId($captureId); if ($capture->isSuccess()) { + $payment->setIsTransactionPending($unzer->isPartlyPaid()); $payment->registerCaptureNotification($capture->getAmount(), true); $this->paymentRepository->save($payment); } @@ -142,6 +146,8 @@ public function applyCancellationOnMagento(OrderInterface $order, UnzerPayment $ * @param UnzerPayment $unzer * * @return void + * @throws LocalizedException + * @throws Exception */ public function applyChargebackOnMagento(OrderInterface $order, UnzerPayment $unzer): void { @@ -158,20 +164,28 @@ public function applyChargebackOnMagento(OrderInterface $order, UnzerPayment $un return; } - if ($this->hasTransaction($payment, $order, $chargebackId)) { - return; - } - $parent = $chargeback->getParentResource(); $parentTxnId = $parent->getId(); $chargebackTxnId = $parentTxnId . '-' . $chargebackId; + if ($this->hasTransaction($payment, $order, $chargebackTxnId)) { + return; + } + $payment->setParentTransactionId($parentTxnId); $payment->setTransactionId($chargebackTxnId); $payment->registerRefundNotification($chargeback->getAmount()); + $transaction = $payment->addTransaction(TransactionInterface::TYPE_REFUND, null, true); + + if ($transaction) { + $transaction->setTxnId($chargebackTxnId); + $transaction->setParentTxnId($parentTxnId); + $transaction->setIsClosed(true); + } + $this->paymentRepository->save($payment); } diff --git a/Model/Config.php b/Model/Config.php index a627f14..c3c29e1 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -31,10 +31,9 @@ class Config extends \Magento\Payment\Gateway\Config\Config public const METHOD_BASE = 'unzer'; public const METHOD_CARDS = 'unzer_cards'; - public const METHOD_CARDS_VAULT = 'unzer_cards_vault'; - public const METHOD_DIRECT_DEBIT = 'unzer_direct_debit'; + public const METHOD_DIRECT_DEBIT_VAULT = 'unzer_direct_debit_vault'; public const METHOD_EPS = 'unzer_eps'; public const METHOD_IDEAL = 'unzer_ideal'; public const METHOD_PAYLATER_INVOICE = 'unzer_paylater_invoice'; @@ -53,6 +52,7 @@ class Config extends \Magento\Payment\Gateway\Config\Config public const METHOD_TWINT = 'unzer_twint'; public const METHOD_OPEN_BANKING = 'unzer_open_banking'; public const METHOD_KLARNA = 'unzer_klarna'; + public const METHOD_WERO = 'unzer_wero'; /** * @var DebugHandler diff --git a/Model/Config/Provider.php b/Model/Config/Provider.php index 5396daa..3edd2ce 100644 --- a/Model/Config/Provider.php +++ b/Model/Config/Provider.php @@ -40,7 +40,8 @@ class Provider implements ConfigProviderInterface Config::METHOD_GOOGLEPAY, Config::METHOD_TWINT, Config::METHOD_OPEN_BANKING, - Config::METHOD_KLARNA + Config::METHOD_KLARNA, + Config::METHOD_WERO, ]; /** diff --git a/Model/InstantPurchase/DirectDebit/AvailabilityChecker.php b/Model/InstantPurchase/DirectDebit/AvailabilityChecker.php new file mode 100644 index 0000000..4edc7f5 --- /dev/null +++ b/Model/InstantPurchase/DirectDebit/AvailabilityChecker.php @@ -0,0 +1,41 @@ +scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function isAvailable(): bool + { + return (bool)$this->scopeConfig->getValue( + self::CONFIG_INSTANT_PURCHASE_ACTIVE + ); + } +} diff --git a/Model/InstantPurchase/DirectDebit/TokenFormatter.php b/Model/InstantPurchase/DirectDebit/TokenFormatter.php new file mode 100644 index 0000000..2677d2b --- /dev/null +++ b/Model/InstantPurchase/DirectDebit/TokenFormatter.php @@ -0,0 +1,35 @@ +getTokenDetails() ?: '{}', true, 512, JSON_THROW_ON_ERROR); + if (!isset($details['maskedIban'], $details['accountHolder'])) { + throw new InvalidArgumentException('Invalid Unzer SEPA Direct Debit token details.'); + } + + return sprintf( + '%s: %s (%s)', + __('SEPA Direct Debit'), + $details['maskedIban'], + $details['accountHolder'] + ); + } +} diff --git a/Model/Method/DirectDebit.php b/Model/Method/DirectDebit.php index 981be89..c766eec 100644 --- a/Model/Method/DirectDebit.php +++ b/Model/Method/DirectDebit.php @@ -6,13 +6,12 @@ /** * Direct debit payment method * - * @deprecated use paylater direct debit - * * @link https://docs.unzer.com/ */ class DirectDebit extends Base { public const CONFIG_PATH_STORE_NAME = 'general/store_information/name'; + public const VAULT_CODE = 'unzer_direct_debit_vault'; /** * @inheritDoc @@ -20,6 +19,7 @@ class DirectDebit extends Base public function getFrontendConfig(): array { $parentConfig = parent::getFrontendConfig(); + $parentConfig['vault_code'] = $this->getVaultCode(); $merchantName = $this->getConfigData('merchant_name'); @@ -31,4 +31,9 @@ public function getFrontendConfig(): array return $parentConfig; } + + public function getVaultCode(): ?string + { + return self::VAULT_CODE; + } } diff --git a/Model/Method/Wero.php b/Model/Method/Wero.php new file mode 100644 index 0000000..d3aabf9 --- /dev/null +++ b/Model/Method/Wero.php @@ -0,0 +1,19 @@ +paymentTokenFactory = $paymentTokenFactory; + $this->paymentExtensionFactory = $paymentExtensionFactory; + $this->serializer = $serializer; + $this->dateTimeFactory = $dateTimeFactory; + $this->paymentTokenResourceModel = $paymentTokenResourceModel; + } + + /** + * Handle tokens + * + * @param PaymentDataObject $payment + * @param AbstractTransactionType $transaction + * + * @return void + * + * @throws UnzerApiException + * @throws Exception + */ + public function handle(PaymentDataObject $payment, AbstractTransactionType $transaction): void + { + $isSaveToVaultActive = $payment->getPayment()->getAdditionalInformation( + VaultConfigProvider::IS_ACTIVE_CODE + ); + if ($isSaveToVaultActive === false) { + return; + } + + $isInstantPurchase = $payment->getPayment()->getAdditionalInformation( + PaymentConfiguration::MARKER + ); + if ($isInstantPurchase === true) { + return; + } + + $paymentToken = $this->createVaultPaymentToken($transaction, $payment); + if ($paymentToken !== null) { + $extensionAttributes = $this->getExtensionAttributes($payment->getPayment()); + $extensionAttributes->setVaultPaymentToken($paymentToken); + } + } + + /** + * Create vault payment token for SEPA + * + * @param AbstractTransactionType $transaction + * @param PaymentDataObject $payment + * + * @return PaymentTokenInterface|null + * + * @throws Exception + */ + private function createVaultPaymentToken( + AbstractTransactionType $transaction, + PaymentDataObject $payment + ): ?PaymentTokenInterface { + $paymentType = $transaction->getPayment()->getPaymentType(); + + if (!$paymentType instanceof SepaDirectDebit) { + return null; + } + + $token = $paymentType->getId(); + if (empty($token)) { + return null; + } + $iban = preg_replace('/\s+/', '', (string)$paymentType->getIban()); + $holder = (string)$paymentType->getHolder(); + + if ($iban === '' || $holder === '') { + return null; + } + $maskedIban = $this->maskIban($iban); + + $tokenData = $this->paymentTokenResourceModel->getByOrderPaymentId($payment->getPayment()->getId()); + + $paymentToken = null; + if (empty($tokenData)) { + $paymentToken = $this->paymentTokenFactory->create(PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT); + + $paymentToken->setGatewayToken($token); + $paymentToken->setExpiresAt($this->getExpirationDate()); + $paymentToken->setTokenDetails($this->convertDetailsToJSON([ + 'gatewayToken' => $token, + 'maskedIban' => $maskedIban, + 'accountHolder' => $holder + ])); + } + + return $paymentToken; + } + + /** + * @param array $details + * + * @return string + */ + private function convertDetailsToJSON(array $details): string + { + $json = $this->serializer->serialize($details); + return $json ?: '{}'; + } + + /** + * @param OrderPaymentInterface $payment + * + * @return OrderPaymentExtensionInterface + */ + private function getExtensionAttributes(OrderPaymentInterface $payment): OrderPaymentExtensionInterface + { + $extensionAttributes = $payment->getExtensionAttributes() + ?: $this->paymentExtensionFactory->create(); + + $payment->setExtensionAttributes($extensionAttributes); + return $extensionAttributes; + } + + /** + * @return string + */ + private function getExpirationDate(): string + { + $expDate = $this->dateTimeFactory->create('now', new DateTimeZone('UTC')); + $expDate->add(new DateInterval('P1Y')); + return $expDate->format('Y-m-d 00:00:00'); + } + + /** + * @param string $iban + * + * @return string + */ + private function maskIban(string $iban): string + { + $len = strlen($iban); + if ($len <= 6) { + return $iban; + } + $head = substr($iban, 0, 2); + $tail = substr($iban, -4); + return $head . str_repeat('*', max(0, $len - 6)) . $tail; + } +} diff --git a/Model/Vault/Type/DirectDebitTokenUiComponentProvider.php b/Model/Vault/Type/DirectDebitTokenUiComponentProvider.php new file mode 100644 index 0000000..9514873 --- /dev/null +++ b/Model/Vault/Type/DirectDebitTokenUiComponentProvider.php @@ -0,0 +1,54 @@ +componentFactory = $componentFactory; + } + + /** + * Get UI component for token + * + * @param PaymentTokenInterface $paymentToken + * @return TokenUiComponentInterface + * @throws \JsonException + */ + public function getComponentForToken(PaymentTokenInterface $paymentToken): TokenUiComponentInterface + { + $jsonDetails = json_decode($paymentToken->getTokenDetails() ?: '{}', true, 512, JSON_THROW_ON_ERROR); + return $this->componentFactory->create( + [ + 'config' => [ + 'code' => Config::METHOD_DIRECT_DEBIT_VAULT, + TokenUiComponentProviderInterface::COMPONENT_DETAILS => $jsonDetails, + TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash() + ], + 'name' => 'Unzer_PAPI/js/view/payment/method-renderer/direct_debit_vault', + ] + ); + } +} diff --git a/composer.json b/composer.json index 2c4a1fc..7ec6365 100644 --- a/composer.json +++ b/composer.json @@ -2,11 +2,11 @@ "name": "unzerdev/magento2", "description": "This extension for Magento 2 provides a direct integration of the Unzer payment types to your Magento 2 shop via the Unzer Payment API (PAPI).", "type": "magento2-module", - "version": "4.0.0", + "version": "4.0.1", "license": "Apache-2.0", "require": { "php": "~7.4.0|~8.1.0|~8.2.0|~8.3.0|~8.4.0", - "unzerdev/php-sdk": "^3.13.0", + "unzerdev/php-sdk": "^3.13.1", "ext-json": "*", "magento/framework": "*", "magento/module-backend": "*", @@ -42,5 +42,16 @@ "payment", "php", "payment-gateway" - ] + ], + "repositories": { + "magento": { + "type": "composer", + "url": "https://repo.magento.com" + } + }, + "config": { + "allow-plugins": { + "magento/composer-dependency-version-audit-plugin": true + } + } } diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml index da0f5b9..294c5f3 100644 --- a/etc/adminhtml/routes.xml +++ b/etc/adminhtml/routes.xml @@ -5,5 +5,8 @@ + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index d325bdf..c3fe4a7 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -67,6 +67,7 @@ + diff --git a/etc/adminhtml/system/direct_debit.xml b/etc/adminhtml/system/direct_debit.xml index 49d5ea8..7d4f38c 100644 --- a/etc/adminhtml/system/direct_debit.xml +++ b/etc/adminhtml/system/direct_debit.xml @@ -40,5 +40,22 @@ Sort Order payment/unzer_direct_debit/sort_order + + + Enable Vault for SEPA Payments + Magento\Config\Model\Config\Source\Yesno + payment/unzer_direct_debit_vault/active + Save IBAN and account holder for registered customers + + + Enable Instant Purchase + Magento\Config\Model\Config\Source\Yesno + payment/unzer_direct_debit_vault/instant_purchase_active + + 1 + + diff --git a/etc/adminhtml/system/wero.xml b/etc/adminhtml/system/wero.xml new file mode 100644 index 0000000..278ebb0 --- /dev/null +++ b/etc/adminhtml/system/wero.xml @@ -0,0 +1,34 @@ + + + + UNZER_WERO + + Enabled + Magento\Config\Model\Config\Source\Yesno + payment/unzer_wero/active + + + Title + payment/unzer_wero/title + + + Booking Mode + payment/unzer_wero/order_payment_action + Unzer\PAPI\Model\System\Config\Source\PaymentAction + + + Minimum Order Total + payment/unzer_wero/min_order_total + + + Maximum Order Total + payment/unzer_wero/max_order_total + Insert 0 to disable limit. + + + Sort Order + payment/unzer_wero/sort_order + + + diff --git a/etc/config.xml b/etc/config.xml index 2a97877..03240f7 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -48,13 +48,27 @@ 0 0 0 + 0 1 + 1 0 1 0 + 1 EUR Unzer\PAPI\Model\Method\DirectDebit + + 0 + 0 + + Unzer\PAPI\Model\InstantPurchase\DirectDebit\AvailabilityChecker + Unzer\PAPI\Model\InstantPurchase\DirectDebit\TokenFormatter + + UnzerDirectDebitVault + + EUR + 0 order @@ -376,6 +390,25 @@ 0 Unzer\PAPI\Model\Method\Klarna + + 0 + order + authorize + + 0 + 0 + 0 + EUR + DE + 1 + 1 + 1 + 1 + 1 + 1 + 0 + Unzer\PAPI\Model\Method\Wero + diff --git a/etc/di.xml b/etc/di.xml index 3a156cf..df3778e 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -6,6 +6,7 @@ + @@ -83,6 +84,7 @@ UnzerCardsCommandManager UnzerPaypalCommandManager + UnzerDirectDebitCommandManager @@ -95,6 +97,7 @@ Unzer\PAPI\Model\Vault\Handlers\PaypalVaultDetailsHandler + Unzer\PAPI\Model\Vault\Handlers\DirectDebitVaultDetailsHandler @@ -296,6 +299,44 @@ + + + + Unzer\PAPI\Model\Config::METHOD_DIRECT_DEBIT_VAULT + + + + + + UnzerDirectDebitVaultPaymentConfig + + + + + + + UnzerDirectDebitVaultPaymentValueHandler + + + + + + + UnzerDirectDebitVaultPaymentConfig + UnzerDirectDebitVaultPaymentValueHandlerPool + Unzer\PAPI\Model\Method\DirectDebit + Unzer\PAPI\Model\Config::METHOD_DIRECT_DEBIT_VAULT + UnzerVaultCommandManagerPool + + + + + + + UnzerAuthorizeAndCaptureVaultCommandPool + + + @@ -1335,4 +1376,54 @@ UnzerAuthorizeAndCaptureKlarnaCommandPool + + + + + Unzer\PAPI\Model\Config::METHOD_WERO + + + + + + UnzerWeroConfig + + + + + + + UnzerWeroConfigValueHandler + Unzer\PAPI\Model\Config\CanCancelHandler + Unzer\PAPI\Model\Config\CanRefundHandler + Unzer\PAPI\Model\Config\CanRefundHandler + Unzer\PAPI\Model\Config\CanVoidHandler + + + + + + + UnzerWeroConfig + + + + + + + UnzerWeroValidatorCountry + + + + + + + Unzer\PAPI\Model\Config::METHOD_WERO + Magento\Payment\Block\Form + Magento\Payment\Block\Info + UnzerWeroValueHandlerPool + UnzerWeroValidatorPool + UnzerAuthorizeAndCaptureCommandPool + + diff --git a/etc/events.xml b/etc/events.xml index 1454606..0314925 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -57,4 +57,7 @@ + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index ab3f252..2012ce8 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -17,6 +17,7 @@ Unzer\PAPI\Model\Vault\Type\CardsTokenUiComponentProvider Unzer\PAPI\Model\Vault\Type\PaypalTokenUiComponentProvider + Unzer\PAPI\Model\Vault\Type\DirectDebitTokenUiComponentProvider diff --git a/etc/module.xml b/etc/module.xml index d3dce32..32eb361 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + diff --git a/i18n/de_DE.csv b/i18n/de_DE.csv index ad0180c..c4e4b58 100644 --- a/i18n/de_DE.csv +++ b/i18n/de_DE.csv @@ -21,6 +21,7 @@ "UNZER_TWINT","TWINT" "UNZER_OPEN_BANKING","Unzer Direktüberweisung" "UNZER_KLARNA","Klarna" +"UNZER_WERO","Wero" "MODULE_VERSION_LABEL","Modul-Version:" "ABOUT_UNZER_LABEL","Über Unzer:" @@ -86,10 +87,3 @@ "There will be no notification if the payment has been aborted.","Sollte die Zahlung abgebrochen worden sein, erfolgt keine weitere Mitteilung." "Please contact us for further information.","Bitte kontaktieren Sie uns für weitere Informationen." "Unknown Email Address","Unbekannte Email Adresse" - -"Company type","Art des Unternehmens" -"Other","Sonstige" -"Authority","Behörde" -"Association","Gesellschaft" -"Sole","Einzelunternehmen", -"Company","Firma" diff --git a/i18n/en_US.csv b/i18n/en_US.csv index 30b4d4e..9ec9d04 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -21,6 +21,7 @@ "UNZER_TWINT","TWINT" "UNZER_OPEN_BANKING","Direct Bank Transfer" "UNZER_KLARNA","Klarna" +"UNZER_WERO","Wero" "MODULE_VERSION_LABEL","Module version:" "ABOUT_UNZER_LABEL","About Unzer:" @@ -86,10 +87,3 @@ "There will be no notification if the payment has been aborted. Please contact us for further information.","There will be no notification if the payment has been aborted." "Please contact us for further information.","Please contact us for further information." "Unknown Email Address","Unknown Email Address" - -"Company type","Company type" -"Other","Other" -"Authority","Authority" -"Association","Association" -"Sole","Sole", -"Company","Company" diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index 0bbb05b..e4a142f 100644 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -32,6 +32,9 @@ true + + true + true @@ -86,6 +89,9 @@ true + + true + diff --git a/view/frontend/layout/vault_cards_listaction.xml b/view/frontend/layout/vault_cards_listaction.xml index 89cd3f5..7de72d8 100644 --- a/view/frontend/layout/vault_cards_listaction.xml +++ b/view/frontend/layout/vault_cards_listaction.xml @@ -1,11 +1,24 @@ - + - + - + + Unzer_PAPI::token_list.phtml + + + + + diff --git a/view/frontend/templates/customer_account/direct_debit.phtml b/view/frontend/templates/customer_account/direct_debit.phtml new file mode 100644 index 0000000..402c5a7 --- /dev/null +++ b/view/frontend/templates/customer_account/direct_debit.phtml @@ -0,0 +1,52 @@ +escapeHtml($block->getMaskedIban()); +$holder = (string)$block->escapeHtml($block->getAccountHolder()); +?> + + + + = /* @noEscape */ + $maskedIban ?> + + + = /* @noEscape */ + $holder ?> + + + = $block->escapeHtml(__('SEPA Direct Debit')) ?> + + + + = $block->getBlockHtml('formkey') ?> + + ", + "content": "= $block->escapeHtmlAttr(__('Are you sure you want to delete this SEPA mandate: %1 (%2)?', + $maskedIban, $holder)) ?>" + } + }'> + = $block->escapeHtml(__('Delete')) ?> + + + + diff --git a/view/frontend/templates/customer_account/paypal.phtml b/view/frontend/templates/customer_account/paypal.phtml index 332e375..d5c989b 100644 --- a/view/frontend/templates/customer_account/paypal.phtml +++ b/view/frontend/templates/customer_account/paypal.phtml @@ -1,33 +1,48 @@ escapeHtml($block->getPayerEmail()); ?> - - -escapeHtmlAttr($block->getIconUrl()) ?> -escapeHtmlAttr($block->getIconWidth()) ?> -escapeHtmlAttr($block->getIconHeight()) ?> -escapeHtmlAttr(__('PayPal Logo')) ?> - = $block->escapeHtml($block->getPayerEmail()) ?> + + + + = /* @noEscape */ + $payerEmail ?> + + + + + + = $block->escapeHtml(__('PayPal')) ?> - + + = $block->getBlockHtml('formkey') ?> - + ", - "content": "Are you sure you want to delete this PayPal account: = $block->escapeHtmlAttr($block->getPayerEmail()) ?>?" + "content": "= $block->escapeHtmlAttr(__('Are you sure you want to delete this PayPal account: %1?', + $payerEmail)) ?>" } }'> = $block->escapeHtml(__('Delete')) ?> diff --git a/view/frontend/templates/token_list.phtml b/view/frontend/templates/token_list.phtml new file mode 100644 index 0000000..e9aaa18 --- /dev/null +++ b/view/frontend/templates/token_list.phtml @@ -0,0 +1,46 @@ +getPaymentTokens(); +if (empty($tokens)) { + return; +} + +$groups = [ + 'paypal' => [], + 'direct_debit' => [], +]; + +foreach ($tokens as $token) { + $code = $token->getPaymentMethodCode(); + + if (strpos($code, 'paypal')) { + $groups['paypal'][] = $token; + } + + if (strpos($code, 'direct_debit')) { + $groups['direct_debit'][] = $token; + } +} + +$titles = [ + 'paypal' => __('PayPal Account'), + 'direct_debit' => __('SEPA Direct Debit'), +]; +?> + + + + + + + + = $block->escapeHtml($titles[$groupKey]) ?> + + + = /* @noEscape */ $block->renderTokenHtml($token) ?> + + + + + + diff --git a/view/frontend/web/css/unzer.css b/view/frontend/web/css/unzer.css index 86a10e6..0f048a5 100644 --- a/view/frontend/web/css/unzer.css +++ b/view/frontend/web/css/unzer.css @@ -5335,11 +5335,7 @@ i.h-iconideal-van-lanschot:before { margin: 12px; } -.unzerCompanyTypeSelect { - margin-bottom: 15px; -} - .apple-pay-button { width: 240px; height: 40px; -} \ No newline at end of file +} diff --git a/view/frontend/web/js/view/payment/method-renderer.js b/view/frontend/web/js/view/payment/method-renderer.js index 4986f49..646129f 100644 --- a/view/frontend/web/js/view/payment/method-renderer.js +++ b/view/frontend/web/js/view/payment/method-renderer.js @@ -84,6 +84,10 @@ define( { type: 'unzer_klarna', component: 'Unzer_PAPI/js/view/payment/method-renderer/klarna' + }, + { + type: 'unzer_wero', + component: 'Unzer_PAPI/js/view/payment/method-renderer/wero' } ); return Component.extend({}); diff --git a/view/frontend/web/js/view/payment/method-renderer/basev2.js b/view/frontend/web/js/view/payment/method-renderer/basev2.js index bc37c4c..d14a2db 100644 --- a/view/frontend/web/js/view/payment/method-renderer/basev2.js +++ b/view/frontend/web/js/view/payment/method-renderer/basev2.js @@ -36,7 +36,7 @@ define( isThreatMetrixNeeded: false, buttonNeeded: true, paymentCode: null, - customersBirthDayNeeded: false, + customerNeeded: false, customerType: null, threatMetrixId: null, lastGrandTotal: null, @@ -107,7 +107,7 @@ define( componentContainer.append(unzerPayment); componentContainer.append(unzerCheckout); - if (this.customersBirthDayNeeded) { + if (this.customerNeeded) { this.waitForSetBasketData(); } @@ -275,7 +275,13 @@ define( zip: shipping.postcode, city: shipping.city, country: shipping.countryId - } : {} + } : {}, + ...(billing?.company && billing.company.trim() !== '' + ? { company: billing.company.trim() } + : {}), + customerSettings: { + type: billing?.company && billing.company.trim() !== '' ? 'B2B' : 'B2C' + } }; unzerPayment.setCustomerData(customer); @@ -298,11 +304,11 @@ define( const unzerCheckout = document.getElementById(unzerCheckoutElementId); unzerCheckout.onPaymentSubmit = response => { if (response.submitResponse && response.submitResponse.success) { - this.resourceId = response.submitResponse.data.id; - if (this.customersBirthDayNeeded) { - this.customersBirthDay = document.querySelector(this.paymentCode).shadowRoot?.querySelector('uds-input-date[name="birthDate"]').value; + if(response.customerResponse && response.customerResponse.success) { + this.customer = response.customerResponse.data.id; } + this.resourceId = response.submitResponse.data.id; if (response.threatMetrixId) { this.threatMetrixId = response.threatMetrixId; @@ -344,7 +350,15 @@ define( message: error }); }); - } + }, + + isVaultEnabled: function () { + return this.vaultEnabler.isVaultEnabled(); + }, + + getVaultCode: function () { + return window.checkoutConfig.payment[this.getCode()].vault_code; + }, }); } ); diff --git a/view/frontend/web/js/view/payment/method-renderer/cards.js b/view/frontend/web/js/view/payment/method-renderer/cards.js index 4de6a6b..5412bb9 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cards.js +++ b/view/frontend/web/js/view/payment/method-renderer/cards.js @@ -76,22 +76,6 @@ define( return retVal; }, - /** - * @returns {Boolean} - */ - isVaultEnabled: function () { - return this.vaultEnabler.isVaultEnabled(); - }, - - /** - * Returns vault code. - * - * @returns {String} - */ - getVaultCode: function () { - return window.checkoutConfig.payment[this.getCode()].vault_code; - }, - getData: function () { const data = this._super(); diff --git a/view/frontend/web/js/view/payment/method-renderer/direct_debit.js b/view/frontend/web/js/view/payment/method-renderer/direct_debit.js index 6aff3d5..adb47e8 100644 --- a/view/frontend/web/js/view/payment/method-renderer/direct_debit.js +++ b/view/frontend/web/js/view/payment/method-renderer/direct_debit.js @@ -1,16 +1,67 @@ define( [ - 'Unzer_PAPI/js/view/payment/method-renderer/basev2' + 'jquery', + 'mage/translate', + 'Magento_Checkout/js/action/place-order', + 'Magento_Ui/js/model/messageList', + 'Unzer_PAPI/js/view/payment/method-renderer/basev2', + 'Magento_Vault/js/view/payment/vault-enabler', ], function ( - Component + $, + $t, + placeOrderAction, + globalMessageList, + Component, + VaultEnabler ) { 'use strict'; return Component.extend({ defaults: { + isActivePaymentTokenEnabler: false, template: 'Unzer_PAPI/payment/direct_debit', - paymentCode: 'unzer-sepa-direct-debit', + paymentCode: 'unzer-sepa-direct-debit' + }, + + initialize: function () { + this._super(); + + this.vaultEnabler = new VaultEnabler(); + this.vaultEnabler.isActivePaymentTokenEnabler(this.isActivePaymentTokenEnabler); + this.vaultEnabler.setPaymentCode(this.getVaultCode()); + + return this; + }, + + selectPaymentMethod: function () { + const retVal = this._super(); + + // Vault checkbox + if (this.isVaultEnabled()) { + const checkbox = document.getElementById('unzer-card-save-sepa-checkbox'); + const checkboxLabel = document.getElementById('unzer-card-save-sepa-typography'); + + if (checkbox && checkboxLabel) { + checkbox.removeAttribute('hidden'); + checkbox.checked = !!this.isActivePaymentTokenEnabler; + + checkbox.addEventListener('click', () => { + this.isActivePaymentTokenEnabler = !this.isActivePaymentTokenEnabler; + this.vaultEnabler.isActivePaymentTokenEnabler(this.isActivePaymentTokenEnabler); + }); + + checkboxLabel.textContent = $t('Save for later use.'); + } + } + + return retVal; + }, + + getData: function () { + const data = this._super(); + this.vaultEnabler.visitAdditionalData(data); + return data; } }); } diff --git a/view/frontend/web/js/view/payment/method-renderer/direct_debit_vault.js b/view/frontend/web/js/view/payment/method-renderer/direct_debit_vault.js new file mode 100644 index 0000000..f9f03e5 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/direct_debit_vault.js @@ -0,0 +1,32 @@ +define([ + 'Unzer_PAPI/js/view/payment/method-renderer/base_vault', +], function (VaultComponent) { + 'use strict'; + + return VaultComponent.extend({ + defaults: { + template: 'Unzer_PAPI/payment/direct_debit_vault', + }, + + /** + * @returns {String} + */ + getMaskedIban: function () { + return this.details.maskedIban; + }, + + /** + * @returns {String} + */ + getAccountHolder: function () { + return this.details.accountHolder; + }, + + /** + * @returns {String} + */ + getPaymentIcon: function () { + return window.checkoutConfig.payment['unzer_direct_debit'].paymentIcon; + }, + }); +}); diff --git a/view/frontend/web/js/view/payment/method-renderer/paylater_direct_debit.js b/view/frontend/web/js/view/payment/method-renderer/paylater_direct_debit.js index 8e5c37b..68efb23 100644 --- a/view/frontend/web/js/view/payment/method-renderer/paylater_direct_debit.js +++ b/view/frontend/web/js/view/payment/method-renderer/paylater_direct_debit.js @@ -12,7 +12,7 @@ define( defaults: { template: 'Unzer_PAPI/payment/paylater_direct_debit', paymentCode: 'unzer-paylater-direct-debit', - customersBirthDayNeeded: true, + customerNeeded: true, } }); } diff --git a/view/frontend/web/js/view/payment/method-renderer/paylater_installment.js b/view/frontend/web/js/view/payment/method-renderer/paylater_installment.js index a8c7c77..b64eebf 100644 --- a/view/frontend/web/js/view/payment/method-renderer/paylater_installment.js +++ b/view/frontend/web/js/view/payment/method-renderer/paylater_installment.js @@ -8,7 +8,7 @@ define( return Component.extend({ isThreatMetrixNeeded: true, - customersBirthDayNeeded: true, + customerNeeded: true, defaults: { template: 'Unzer_PAPI/payment/paylater_installment', paymentCode: 'unzer-paylater-installment' diff --git a/view/frontend/web/js/view/payment/method-renderer/paylater_invoice.js b/view/frontend/web/js/view/payment/method-renderer/paylater_invoice.js index 3dc7e9e..26f625b 100644 --- a/view/frontend/web/js/view/payment/method-renderer/paylater_invoice.js +++ b/view/frontend/web/js/view/payment/method-renderer/paylater_invoice.js @@ -13,7 +13,7 @@ define( defaults: { template: 'Unzer_PAPI/payment/paylater_invoice', paymentCode: 'unzer-paylater-invoice', - customersBirthDayNeeded: true, + customerNeeded: true, } }); } diff --git a/view/frontend/web/js/view/payment/method-renderer/paylater_invoice_b2b.js b/view/frontend/web/js/view/payment/method-renderer/paylater_invoice_b2b.js index b54f448..193cafd 100644 --- a/view/frontend/web/js/view/payment/method-renderer/paylater_invoice_b2b.js +++ b/view/frontend/web/js/view/payment/method-renderer/paylater_invoice_b2b.js @@ -24,70 +24,8 @@ define( customerType: 'b2b', template: 'Unzer_PAPI/payment/paylater_invoice_b2b', paymentCode: 'unzer-paylater-invoice', - customersBirthDayNeeded: true, + customerNeeded: true, }, - - getPlaceOrderDeferredObject: function () { - let deferred = $.Deferred(), - self = this; - - Promise.all([ - customElements.whenDefined(this.paymentCode) - ]).then(() => { - const unzerCheckoutElementId = 'unzer-checkout-' + this.getCode(); - const unzerCheckout = document.getElementById(unzerCheckoutElementId); - - unzerCheckout.onPaymentSubmit = response => { - if (response.submitResponse && response.submitResponse.success) { - this.resourceId = response.submitResponse.data.id; - const dropdown = document.getElementById('unzer-dropdown-field'); - this.customerType = dropdown.value; - - if (this.customersBirthDayNeeded) { - this.customersBirthDay = document.querySelector(this.paymentCode).shadowRoot?.querySelector('uds-input-date[name="birthDate"]').value; - } - - if (response.threatMetrixId) { - this.threatMetrixId = response.threatMetrixId; - } - - placeOrderAction(self.getData(), self.messageContainer) - .done(function () { - deferred.resolve.apply(deferred, arguments); - }) - .fail(function (request) { - if (request.responseJSON && request.responseJSON.message) { - globalMessageList.addErrorMessage({ - message: request.responseJSON.message - }); - deferred.reject(request.responseJSON.message); - } else { - globalMessageList.addErrorMessage({ - message: 'An unknown error occurred. Please try again.' - }); - deferred.reject('An unknown error occurred.'); - } - }); - } else { - globalMessageList.addErrorMessage({ - message: 'There was an error placing your order. ' + response.submitResponse.message - }); - deferred.reject($t('There was an error placing your order. ' + response.submitResponse.message)); - } - }; - }).catch(error => { - globalMessageList.addErrorMessage({ - message: 'There was an error placing your order. ' + error - }); - deferred.reject($t('There was an error placing your order. ' + error)); - }); - - return deferred.fail(function (error) { - globalMessageList.addErrorMessage({ - message: error - }); - }); - } }); } ); diff --git a/view/frontend/web/js/view/payment/method-renderer/paypal.js b/view/frontend/web/js/view/payment/method-renderer/paypal.js index d9b57f9..00b4ca2 100644 --- a/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -56,22 +56,6 @@ define( return retVal; }, - /** - * @returns {Boolean} - */ - isVaultEnabled: function () { - return this.vaultEnabler.isVaultEnabled(); - }, - - /** - * Returns vault code. - * - * @returns {String} - */ - getVaultCode: function () { - return window.checkoutConfig.payment[this.getCode()].vault_code; - }, - getData: function () { var data = this._super(); diff --git a/view/frontend/web/js/view/payment/method-renderer/wero.js b/view/frontend/web/js/view/payment/method-renderer/wero.js new file mode 100644 index 0000000..2c4281f --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/wero.js @@ -0,0 +1,16 @@ +define( + [ + 'Unzer_PAPI/js/view/payment/method-renderer/basev2' + ], + function ( + Component + ) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Unzer_PAPI/payment/wero', + paymentCode: 'unzer-wero', + } + }); + }); diff --git a/view/frontend/web/template/payment/direct_debit.html b/view/frontend/web/template/payment/direct_debit.html index 7641688..68dde5b 100644 --- a/view/frontend/web/template/payment/direct_debit.html +++ b/view/frontend/web/template/payment/direct_debit.html @@ -17,6 +17,10 @@ + + + + diff --git a/view/frontend/web/template/payment/direct_debit_vault.html b/view/frontend/web/template/payment/direct_debit_vault.html new file mode 100644 index 0000000..867c95c --- /dev/null +++ b/view/frontend/web/template/payment/direct_debit_vault.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/web/template/payment/paylater_invoice_b2b.html b/view/frontend/web/template/payment/paylater_invoice_b2b.html index 2177b0a..b2a4f35 100644 --- a/view/frontend/web/template/payment/paylater_invoice_b2b.html +++ b/view/frontend/web/template/payment/paylater_invoice_b2b.html @@ -15,16 +15,6 @@ - - - - - - - - - - diff --git a/view/frontend/web/template/payment/wero.html b/view/frontend/web/template/payment/wero.html new file mode 100644 index 0000000..de0d199 --- /dev/null +++ b/view/frontend/web/template/payment/wero.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + +