Skip to content

Commit

Permalink
Merge branch 'main' into ECP-9608
Browse files Browse the repository at this point in the history
  • Loading branch information
candemiralp authored Feb 27, 2025
2 parents 37e023d + 0f6ddab commit cec0467
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 36 deletions.
1 change: 0 additions & 1 deletion Gateway/Http/Client/TransactionPayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Adyen\Client;
use Adyen\ConnectionException;
use Adyen\Model\Checkout\PaymentRequest;
use Adyen\Model\Checkout\PaymentResponse as CheckoutPaymentResponse;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\GiftcardPayment;
use Adyen\Payment\Helper\Idempotency;
Expand Down
5 changes: 2 additions & 3 deletions Gateway/Request/CaptureDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public function build(array $buildSubject): array
]
];

//Check additionaldata
if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) {
if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) {
$openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForInvoice($latestInvoice);
$requestBody = array_merge($requestBody, $openInvoiceFields);
}
Expand Down Expand Up @@ -171,7 +170,7 @@ public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrd
"paymentPspReference" => $adyenOrderPayment[OrderPaymentInterface::PSPREFRENCE]
];

if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) {
if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) {
$order = $payment->getOrder();
$invoices = $order->getInvoiceCollection();
// The latest invoice will contain only the selected items(and quantities) for the (partial) capture
Expand Down
5 changes: 2 additions & 3 deletions Gateway/Request/CheckoutDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,6 @@ public function build(array $buildSubject): array
$this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance) ||
$payment->getMethod() === AdyenPayByLinkConfigProvider::CODE
) {
$openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForOrder($order);
$requestBody = array_merge($requestBody, $openInvoiceFields);

if (isset($brandCode) &&
$this->adyenHelper->isPaymentMethodOfType($brandCode, Data::KLARNA) &&
$this->configHelper->getAutoCaptureOpenInvoice($storeId)) {
Expand Down Expand Up @@ -231,6 +228,8 @@ protected function getImageUrl($item): string
}

/**
* @deprecated Use Adyen\Payment\Helper\OpenInvoice::getOpenInvoiceDataForOrder() instead.
*
* @param Order $order
*
* @return array
Expand Down
61 changes: 61 additions & 0 deletions Gateway/Request/LineItemsDataBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/

namespace Adyen\Payment\Gateway\Request;

use Adyen\Payment\Helper\OpenInvoice;
use Adyen\Payment\Helper\PaymentMethods;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Sales\Model\Order;

class LineItemsDataBuilder implements BuilderInterface
{
/**
* @param PaymentMethods $paymentMethodsHelper
* @param OpenInvoice $openInvoiceHelper
*/
public function __construct(
private readonly PaymentMethods $paymentMethodsHelper,
private readonly OpenInvoice $openInvoiceHelper
) { }

/**
* Add `lineItems` to the request if the payment method requires this field
*
* @param array $buildSubject
* @return array
* @throws LocalizedException
*/
public function build(array $buildSubject): array
{
/** @var PaymentDataObject $paymentDataObject */
$paymentDataObject = SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$paymentMethodInstance = $payment->getMethodInstance();
/** @var Order $order */
$order = $payment->getOrder();

$requestBody = [];

$isLineItemsRequired = $this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance);
if ($isLineItemsRequired === true) {
$requestLineItems = $this->openInvoiceHelper->getOpenInvoiceDataForOrder($order);
$requestBody = array_merge($requestBody, $requestLineItems);
}

return [
'body' => $requestBody
];
}
}
2 changes: 1 addition & 1 deletion Gateway/Request/RefundDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public function build(array $buildSubject): array
]
];

