diff --git a/meta/documents/user_guide_de.md b/meta/documents/user_guide_de.md index b34c34f..1558f63 100755 --- a/meta/documents/user_guide_de.md +++ b/meta/documents/user_guide_de.md @@ -110,8 +110,10 @@ Dieses Modul stellt einen Container zur Verfügung, der Zahlungsinformationen (z Um diese Informationen auf der Buchungsbestätigungs-Seite darzustellen folgen Sie bitte folgenden Schritten. 1. wechseln Sie im Backend auf den Menüpunkt *CMS > Container-Verknüpfungen* 2. wählen Sie im Drop-Down-Menü das Plug-in Set, für welches Sie die Änderung vornehmen möchten aus -3. klappen Sie das Menü *Invoice Details (Heidelpay)* auf und aktivieren hier den Ceres-Container ``Order confirmation: Additional payment information`` -4. klicken Sie den speichern Button +3. klappen Sie das Menü *Invoice Details (Heidelpay)* auf +4. aktivieren sie den Ceres-Container ``Order confirmation: Additional payment information`` +5. klicken Sie den speichern Button + ![Container-Verknüpfung](../images/preview_4.png) ## Beschreibung der Zahlungsabläufe @@ -133,10 +135,11 @@ Wenn die Zahlung erfolgreich ist, wird die Bestellung im Backend direkt als beza Wenn die Zahlung fehlschlägt, wird die Bestellung nicht erzeugt und der Kunde wird wieder auf die Checkout-Seite des Shops geleitet. ### Gesicherter Rechnungskauf B2C -Um die Sicherung zu aktivieren müssen Sie im hIP eine Finalisierung (FIN) ausführen.\ +* Um die Sicherung zu aktivieren müssen Sie im hIP eine Finalisierung (FIN) ausführen.\ Ab diesem Zeitpunkt startet der vertraglich festgelegte Versicherungzeitraum innerhalb dessen die Zahlung durch den Kunden erwartet wird.\ -Wenn der Kunde die Überweisung tätigt erscheint diese im hIP als Receipt (REC) und wird an die Push-URL ihres Shops gesendet.\ +* Wenn der Kunde die Überweisung tätigt erscheint diese im hIP als Receipt (REC) und wird an die Push-URL ihres Shops gesendet.\ Hier wird daraufhin eine Zahlung angelegt und mit der Buchung verknüpft. +* Die Überweisungsinformationen werden automatisch beim Erstellen auf die PDF-Rechnung gedruckt. ### Alle Zahlarten * Zahlungen im Plenty-Backend enthalten die txnId (heidelpay Bestellnummer), die shortId (die eindeutige id der Transaktion d. h. Receipt, Debit oder Capture) und den Hinweis, dass es sich um eine durch heidelpay angelegte Zahlung handelt. @@ -144,4 +147,4 @@ Hier wird daraufhin eine Zahlung angelegt und mit der Buchung verknüpft. ## Technische Besonderheiten * Leider ist es nicht möglich die Bestellung im Nachhinein zu erzeugen, d. h. wenn zum Beispiel die Rückleitung in den Shop nach dem Bezahlen schief geht. Auch wenn die Zahlung erfolgreich im heidelpay backend gespeichert worden ist.\ -Durch die Fehlermeldung im Buchungstext von Zahlungen, die nicht zugeordnet werden konnten, ist es möglich diese Fehlerfälle zu erkennen und zu behandeln. \ No newline at end of file +Durch die Fehlermeldung im Buchungstext von Zahlungen, die nicht zugeordnet werden konnten, ist es möglich diese Fehlerfälle zu erkennen und zu behandeln. diff --git a/meta/documents/user_guide_en.md b/meta/documents/user_guide_en.md index 5f39641..0d6f58e 100755 --- a/meta/documents/user_guide_en.md +++ b/meta/documents/user_guide_en.md @@ -110,8 +110,10 @@ This modul provides for a data container to render additional payment informatio To show the information on your order confirmation page please follow these steps: 1. switch to the menu item *CMS > Container Links* 2. choose the corresponding plug-in set from the drop down -3. open the menu *Invoice Details (Heidelpay)* and enable the ceres container ``Order confirmation: Additional payment information`` -4. click the save button +3. open the menu *Invoice Details (Heidelpay)* +4. enable the ceres container ``Order confirmation: Additional payment information`` +5. click the save button + ![Container links](../images/preview_4.png) ## Workflow description @@ -135,11 +137,12 @@ If the payment is successful, the order is immediately marked paid in your backe If the payment fails, the order is not created and the customer will be redirected to the checkout page. ### Invoice secured B2C -In order to start the insurance of a Payment you need to trigger a finalize transaction (FIN) from the hIP.\ +* In order to start the insurance of a Payment you need to trigger a finalize transaction (FIN) from the hIP.\ This starts the insurance period in which the customer has to transfert the total amount of the order.\ -This period is determined within your contract with heidelpay.\ -As soon as the customer transferred the total amount a receipt transaction (REC) appears within the hIP and is sent to the pushUrl of your shop.\ +* This period is determined within your contract with heidelpay.\ +As soon as the customer transferred the total amount a receipt transaction (REC) appears within the hIP and is sent to the pushUrl of your shop. The shop module will then create a new payment and link it to the corresponding order. +* The bank information for the customer will be written on the invoice pdf automatically on creation. ### All payment methods * Payments contain the txnId (which is the heidelpay orderId), the shortId (the id of the transaction which lead to the payment i.e. Receipt, Debit or Capture) and the origin (i.e. heidelpay). @@ -147,4 +150,4 @@ The shop module will then create a new payment and link it to the corresponding ## Known Issues * Unfortunately there is no way for us to create the order if the initial order creation fails, even if the payment has been successfully booked in our backend.\ -However you will be able to tell there has been an error when there are unassigned payments in your plenty backend showing an error in the booking text. \ No newline at end of file +However you will be able to tell there has been an error when there are unassigned payments in your plenty backend showing an error in the booking text. diff --git a/resources/lang/de/payment.properties b/resources/lang/de/payment.properties index c58f178..12e34bd 100755 --- a/resources/lang/de/payment.properties +++ b/resources/lang/de/payment.properties @@ -11,4 +11,5 @@ errorBookingTextIsMissing = "PaymentProperty::TYPE_BOOKING_TEXT ist nicht gesetz errorTransactionTypeUndefined = "Transaction type ist nicht definiert" debugPaymentFound = "Plenty Payment gefunden" debugHandleIncomingPayment = "Eingehende Zahlungsinformationen wird verarbeitet." -warningOrderDoesNotExist = "Die Bestellung existiert (noch) nicht." \ No newline at end of file +warningOrderDoesNotExist = "Die Bestellung existiert (noch) nicht." +addressesShouldMatch = "Rechnungs- und Versandadresse müssen gleich sein." diff --git a/resources/lang/en/payment.properties b/resources/lang/en/payment.properties index dd57823..b0dd225 100755 --- a/resources/lang/en/payment.properties +++ b/resources/lang/en/payment.properties @@ -11,4 +11,5 @@ errorBookingTextIsMissing = "PaymentProperty::TYPE_BOOKING_TEXT is not set" errorTransactionTypeUndefined = "Transaction type is undefined" debugPaymentFound = "Plenty Payment found" debugHandleIncomingPayment = "Handle incoming payment information." -warningOrderDoesNotExist = "Order does not exist (yet)." \ No newline at end of file +warningOrderDoesNotExist = "Order does not exist (yet)." +addressesShouldMatch = "Invoice and shipping address must match." diff --git a/resources/views/content/InvoiceDetails.twig b/resources/views/content/InvoiceDetails.twig index 816ef6e..518c053 100755 --- a/resources/views/content/InvoiceDetails.twig +++ b/resources/views/content/InvoiceDetails.twig @@ -1,22 +1,18 @@
+{{ trans("Heidelpay::template.pleaseTransferTheTotalTo") }}
- {{ trans("Heidelpay::template.pleaseTransferTheTotalTo") }} - -
-
{{ trans("Heidelpay::template.accountIban") }}:
-
{{ accountIBAN }}
-
-
-
{{ trans("Heidelpay::template.accountBic") }}:
-
{{ accountBIC }}
-
-
-
{{ trans("Heidelpay::template.accountHolder") }}:
-
{{ accountHolder }}
-
-
-
{{ trans("Heidelpay::template.accountUsage") }}:
-
{{ accountUsage }}
-
-
+
{{ trans("Heidelpay::template.accountIban") }}:
+
{{ accountIBAN }}
+
+
+
{{ trans("Heidelpay::template.accountBic") }}:
+
{{ accountBIC }}
+
+
+
{{ trans("Heidelpay::template.accountHolder") }}:
+
{{ accountHolder }}
+
+
+
{{ trans("Heidelpay::template.accountUsage") }}:
+
{{ accountUsage }}
diff --git a/resources/views/invoiceSecuredB2CForm.twig b/resources/views/invoiceSecuredB2CForm.twig index f54d577..97fe089 100755 --- a/resources/views/invoiceSecuredB2CForm.twig +++ b/resources/views/invoiceSecuredB2CForm.twig @@ -3,11 +3,11 @@ {% block content %}
-
+
-
+
-
+
{% include "Heidelpay::partials/cancelSubmitButtons" %}
{% endblock %} {% block scripts %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Helper/PaymentHelper.php b/src/Helper/PaymentHelper.php index 1182c06..262f4ff 100755 --- a/src/Helper/PaymentHelper.php +++ b/src/Helper/PaymentHelper.php @@ -13,16 +13,18 @@ use Heidelpay\Methods\InvoiceSecuredB2C; use Heidelpay\Methods\PaymentMethodContract; use Heidelpay\Methods\Sofort; +use Heidelpay\Models\Contracts\OrderTxnIdRelationRepositoryContract; +use Heidelpay\Models\Contracts\TransactionRepositoryContract; +use Heidelpay\Models\OrderTxnIdRelation; use Heidelpay\Models\Transaction; use Heidelpay\Services\ArraySerializerService; -use Plenty\Modules\Authorization\Services\AuthHelper; +use Heidelpay\Services\OrderServiceContract; use Plenty\Modules\Basket\Events\Basket\AfterBasketChanged; use Plenty\Modules\Basket\Events\Basket\AfterBasketCreate; use Plenty\Modules\Basket\Events\BasketItem\AfterBasketItemAdd; use Plenty\Modules\Frontend\Events\FrontendLanguageChanged; use Plenty\Modules\Frontend\Events\FrontendShippingCountryChanged; use Plenty\Modules\Helper\Services\WebstoreHelper; -use Plenty\Modules\Order\Contracts\OrderRepositoryContract; use Plenty\Modules\Order\Models\Order; use Plenty\Modules\Payment\Contracts\PaymentOrderRelationRepositoryContract; use Plenty\Modules\Payment\Contracts\PaymentPropertyRepositoryContract; @@ -53,8 +55,6 @@ class PaymentHelper /** @var PaymentMethodRepositoryContract $paymentMethodRepo */ protected $paymentMethodRepo; - /** @var OrderRepositoryContract */ - private $orderRepo; /** @var PaymentOrderRelationRepositoryContract */ private $paymentOrderRelationRepo; /** @var MainConfigContract */ @@ -63,29 +63,43 @@ class PaymentHelper private $methodConfig; /** @var PaymentPropertyRepositoryContract */ private $paymentPropertyRepo; + /** @var OrderServiceContract */ + private $orderService; + /** @var OrderTxnIdRelationRepositoryContract */ + private $orderTxnIdRelationRepo; + /** + * @var TransactionRepositoryContract + */ + private $transactionRepo; /** * @param PaymentMethodRepositoryContract $paymentMethodRepo - * @param OrderRepositoryContract $orderRepository * @param PaymentOrderRelationRepositoryContract $paymentOrderRepo * @param MainConfigContract $mainConfig * @param MethodConfigContract $methodConfig * @param PaymentPropertyRepositoryContract $propertyRepo + * @param OrderServiceContract $orderService + * @param OrderTxnIdRelationRepositoryContract $orderTxnIdRelationRepo + * @param TransactionRepositoryContract $transactionRepo */ public function __construct( PaymentMethodRepositoryContract $paymentMethodRepo, - OrderRepositoryContract $orderRepository, PaymentOrderRelationRepositoryContract $paymentOrderRepo, MainConfigContract $mainConfig, MethodConfigContract $methodConfig, - PaymentPropertyRepositoryContract $propertyRepo + PaymentPropertyRepositoryContract $propertyRepo, + OrderServiceContract $orderService, + OrderTxnIdRelationRepositoryContract $orderTxnIdRelationRepo, + TransactionRepositoryContract $transactionRepo ) { $this->paymentMethodRepo = $paymentMethodRepo; - $this->orderRepo = $orderRepository; $this->paymentOrderRelationRepo = $paymentOrderRepo; $this->mainConfig = $mainConfig; $this->methodConfig = $methodConfig; $this->paymentPropertyRepo = $propertyRepo; + $this->orderService = $orderService; + $this->orderTxnIdRelationRepo = $orderTxnIdRelationRepo; + $this->transactionRepo = $transactionRepo; } /** @@ -287,7 +301,7 @@ public function mapHeidelpayTransactionType(string $paymentCode): string public function assignPlentyPaymentToPlentyOrder(Payment $payment, int $orderId): Order { /** @var Order $order */ - $order = $this->getOrder($orderId); + $order = $this->orderService->getOrder($orderId); $additionalInfo = ['Order' => $order, 'Payment' => $payment]; $this->getLogger(__METHOD__)->debug('Heidelpay::payment.debugAssignPaymentToOrder', $additionalInfo); @@ -469,37 +483,6 @@ public function setBookingTextError(Payment $paymentObject, string $bookingError return $this; } - /** - * Fetches the Order object to the given orderId. - * - * @param int $orderId - * @return Order - * @throws \RuntimeException - */ - private function getOrder(int $orderId): Order - { - $order = null; - - /** @var AuthHelper $authHelper */ - $authHelper = pluginApp(AuthHelper::class); - - try {// Get the order by the given order ID - $order = $authHelper->processUnguarded( - function () use ($orderId) { - return $this->orderRepo->findOrderById($orderId); - } - ); - } catch (\Exception $e) { - // no need to handle here - } - - // Check whether the order exists - if (!$order instanceof Order) { - throw new \RuntimeException('payment.warningOrderDoesNotExist'); - } - return $order; - } - /** * Returns the property with of the given type. * @@ -518,4 +501,64 @@ public function getPaymentProperty(Payment $paymentObject, int $typeId): Payment return null; } + + /** + * Returns an array holding the bank details for the given order. + * The array will be empty when no details are available. + * + * @param Order $order + * @return array + */ + public function getPaymentDetailsForOrder(Order $order): array + { + $relation = $this->orderTxnIdRelationRepo->getOrderTxnIdRelationByOrderId($order->id); + + if ($relation instanceof OrderTxnIdRelation) { + return $this->getPaymentDetailsByTxnId($relation->txnId); + } + + return []; + } + + /** + * Returns an array holding the bank details for the given txnId. + * The array will be empty when no details are available. + * + * @param string $txnId + * @return array + */ + public function getPaymentDetailsByTxnId($txnId): array + { + $transactions = $this->transactionRepo->getTransactionsByTxnId($txnId); + $paymentDetails = []; + + foreach ($transactions as $transaction) { + /** @var Transaction $transaction */ + if ($transaction->transactionType === TransactionType::AUTHORIZE) { + $details = $transaction->transactionDetails; + if (!isset( + $details['CONNECTOR.ACCOUNT_IBAN'], + $details['CONNECTOR.ACCOUNT_IBAN'], + $details['CONNECTOR.ACCOUNT_IBAN'], + $details['CONNECTOR.ACCOUNT_IBAN'] + )) { + break; + } + + $accountIBAN = $details['CONNECTOR.ACCOUNT_IBAN']; + $accountBIC = $details['CONNECTOR.ACCOUNT_BIC']; + $accountHolder = $details['CONNECTOR.ACCOUNT_HOLDER']; + $accountUsage = $details['CONNECTOR.ACCOUNT_USAGE'] ?? $transaction->shortId; + + $paymentDetails = [ + 'accountIBAN' => $accountIBAN, + 'accountBIC' => $accountBIC, + 'accountHolder' => $accountHolder, + 'accountUsage' => $accountUsage + ]; + } + } + + return $paymentDetails; + } } diff --git a/src/Methods/AbstractMethod.php b/src/Methods/AbstractMethod.php index fff5696..cac329d 100755 --- a/src/Methods/AbstractMethod.php +++ b/src/Methods/AbstractMethod.php @@ -4,7 +4,8 @@ use Heidelpay\Configs\MethodConfigContract; use Heidelpay\Helper\PaymentHelper; -use Plenty\Modules\Basket\Contracts\BasketRepositoryContract; +use Heidelpay\Services\BasketServiceContract; +use Heidelpay\Services\NotificationServiceContract; use Plenty\Modules\Payment\Events\Checkout\GetPaymentMethodContent; use Plenty\Modules\Payment\Method\Contracts\PaymentMethodService; use Plenty\Plugin\Application; @@ -34,36 +35,43 @@ abstract class AbstractMethod extends PaymentMethodService implements PaymentMet const NEEDS_CUSTOMER_INPUT = true; const NEEDS_BASKET = false; const RENDER_INVOICE_DATA = false; + const B2C_ONLY = false; + const COUNTRY_RESTRICTION = []; + const ADDRESSES_MUST_MATCH = false; /** * @var PaymentHelper $helper */ protected $helper; - /** - * @var BasketRepositoryContract $basketRepository - */ - protected $basketRepository; /** * @var MethodConfigContract */ private $config; + /** + * @var BasketServiceContract + */ + private $basketService; + /** + * @var NotificationServiceContract + */ + private $notificationService; /** * AbstractMethod constructor. * * @param PaymentHelper $paymentHelper - * @param BasketRepositoryContract $basketRepository * @param MethodConfigContract $config + * @param BasketServiceContract $basketService */ public function __construct( PaymentHelper $paymentHelper, - BasketRepositoryContract $basketRepository, - MethodConfigContract $config + MethodConfigContract $config, + BasketServiceContract $basketService ) { $this->helper = $paymentHelper; - $this->basketRepository = $basketRepository; $this->config = $config; + $this->basketService = $basketService; } /** @@ -76,7 +84,7 @@ public function isActive(): bool return false; } - $basket = $this->basketRepository->load(); + $basket = $this->basketService->getBasket(); // check the configured minimum cart amount and return false if an amount is configured // (which means > 0.00) and the cart amount is below the configured value. @@ -88,7 +96,23 @@ public function isActive(): bool // check the configured maximum cart amount and return false if an amount is configured // (which means > 0.00) and the cart amount is above the configured value. $maxAmount = $this->config->getMaxAmount($this); - return !($maxAmount > 0.00 && $basket->basketAmount > $maxAmount); + if ($maxAmount > 0.00 && $basket->basketAmount > $maxAmount) { + return false; + } + + // enable the payment method only if it is enabled for the current transaction (B2C||B2B) + if ($this->isB2cOnly() && $this->basketService->isBasketB2B()) { + return false; + } + + // enable the payment method only if it is allowed for the given billing country + $countryRestrictions = $this->getCountryRestrictions(); + if (!empty($countryRestrictions) && + !in_array($this->basketService->getBillingCountryCode(), $countryRestrictions, true)) { + return false; + } + + return true; } /** @@ -274,4 +298,28 @@ public function renderInvoiceData(): bool { return static::RENDER_INVOICE_DATA; } + + /** + * {@inheritDoc} + */ + public function isB2cOnly(): bool + { + return static::B2C_ONLY; + } + + /** + * {@inheritDoc} + */ + public function getCountryRestrictions(): array + { + return static::COUNTRY_RESTRICTION; + } + + /** + * {@inheritDoc} + */ + public function needsMatchingAddresses(): bool + { + return static::ADDRESSES_MUST_MATCH; + } } diff --git a/src/Methods/InvoiceSecuredB2C.php b/src/Methods/InvoiceSecuredB2C.php index 05d96d9..f1d833e 100755 --- a/src/Methods/InvoiceSecuredB2C.php +++ b/src/Methods/InvoiceSecuredB2C.php @@ -28,4 +28,7 @@ class InvoiceSecuredB2C extends AbstractMethod const NEEDS_CUSTOMER_INPUT = false; const NEEDS_BASKET = true; const RENDER_INVOICE_DATA = true; + const B2C_ONLY = true; + const COUNTRY_RESTRICTION = ['DE', 'AT']; + const ADDRESSES_MUST_MATCH = true; } diff --git a/src/Methods/PaymentMethodContract.php b/src/Methods/PaymentMethodContract.php index a6404c8..0332d63 100755 --- a/src/Methods/PaymentMethodContract.php +++ b/src/Methods/PaymentMethodContract.php @@ -144,4 +144,21 @@ public function needsBasket(): bool; * @return bool */ public function renderInvoiceData(): bool; + + /** + * Returns true if the payment method is meant for B2C orders. + */ + public function isB2cOnly(): bool; + + /** + * Returns an array with the countries the method is restricted to. + */ + public function getCountryRestrictions(): array; + + /** + * Returns true if the shipping and billing address have to match for this payment method. + * + * @return bool + */ + public function needsMatchingAddresses(): bool; } diff --git a/src/Providers/HeidelpayServiceProvider.php b/src/Providers/HeidelpayServiceProvider.php index c74fde8..35f7bbb 100755 --- a/src/Providers/HeidelpayServiceProvider.php +++ b/src/Providers/HeidelpayServiceProvider.php @@ -7,15 +7,24 @@ use Heidelpay\Configs\MethodConfig; use Heidelpay\Configs\MethodConfigContract; use Heidelpay\Helper\PaymentHelper; +use Heidelpay\Methods\AbstractMethod; use Heidelpay\Models\Contracts\OrderTxnIdRelationRepositoryContract; use Heidelpay\Models\Contracts\TransactionRepositoryContract; use Heidelpay\Models\Repositories\OrderTxnIdRelationRepository; use Heidelpay\Models\Repositories\TransactionRepository; +use Heidelpay\Services\BasketService; +use Heidelpay\Services\BasketServiceContract; use Heidelpay\Services\NotificationService; use Heidelpay\Services\NotificationServiceContract; +use Heidelpay\Services\OrderService; +use Heidelpay\Services\OrderServiceContract; use Heidelpay\Services\PaymentService; use Heidelpay\Services\UrlService; use Heidelpay\Services\UrlServiceContract; +use Plenty\Modules\Document\Models\Document; +use Plenty\Modules\Order\Models\Order; +use Plenty\Modules\Order\Pdf\Events\OrderPdfGenerationEvent; +use Plenty\Modules\Order\Pdf\Models\OrderPdfGeneration; use Plenty\Modules\Payment\Events\Checkout\ExecutePayment; use Plenty\Modules\Payment\Events\Checkout\GetPaymentMethodContent; use Plenty\Modules\Payment\Method\Contracts\PaymentMethodContainer; @@ -49,22 +58,28 @@ public function register() $app->bind(NotificationServiceContract::class, NotificationService::class); $app->bind(OrderTxnIdRelationRepositoryContract::class, OrderTxnIdRelationRepository::class); $app->bind(UrlServiceContract::class, UrlService::class); + $app->bind(BasketServiceContract::class, BasketService::class); + $app->bind(OrderServiceContract::class, OrderService::class); } /** * Boot the heidelpay Service Provider * Register payment methods, add event listeners, ... * - * @param PaymentHelper $paymentHelper - * @param PaymentMethodContainer $methodContainer - * @param PaymentService $paymentService - * @param Dispatcher $eventDispatcher + * @param PaymentHelper $paymentHelper + * @param PaymentMethodContainer $methodContainer + * @param PaymentService $paymentService + * @param Dispatcher $eventDispatcher + * @param NotificationServiceContract $notificationService + * @param OrderServiceContract $orderService */ public function boot( PaymentHelper $paymentHelper, PaymentMethodContainer $methodContainer, PaymentService $paymentService, - Dispatcher $eventDispatcher + Dispatcher $eventDispatcher, + NotificationServiceContract $notificationService, + OrderServiceContract $orderService ) { // loop through all of the plugin's available payment methods /** @var string $paymentMethodClass */ @@ -114,5 +129,52 @@ function (ExecutePayment $event) use ( } } ); + + // add payment information to the invoice pdf + $eventDispatcher->listen( + OrderPdfGenerationEvent::class, + function (OrderPdfGenerationEvent $event) use ( + $notificationService, $paymentHelper, $orderService + ) { + /** @var Order $order */ + $order = $event->getOrder(); + $docType = $event->getDocType(); + $mopId = $order->methodOfPaymentId; + + /** @var AbstractMethod $paymentMethod */ + $paymentMethod = $paymentHelper->getPaymentMethodInstanceByMopId($mopId); + + if ($docType !== Document::INVOICE) { + return; + } + + /** @var OrderPdfGeneration $orderPdfGeneration */ + $orderPdfGeneration = pluginApp(OrderPdfGeneration::class); + $language = $orderService->getLanguage($order); + $orderPdfGeneration->language = $language; + + $paymentDetails = $paymentHelper->getPaymentDetailsForOrder($order); + + if (!$paymentMethod->renderInvoiceData()) { + // do nothing if invoice data does not need to be rendered + return; + } + + $adviceParts = [ + $notificationService->getTranslation('Heidelpay::template.pleaseTransferTheTotalTo', [], $language), + $notificationService->getTranslation('Heidelpay::template.accountIban', [], $language) . ': ' . + $paymentDetails['accountIBAN'], + $notificationService->getTranslation('Heidelpay::template.accountBic', [], $language) . ': ' . + $paymentDetails['accountBIC'], + $notificationService->getTranslation('Heidelpay::template.accountHolder', [], $language) . ': ' . + $paymentDetails['accountHolder'], + $notificationService->getTranslation('Heidelpay::template.accountUsage', [], $language) . ': ' . + $paymentDetails['accountUsage'] + ]; + $orderPdfGeneration->advice = implode(PHP_EOL, $adviceParts); + + $event->addOrderPdfGeneration($orderPdfGeneration); + } + ); } } diff --git a/src/Providers/InvoiceDetailsProvider.php b/src/Providers/InvoiceDetailsProvider.php index 4b74bdd..a812d83 100755 --- a/src/Providers/InvoiceDetailsProvider.php +++ b/src/Providers/InvoiceDetailsProvider.php @@ -4,7 +4,6 @@ use Heidelpay\Constants\SessionKeys; use Heidelpay\Helper\PaymentHelper; use Heidelpay\Methods\PaymentMethodContract; -use Heidelpay\Models\Contracts\TransactionRepositoryContract; use Plenty\Modules\Frontend\Session\Storage\Contracts\FrontendSessionStorageFactoryContract; use Plenty\Plugin\Templates\Twig; @@ -13,7 +12,6 @@ class InvoiceDetailsProvider public function call( Twig $twig, FrontendSessionStorageFactoryContract $sessionStorage, - TransactionRepositoryContract $transactionRepos, PaymentHelper $helper ): string { $mopId = $sessionStorage->getOrder()->methodOfPayment; @@ -25,22 +23,7 @@ public function call( } $txnId = $sessionStorage->getPlugin()->getValue(SessionKeys::SESSION_KEY_TXN_ID); - $transaction = $transactionRepos->getTransactionsByTxnId($txnId)[0]; - - $details = $transaction->transactionDetails; - $accountIBAN = $details['CONNECTOR.ACCOUNT_IBAN']; - $accountBIC = $details['CONNECTOR.ACCOUNT_BIC']; - $accountHolder = $details['CONNECTOR.ACCOUNT_HOLDER']; - $accountUsage = $details['CONNECTOR.ACCOUNT_USAGE'] ?? $transaction->shortId; - - return $twig->render( - 'Heidelpay::content/InvoiceDetails', - [ - 'accountIBAN' => $accountIBAN, - 'accountBIC' => $accountBIC, - 'accountHolder' => $accountHolder, - 'accountUsage' => $accountUsage - ] - ); + $paymentDetails = $helper->getPaymentDetailsByTxnId($txnId); + return $twig->render('Heidelpay::content/InvoiceDetails', $paymentDetails); } -} \ No newline at end of file +} diff --git a/src/Services/BasketService.php b/src/Services/BasketService.php index 88ee76d..e80b9ae 100755 --- a/src/Services/BasketService.php +++ b/src/Services/BasketService.php @@ -3,11 +3,15 @@ namespace Heidelpay\Services; use Heidelpay\Configs\MainConfigContract; +use Plenty\Modules\Account\Address\Contracts\AddressRepositoryContract; +use Plenty\Modules\Account\Address\Models\Address; use Plenty\Modules\Authorization\Services\AuthHelper; +use Plenty\Modules\Basket\Contracts\BasketRepositoryContract; use Plenty\Modules\Basket\Models\Basket; use Plenty\Modules\Basket\Models\BasketItem; use Plenty\Modules\Item\Item\Contracts\ItemRepositoryContract; use Plenty\Modules\Item\Item\Models\Item; +use Plenty\Modules\Order\Shipping\Countries\Contracts\CountryRepositoryContract; /** * Provides connection to heidelpay basketApi. @@ -21,7 +25,7 @@ * * @package heidelpay\plentymarkets-gateway\services */ -class BasketService +class BasketService implements BasketServiceContract { /** * @var LibService @@ -39,15 +43,33 @@ class BasketService * @var ItemRepositoryContract */ private $itemRepo; + /** + * @var AddressRepositoryContract + */ + private $addressRepository; + /** + * @var BasketRepositoryContract + */ + private $basketRepo; + /** + * @var CountryRepositoryContract + */ + private $countryRepository; /** * BasketService constructor. + * @param CountryRepositoryContract $countryRepository + * @param AddressRepositoryContract $addressRepository + * @param BasketRepositoryContract $basketRepo * @param LibService $libraryService * @param MainConfigContract $config * @param ItemRepositoryContract $itemRepo * @param AuthHelper $authHelper */ public function __construct( + CountryRepositoryContract $countryRepository, + AddressRepositoryContract $addressRepository, + BasketRepositoryContract $basketRepo, LibService $libraryService, MainConfigContract $config, ItemRepositoryContract $itemRepo, @@ -57,6 +79,9 @@ public function __construct( $this->config = $config; $this->authHelper = $authHelper; $this->itemRepo = $itemRepo; + $this->addressRepository = $addressRepository; + $this->basketRepo = $basketRepo; + $this->countryRepository = $countryRepository; } /** @@ -96,4 +121,94 @@ function () use ($basketItem) { $response = $this->libService->submitBasket($params); return $response['basketId']; } + + /** + * {@inheritDoc} + */ + public function shippingMatchesBillingAddress(): bool + { + $basket = $this->getBasket(); + if ($basket->customerShippingAddressId === null || $basket->customerShippingAddressId === -99) { + return true; + } + + $addresses = $this->getCustomerAddressData(); + $billingAddress = $addresses['billing']->toArray(); + $shippingAddress = $addresses['shipping']->toArray(); + + return $billingAddress['gender'] === $shippingAddress['gender'] && + $this->strCompare($billingAddress['address1'], $shippingAddress['address1']) && + $this->strCompare($billingAddress['address2'], $shippingAddress['address2']) && + $billingAddress['postalCode'] === $shippingAddress['postalCode'] && + $this->strCompare($billingAddress['town'], $shippingAddress['town']) && + ( + ($this->isBasketB2B() && $this->strCompare($billingAddress['name1'], $shippingAddress['name1'])) || + (!$this->isBasketB2B() && $this->strCompare($billingAddress['name2'], $shippingAddress['name2']) + && $this->strCompare($billingAddress['name3'], $shippingAddress['name3'])) + ); + } + /** + * Gathers address data (billing/invoice and shipping) and returns them as an array. + * + * @return Address[] + */ + public function getCustomerAddressData(): array + { + $basket = $this->getBasket(); + + $addresses = []; + $addresses['billing'] = $basket->customerInvoiceAddressId ? + $this->addressRepository->findAddressById($basket->customerInvoiceAddressId) : null; + + // if the shipping address is -99 or null, it is matching the billing address. + if ($basket->customerShippingAddressId === null || $basket->customerShippingAddressId === -99) { + $addresses['shipping'] = $addresses['billing']; + return $addresses; + } + + $addresses['shipping'] = $this->addressRepository->findAddressById($basket->customerShippingAddressId); + return $addresses; + } + + /** + * Returns true if the billing address is B2B. + */ + public function isBasketB2B(): bool + { + $billingAddress = $this->getCustomerAddressData()['billing']; + + return $billingAddress ? $billingAddress->gender === null : false; + } + + /** + * Fetches current basket and returns it. + * + * @return Basket + */ + public function getBasket(): Basket + { + return $this->basketRepo->load(); + } + + /** + * {@inheritDoc} + */ + public function getBillingCountryCode(): string + { + $billingAddress = $this->getCustomerAddressData()['billing']; + return $billingAddress ? + $this->countryRepository->findIsoCode($billingAddress->countryId, 'isoCode2') : ''; + } + + /** + * Returns true if the strings match case insensitive. + * + * @param string $string1 + * @param string $string2 + * @return bool + */ + private function strCompare($string1, $string2): bool + { + return strtolower($string1) === strtolower($string2); + } } diff --git a/src/Services/BasketServiceContract.php b/src/Services/BasketServiceContract.php new file mode 100644 index 0000000..33c0f78 --- /dev/null +++ b/src/Services/BasketServiceContract.php @@ -0,0 +1,75 @@ + + * + * @package heidelpay/${Package} + */ + +namespace Heidelpay\Services; + + +use Plenty\Modules\Account\Address\Models\Address; +use Plenty\Modules\Basket\Models\Basket; + +/** + * Provides connection to heidelpay basketApi. + * + * @license Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file. + * @copyright Copyright © 2016-present heidelpay GmbH. All rights reserved. + * + * @link http://dev.heidelpay.com/ + * + * @author Simon Gabriel + * + * @package heidelpay\plentymarkets-gateway\services + */ +interface BasketServiceContract +{ + /** + * Submits the Basket to the Basket-API and returns its ID. + * + * @param Basket $basket + * @param array $authData + * + * @return string + */ + public function requestBasketId(Basket $basket, array $authData): string; + + /** + * Gathers address data (billing/invoice and shipping) and returns them as an array. + * + * @return Address[] + */ + public function getCustomerAddressData(): array; + + /** + * Returns true if the billing address is B2B. + */ + public function isBasketB2B(): bool; + + /** + * Fetches current basket and returns it. + * + * @return Basket + */ + public function getBasket(): Basket; + + /** + * Returns the country code of the billing address as isoCode2. + * + * @return string + */ + public function getBillingCountryCode(): string; + + /** + * Returns true if the shipping and billing address are equal. + */ + public function shippingMatchesBillingAddress(): bool; +} diff --git a/src/Services/NotificationService.php b/src/Services/NotificationService.php index 0d9f29f..dabb8c5 100755 --- a/src/Services/NotificationService.php +++ b/src/Services/NotificationService.php @@ -148,12 +148,11 @@ protected function notify($level, $message, $method, array $logData, $justLog = } /** - * @param $message - * @return mixed + * {@inheritDoc} */ - protected function getTranslation($message) + public function getTranslation($message, $parameters = [], $locale = null) { - return $this->translator->trans($message); + return $this->translator->trans($message, $parameters, $locale); } /** diff --git a/src/Services/NotificationServiceContract.php b/src/Services/NotificationServiceContract.php index 80e2122..6f5ed9e 100755 --- a/src/Services/NotificationServiceContract.php +++ b/src/Services/NotificationServiceContract.php @@ -73,4 +73,14 @@ public function error($message, $method = 'no context given', array $logData = [ * @param array $logData */ public function critical($message, $method = 'no context given', array $logData = []); + + /** + * Translates the given message using the given locale. + * + * @param $message + * @param array $parameters + * @param string $locale + * @return mixed + */ + public function getTranslation($message, $parameters = [], $locale = null); } diff --git a/src/Services/OrderService.php b/src/Services/OrderService.php new file mode 100644 index 0000000..a4ab6a2 --- /dev/null +++ b/src/Services/OrderService.php @@ -0,0 +1,86 @@ +orderRepo = $orderRepository; + $this->orderTxnIdRelationRepo = $orderTxnIdRelationRepo; + } + + /** + * {@inheritDoc} + */ + public function getLanguage(Order $order): string + { + /** @var OrderProperty $property */ + foreach ($order->properties as $property) { + if ($property->typeId === OrderPropertyType::DOCUMENT_LANGUAGE) { + return $property->value; + } + } + + return 'DE'; + } + + /** + * {@inheritDoc} + */ + public function getOrder(int $orderId): Order + { + $order = null; + + /** @var AuthHelper $authHelper */ + $authHelper = pluginApp(AuthHelper::class); + + try {// Get the order by the given order ID + $order = $authHelper->processUnguarded( + function () use ($orderId) { + return $this->orderRepo->findOrderById($orderId, ['comments']); + } + ); + } catch (\Exception $e) { + // no need to handle here + } + + // Check whether the order exists + if (!$order instanceof Order) { + throw new \RuntimeException('payment.warningOrderDoesNotExist'); + } + return $order; + } + + /** + * {@inheritDoc} + */ + public function getOrderByTxnId($txnId): Order + { + $orderId = $this->orderTxnIdRelationRepo->getOrderIdByTxnId($txnId); + return $this->getOrder($orderId); + } +} diff --git a/src/Services/OrderServiceContract.php b/src/Services/OrderServiceContract.php new file mode 100644 index 0000000..b4a2835 --- /dev/null +++ b/src/Services/OrderServiceContract.php @@ -0,0 +1,46 @@ + + * + * @package heidelpay/${Package} + */ + +namespace Heidelpay\Services; + +use Plenty\Modules\Order\Models\Order; + +interface OrderServiceContract +{ + /** + * Returns the language code of the given order or 'DE' as default. + * + * @param Order $order + * @return string + */ + public function getLanguage(Order $order): string; + + /** + * Fetches the Order object to the given orderId. + * + * @param int $orderId + * @return Order + * @throws \RuntimeException + */ + public function getOrder(int $orderId): Order; + + /** + * Returns the order object corresponding to the given txnId. + * + * @param $txnId + * @return Order + * @throws \RuntimeException + */ + public function getOrderByTxnId($txnId): Order; +} diff --git a/src/Services/PaymentService.php b/src/Services/PaymentService.php index eaa7190..4d2787d 100755 --- a/src/Services/PaymentService.php +++ b/src/Services/PaymentService.php @@ -18,16 +18,14 @@ use Heidelpay\Models\OrderTxnIdRelation; use Heidelpay\Models\Transaction; use Heidelpay\Traits\Translator; -use Plenty\Modules\Account\Address\Contracts\AddressRepositoryContract; use Plenty\Modules\Account\Address\Models\Address; use Plenty\Modules\Account\Contact\Contracts\ContactRepositoryContract; -use Plenty\Modules\Basket\Contracts\BasketRepositoryContract; use Plenty\Modules\Basket\Models\Basket; +use Plenty\Modules\Comment\Contracts\CommentRepositoryContract; use Plenty\Modules\Frontend\Session\Storage\Contracts\FrontendSessionStorageFactoryContract; use Plenty\Modules\Order\Contracts\OrderRepositoryContract; use Plenty\Modules\Order\Property\Models\OrderProperty; use Plenty\Modules\Order\Property\Models\OrderPropertyType; -use Plenty\Modules\Order\Shipping\Countries\Contracts\CountryRepositoryContract; use Plenty\Modules\Payment\Contracts\PaymentRepositoryContract; use Plenty\Modules\Payment\Events\Checkout\ExecutePayment; use Plenty\Modules\Payment\Events\Checkout\GetPaymentMethodContent; @@ -57,14 +55,6 @@ class PaymentService * @var array */ private $heidelpayRequest = []; - /** - * @var AddressRepositoryContract - */ - private $addressRepository; - /** - * @var CountryRepositoryContract - */ - private $countryRepository; /** * @var PaymentRepositoryContract */ @@ -109,20 +99,22 @@ class PaymentService * @var UrlServiceContract */ private $urlService; - /** - * @var BasketRepositoryContract - */ - private $basketRepository; /** * @var ContactRepositoryContract */ private $contactRepo; + /** + * @var BasketServiceContract + */ + private $basketService; + /** + * @var CommentRepositoryContract + */ + private $commentRepo; /** * PaymentService constructor. * - * @param AddressRepositoryContract $addressRepository - * @param CountryRepositoryContract $countryRepository * @param LibService $libraryService * @param PaymentRepositoryContract $paymentRepository * @param TransactionRepositoryContract $transactionRepo @@ -134,12 +126,10 @@ class PaymentService * @param OrderTxnIdRelationRepositoryContract $orderTxnIdRepo * @param OrderRepositoryContract $orderRepo * @param UrlServiceContract $urlService - * @param BasketRepositoryContract $basketRepository + * @param BasketServiceContract $basketService * @param ContactRepositoryContract $contactRepo */ public function __construct( - AddressRepositoryContract $addressRepository, - CountryRepositoryContract $countryRepository, LibService $libraryService, PaymentRepositoryContract $paymentRepository, TransactionRepositoryContract $transactionRepo, @@ -151,11 +141,9 @@ public function __construct( OrderTxnIdRelationRepositoryContract $orderTxnIdRepo, OrderRepositoryContract $orderRepo, UrlServiceContract $urlService, - BasketRepositoryContract $basketRepository, + BasketServiceContract $basketService, ContactRepositoryContract $contactRepo ) { - $this->addressRepository = $addressRepository; - $this->countryRepository = $countryRepository; $this->libService = $libraryService; $this->paymentRepository = $paymentRepository; $this->transactionRepository = $transactionRepo; @@ -167,14 +155,14 @@ public function __construct( $this->orderTxnIdRepo = $orderTxnIdRepo; $this->orderRepo = $orderRepo; $this->urlService = $urlService; - $this->basketRepository = $basketRepository; $this->contactRepo = $contactRepo; + $this->basketService = $basketService; } /** * Executes payment tasks after an order has been created. * - * @param string $paymentMethod + * @param string $paymentMethod * @param ExecutePayment $event * * @return array @@ -271,34 +259,34 @@ public function getPaymentMethodContent( ): array { $value = ''; - $clientErrorMessage = 'Heidelpay::payment.errorInternalErrorTryAgainLater'; + $clientErrorMessage = $this->notification->getTranslation('Heidelpay::payment.errorInternalErrorTryAgainLater'); /** @var AbstractMethod $methodInstance */ $methodInstance = $this->paymentHelper->getPaymentMethodInstance($paymentMethod); - if (!$methodInstance instanceof PaymentMethodContract) { $type = GetPaymentMethodContent::RETURN_TYPE_ERROR; $value = $clientErrorMessage; return [$type, $value]; } - $type = $methodInstance->getReturnType(); + if ($methodInstance->needsMatchingAddresses() && !$this->basketService->shippingMatchesBillingAddress()) { + $value = $this->notification->getTranslation('Heidelpay::payment.addressesShouldMatch'); + return [GetPaymentMethodContent::RETURN_TYPE_ERROR, $value]; + } + $type = $methodInstance->getReturnType(); if ($type === GetPaymentMethodContent::RETURN_TYPE_CONTINUE) { return [$type, $value]; } $value = $this->urlService->generateURL(Routes::HANDLE_FORM_URL); + $basket = $this->basketService->getBasket(); if ($methodInstance->hasToBeInitialized()) { try { - $result = $this->sendPaymentRequest( - $this->basketRepository->load(), - $paymentMethod, - $methodInstance->getTransactionType(), - $mopId - ); - $value = $this->handleSyncResponse($type, $result); + $transactionType = $methodInstance->getTransactionType(); + $result = $this->sendPaymentRequest($basket, $paymentMethod, $transactionType, $mopId); + $value = $this->handleSyncResponse($type, $result); } catch (\RuntimeException $e) { $this->notification->error($clientErrorMessage, __METHOD__, [$type, $e->getMessage()], true); $type = GetPaymentMethodContent::RETURN_TYPE_ERROR; @@ -307,11 +295,9 @@ public function getPaymentMethodContent( } } - $basket = $this->basketRepository->load(); $customerId = $basket->customerId; - - $contact = $this->contactRepo->findContactById($customerId); - $birthday = explode('-', substr($contact->birthdayAt, 0, 10)); + $contact = $customerId ? $this->contactRepo->findContactById($customerId) : null; + $birthday = $contact ? explode('-', substr($contact->birthdayAt, 0, 10)): null; if ($type === GetPaymentMethodContent::RETURN_TYPE_HTML) { // $value should contain the payment frame url (also form url) @@ -345,9 +331,6 @@ private function prepareRequest( { $basketArray = $basket->toArray(); - /** @var BasketService $basketService */ - $basketService = pluginApp(BasketService::class); - /** @var SecretService $secretService */ $secretService = pluginApp(SecretService::class); @@ -359,7 +342,7 @@ private function prepareRequest( $this->heidelpayRequest = array_merge($this->heidelpayRequest, $heidelpayAuth); // set customer personal information & address data - $addresses = $this->getCustomerAddressData($basket); + $addresses = $this->basketService->getCustomerAddressData(); $billingAddress = $addresses['billing']; $this->heidelpayRequest['IDENTIFICATION_SHOPPERID'] = $basketArray['customerId']; $this->heidelpayRequest['NAME_GIVEN'] = $billingAddress->firstName; @@ -368,12 +351,9 @@ private function prepareRequest( $this->heidelpayRequest['ADDRESS_STREET'] = $this->getFullStreetAndHouseNumber($billingAddress); $this->heidelpayRequest['ADDRESS_ZIP'] = $billingAddress->postalCode; $this->heidelpayRequest['ADDRESS_CITY'] = $billingAddress->town; - $this->heidelpayRequest['ADDRESS_COUNTRY'] = $this->countryRepository->findIsoCode( - $billingAddress->countryId, - 'isoCode2' - ); + $this->heidelpayRequest['ADDRESS_COUNTRY'] = $this->basketService->getBillingCountryCode(); - if ($billingAddress->companyName !== null) { + if ($this->basketService->isBasketB2B()) { $this->heidelpayRequest['NAME_COMPANY'] = $billingAddress->companyName; } @@ -410,7 +390,7 @@ private function prepareRequest( } if ($methodInstance->needsBasket()) { - $this->heidelpayRequest['BASKET_ID'] = $basketService->requestBasketId($basket, $heidelpayAuth); + $this->heidelpayRequest['BASKET_ID'] = $this->basketService->requestBasketId($basket, $heidelpayAuth); } // shop + module information @@ -500,28 +480,6 @@ public function handlePushNotification(array $post): array } // - /** - * Gathers address data (billing/invoice and shipping) and returns them as an array. - * - * @param Basket $basket - * - * @return Address[] - */ - private function getCustomerAddressData(Basket $basket): array - { - $addresses = []; - $addresses['billing'] = $this->addressRepository->findAddressById($basket->customerInvoiceAddressId); - - // if the shipping address is -99 or null, it is matching the billing address. - if ($basket->customerShippingAddressId === null || $basket->customerShippingAddressId === -99) { - $addresses['shipping'] = $addresses['billing']; - return $addresses; - } - - $addresses['shipping'] = $this->addressRepository->findAddressById($basket->customerShippingAddressId); - return $addresses; - } - /** * Returns street and house number as a single string. *