diff --git a/Api/Config/RepositoryInterface.php b/Api/Config/RepositoryInterface.php index 24bb771d..f42eb496 100755 --- a/Api/Config/RepositoryInterface.php +++ b/Api/Config/RepositoryInterface.php @@ -30,6 +30,7 @@ interface RepositoryInterface public const XML_PATH_ENABLE_ADDRESS_SEARCH = 'payment/two_payment/enable_address_search'; public const XML_PATH_ENABLE_TAX_SUBTOTALS = 'payment/two_payment/enable_tax_subtotals'; public const XML_PATH_ENABLE_ORDER_INTENT = 'payment/two_payment/enable_order_intent'; + public const XML_PATH_ENABLE_MULTIPLE_INVOICE_EMAILS = 'payment/two_payment/enable_multiple_invoice_emails'; public const XML_PATH_ENABLE_DEPARTMENT_NAME = 'payment/two_payment/enable_department'; public const XML_PATH_ENABLE_PROJECT_NAME = 'payment/two_payment/enable_project'; public const XML_PATH_ENABLE_ORDER_NOTE = 'payment/two_payment/enable_order_note'; @@ -122,6 +123,15 @@ public function isCompanySearchEnabled(?int $storeId = null): bool; */ public function isOrderIntentEnabled(?int $storeId = null): bool; + /** + * Check if order intent is enabled + * + * @param int|null $storeId + * + * @return bool + */ + public function isMultipleInvoiceEmailsEnabled(?int $storeId = null): bool; + /** * Check if tax subtotals is enabled * diff --git a/Model/Config/Repository.php b/Model/Config/Repository.php index e195bb94..f6859dd5 100755 --- a/Model/Config/Repository.php +++ b/Model/Config/Repository.php @@ -156,6 +156,14 @@ public function isOrderIntentEnabled(?int $storeId = null): bool return $this->isSetFlag(self::XML_PATH_ENABLE_ORDER_INTENT, $storeId); } + /** + * @inheritDoc + */ + public function isMultipleInvoiceEmailsEnabled(?int $storeId = null): bool + { + return $this->isSetFlag(self::XML_PATH_ENABLE_MULTIPLE_INVOICE_EMAILS, $storeId); + } + /** * @inheritDoc */ diff --git a/Model/Two.php b/Model/Two.php index 2ee04e5c..b89e1840 100755 --- a/Model/Two.php +++ b/Model/Two.php @@ -204,12 +204,19 @@ public function authorize(InfoInterface $payment, $amount) $order = $payment->getOrder(); $this->urlCookie->delete(); $orderReference = (string)rand(); + + $additionalInformation = $payment->getAdditionalInformation(); + + $multipleInvoiceEmails = $additionalInformation['multipleInvoiceEmails'] ?? null; + $payload = $this->compositeOrder->execute( $order, $orderReference, - $payment->getAdditionalInformation() + $additionalInformation ); + + // Create order $response = $this->apiAdapter->execute('/v1/order', $payload); $error = $this->getErrorFromResponse($response); diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php index 057c6c06..30d2c996 100755 --- a/Model/Ui/ConfigProvider.php +++ b/Model/Ui/ConfigProvider.php @@ -91,6 +91,7 @@ public function getConfig(): array 'checkoutPageUrl' => $this->configRepository->getCheckoutPageUrl(), 'redirectUrlCookieCode' => UrlCookie::COOKIE_NAME, 'isOrderIntentEnabled' => $this->configRepository->isOrderIntentEnabled(), + 'isMultipleInvoiceEmailsEnabled' => $this->configRepository->isMultipleInvoiceEmailsEnabled(), 'orderIntentConfig' => $orderIntentConfig, 'isCompanySearchEnabled' => $this->configRepository->isCompanySearchEnabled(), 'isAddressSearchEnabled' => $this->configRepository->isAddressSearchEnabled(), @@ -115,6 +116,7 @@ public function getConfig(): array $provider, $tryAgainLater ), + 'invalidEmailListMessage' => __('Please ensure your forward to email list only contains valid emails seperated by commas.'), 'paymentTermsMessage' => __( 'By checking this box, I confirm that I have read and agree to %1.', sprintf('%s', $paymentTermsLink, $paymentTerms) diff --git a/Observer/DataAssignObserver.php b/Observer/DataAssignObserver.php index e91d0ed5..7fc7abdd 100755 --- a/Observer/DataAssignObserver.php +++ b/Observer/DataAssignObserver.php @@ -24,6 +24,7 @@ class DataAssignObserver extends AbstractDataAssignObserver 'department', 'orderNote', 'poNumber', + 'multipleInvoiceEmails' ]; /** diff --git a/Service/Order/ComposeOrder.php b/Service/Order/ComposeOrder.php index 00aad44a..8dd8513a 100755 --- a/Service/Order/ComposeOrder.php +++ b/Service/Order/ComposeOrder.php @@ -27,41 +27,61 @@ class ComposeOrder extends OrderService * @throws LocalizedException */ public function execute(Order $order, string $orderReference, array $additionalData): array - { - $lineItems = $this->getLineItemsOrder($order); +{ + // Fetch line items from the order + $lineItems = $this->getLineItemsOrder($order); + + + // Initialize invoice_details only if multipleInvoiceEmails is present + $invoiceDetails = []; + if (!empty($additionalData['multipleInvoiceEmails'])) { + $invoiceDetails['invoice_emails'] = explode(',', $additionalData['multipleInvoiceEmails']); + // Required placeholders to pass create order API Schema requirements + $invoiceDetails['payment_reference_message'] = ""; + $invoiceDetails['payment_reference_ocr'] = ""; + } + + // Compose the final payload for the API call + $payload = [ + 'billing_address' => $this->getAddress($order, $additionalData, 'billing'), + 'shipping_address' => $this->getAddress($order, $additionalData, 'shipping'), + 'buyer' => $this->getBuyer($order, $additionalData), + 'buyer_department' => $additionalData['department'] ?? '', + 'buyer_project' => $additionalData['project'] ?? '', + 'buyer_purchase_order_number' => $additionalData['poNumber'] ?? '', + 'currency' => $order->getOrderCurrencyCode(), + 'discount_amount' => $this->roundAmt($this->getDiscountAmountItem($order)), + 'gross_amount' => $this->roundAmt($order->getGrandTotal()), + 'net_amount' => $this->roundAmt($order->getGrandTotal() - $order->getTaxAmount()), + 'tax_amount' => $this->roundAmt($order->getTaxAmount()), + 'tax_subtotals' => $this->getTaxSubtotals($lineItems), + 'invoice_type' => 'FUNDED_INVOICE', + 'line_items' => $lineItems, + 'merchant_order_id' => (string)($order->getIncrementId()), + 'merchant_urls' => [ + 'merchant_confirmation_url' => $this->url->getUrl( + 'two/payment/confirm', + ['_two_order_reference' => base64_encode($orderReference)] + ), + 'merchant_cancel_order_url' => $this->url->getUrl( + 'two/payment/cancel', + ['_two_order_reference' => base64_encode($orderReference)] + ), + 'merchant_edit_order_url' => '', + 'merchant_order_verification_failed_url' => $this->url->getUrl( + 'two/payment/verificationfailed', + ['_two_order_reference' => base64_encode($orderReference)] + ), + ], + 'order_note' => $additionalData['orderNote'] ?? '' + ]; + + // Add invoice_details only if invoiceEmails are present + if (!empty($invoiceDetails)) { + $payload['invoice_details'] = $invoiceDetails; + } + - return [ - 'billing_address' => $this->getAddress($order, $additionalData, 'billing'), - 'shipping_address' => $this->getAddress($order, $additionalData, 'shipping'), - 'buyer' => $this->getBuyer($order, $additionalData), - 'buyer_department' => $additionalData['department'] ?? '', - 'buyer_project' => $additionalData['project'] ?? '', - 'buyer_purchase_order_number' => $additionalData['poNumber'] ?? '', - 'currency' => $order->getOrderCurrencyCode(), - 'discount_amount' => $this->roundAmt($this->getDiscountAmountItem($order)), - 'gross_amount' => $this->roundAmt($order->getGrandTotal()), - 'net_amount' => $this->roundAmt($order->getGrandTotal() - $order->getTaxAmount()), - 'tax_amount' => $this->roundAmt($order->getTaxAmount()), - 'tax_subtotals' => $this->getTaxSubtotals($lineItems), - 'invoice_type' => 'FUNDED_INVOICE', - 'line_items' => $lineItems, - 'merchant_order_id' => (string)($order->getIncrementId()), - 'merchant_urls' => [ - 'merchant_confirmation_url' => $this->url->getUrl( - 'two/payment/confirm', - ['_two_order_reference' => base64_encode($orderReference)] - ), - 'merchant_cancel_order_url' => $this->url->getUrl( - 'two/payment/cancel', - ['_two_order_reference' => base64_encode($orderReference)] - ), - 'merchant_edit_order_url' => '', - 'merchant_order_verification_failed_url' => $this->url->getUrl( - 'two/payment/verificationfailed', - ['_two_order_reference' => base64_encode($orderReference)] - ), - ], - 'order_note' => $additionalData['orderNote'] ?? '' - ]; + return $payload; } } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 4dc7df6d..18e9a983 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -158,6 +158,16 @@ payment/two_payment/enable_order_intent + + + Let your buyer input additional emails to forward the invoice to on order fulfilment. + Magento\Config\Model\Config\Source\Yesno + + 1 + + payment/two_payment/enable_multiple_invoice_emails + diff --git a/etc/config.xml b/etc/config.xml index dc7d6256..ee63cede 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -18,6 +18,7 @@ 14 1 1 + 0 1 shipment complete @@ -35,4 +36,3 @@ - diff --git a/i18n/en_US.csv b/i18n/en_US.csv index bac1c0a0..14086f87 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -113,3 +113,5 @@ Version,Version "Your request to %1 failed. Reason: %2","Your request to %1 failed. Reason: %2" "Your sole trader account could not be verified.","Your sole trader account could not be verified." "Zip/Postal Code is not valid.","Zip/Postal Code is not valid." +"Forward invoice to email list (optional)","Forward invoice to email list (optional)" +"Please ensure your forward to email list only contains valid emails seperated by commas.", "Please ensure your forward to email list only contains valid emails seperated by commas." diff --git a/i18n/nb_NO.csv b/i18n/nb_NO.csv index aca71026..feac3df4 100644 --- a/i18n/nb_NO.csv +++ b/i18n/nb_NO.csv @@ -113,3 +113,5 @@ Version,Versjon "Your request to %1 failed. Reason: %2","Din forespørsel til %1 mislyktes. Årsak: %2" "Your sole trader account could not be verified.","Din eneforhandlerkonto kunne ikke bekreftes." "Zip/Postal Code is not valid.","Postnummer er ikke gyldig." +"Forward invoice to email list (optional)","Videresend faktura til e-postliste (valgfritt)" +"Please ensure your forward to email list only contains valid emails seperated by commas.","Sørg for at listen over videresending til e-post kun inneholder gyldige e-poster atskilt med komma." diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index cf7bec17..fb4400c1 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -113,3 +113,5 @@ Version,Versie "Your request to %1 failed. Reason: %2","Uw verzoek aan %1 is mislukt. Reden: %2" "Your sole trader account could not be verified.","Uw eenmanszaakaccount kon niet worden geverifieerd." "Zip/Postal Code is not valid.","Postcode is niet geldig." +"Forward invoice to email list (optional)","Factuur doorsturen naar e-maillijst (optioneel)" +"Please ensure your forward to email list only contains valid emails seperated by commas.","Zorg ervoor dat uw doorstuur-naar-e-maillijst alleen geldige e-mails bevat, gescheiden door komma's." diff --git a/i18n/sv_SE.csv b/i18n/sv_SE.csv index bb6247a8..f8bf2de6 100644 --- a/i18n/sv_SE.csv +++ b/i18n/sv_SE.csv @@ -113,3 +113,5 @@ Version,Version "Your request to %1 failed. Reason: %2","Din begäran till %1 misslyckades. Orsak: %2" "Your sole trader account could not be verified.","Ditt enskild näringsidkarekonto kunde inte verifieras." "Zip/Postal Code is not valid.","Postnummer är inte giltigt." +"Forward invoice to email list (optional)","Vidarebefordra faktura till e-postlista (valfritt)" +"Please ensure your forward to email list only contains valid emails seperated by commas.","Se till att din vidarebefordran till e-postlista endast innehåller giltiga e-postmeddelanden separerade med kommatecken." diff --git a/view/frontend/web/js/view/payment/method-renderer/two_payment.js b/view/frontend/web/js/view/payment/method-renderer/two_payment.js index e2f09413..9e59ab68 100755 --- a/view/frontend/web/js/view/payment/method-renderer/two_payment.js +++ b/view/frontend/web/js/view/payment/method-renderer/two_payment.js @@ -49,8 +49,10 @@ define([ orderIntentApprovedMessage: config.orderIntentApprovedMessage, orderIntentDeclinedMessage: config.orderIntentDeclinedMessage, generalErrorMessage: config.generalErrorMessage, + invalidEmailListMessage: config.invalidEmailListMessage, soleTraderErrorMessage: config.soleTraderErrorMessage, isOrderIntentEnabled: config.isOrderIntentEnabled, + isMultipleInvoiceEmailsEnabled: config.isMultipleInvoiceEmailsEnabled, isDepartmentFieldEnabled: config.isDepartmentFieldEnabled, isProjectFieldEnabled: config.isProjectFieldEnabled, isOrderNoteFieldEnabled: config.isOrderNoteFieldEnabled, @@ -67,6 +69,7 @@ define([ autofillToken: '', companyName: ko.observable(''), companyId: ko.observable(''), + multipleInvoiceEmails: ko.observable(''), project: ko.observable(''), department: ko.observable(''), orderNote: ko.observable(''), @@ -84,6 +87,28 @@ define([ this.configureFormValidation(); this.popupMessageListener(); }, + showErrorMessage: function (message, duration) { + messageList.addErrorMessage({ message: message }); + + if (duration) { + setTimeout(function () { + messageList.messages.remove(function (item) { + return item.message === message; + }); + }, duration); + } + }, + validateMultipleEmails: function () { + const emails = this.multipleInvoiceEmails(); + let emailArray = emails.split(',').map((email) => email.trim()); + + const isValid = emailArray.every((email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)); + if (!isValid && emails) { + this.showErrorMessage(this.invalidEmailListMessage, 3); + return false; + } + return true; + }, logIsPaymentsAccepted: function (data, event) { console.debug({ logger: 'logIsPaymentsAccepted', @@ -217,6 +242,13 @@ define([ this.processTermsNotAcceptedErrorResponse(); return; } + + // Validate emails on the forward list + if (this.isMultipleInvoiceEmailsEnabled && !this.validateMultipleEmails()) { + this.showErrorMessage(this.invalidEmailListMessage); + return; + } + if ( this.validate() && additionalValidators.validate() && @@ -392,7 +424,8 @@ define([ project: this.project(), department: this.department(), orderNote: this.orderNote(), - poNumber: this.poNumber() + poNumber: this.poNumber(), + multipleInvoiceEmails: this.multipleInvoiceEmails() } }; }, diff --git a/view/frontend/web/template/payment/two_payment.html b/view/frontend/web/template/payment/two_payment.html index 31f1ea05..4783bedc 100755 --- a/view/frontend/web/template/payment/two_payment.html +++ b/view/frontend/web/template/payment/two_payment.html @@ -172,6 +172,19 @@ /> +
+ +
+ +
+