diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68cb599e..5083a01d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,9 +193,10 @@ jobs: - name: Upload Behat logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: Behat logs path: etc/build/ if-no-files-found: ignore + overwrite: true diff --git a/UPGRADE.md b/UPGRADE.md index 9dfd6fc6..9ce27408 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,4 +1,74 @@ -### UPGRADE FROM 1.5.1 to 1.6 +### UPGRADE FROM 1.6.1 to 1.6.2 + +1. The following constructor signatures have been changed: + + `Sylius\PayPalPlugin\Controller\CreatePayPalOrderFromCartAction`: + ```diff + public function __construct( + private readonly ?Payum $payum, + private readonly ?OrderRepositoryInterface $orderRepository, + private readonly ?FactoryInterface $stateMachineFactory, + private readonly ObjectManager $paymentManager, + private readonly OrderProviderInterface $orderProvider, + private readonly CapturePaymentResolverInterface $capturePaymentResolver, + + private readonly ?OrderPaymentsRemoverInterface $orderPaymentsRemover = null, + + private readonly ?OrderProcessorInterface $orderProcessor = null, + ) + ``` + +### UPGRADE FROM 1.6.0 to 1.6.1 + +1. The following constructor signatures have been changed: + + `Sylius\PayPalPlugin\Controller\UpdatePayPalOrderAction`: + ```diff + public function __construct( + private readonly PaymentProviderInterface $paymentProvider, + private readonly CacheAuthorizeClientApiInterface $authorizeClientApi, + - private readonly OrderDetailsApiInterface $orderDetailsApi, + private readonly UpdateOrderApiInterface $updateOrderApi, + private readonly AddressFactoryInterface $addressFactory, + private readonly OrderProcessorInterface $orderProcessor, + ) + ``` + + `Sylius\PayPalPlugin\Model\PayPalPurchaseUnit`: + ```diff + public function __construct( + private readonly string $referenceId, + private readonly string $invoiceNumber, + private readonly string $currencyCode, + private readonly int $totalAmount, + private readonly int $shippingValue, + private readonly float $itemTotalValue, + private readonly float $taxTotalValue, + private readonly int $discountValue, + private readonly string $merchantId, + private readonly array $items, + private readonly bool $shippingRequired, + private readonly ?AddressInterface $shippingAddress = null, + private readonly string $softDescriptor = 'Sylius PayPal Payment', + + private readonly int $shippingDiscountValue = 0, + ) + ``` + + `Sylius\PayPalPlugin\Controller\ProcessPayPalOrderAction`: + ```diff + public function __construct( + - private readonly OrderRepositoryInterface $orderRepository, + private readonly CustomerRepositoryInterface $customerRepository, + private readonly FactoryInterface $customerFactory, + private readonly AddressFactoryInterface $addressFactory, + private readonly ObjectManager $orderManager, + private readonly StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, + private readonly PaymentStateManagerInterface $paymentStateManager, + private readonly CacheAuthorizeClientApiInterface $authorizeClientApi, + private readonly OrderDetailsApiInterface $orderDetailsApi, + private readonly OrderProviderInterface $orderProvider, + ) + ``` + +### UPGRADE FROM 1.5.1 to 1.6.0 1. Support for Sylius 1.13 has been added, it is now the recommended Sylius version to use. @@ -12,9 +82,9 @@ use GuzzleHttp\ClientInterface as GuzzleClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; - + public function __construct( - - private readonly GuzzleClientInterface $client, + - private readonly GuzzleClientInterface $client, + private readonly GuzzleClientInterface|ClientInterface $client, private readonly LoggerInterface $logger, private readonly UuidProviderInterface $uuidProvider, @@ -33,7 +103,7 @@ use Psr\Http\Client\ClientInterface; use GuzzleHttp\ClientInterface as GuzzleClientInterface; use Psr\Http\Message\RequestFactoryInterface; - + public function __construct( - private readonly GuzzleClientInterface $client, + private readonly GuzzleClientInterface|ClientInterface $client, @@ -47,7 +117,7 @@ use GuzzleHttp\ClientInterface as GuzzleClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; - + public function __construct( - private readonly GuzzleClientInterface $client, + private readonly GuzzleClientInterface|ClientInterface $client, @@ -62,7 +132,7 @@ use Psr\Http\Client\ClientInterface; use GuzzleHttp\ClientInterface as GuzzleClientInterface; use Psr\Http\Message\RequestFactoryInterface; - + public function __construct( - private readonly GuzzleClientInterface $client, + private readonly GuzzleClientInterface|ClientInterface $client, @@ -71,7 +141,7 @@ + private readonly ?RequestFactoryInterface $requestFactory = null, ) ``` - + 1. Added doctrine migration for PostgreSQL. For more information, please refer to the [Sylius 1.13 UPGRADE.md](https://github.com/Sylius/Sylius/blob/1.13/UPGRADE-1.13.md) ### UPGRADE FROM 1.3.0 to 1.3.1 diff --git a/spec/Api/CreateOrderApiSpec.php b/spec/Api/CreateOrderApiSpec.php index d78e47a3..042e312f 100644 --- a/spec/Api/CreateOrderApiSpec.php +++ b/spec/Api/CreateOrderApiSpec.php @@ -17,6 +17,7 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Sylius\Component\Core\Model\AddressInterface; +use Sylius\Component\Core\Model\AdjustmentInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -57,6 +58,7 @@ function it_creates_pay_pal_order_based_on_given_payment( $order->getShippingTotal()->willReturn(1000); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $payPalItemDataProvider->provide($order)->willReturn([ 'items' => [ @@ -92,7 +94,7 @@ function it_creates_pay_pal_order_based_on_given_payment( Argument::that(function (array $data): bool { return $data['intent'] === 'CAPTURE' && - $data['purchase_units'][0]['invoice_number'] === 'REFERENCE-NUMBER' && + $data['purchase_units'][0]['invoice_id'] === 'REFERENCE-NUMBER' && $data['purchase_units'][0]['amount']['value'] === '100.00' && $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' && $data['purchase_units'][0]['amount']['breakdown']['shipping']['currency_code'] === 'PLN' && @@ -126,6 +128,7 @@ function it_creates_pay_pal_order_with_shipping_address_based_on_given_payment( $order->getShippingTotal()->willReturn(1000); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $shippingAddress->getFullName()->willReturn('Gandalf The Grey'); $shippingAddress->getStreet()->willReturn('Hobbit St. 123'); @@ -167,7 +170,7 @@ function it_creates_pay_pal_order_with_shipping_address_based_on_given_payment( Argument::that(function (array $data): bool { return $data['intent'] === 'CAPTURE' && - $data['purchase_units'][0]['invoice_number'] === 'REFERENCE-NUMBER' && + $data['purchase_units'][0]['invoice_id'] === 'REFERENCE-NUMBER' && $data['purchase_units'][0]['amount']['value'] === '100.00' && $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' && $data['purchase_units'][0]['shipping']['name']['full_name'] === 'Gandalf The Grey' && @@ -204,6 +207,7 @@ function it_creates_pay_pal_order_with_more_than_one_product( $order->getShippingTotal()->willReturn(3000); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $shippingAddress->getFullName()->willReturn('Gandalf The Grey'); $shippingAddress->getStreet()->willReturn('Hobbit St. 123'); @@ -297,6 +301,7 @@ function it_creates_pay_pal_order_with_non_neutral_tax_and_changed_quantity( $order->getShippingTotal()->willReturn(1000); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $shippingAddress->getFullName()->willReturn('Gandalf The Grey'); $shippingAddress->getStreet()->willReturn('Hobbit St. 123'); @@ -394,6 +399,7 @@ function it_creates_pay_pal_order_with_more_than_one_product_with_different_tax_ $order->getShippingTotal()->willReturn(3000); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $shippingAddress->getFullName()->willReturn('Gandalf The Grey'); $shippingAddress->getStreet()->willReturn('Hobbit St. 123'); @@ -509,6 +515,7 @@ function it_allows_to_create_digital_order( $order->getShippingTotal()->willReturn(0); $order->isShippingRequired()->willReturn(false); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $payPalItemDataProvider->provide($order)->willReturn([ 'items' => [ @@ -573,6 +580,7 @@ function it_creates_pay_pal_order_with_promotion( $order->getShippingTotal()->willReturn(749); $order->isShippingRequired()->willReturn(true); $order->getOrderPromotionTotal()->willReturn(-250); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $payPalItemDataProvider->provide($order)->willReturn([ 'items' => [ @@ -608,7 +616,7 @@ function it_creates_pay_pal_order_with_promotion( Argument::that(function (array $data): bool { return $data['intent'] === 'CAPTURE' && - $data['purchase_units'][0]['invoice_number'] === 'REFERENCE-NUMBER' && + $data['purchase_units'][0]['invoice_id'] === 'REFERENCE-NUMBER' && $data['purchase_units'][0]['amount']['value'] === '29.99' && $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' && $data['purchase_units'][0]['amount']['breakdown']['shipping']['currency_code'] === 'PLN' && @@ -627,4 +635,80 @@ function it_creates_pay_pal_order_with_promotion( $this->create('TOKEN', $payment, 'REFERENCE_ID')->shouldReturn(['status' => 'CREATED', 'id' => 123]); } + + function it_creates_pay_pal_order_with_shipping_promotion( + PayPalClientInterface $client, + PaymentReferenceNumberProviderInterface $paymentReferenceNumberProvider, + PaymentInterface $payment, + OrderInterface $order, + PaymentMethodInterface $paymentMethod, + GatewayConfigInterface $gatewayConfig, + PayPalItemDataProviderInterface $payPalItemDataProvider, + ): void { + $payment->getOrder()->willReturn($order); + $payment->getAmount()->willReturn(3000); + $order->getCurrencyCode()->willReturn('PLN'); + $order->getShippingAddress()->willReturn(null); + $order->getItemsTotal()->willReturn(2500); + $order->getShippingTotal()->willReturn(500); + $order->isShippingRequired()->willReturn(true); + $order->getOrderPromotionTotal()->willReturn(0); + $order + ->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT) + ->willReturn(-249) + ; + + $payPalItemDataProvider->provide($order)->willReturn([ + 'items' => [ + [ + 'name' => 'PRODUCT_ONE', + 'unit_amount' => [ + 'value' => '25.00', + 'currency_code' => 'PLN', + ], + 'quantity' => 1, + 'tax' => [ + 'value' => '0.00', + 'currency_code' => 'PLN', + ], + ], + ], + 'total_item_value' => '25.00', + 'total_tax' => '0.00', + ]); + + $payment->getMethod()->willReturn($paymentMethod); + $paymentMethod->getGatewayConfig()->willReturn($gatewayConfig); + + $paymentReferenceNumberProvider->provide($payment)->willReturn('REFERENCE-NUMBER'); + + $gatewayConfig->getConfig()->willReturn( + ['merchant_id' => 'merchant-id', 'sylius_merchant_id' => 'sylius-merchant-id'], + ); + + $client->post( + 'v2/checkout/orders', + 'TOKEN', + Argument::that(function (array $data): bool { + return + $data['intent'] === 'CAPTURE' && + $data['purchase_units'][0]['invoice_id'] === 'REFERENCE-NUMBER' && + $data['purchase_units'][0]['amount']['value'] === '30.00' && + $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' && + $data['purchase_units'][0]['amount']['breakdown']['shipping']['currency_code'] === 'PLN' && + $data['purchase_units'][0]['amount']['breakdown']['shipping']['value'] === '7.49' && + $data['purchase_units'][0]['amount']['breakdown']['item_total']['currency_code'] === 'PLN' && + $data['purchase_units'][0]['amount']['breakdown']['item_total']['value'] === '25.00' && + $data['purchase_units'][0]['amount']['breakdown']['shipping_discount']['currency_code'] === 'PLN' && + $data['purchase_units'][0]['amount']['breakdown']['shipping_discount']['value'] === '2.49' && + $data['purchase_units'][0]['items'][0]['name'] === 'PRODUCT_ONE' && + $data['purchase_units'][0]['items'][0]['quantity'] === 1 && + $data['purchase_units'][0]['items'][0]['unit_amount']['value'] === '25.00' && + $data['purchase_units'][0]['items'][0]['unit_amount']['currency_code'] === 'PLN' + ; + }), + )->willReturn(['status' => 'CREATED', 'id' => 123]); + + $this->create('TOKEN', $payment, 'REFERENCE_ID')->shouldReturn(['status' => 'CREATED', 'id' => 123]); + } } diff --git a/spec/Api/RefundPaymentApiSpec.php b/spec/Api/RefundPaymentApiSpec.php index e7383965..5bd13146 100644 --- a/spec/Api/RefundPaymentApiSpec.php +++ b/spec/Api/RefundPaymentApiSpec.php @@ -35,7 +35,7 @@ function it_refunds_pay_pal_payment_with_given_id(PayPalClientInterface $client) ->post( 'v2/payments/captures/123123/refund', 'TOKEN', - ['amount' => ['value' => '10.99', 'currency_code' => 'USD'], 'invoice_number' => '123-11-11-2010'], + ['amount' => ['value' => '10.99', 'currency_code' => 'USD'], 'invoice_id' => '123-11-11-2010'], ['PayPal-Auth-Assertion' => 'PAY-PAL-AUTH-ASSERTION'], ) ->willReturn(['status' => 'COMPLETED', 'id' => '123123']) diff --git a/spec/Api/UpdateOrderApiSpec.php b/spec/Api/UpdateOrderApiSpec.php index 0bbf97db..203820d1 100644 --- a/spec/Api/UpdateOrderApiSpec.php +++ b/spec/Api/UpdateOrderApiSpec.php @@ -16,6 +16,7 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Sylius\Component\Core\Model\AddressInterface; +use Sylius\Component\Core\Model\AdjustmentInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\PayPalPlugin\Api\UpdateOrderApiInterface; @@ -55,12 +56,13 @@ function it_updates_pay_pal_order_with_given_new_total( ->willReturn(['items' => ['data'], 'total_item_value' => '10.00', 'total_tax' => '1.00']) ; - $paymentReferenceNumberProvider->provide($payment)->willReturn('INVOICE_NUMBER'); + $paymentReferenceNumberProvider->provide($payment)->willReturn('INVOICE_ID'); $order->getTotal()->willReturn(1122); $order->getCurrencyCode()->willReturn('USD'); $order->getShippingTotal()->willReturn(22); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $shippingAddress->getFullName()->willReturn('John Doe'); $shippingAddress->getStreet()->willReturn('Main St. 123'); @@ -78,7 +80,7 @@ function it_updates_pay_pal_order_with_given_new_total( $data[0]['op'] === 'replace' && $data[0]['path'] === '/purchase_units/@reference_id==\'REFERENCE-ID\'' && $data[0]['value']['reference_id'] === 'REFERENCE-ID' && - $data[0]['value']['invoice_number'] === 'INVOICE_NUMBER' && + $data[0]['value']['invoice_id'] === 'INVOICE_ID' && $data[0]['value']['amount']['value'] === '11.22' && $data[0]['value']['amount']['currency_code'] === 'USD' && $data[0]['value']['amount']['breakdown']['shipping']['value'] === '0.22' && @@ -115,12 +117,13 @@ function it_updates_digital_order( ->willReturn(['items' => ['data'], 'total_item_value' => '10.00', 'total_tax' => '1.22']) ; - $paymentReferenceNumberProvider->provide($payment)->willReturn('INVOICE_NUMBER'); + $paymentReferenceNumberProvider->provide($payment)->willReturn('INVOICE_ID'); $order->getTotal()->willReturn(1122); $order->getCurrencyCode()->willReturn('USD'); $order->getShippingTotal()->willReturn(0); $order->getOrderPromotionTotal()->willReturn(0); + $order->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)->willReturn(0); $order->isShippingRequired()->willReturn(false); @@ -132,7 +135,7 @@ function it_updates_digital_order( $data[0]['op'] === 'replace' && $data[0]['path'] === '/purchase_units/@reference_id==\'REFERENCE-ID\'' && $data[0]['value']['reference_id'] === 'REFERENCE-ID' && - $data[0]['value']['invoice_number'] === 'INVOICE_NUMBER' && + $data[0]['value']['invoice_id'] === 'INVOICE_ID' && $data[0]['value']['amount']['value'] === '11.22' && $data[0]['value']['amount']['currency_code'] === 'USD' && $data[0]['value']['amount']['breakdown']['shipping']['value'] === '0.00' && diff --git a/spec/Model/PayPalOrderSpec.php b/spec/Model/PayPalOrderSpec.php index d08b1b9c..5698054e 100644 --- a/spec/Model/PayPalOrderSpec.php +++ b/spec/Model/PayPalOrderSpec.php @@ -36,7 +36,7 @@ public function it_returns_full_paypal_order_data( $payPalPurchaseUnit->toArray()->willReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, @@ -86,7 +86,7 @@ public function it_returns_full_paypal_order_data( 'purchase_units' => [ [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, @@ -146,7 +146,7 @@ public function it_returns_paypal_order_data_without_shipping_address( $payPalPurchaseUnit->toArray()->willReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, @@ -185,7 +185,7 @@ public function it_returns_paypal_order_data_without_shipping_address( 'purchase_units' => [ [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, @@ -234,7 +234,7 @@ public function it_returns_paypal_order_data_if_shipping_is_not_required( $payPalPurchaseUnit->toArray()->willReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, @@ -273,7 +273,7 @@ public function it_returns_paypal_order_data_if_shipping_is_not_required( 'purchase_units' => [ [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => 100, diff --git a/spec/Model/PayPalPurchaseUnitSpec.php b/spec/Model/PayPalPurchaseUnitSpec.php index 72656a81..d174f26f 100644 --- a/spec/Model/PayPalPurchaseUnitSpec.php +++ b/spec/Model/PayPalPurchaseUnitSpec.php @@ -22,7 +22,7 @@ function let(AddressInterface $shippingAddress): void { $this->beConstructedWith( 'REFERENCE_ID', - 'INVOICE_NUMBER', + 'INVOICE_ID', 'CURRENCY_CODE', 10000, 1000, @@ -48,7 +48,7 @@ function it_returns_proper_paypal_purchase_unit(AddressInterface $shippingAddres $this->toArray()->shouldReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => '100.00', @@ -69,6 +69,10 @@ function it_returns_proper_paypal_purchase_unit(AddressInterface $shippingAddres 'currency_code' => 'CURRENCY_CODE', 'value' => '0.00', ], + 'shipping_discount' => [ + 'currency_code' => 'CURRENCY_CODE', + 'value' => '0.00', + ], ], ], 'payee' => [ @@ -97,7 +101,7 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_required(Addr { $this->beConstructedWith( 'REFERENCE_ID', - 'INVOICE_NUMBER', + 'INVOICE_ID', 'CURRENCY_CODE', 10000, 1000, @@ -113,7 +117,7 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_required(Addr $this->toArray()->shouldReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => '100.00', @@ -134,6 +138,10 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_required(Addr 'currency_code' => 'CURRENCY_CODE', 'value' => '0.00', ], + 'shipping_discount' => [ + 'currency_code' => 'CURRENCY_CODE', + 'value' => '0.00', + ], ], ], 'payee' => [ @@ -151,7 +159,7 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_set(): void { $this->beConstructedWith( 'REFERENCE_ID', - 'INVOICE_NUMBER', + 'INVOICE_ID', 'CURRENCY_CODE', 10000, 1000, @@ -167,7 +175,7 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_set(): void $this->toArray()->shouldReturn( [ 'reference_id' => 'REFERENCE_ID', - 'invoice_number' => 'INVOICE_NUMBER', + 'invoice_id' => 'INVOICE_ID', 'amount' => [ 'currency_code' => 'CURRENCY_CODE', 'value' => '100.00', @@ -188,6 +196,10 @@ function it_returns_proper_paypal_purchase_unit_if_shipping_is_not_set(): void 'currency_code' => 'CURRENCY_CODE', 'value' => '0.00', ], + 'shipping_discount' => [ + 'currency_code' => 'CURRENCY_CODE', + 'value' => '0.00', + ], ], ], 'payee' => [ diff --git a/src/Api/CreateOrderApi.php b/src/Api/CreateOrderApi.php index ac197631..b196e9af 100644 --- a/src/Api/CreateOrderApi.php +++ b/src/Api/CreateOrderApi.php @@ -14,6 +14,7 @@ namespace Sylius\PayPalPlugin\Api; use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; +use Sylius\Component\Core\Model\AdjustmentInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -62,12 +63,16 @@ public function create(string $token, PaymentInterface $payment, string $referen Assert::keyExists($config, 'merchant_id'); Assert::keyExists($config, 'sylius_merchant_id'); + $shippingDiscount = $order->getAdjustmentsTotalRecursively( + AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT, + ); + $payPalPurchaseUnit = new PayPalPurchaseUnit( $referenceId, $this->paymentReferenceNumberProvider->provide($payment), (string) $order->getCurrencyCode(), (int) $payment->getAmount(), - $order->getShippingTotal(), + $order->getShippingTotal() - $shippingDiscount, (float) $payPalItemData['total_item_value'], (float) $payPalItemData['total_tax'], $order->getOrderPromotionTotal(), @@ -75,6 +80,7 @@ public function create(string $token, PaymentInterface $payment, string $referen (array) $payPalItemData['items'], $order->isShippingRequired(), $order->getShippingAddress(), + shippingDiscountValue: $shippingDiscount, ); $payPalOrder = new PayPalOrder($order, $payPalPurchaseUnit, self::PAYPAL_INTENT_CAPTURE); diff --git a/src/Api/RefundPaymentApi.php b/src/Api/RefundPaymentApi.php index cb9e4ce2..aaa40d9a 100644 --- a/src/Api/RefundPaymentApi.php +++ b/src/Api/RefundPaymentApi.php @@ -35,7 +35,7 @@ public function refund( return $this->client->post( sprintf('v2/payments/captures/%s/refund', $paymentId), $token, - ['amount' => ['value' => $amount, 'currency_code' => $currencyCode], 'invoice_number' => $invoiceNumber], + ['amount' => ['value' => $amount, 'currency_code' => $currencyCode], 'invoice_id' => $invoiceNumber], ['PayPal-Auth-Assertion' => $payPalAuthAssertion], ); } diff --git a/src/Api/UpdateOrderApi.php b/src/Api/UpdateOrderApi.php index 20b6ec7d..7989b371 100644 --- a/src/Api/UpdateOrderApi.php +++ b/src/Api/UpdateOrderApi.php @@ -13,6 +13,7 @@ namespace Sylius\PayPalPlugin\Api; +use Sylius\Component\Core\Model\AdjustmentInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\PayPalPlugin\Client\PayPalClientInterface; @@ -50,12 +51,16 @@ public function update( $payPalItemData = $this->payPalItemsDataProvider->provide($order); + $shippingDiscount = $order->getAdjustmentsTotalRecursively( + AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT, + ); + $data = new PayPalPurchaseUnit( $referenceId, $this->paymentReferenceNumberProvider->provide($payment), (string) $order->getCurrencyCode(), (int) $payment->getAmount(), - $order->getShippingTotal(), + $order->getShippingTotal() - $shippingDiscount, (float) $payPalItemData['total_item_value'], (float) $payPalItemData['total_tax'], $order->getOrderPromotionTotal(), @@ -63,6 +68,7 @@ public function update( (array) $payPalItemData['items'], $order->isShippingRequired(), $order->getShippingAddress(), + shippingDiscountValue: $shippingDiscount, ); return $this->client->patch( diff --git a/src/Controller/CreatePayPalOrderFromCartAction.php b/src/Controller/CreatePayPalOrderFromCartAction.php index 6948a735..0a44e42a 100644 --- a/src/Controller/CreatePayPalOrderFromCartAction.php +++ b/src/Controller/CreatePayPalOrderFromCartAction.php @@ -17,8 +17,12 @@ use GuzzleHttp\Exception\GuzzleException; use Payum\Core\Payum; use SM\Factory\FactoryInterface; +use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; +use Sylius\Component\Core\Model\PaymentMethodInterface; +use Sylius\Component\Core\Payment\Remover\OrderPaymentsRemoverInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; +use Sylius\Component\Order\Processor\OrderProcessorInterface; use Sylius\PayPalPlugin\Provider\OrderProviderInterface; use Sylius\PayPalPlugin\Resolver\CapturePaymentResolverInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -35,6 +39,8 @@ public function __construct( private readonly ObjectManager $paymentManager, private readonly OrderProviderInterface $orderProvider, private readonly CapturePaymentResolverInterface $capturePaymentResolver, + private readonly ?OrderPaymentsRemoverInterface $orderPaymentsRemover = null, + private readonly ?OrderProcessorInterface $orderProcessor = null, ) { if (null !== $this->payum) { trigger_deprecation( @@ -66,6 +72,22 @@ public function __construct( ), ); } + if (null === $this->orderPaymentsRemover) { + trigger_deprecation( + 'sylius/paypal-plugin', + '1.6', + 'Not passing an $orderPaymentsRemover to %s constructor is deprecated and will be prohibited in 3.0', + self::class, + ); + } + if (null === $this->orderProcessor) { + trigger_deprecation( + 'sylius/paypal-plugin', + '1.6', + 'Not passing an $orderProcessor to %s constructor is deprecated and will be prohibited in 3.0', + self::class, + ); + } } public function __invoke(Request $request): Response @@ -73,12 +95,10 @@ public function __invoke(Request $request): Response $id = $request->attributes->getInt('id'); $order = $this->orderProvider->provideOrderById($id); - /** @var PaymentInterface $payment */ - $payment = $order->getLastPayment(PaymentInterface::STATE_CART); - try { + $payment = $this->getPayment($order); $this->capturePaymentResolver->resolve($payment); - } catch (GuzzleException $exception) { + } catch (\DomainException|GuzzleException) { /** @var FlashBagInterface $flashBag */ $flashBag = $request->getSession()->getBag('flashes'); $flashBag->add('error', 'sylius.pay_pal.something_went_wrong'); @@ -94,4 +114,26 @@ public function __invoke(Request $request): Response 'status' => $payment->getState(), ]); } + + private function getPayment(OrderInterface $order): PaymentInterface + { + /** @var PaymentInterface $payment */ + $payment = $order->getLastPayment(PaymentInterface::STATE_CART); + /** @var PaymentMethodInterface|null $paymentMethod */ + $paymentMethod = $payment->getMethod(); + $factoryName = $paymentMethod?->getGatewayConfig()?->getFactoryName(); + + if ($factoryName === 'sylius.pay_pal') { + return $payment; + } + + if ($this->orderPaymentsRemover === null || $this->orderProcessor === null) { + throw new \DomainException('OrderPaymentsRemover and OrderProcessor must be provided to create a new payment.'); + } + + $this->orderPaymentsRemover->removePayments($order); + $this->orderProcessor->process($order); + + return $order->getLastPayment(PaymentInterface::STATE_CART); + } } diff --git a/src/Controller/ProcessPayPalOrderAction.php b/src/Controller/ProcessPayPalOrderAction.php index 5b1475a8..228276b3 100644 --- a/src/Controller/ProcessPayPalOrderAction.php +++ b/src/Controller/ProcessPayPalOrderAction.php @@ -23,7 +23,6 @@ use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Component\Core\OrderCheckoutTransitions; use Sylius\Component\Core\Repository\CustomerRepositoryInterface; -use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface; use Sylius\PayPalPlugin\Api\OrderDetailsApiInterface; @@ -36,7 +35,6 @@ final class ProcessPayPalOrderAction { public function __construct( - private readonly OrderRepositoryInterface $orderRepository, private readonly CustomerRepositoryInterface $customerRepository, private readonly FactoryInterface $customerFactory, private readonly AddressFactoryInterface $addressFactory, @@ -78,12 +76,12 @@ public function __invoke(Request $request): Response $purchaseUnit = (array) $data['purchase_units'][0]; - $address = $this->addressFactory->createForCustomer($customer); + $address = $this->addressFactory->createNew(); if ($order->isShippingRequired()) { $name = explode(' ', $purchaseUnit['shipping']['name']['full_name']); - $address->setFirstName($name[0]); - $address->setLastName($name[1]); + $address->setLastName(array_pop($name) ?? ''); + $address->setFirstName(implode(' ', $name)); $address->setStreet($purchaseUnit['shipping']['address']['address_line_1']); $address->setCity($purchaseUnit['shipping']['address']['admin_area_2']); $address->setPostcode($purchaseUnit['shipping']['address']['postal_code']); diff --git a/src/Controller/UpdatePayPalOrderAction.php b/src/Controller/UpdatePayPalOrderAction.php index 22adc9f1..d13c7fd2 100644 --- a/src/Controller/UpdatePayPalOrderAction.php +++ b/src/Controller/UpdatePayPalOrderAction.php @@ -20,7 +20,6 @@ use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Component\Order\Processor\OrderProcessorInterface; use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface; -use Sylius\PayPalPlugin\Api\OrderDetailsApiInterface; use Sylius\PayPalPlugin\Api\UpdateOrderApiInterface; use Sylius\PayPalPlugin\Provider\PaymentProviderInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -29,32 +28,13 @@ final class UpdatePayPalOrderAction { - private PaymentProviderInterface $paymentProvider; - - private CacheAuthorizeClientApiInterface $authorizeClientApi; - - private OrderDetailsApiInterface $orderDetailsApi; - - private UpdateOrderApiInterface $updateOrderApi; - - private AddressFactoryInterface $addressFactory; - - private OrderProcessorInterface $orderProcessor; - public function __construct( - PaymentProviderInterface $paymentProvider, - CacheAuthorizeClientApiInterface $authorizeClientApi, - OrderDetailsApiInterface $orderDetailsApi, - UpdateOrderApiInterface $updateOrderApi, - AddressFactoryInterface $addressFactory, - OrderProcessorInterface $orderProcessor, + private readonly PaymentProviderInterface $paymentProvider, + private readonly CacheAuthorizeClientApiInterface $authorizeClientApi, + private readonly UpdateOrderApiInterface $updateOrderApi, + private readonly AddressFactoryInterface $addressFactory, + private readonly OrderProcessorInterface $orderProcessor, ) { - $this->paymentProvider = $paymentProvider; - $this->authorizeClientApi = $authorizeClientApi; - $this->orderDetailsApi = $orderDetailsApi; - $this->updateOrderApi = $updateOrderApi; - $this->addressFactory = $addressFactory; - $this->orderProcessor = $orderProcessor; } public function __invoke(Request $request): Response @@ -67,8 +47,7 @@ public function __invoke(Request $request): Response $paymentMethod = $payment->getMethod(); $token = $this->authorizeClientApi->authorize($paymentMethod); - /** @var array $shippingAddress */ - $shippingAddress = $request->request->get('shipping_address'); + $shippingAddress = $request->request->all('shipping_address'); /** @var AddressInterface $address */ $address = $this->addressFactory->createNew(); diff --git a/src/Factory/PayPalPaymentMethodNewResourceFactory.php b/src/Factory/PayPalPaymentMethodNewResourceFactory.php index ef0dea41..a54c2230 100644 --- a/src/Factory/PayPalPaymentMethodNewResourceFactory.php +++ b/src/Factory/PayPalPaymentMethodNewResourceFactory.php @@ -16,9 +16,9 @@ use Sylius\Bundle\ResourceBundle\Controller\NewResourceFactoryInterface; use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration; use Sylius\Component\Core\Model\PaymentMethodInterface; -use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\Component\Resource\Model\ResourceInterface; use Sylius\PayPalPlugin\Onboarding\Processor\OnboardingProcessorInterface; +use Sylius\Resource\Factory\FactoryInterface; final class PayPalPaymentMethodNewResourceFactory implements NewResourceFactoryInterface { @@ -36,6 +36,7 @@ public function __construct( public function create(RequestConfiguration $requestConfiguration, FactoryInterface $factory): ResourceInterface { + /** @var ResourceInterface $resource */ $resource = $this->newResourceFactory->create($requestConfiguration, $factory); if (!$resource instanceof PaymentMethodInterface) { diff --git a/src/Model/PayPalPurchaseUnit.php b/src/Model/PayPalPurchaseUnit.php index e28f76f8..d587fa7e 100644 --- a/src/Model/PayPalPurchaseUnit.php +++ b/src/Model/PayPalPurchaseUnit.php @@ -18,80 +18,29 @@ class PayPalPurchaseUnit { - /** @var string */ - private $referenceId; - - /** @var string */ - private $invoiceNumber; - - /** @var string */ - private $currencyCode; - - /** @var int */ - private $totalAmount; - - /** @var int */ - private $shippingValue; - - /** @var float */ - private $itemTotalValue; - - /** @var float */ - private $taxTotalValue; - - /** @var int */ - private $discountValue; - - /** @var string */ - private $softDescriptor; - - /** @var string */ - private $merchantId; - - /** @var ?AddressInterface */ - private $shippingAddress; - - /** @var bool */ - private $shippingRequired; - - /** @var array */ - private $items; - public function __construct( - string $referenceId, - string $invoiceNumber, - string $currencyCode, - int $totalAmount, - int $shippingValue, - float $itemTotalValue, - float $taxTotalValue, - int $discountValue, - string $merchantId, - array $items, - bool $shippingRequired, - ?AddressInterface $shippingAddress = null, - string $softDescriptor = 'Sylius PayPal Payment', + private readonly string $referenceId, + private readonly string $invoiceNumber, + private readonly string $currencyCode, + private readonly int $totalAmount, + private readonly int $shippingValue, + private readonly float $itemTotalValue, + private readonly float $taxTotalValue, + private readonly int $discountValue, + private readonly string $merchantId, + private readonly array $items, + private readonly bool $shippingRequired, + private readonly ?AddressInterface $shippingAddress = null, + private readonly string $softDescriptor = 'Sylius PayPal Payment', + private readonly int $shippingDiscountValue = 0, ) { - $this->referenceId = $referenceId; - $this->invoiceNumber = $invoiceNumber; - $this->currencyCode = $currencyCode; - $this->totalAmount = $totalAmount; - $this->shippingValue = $shippingValue; - $this->itemTotalValue = $itemTotalValue; - $this->taxTotalValue = $taxTotalValue; - $this->discountValue = $discountValue; - $this->merchantId = $merchantId; - $this->items = $items; - $this->shippingRequired = $shippingRequired; - $this->shippingAddress = $shippingAddress; - $this->softDescriptor = $softDescriptor; } public function toArray(): array { $paypalPurchaseUnit = [ 'reference_id' => $this->referenceId, - 'invoice_number' => $this->invoiceNumber, + 'invoice_id' => $this->invoiceNumber, 'amount' => [ 'currency_code' => $this->currencyCode, 'value' => number_format($this->totalAmount / 100, 2, '.', ''), @@ -112,6 +61,10 @@ public function toArray(): array 'currency_code' => $this->currencyCode, 'value' => number_format(abs($this->discountValue) / 100, 2, '.', ''), ], + 'shipping_discount' => [ + 'currency_code' => $this->currencyCode, + 'value' => number_format(abs($this->shippingDiscountValue) / 100, 2, '.', ''), + ], ], ], 'payee' => [ diff --git a/src/Resources/config/services/controller.xml b/src/Resources/config/services/controller.xml index d9ae9937..61b48f17 100644 --- a/src/Resources/config/services/controller.xml +++ b/src/Resources/config/services/controller.xml @@ -80,6 +80,8 @@ + + @@ -117,7 +119,6 @@ - diff --git a/tests/DataFixtures/ORM/resources/new_cart_with_cash_on_delivery_method.yaml b/tests/DataFixtures/ORM/resources/new_cart_with_cash_on_delivery_method.yaml new file mode 100644 index 00000000..3ec3c74c --- /dev/null +++ b/tests/DataFixtures/ORM/resources/new_cart_with_cash_on_delivery_method.yaml @@ -0,0 +1,30 @@ +Sylius\Component\Core\Model\Order: + new_cart: + channel: "@channel_web" + items: ["@sw_mug_item"] + currencyCode: "USD" + localeCode: "en_US" + customer: "@customer_oliver" + state: "cart" + checkoutState: "shipping_selected" + tokenValue: "TOKEN" + payments: ["@paypal_payment"] + +Sylius\Component\Core\Model\OrderItem: + sw_mug_item: + units: ["@sw_mug_item_unit1", "@sw_mug_item_unit2"] + variant: "@mug_sw" + order: "@new_cart" + +Sylius\Component\Core\Model\OrderItemUnit: + sw_mug_item_unit1: + __construct: ["@sw_mug_item"] + sw_mug_item_unit2: + __construct: ["@sw_mug_item"] + +Sylius\Component\Core\Model\Payment: + paypal_payment: + method: "@cash_on_delivery" + currencyCode: "USD" + amount: 40 + state: "cart" diff --git a/tests/DataFixtures/ORM/resources/shop.yaml b/tests/DataFixtures/ORM/resources/shop.yaml index 2c149940..aa7bf8bc 100644 --- a/tests/DataFixtures/ORM/resources/shop.yaml +++ b/tests/DataFixtures/ORM/resources/shop.yaml @@ -90,6 +90,14 @@ Sylius\Component\Core\Model\PaymentMethod: translations: - "@paypal_translation" channels: ["@channel_web"] + cash_on_delivery: + code: CASH_ON_DELIVERY + enabled: true + gatewayConfig: "@cash_on_delivery_config" + currentLocale: en_US + translations: + - "@cash_on_delivery_translation" + channels: ["@channel_web"] Sylius\Bundle\PayumBundle\Model\GatewayConfig: paypal_config: @@ -102,6 +110,10 @@ Sylius\Bundle\PayumBundle\Model\GatewayConfig: sylius_merchant_id: "SYLIUS_MERCHANT_ID" partner_attribution_id: "PARTNER_ATTRIBUTION_ID" use_authorize: true + cash_on_delivery_config: + gatewayName: "offline" + factoryName: "sylius.offline" + config: [] Sylius\Component\Payment\Model\PaymentMethodTranslation: paypal_translation: @@ -109,3 +121,8 @@ Sylius\Component\Payment\Model\PaymentMethodTranslation: locale: "en_US" description: translatable: "@paypal" + cash_on_delivery_translation: + name: "Cash on delivery" + locale: "en_US" + description: + translatable: "@cash_on_delivery" diff --git a/tests/Functional/CreatePayPalOrderFromCartActionTest.php b/tests/Functional/CreatePayPalOrderFromCartActionTest.php index 86821bf5..73fbc444 100644 --- a/tests/Functional/CreatePayPalOrderFromCartActionTest.php +++ b/tests/Functional/CreatePayPalOrderFromCartActionTest.php @@ -33,4 +33,21 @@ public function it_creates_pay_pal_order_from_cart_and_returns_its_data(): void $this->assertSame($content['orderID'], 'PAYPAL_ORDER_ID'); $this->assertSame($content['status'], 'cart'); } + + /** @test */ + public function it_creates_pay_pal_order_from_cart_and_returns_its_data_if_payment_method_is_different_then_pay_pal(): void + { + $order = $this->loadFixturesFromFiles(['resources/shop.yaml', 'resources/new_cart_with_cash_on_delivery_method.yaml']); + /** @var int $orderId */ + $orderId = $order['new_cart']->getId(); + + $this->client->request('POST', '/en_US/create-pay-pal-order-from-cart/' . $orderId); + + $response = $this->client->getResponse(); + $content = (array) json_decode($response->getContent(), true); + + $this->assertSame($content['id'], $orderId); + $this->assertSame($content['orderID'], 'PAYPAL_ORDER_ID'); + $this->assertSame($content['status'], 'cart'); + } }