diff --git a/api/order-functions.php b/api/order-functions.php index f477651fd..701b064fb 100644 --- a/api/order-functions.php +++ b/api/order-functions.php @@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\PPCP; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; @@ -107,3 +108,14 @@ function ppcp_void_order( WC_Order $wc_order ): void { $refund_processor->void( $order ); } + +/** + * Updates the PayPal refund fees totals on an order. + * + * @param WC_Order $wc_order The WC order. + */ +function ppcp_update_order_refund_fees( WC_Order $wc_order ): void { + $updater = PPCP::container()->get( 'wcgateway.helper.refund-fees-updater' ); + assert( $updater instanceof RefundFeesUpdater ); + $updater->update( $wc_order ); +} diff --git a/changelog.txt b/changelog.txt index b1a4ab20d..500940c22 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,11 @@ *** Changelog *** -= 2.2.1 - xxxx-xx-xx = += 2.2.2 - 2023-08-29 = +* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529 +* Enhancement - HPOS compatibility issues #1594 +* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607 + += 2.2.1 - 2023-08-24 = * Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536 * Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565 * Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533 diff --git a/composer.json b/composer.json index 4a94eb54b..bb2ccbf7c 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "autoload": { "psr-4": { "WooCommerce\\PayPalCommerce\\": "src", + "WooCommerce\\PayPalCommerce\\Common\\": "lib/common/", "WooCommerce\\PayPalCommerce\\Vendor\\": "lib/packages/" }, "files": [ diff --git a/lib/README.md b/lib/README.md index 9114c43ef..d38e3edd4 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,5 +1,10 @@ +## packages The packages that are likely to cause conflicts with other plugins (by loading multiple incompatible versions). Their namespaces are isolated by [Mozart](https://github.com/coenjacobs/mozart). Currently, the packages are simply added in the repo to avoid making the build process more complex (Mozart has different PHP requirements). We need to isolate only PSR-11 containers and Dhii modularity packages, which are not supposed to change often. + +## common +This folder contains reusable classes or components that do not fit into any specific module. +They are designed to be versatile and can be used by any module within the plugin. diff --git a/lib/common/Pattern/SingletonDecorator.php b/lib/common/Pattern/SingletonDecorator.php new file mode 100644 index 000000000..54282dc91 --- /dev/null +++ b/lib/common/Pattern/SingletonDecorator.php @@ -0,0 +1,68 @@ +callable = $callable; + } + + /** + * The make constructor. + * + * @param callable $callable + * @return self + */ + public static function make( callable $callable ): self { + return new static( $callable ); + } + + /** + * Invokes a callable once and returns the same result on subsequent invokes. + * + * @param mixed ...$args Arguments to be passed to the callable. + * @return mixed + */ + public function __invoke( ...$args ) { + if ( ! $this->executed ) { + $this->result = call_user_func_array( $this->callable, $args ); + $this->executed = true; + } + + return $this->result; + } +} diff --git a/lib/common/Pattern/SingletonTrait.php b/lib/common/Pattern/SingletonTrait.php new file mode 100644 index 000000000..cf09570c7 --- /dev/null +++ b/lib/common/Pattern/SingletonTrait.php @@ -0,0 +1,41 @@ +type = $type; $this->message = $message; $this->dismissable = $dismissable; + $this->wrapper = $wrapper; } /** @@ -74,4 +83,13 @@ class Message { public function is_dismissable(): bool { return $this->dismissable; } + + /** + * Returns the wrapper selector that will contain the notice. + * + * @return string + */ + public function wrapper(): string { + return $this->wrapper; + } } diff --git a/modules/ppcp-admin-notices/src/Renderer/Renderer.php b/modules/ppcp-admin-notices/src/Renderer/Renderer.php index b67da1c30..1202d2ab4 100644 --- a/modules/ppcp-admin-notices/src/Renderer/Renderer.php +++ b/modules/ppcp-admin-notices/src/Renderer/Renderer.php @@ -41,9 +41,10 @@ class Renderer implements RendererInterface { $messages = $this->repository->current_message(); foreach ( $messages as $message ) { printf( - '
%s
%s
DAY_IN_SECONDS
+ DAY_IN_SECONDS
+ realpath( __FILE__ )
diff --git a/readme.txt b/readme.txt
index 4837aa324..c36b06606 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell,
Requires at least: 5.3
Tested up to: 6.3
Requires PHP: 7.2
-Stable tag: 2.2.1
+Stable tag: 2.2.2
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -81,7 +81,12 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog ==
-= 2.2.1 - xxxx-xx-xx =
+= 2.2.2 - 2023-08-29 =
+* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529
+* Enhancement - HPOS compatibility issues #1594
+* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607
+
+= 2.2.1 - 2023-08-24 =
* Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536
* Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565
* Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533
diff --git a/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php b/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
new file mode 100644
index 000000000..c9ccedc82
--- /dev/null
+++ b/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
@@ -0,0 +1,130 @@
+order_endpoint = $this->createMock(OrderEndpoint::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->refundFeesUpdater = new RefundFeesUpdater($this->order_endpoint, $this->logger);
+ }
+
+ public function testUpdateWithoutPaypalOrderId(): void
+ {
+ $wc_order_id = 123;
+
+ $wc_order = Mockery::mock(WC_Order::class);
+ $wc_order->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn(null);
+
+ $wc_order->expects('get_id')->andReturn($wc_order_id);
+
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->refundFeesUpdater->update($wc_order);
+ }
+
+ public function testUpdateWithValidData(): void
+ {
+ $wc_order_id = 123;
+ $paypal_order_id = 'test_order_id';
+ $refund_id = 'XYZ123';
+ $meta_data = [
+ 'gross_amount' => ['value' => 10.0, 'currency_code' => 'USD'],
+ 'paypal_fee' => ['value' => 7.0, 'currency_code' => 'USD'],
+ 'net_amount' => ['value' => 3.0, 'currency_code' => 'USD'],
+ ];
+
+ when('get_comments')->justReturn([]);
+
+ $wc_order = Mockery::mock(WC_Order::class);
+ $wc_order->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn($paypal_order_id);
+
+ $wc_order->expects('get_id')
+ ->times(3)
+ ->andReturn($wc_order_id);
+
+ $wc_order->expects('update_meta_data')
+ ->once()
+ ->with('_ppcp_paypal_refund_fees', $meta_data);
+
+ $wc_order->expects('add_order_note')
+ ->once()
+ ->withArgs(function ($arg) use ($refund_id) {
+ return strpos($arg, $refund_id) !== false;
+ });
+
+ $wc_order->expects('save')->once();
+
+ $moneyGross = Mockery::mock(Money::class);
+ $moneyGross->expects('value')->once()->andReturn($meta_data['gross_amount']['value']);
+ $moneyGross->expects('currency_code')->once()->andReturn($meta_data['gross_amount']['currency_code']);
+
+ $moneyFee = Mockery::mock(Money::class);
+ $moneyFee->expects('value')->once()->andReturn($meta_data['paypal_fee']['value']);
+ $moneyFee->expects('currency_code')->once()->andReturn($meta_data['paypal_fee']['currency_code']);
+
+ $moneyNet = Mockery::mock(Money::class);
+ $moneyNet->expects('value')->once()->andReturn($meta_data['net_amount']['value']);
+ $moneyNet->expects('currency_code')->once()->andReturn($meta_data['net_amount']['currency_code']);
+
+ $breakdown = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['gross_amount', 'paypal_fee', 'net_amount'])
+ ->getMock();
+ $breakdown->method('gross_amount')->willReturn($moneyGross);
+ $breakdown->method('paypal_fee')->willReturn($moneyFee);
+ $breakdown->method('net_amount')->willReturn($moneyNet);
+
+ $refund = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['id', 'seller_payable_breakdown'])
+ ->getMock();
+ $refund->method('id')->willReturn($refund_id);
+ $refund->method('seller_payable_breakdown')->willReturn($breakdown);
+
+ $payments = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['refunds'])
+ ->getMock();
+ $payments->method('refunds')->willReturn([$refund]);
+
+ $purchase_unit = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['payments'])
+ ->getMock();
+ $purchase_unit->method('payments')->willReturn($payments);
+
+ $paypal_order = Mockery::mock(Order::class);
+ $paypal_order->expects('purchase_units')->andReturn([$purchase_unit]);
+
+ $this->order_endpoint->method('order')->with($paypal_order_id)->willReturn($paypal_order);
+
+ $this->logger->expects($this->exactly(2))
+ ->method('debug')
+ ->withConsecutive(
+ [$this->stringContains('Updating order paypal refund fees.')],
+ [$this->stringContains('Updated order paypal refund fees.')]
+ );
+
+ $this->refundFeesUpdater->update($wc_order);
+ }
+}
diff --git a/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php b/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
index 240735f73..2793039aa 100644
--- a/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
@@ -43,10 +43,19 @@ class PaymentsTest extends TestCase
'status' => 'CREATED',
]
);
- $captures = [$capture];
- $authorizations = [$authorization];
+ $refund = \Mockery::mock(Refund::class);
+ $refund->shouldReceive('to_array')->andReturn(
+ [
+ 'id' => 'refund',
+ 'status' => 'CREATED',
+ ]
+ );
- $testee = new Payments($authorizations, $captures);
+ $authorizations = [$authorization];
+ $captures = [$capture];
+ $refunds = [$refund];
+
+ $testee = new Payments($authorizations, $captures, $refunds);
$this->assertEquals(
[
@@ -62,6 +71,12 @@ class PaymentsTest extends TestCase
'status' => 'CREATED',
],
],
+ 'refunds' => [
+ [
+ 'id' => 'refund',
+ 'status' => 'CREATED',
+ ],
+ ],
],
$testee->to_array()
);
diff --git a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
index dbb18c4de..c9d81f8aa 100644
--- a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@@ -75,24 +76,43 @@ class PurchaseUnitTest extends TestCase
$this->assertEquals($expected, $testee->to_array());
}
- /**
- * @dataProvider dataForDitchTests
- * @param array $items
- * @param Amount $amount
- * @param bool $doDitch
- */
- public function testDitchMethod(array $items, Amount $amount, bool $doDitch, string $message)
+ /**
+ * @dataProvider dataForDitchTests
+ * @param array $items
+ * @param Amount $amount
+ * @param bool|array $doDitch
+ * @param string $message
+ */
+ public function testDitchMethod(array $items, Amount $amount, $doDitch, string $message)
{
+ if (is_array($doDitch)) {
+ $doDitchItems = $doDitch['items'];
+ $doDitchBreakdown = $doDitch['breakdown'];
+ $doDitchTax = $doDitch['tax'];
+ } else {
+ $doDitchItems = $doDitch;
+ $doDitchBreakdown = $doDitch;
+ $doDitchTax = $doDitch;
+ }
+
$testee = new PurchaseUnit(
$amount,
$items
);
+ $testee->set_sanitizer(new PurchaseUnitSanitizer(PurchaseUnitSanitizer::MODE_DITCH));
+
$array = $testee->to_array();
- $resultItems = $doDitch === ! array_key_exists('items', $array);
- $resultBreakdown = $doDitch === ! array_key_exists('breakdown', $array['amount']);
+ $resultItems = $doDitchItems === ! array_key_exists('items', $array);
+
+ $resultBreakdown = $doDitchBreakdown === ! array_key_exists('breakdown', $array['amount']);
$this->assertTrue($resultItems, $message);
$this->assertTrue($resultBreakdown, $message);
+
+ foreach ($array['items'] ?? [] as $item) {
+ $resultTax = $doDitchTax === ! array_key_exists('tax', $item);
+ $this->assertTrue($resultTax, $message);
+ }
}
public function dataForDitchTests() : array
@@ -406,6 +426,58 @@ class PurchaseUnitTest extends TestCase
'insurance' => null,
],
],
+ 'ditch_items_total_but_not_breakdown' => [
+ 'message' => 'Items should be ditched because the item total does not add up. But not breakdown because it adds up.',
+ 'ditch' => [
+ 'items' => true,
+ 'breakdown' => false,
+ 'tax' => true,
+ ],
+ 'items' => [
+ [
+ 'value' => 11,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26,
+ 'breakdown' => [
+ 'item_total' => 20,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'ditch_items_tax_with_incorrect_tax_total' => [
+ 'message' => 'Ditch tax from items. Items should not be ditched because the mismatch is on the tax.',
+ 'ditch' => [
+ 'items' => false,
+ 'breakdown' => false,
+ 'tax' => true,
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 4,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26,
+ 'breakdown' => [
+ 'item_total' => 20,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
];
$values = [];
@@ -421,10 +493,16 @@ class PurchaseUnitTest extends TestCase
'tax' => $tax,
'quantity'=> $item['quantity'],
'category' => $item['category'],
- 'to_array' => [],
+ 'to_array' => [
+ 'unit_amount' => $unitAmount->to_array(),
+ 'tax' => $tax->to_array(),
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ],
]
);
}
+
$breakdown = null;
if ($test['breakdown']) {
$breakdown = Mockery::mock(AmountBreakdown::class);
@@ -438,10 +516,29 @@ class PurchaseUnitTest extends TestCase
return $money;
});
}
+
+ $breakdown
+ ->shouldReceive('to_array')
+ ->andReturn(
+ array_map(
+ function ($value) {
+ return $value ? (new Money($value, 'EUR'))->to_array() : null;
+ },
+ $test['breakdown']
+ )
+ );
}
+
+ $amountMoney = new Money($test['amount'], 'EUR');
$amount = Mockery::mock(Amount::class);
- $amount->shouldReceive('to_array')->andReturn(['value' => number_format( $test['amount'], 2, '.', '' ), 'breakdown' => []]);
- $amount->shouldReceive('value_str')->andReturn(number_format( $test['amount'], 2, '.', '' ));
+ $amount
+ ->shouldReceive('to_array')
+ ->andReturn([
+ 'value' => $amountMoney->value_str(),
+ 'currency_code' => $amountMoney->currency_code(),
+ 'breakdown' => $breakdown ? $breakdown->to_array() : [],
+ ]);
+ $amount->shouldReceive('value_str')->andReturn($amountMoney->value_str());
$amount->shouldReceive('currency_code')->andReturn('EUR');
$amount->shouldReceive('breakdown')->andReturn($breakdown);
@@ -456,6 +553,262 @@ class PurchaseUnitTest extends TestCase
return $values;
}
+ /**
+ * @dataProvider dataForExtraLineTests
+ * @param array $items
+ * @param Amount $amount
+ * @param array $expected
+ * @param string $message
+ */
+ public function testExtraLineMethod(array $items, Amount $amount, array $expected, string $message)
+ {
+ $testee = new PurchaseUnit(
+ $amount,
+ $items
+ );
+
+ $testee->set_sanitizer(new PurchaseUnitSanitizer(PurchaseUnitSanitizer::MODE_EXTRA_LINE, $expected['extra_line_name'] ?? null));
+
+ $countItemsBefore = count($items);
+ $array = $testee->to_array();
+ $countItemsAfter = count($array['items']);
+ $extraItem = array_pop($array['items']);
+
+ $this->assertEquals($countItemsBefore + 1, $countItemsAfter, $message);
+ $this->assertEquals($expected['extra_line_value'], $extraItem['unit_amount']['value'], $message);
+ $this->assertEquals($expected['extra_line_name'] ?? PurchaseUnitSanitizer::EXTRA_LINE_NAME, $extraItem['name'], $message);
+
+ foreach ($array['items'] as $i => $item) {
+ $this->assertEquals($expected['item_value'][$i], $item['unit_amount']['value'], $message);
+ }
+ }
+
+ public function dataForExtraLineTests() : array
+ {
+ $data = [
+ 'default' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.',
+ 'expected' => [
+ 'item_value' => [10],
+ 'extra_line_value' => 0.01,
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_custom_name' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.',
+ 'expected' => [
+ 'item_value' => [10],
+ 'extra_line_value' => 0.01,
+ 'extra_line_name' => 'My custom line name',
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_rounding_down' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.00.',
+ 'expected' => [
+ 'item_value' => [10.00],
+ 'extra_line_value' => 0.01
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_two_rounding_down' => [
+ 'message' => 'Extra line should be added with price 0.03 and lines amount 10.00 and 4.99.',
+ 'expected' => [
+ 'item_value' => [10.00, 4.99],
+ 'extra_line_value' => 0.03
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 5,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 36.01,
+ 'breakdown' => [
+ 'item_total' => 30.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_many_roundings_down' => [
+ 'message' => 'Extra line should be added with price 0.01 and lines amount 10.00, 5.00 and 6.66.',
+ 'expected' => [
+ 'item_value' => [10.00, 4.99, 6.66],
+ 'extra_line_value' => 0.02
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 5.001,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 6.666,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 27.67,
+ 'breakdown' => [
+ 'item_total' => 21.67,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ]
+ ];
+
+ $values = [];
+ foreach ($data as $testKey => $test) {
+ $items = [];
+ foreach ($test['items'] as $key => $item) {
+ $unitAmount = new Money($item['value'], 'EUR');
+ $tax = new Money($item['tax'], 'EUR');
+ $items[$key] = Mockery::mock(
+ Item::class,
+ [
+ 'unit_amount' => $unitAmount,
+ 'tax' => $tax,
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ]
+ );
+
+ $items[$key]->shouldReceive('to_array')->andReturnUsing(function (bool $roundToFloor = false) use ($unitAmount, $tax, $item) {
+ return [
+ 'unit_amount' => $unitAmount->to_array($roundToFloor),
+ 'tax' => $tax->to_array(),
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ];
+ });
+
+ }
+
+ $breakdown = null;
+ if ($test['breakdown']) {
+ $breakdown = Mockery::mock(AmountBreakdown::class);
+ foreach ($test['breakdown'] as $method => $value) {
+ $breakdown->shouldReceive($method)->andReturnUsing(function () use ($value) {
+ if (! is_numeric($value)) {
+ return null;
+ }
+
+ $money = new Money($value, 'EUR');
+ return $money;
+ });
+ }
+
+ $breakdown
+ ->shouldReceive('to_array')
+ ->andReturn(
+ array_map(
+ function ($value) {
+ return $value ? (new Money($value, 'EUR'))->to_array() : null;
+ },
+ $test['breakdown']
+ )
+ );
+ }
+
+ $amountMoney = new Money($test['amount'], 'EUR');
+ $amount = Mockery::mock(Amount::class);
+ $amount
+ ->shouldReceive('to_array')
+ ->andReturn([
+ 'value' => $amountMoney->value_str(),
+ 'currency_code' => $amountMoney->currency_code(),
+ 'breakdown' => $breakdown ? $breakdown->to_array() : [],
+ ]);
+ $amount->shouldReceive('value_str')->andReturn($amountMoney->value_str());
+ $amount->shouldReceive('currency_code')->andReturn('EUR');
+ $amount->shouldReceive('breakdown')->andReturn($breakdown);
+
+ $values[$testKey] = [
+ $items,
+ $amount,
+ $test['expected'],
+ $test['message'],
+ ];
+ }
+
+ return $values;
+ }
+
public function testPayee()
{
$amount = Mockery::mock(Amount::class);
diff --git a/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
index 28e14b379..0260be7b0 100644
--- a/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
+++ b/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
@@ -7,6 +7,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@@ -18,11 +19,15 @@ class PaymentsFactoryTest extends TestCase
$authorization->shouldReceive('to_array')->andReturn(['id' => 'foo', 'status' => 'CREATED']);
$capture = Mockery::mock(Capture::class);
$capture->shouldReceive('to_array')->andReturn(['id' => 'capture', 'status' => 'CREATED']);
+ $refund = Mockery::mock(Refund::class);
+ $refund->shouldReceive('to_array')->andReturn(['id' => 'refund', 'status' => 'CREATED']);
$authorizationsFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationsFactory->shouldReceive('from_paypal_response')->andReturn($authorization);
$captureFactory = Mockery::mock(CaptureFactory::class);
$captureFactory->shouldReceive('from_paypal_response')->andReturn($capture);
+ $refundFactory = Mockery::mock(RefundFactory::class);
+ $refundFactory->shouldReceive('from_paypal_response')->andReturn($refund);
$response = (object)[
'authorizations' => [
(object)['id' => 'foo', 'status' => 'CREATED'],
@@ -30,9 +35,12 @@ class PaymentsFactoryTest extends TestCase
'captures' => [
(object)['id' => 'capture', 'status' => 'CREATED'],
],
+ 'refunds' => [
+ (object)['id' => 'refund', 'status' => 'CREATED'],
+ ],
];
- $testee = new PaymentsFactory($authorizationsFactory, $captureFactory);
+ $testee = new PaymentsFactory($authorizationsFactory, $captureFactory, $refundFactory);
$result = $testee->from_paypal_response($response);
$this->assertInstanceOf(Payments::class, $result);
@@ -44,6 +52,9 @@ class PaymentsFactoryTest extends TestCase
'captures' => [
['id' => 'capture', 'status' => 'CREATED'],
],
+ 'refunds' => [
+ ['id' => 'refund', 'status' => 'CREATED'],
+ ],
];
$this->assertEquals($expectedToArray, $result->to_array());
}
diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php
index d079012c3..84ecb0447 100644
--- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php
+++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php
@@ -25,6 +25,7 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use function Brain\Monkey\Functions\when;
class RenewalHandlerTest extends TestCase
{
@@ -115,6 +116,9 @@ class RenewalHandlerTest extends TestCase
->shouldReceive('payment_source')
->andReturn(null);
+ $wcOrder
+ ->shouldReceive('get_meta')
+ ->andReturn('');
$wcOrder
->shouldReceive('get_id')
->andReturn(1);
@@ -154,6 +158,8 @@ class RenewalHandlerTest extends TestCase
->with([$purchaseUnit], 'no_shipping', $payer, $token)
->andReturn($order);
+ when('wcs_get_subscriptions_for_order')->justReturn(array());
+
$wcOrder->shouldReceive('update_status');
$wcOrder->shouldReceive('save');
diff --git a/tests/PHPUnit/TestCase.php b/tests/PHPUnit/TestCase.php
index 2289dbc20..4e6c859a1 100644
--- a/tests/PHPUnit/TestCase.php
+++ b/tests/PHPUnit/TestCase.php
@@ -38,6 +38,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
when('wc_clean')->returnArg();
when('get_transient')->returnArg();
when('delete_transient')->returnArg();
+ when('wcs_get_subscription')->returnArg();
setUp();
}
diff --git a/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php b/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
index 0f7ccedfb..a20ee8b72 100644
--- a/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
+++ b/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
@@ -43,11 +43,32 @@ class FeesRendererTest extends TestCase
],
]);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([
+ 'gross_amount' => [
+ 'currency_code' => 'USD',
+ 'value' => '20.52',
+ ],
+ 'paypal_fee' => [
+ 'currency_code' => 'USD',
+ 'value' => '0.51',
+ ],
+ 'net_amount' => [
+ 'currency_code' => 'USD',
+ 'value' => '50.01',
+ ],
+ ]);
+
$result = $this->renderer->render($wcOrder);
$this->assertStringContainsString('Fee', $result);
$this->assertStringContainsString('0.41', $result);
$this->assertStringContainsString('Payout', $result);
$this->assertStringContainsString('10.01', $result);
+ $this->assertStringContainsString('PayPal Refund Fee', $result);
+ $this->assertStringContainsString('0.51', $result);
+ $this->assertStringContainsString('PayPal Refund', $result);
+ $this->assertStringContainsString('50.01', $result);
}
public function testRenderWithoutNet() {
@@ -62,6 +83,10 @@ class FeesRendererTest extends TestCase
],
]);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([]);
+
$result = $this->renderer->render($wcOrder);
$this->assertStringContainsString('Fee', $result);
$this->assertStringContainsString('0.41', $result);
@@ -78,6 +103,10 @@ class FeesRendererTest extends TestCase
->with(PayPalGateway::FEES_META_KEY)
->andReturn($meta);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([]);
+
$this->assertSame('', $this->renderer->render($wcOrder));
}
diff --git a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
index 6bd2ca14f..95aa53e0c 100644
--- a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
+++ b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
@@ -27,8 +27,9 @@ class SettingsPagesAssetsTest extends TestCase
Mockery::mock(Environment::class),
true,
array(),
- array()
- );
+ array(),
+ true
+ );
when('is_admin')
->justReturn(true);
diff --git a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
index 23edee911..6a72c4255 100644
--- a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
+++ b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
@@ -364,6 +364,11 @@ class PurchaseUnitTest extends TestCase
],
self::adaptAmountFormat([
'value' => 10.69,
+ 'breakdown' => [
+ 'item_total' => 10.69,
+ 'tax_total' => 0,
+ 'shipping' => 0,
+ ],
]),
];
}
@@ -432,6 +437,11 @@ class PurchaseUnitTest extends TestCase
],
self::adaptAmountFormat([
'value' => 10.69,
+ 'breakdown' => [
+ 'item_total' => 10.69,
+ 'tax_total' => 0,
+ 'shipping' => 0,
+ ],
], get_woocommerce_currency()),
];
}
diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php
index 13135b327..0f39a44ca 100644
--- a/woocommerce-paypal-payments.php
+++ b/woocommerce-paypal-payments.php
@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
- * Version: 2.2.1
+ * Version: 2.2.2
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* License: GPL-2.0