if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) {
if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) {
$openInvoiceFieldsCreditMemo = $this->openInvoiceHelper->getOpenInvoiceDataForCreditMemo($creditMemo);
//There is only one payment, so we add the fields to the first(and only) result
$requestBody[0] = array_merge($requestBody[0], $openInvoiceFieldsCreditMemo);
Expand Down
67 changes: 58 additions & 9 deletions Helper/OpenInvoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,53 @@

namespace Adyen\Payment\Helper;

use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\AdyenAmountCurrency;
use Exception;
use Magento\Catalog\Helper\Image;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Item as OrderItem;
use Magento\Sales\Model\Order\Invoice;
use Magento\Sales\Model\Order\Invoice\Item;
use Magento\Sales\Model\Order\Invoice\Item as InvoiceItem;
use Magento\Sales\Model\Order\Creditmemo;
use Magento\Sales\Model\Order\Creditmemo\Item as CreditmemoItem;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item as QuoteItem;

class OpenInvoice
{
const ITEM_CATEGORY_DIGITAL_GOODS = 'DIGITAL_GOODS';
const ITEM_CATEGORY_PHYSICAL_GOODS = 'PHYSICAL_GOODS';

protected Data $adyenHelper;
protected CartRepositoryInterface $cartRepository;
protected ChargedCurrency $chargedCurrency;
protected Config $configHelper;
protected Image $imageHelper;
protected AdyenLogger $adyenLogger;

public function __construct(
Data $adyenHelper,
Data $adyenHelper,
CartRepositoryInterface $cartRepository,
ChargedCurrency $chargedCurrency,
Config $configHelper,
Image $imageHelper
ChargedCurrency $chargedCurrency,
Config $configHelper,
Image $imageHelper,
AdyenLogger $adyenLogger
) {
$this->adyenHelper = $adyenHelper;
$this->cartRepository = $cartRepository;
$this->chargedCurrency = $chargedCurrency;
$this->configHelper = $configHelper;
$this->imageHelper = $imageHelper;
$this->adyenLogger = $adyenLogger;
}

public function getOpenInvoiceDataForInvoice(Invoice $invoice): array
{
$formFields = ['lineItems' => []];

/* @var Item $invoiceItem */
/* @var InvoiceItem $invoiceItem */
foreach ($invoice->getItems() as $invoiceItem) {
$numberOfItems = (int)$invoiceItem->getQty();
$orderItem = $invoiceItem->getOrderItem();
Expand Down Expand Up @@ -92,10 +104,11 @@ public function getOpenInvoiceDataForOrder(Order $order): array
return $formFields;
}

public function getOpenInvoiceDataForCreditMemo(Order\Creditmemo $creditMemo)
public function getOpenInvoiceDataForCreditMemo(Creditmemo $creditMemo): array
{
$formFields = ['lineItems' => []];

/* @var CreditmemoItem $creditmemoItem */
foreach ($creditMemo->getItems() as $creditmemoItem) {
// Child items only identifies the variant data and doesn't contain line item information.
$isChildItem = $creditmemoItem->getOrderItem()->getParentItem() !== null;
Expand Down Expand Up @@ -172,16 +185,52 @@ protected function formatLineItem(AdyenAmountCurrency $itemAmountCurrency, $item

$product = $item->getProduct();

return [
$lineItem = [
'id' => $product ? $product->getId() : $item->getProductId(),
'amountIncludingTax' => $formattedPriceIncludingTax,
'amountExcludingTax' => $formattedPriceIncludingTax - $formattedTaxAmount,
'taxAmount' => $formattedTaxAmount,
'taxPercentage' => $formattedTaxPercentage,
'description' => $item->getName(),
'sku' => $item->getSku(),
'quantity' => (int) ($qty ?? $item->getQty()),
'taxPercentage' => $formattedTaxPercentage,
'productUrl' => $product ? $product->getUrlModel()->getUrl($product) : '',
'imageUrl' => $this->getImageUrl($item)
];

if ($itemCategory = $this->buildItemCategory($item)) {
$lineItem['itemCategory'] = $itemCategory;
}

return $lineItem;
}

/**
* Builds the `itemCategory` field required for PayPal
*
* @param QuoteItem|OrderItem $item
* @return string|null
*/
private function buildItemCategory($item): ?string
{
try {
if ($item instanceof QuoteItem) {
$paymentMethod = $item->getQuote()->getPayment()->getMethod();
} elseif ($item instanceof OrderItem) {
$paymentMethod = $item->getOrder()->getPayment()->getMethod();
}

if (isset($paymentMethod) && strcmp($paymentMethod, PaymentMethods::ADYEN_PAYPAL) === 0) {
$isVirtual = boolval($item->getIsVirtual());
$category = $isVirtual ? self::ITEM_CATEGORY_DIGITAL_GOODS : self::ITEM_CATEGORY_PHYSICAL_GOODS;
}
} catch (Exception $e) {
$this->adyenLogger->error(
__('An error occurred while building the line item field `itemCategory`: %1', $e->getMessage())
);
}

return $category ?? null;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions Helper/PaymentMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class PaymentMethods extends AbstractHelper
const ADYEN_CC = 'adyen_cc';
const ADYEN_ONE_CLICK = 'adyen_oneclick';
const ADYEN_PAY_BY_LINK = 'adyen_pay_by_link';
const ADYEN_PAYPAL = 'adyen_paypal';
const ADYEN_PREFIX = 'adyen_';
const ADYEN_CC_VAULT = 'adyen_cc_vault';
const METHODS_WITH_BRAND_LOGO = [
Expand All @@ -66,6 +67,7 @@ class PaymentMethods extends AbstractHelper
const FUNDING_SOURCE_CREDIT = 'credit';

const ADYEN_GROUP_ALTERNATIVE_PAYMENT_METHODS = 'adyen-alternative-payment-method';
const CONFIG_FIELD_REQUIRES_LINE_ITEMS = 'requires_line_items';
const CONFIG_FIELD_IS_OPEN_INVOICE = 'is_open_invoice';

const VALID_CHANNELS = ["iOS", "Android", "Web"];
Expand Down Expand Up @@ -1081,4 +1083,18 @@ public function isOpenInvoice(MethodInterface $paymentMethodInstance): bool
{
return boolval($paymentMethodInstance->getConfigData(self::CONFIG_FIELD_IS_OPEN_INVOICE));
}

/**
* Checks the requirement of line items for the given payment method
*
* @param MethodInterface $paymentMethodInstance
* @return bool
*/
public function getRequiresLineItems(MethodInterface $paymentMethodInstance): bool
{
$isOpenInvoice = $this->isOpenInvoice($paymentMethodInstance);
$requiresLineItemsConfig = boolval($paymentMethodInstance->getConfigData(self::CONFIG_FIELD_REQUIRES_LINE_ITEMS));

return $isOpenInvoice || $requiresLineItemsConfig;
}
}
111 changes: 111 additions & 0 deletions Test/Unit/Gateway/Request/LineItemsDataBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/

namespace Adyen\Payment\Test\Gateway\Request;

use Adyen\Payment\Gateway\Request\LineItemsDataBuilder;
use Adyen\Payment\Helper\OpenInvoice;
use Adyen\Payment\Helper\PaymentMethods;
use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Payment\Model\MethodInterface;
use Magento\Sales\Model\Order;
use PHPUnit\Framework\MockObject\MockObject;

class LineItemsDataBuilderTest extends AbstractAdyenTestCase
{
protected ?LineItemsDataBuilder $lineItemsDataBuilder;
protected PaymentMethods|MockObject $paymentMethodsHelperMock;
protected OpenInvoice|MockObject $openInvoiceHelperMock;

/**
* @return void
*/
protected function setUp(): void
{
$this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class);
$this->openInvoiceHelperMock = $this->createMock(OpenInvoice::class);

$this->lineItemsDataBuilder = new LineItemsDataBuilder(
$this->paymentMethodsHelperMock,
$this->openInvoiceHelperMock
);
}

/**
* @return void
*/
protected function tearDown(): void
{
$this->lineItemsDataBuilder = null;
}

/**
* @return array
*/
private static function buildDataProvider(): array
{
return [
['isLineItemsRequired' => true],
['isLineItemsRequired' => false]
];
}

/**
* @dataProvider buildDataProvider()
*
* @param bool $isLineItemsRequired
* @return void
* @throws LocalizedException
*/
public function testBuild(bool $isLineItemsRequired)
{
$orderMock = $this->createMock(Order::class);

$paymentMethodInstanceMock = $this->createMock(MethodInterface::class);

$paymentMock = $this->createMock(Order\Payment::class);
$paymentMock->expects($this->once())
->method('getOrder')
->willReturn($orderMock);
$paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock);

$paymentDataObjectMock = $this->createMock(PaymentDataObject::class);
$paymentDataObjectMock->expects($this->once())
->method('getPayment')
->willReturn($paymentMock);

$this->paymentMethodsHelperMock->expects($this->once())
->method('getRequiresLineItems')
->willReturn($isLineItemsRequired);

if ($isLineItemsRequired) {
$this->openInvoiceHelperMock->expects($this->once())
->method('getOpenInvoiceDataForOrder')
->with($orderMock)
->willReturn(['lineItems' => [['id' => 1], ['id' => 2]]]);
}

$buildSubject = ['payment' => $paymentDataObjectMock];
$result = $this->lineItemsDataBuilder->build($buildSubject);

$this->assertIsArray($result);
$this->assertArrayHasKey('body', $result);

if ($isLineItemsRequired) {
$this->assertArrayHasKey('lineItems', $result['body']);
$this->assertArrayHasKey('id', $result['body']['lineItems'][0]);
} else {
$this->assertEmpty($result['body']);
}
}
}
Loading

0 comments on commit cec0467

Please sign in to comment.