mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
Merge branch 'trunk' into PCP-4854-phase-1-opt-in-via-banner
This commit is contained in:
commit
d812264529
21 changed files with 1289 additions and 426 deletions
2
.github/workflows/integration.yml
vendored
2
.github/workflows/integration.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
|
||||
name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }}
|
||||
steps:
|
||||
- uses: ddev/github-action-setup-ddev@v1
|
||||
- uses: ddev/github-action-setup-ddev@1c7ef18595da42355373cb6d9417a6f44d758b93 # v1.10.1
|
||||
with:
|
||||
autostart: false
|
||||
|
||||
|
|
2
.github/workflows/package-new.yml
vendored
2
.github/workflows/package-new.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
create_archive:
|
||||
needs: check_version
|
||||
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main
|
||||
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@a9af34f34e95cbe18703198c7e972e97ebcd7473
|
||||
with:
|
||||
PHP_VERSION: 7.4
|
||||
NODE_VERSION: 22
|
||||
|
|
2
.github/workflows/package.yml
vendored
2
.github/workflows/package.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # v2.34.1
|
||||
with:
|
||||
php-version: 7.4
|
||||
|
||||
|
|
4
.github/workflows/php.yml
vendored
4
.github/workflows/php.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # v2.34.1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
|
||||
|
@ -22,7 +22,7 @@ jobs:
|
|||
run: composer validate
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ramsey/composer-install@v1
|
||||
uses: ramsey/composer-install@a7320a0581dcd0432930c48a0e7ced67e6ec17e8 # v1.3.0
|
||||
with:
|
||||
composer-options: "--prefer-dist"
|
||||
|
||||
|
|
2
.github/workflows/spell-check.yml
vendored
2
.github/workflows/spell-check.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
|
||||
- name: Check spelling
|
||||
id: spelling
|
||||
uses: crate-ci/typos@v1.30.2
|
||||
uses: crate-ci/typos@7bc041cbb7ca9167c9e0e4ccbb26f48eb0f9d4e0 # v1.30.2
|
||||
with:
|
||||
# Path to config file
|
||||
config: .github/workflows-config/typos.toml
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 3.0.7 - xxxx-xx-xx =
|
||||
* Enhancement - Deprecate `application_context` in favor of `experience_context` object #3431
|
||||
**NOTE**: If you were extending/modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
|
||||
* Enhancement - Add Contact Module feature
|
||||
* Enhancement - Add WooCommerce Tracks integration
|
||||
* Enhancement - Onboarding notification for Firefox browser #3433
|
||||
* Enhancement - Reset BN code on plugin uninstall #3471
|
||||
* Enhancement - Add "Stay updated with PayPal" option in the old and new settings UI #3430
|
||||
* Enhancement - Add French Territories to the supported ACDC countries list #3438
|
||||
* Enhancement - Auto-enable logging during onboarding #3369
|
||||
* Fix - DUPLICATE_INVOICE_ID in Sandbox due to missing invoice prefix #3435
|
||||
* Fix - Subscription product could not be unlinked from PayPal Subscription #3429
|
||||
* Fix - PayPal button greyed out on single product page for variable products with >2 attributes #3395
|
||||
* Fix - APMs automatically enabled despite selecting "No, ..." during onboarding #3362
|
||||
* Fix - Ditch items logic does not work when using saved card payment #3476
|
||||
|
||||
= 3.0.6 - 2025-05-27 =
|
||||
* Enhancement - Implement 3D secure check for Google Pay #3163
|
||||
* Enhancement - Add options for "Disable Credit Cards" and "Language" #3226
|
||||
|
|
|
@ -2145,7 +2145,7 @@ return array(
|
|||
$feature_enabled = (bool) apply_filters(
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- feature flags use this convention
|
||||
'woocommerce.feature-flags.woocommerce_paypal_payments.contact_module_enabled',
|
||||
getenv( 'PCP_CONTACT_MODULE_ENABLED' ) === '1'
|
||||
getenv( 'PCP_CONTACT_MODULE_ENABLED' ) !== '0'
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "woocommerce-paypal-payments",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.7",
|
||||
"description": "WooCommerce PayPal Payments",
|
||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||
"license": "GPL-2.0",
|
||||
|
|
18
readme.txt
18
readme.txt
|
@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, credit card
|
|||
Requires at least: 6.5
|
||||
Tested up to: 6.8
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 3.0.6
|
||||
Stable tag: 3.0.7
|
||||
License: GPLv2
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
|
@ -156,6 +156,22 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 3.0.7 - xxxx-xx-xx =
|
||||
* Enhancement - Deprecate `application_context` in favor of `experience_context` object #3431
|
||||
**NOTE**: If you were extending/modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
|
||||
* Enhancement - Add Contact Module feature
|
||||
* Enhancement - Add WooCommerce Tracks integration
|
||||
* Enhancement - Onboarding notification for Firefox browser #3433
|
||||
* Enhancement - Reset BN code on plugin uninstall #3471
|
||||
* Enhancement - Add "Stay updated with PayPal" option in the old and new settings UI #3430
|
||||
* Enhancement - Add French Territories to the supported ACDC countries list #3438
|
||||
* Enhancement - Auto-enable logging during onboarding #3369
|
||||
* Fix - DUPLICATE_INVOICE_ID in Sandbox due to missing invoice prefix #3435
|
||||
* Fix - Subscription product could not be unlinked from PayPal Subscription #3429
|
||||
* Fix - PayPal button greyed out on single product page for variable products with >2 attributes #3395
|
||||
* Fix - APMs automatically enabled despite selecting "No, ..." during onboarding #3362
|
||||
* Fix - Ditch items logic does not work when using saved card payment #3476
|
||||
|
||||
= 3.0.6 - 2025-05-27 =
|
||||
* Enhancement - Implement 3D secure check for Google Pay #3163
|
||||
* Enhancement - Add options for "Disable Credit Cards" and "Language" #3226
|
||||
|
|
92
tests/integration/PHPUnit/Factories/CouponFactory.php
Normal file
92
tests/integration/PHPUnit/Factories/CouponFactory.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Factories;
|
||||
|
||||
use WC_Coupon;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\DiscountPresets;
|
||||
|
||||
class CouponFactory
|
||||
{
|
||||
private array $created_coupon_ids = [];
|
||||
|
||||
/**
|
||||
* @param string $preset_name
|
||||
* @return WC_Coupon
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function createFromPreset(string $preset_name): WC_Coupon
|
||||
{
|
||||
$presets = DiscountPresets::get();
|
||||
|
||||
if (!isset($presets[$preset_name])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Coupon preset '{$preset_name}' not found");
|
||||
}
|
||||
|
||||
$preset = $presets[$preset_name];
|
||||
|
||||
if (!isset($preset['coupon_code'])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Preset '{$preset_name}' is not a coupon");
|
||||
}
|
||||
|
||||
return $this->createCoupon($preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $preset
|
||||
* @return WC_Coupon
|
||||
*/
|
||||
private function createCoupon(array $preset): WC_Coupon
|
||||
{
|
||||
$coupon = new WC_Coupon();
|
||||
$coupon->set_code($preset['coupon_code']);
|
||||
$coupon->set_discount_type($preset['type']);
|
||||
$coupon->set_amount($preset['amount']);
|
||||
$coupon->set_status('publish');
|
||||
$coupon->save();
|
||||
|
||||
$this->created_coupon_ids[] = $coupon->get_id();
|
||||
|
||||
return $coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $coupon_code
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $coupon_code): bool
|
||||
{
|
||||
return (bool)wc_get_coupon_id_by_code($coupon_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $coupon_code
|
||||
* @return WC_Coupon|null
|
||||
*/
|
||||
public function getByCode(string $coupon_code): ?WC_Coupon
|
||||
{
|
||||
$coupon_id = wc_get_coupon_id_by_code($coupon_code);
|
||||
|
||||
return $coupon_id ? new WC_Coupon($coupon_id) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all created coupons
|
||||
*/
|
||||
public function cleanup(): void
|
||||
{
|
||||
foreach ($this->created_coupon_ids as $coupon_id) {
|
||||
wp_delete_post($coupon_id, true);
|
||||
}
|
||||
|
||||
$this->created_coupon_ids = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCreatedIds(): array
|
||||
{
|
||||
return $this->created_coupon_ids;
|
||||
}
|
||||
}
|
242
tests/integration/PHPUnit/Factories/OrderFactory.php
Normal file
242
tests/integration/PHPUnit/Factories/OrderFactory.php
Normal file
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Factories;
|
||||
|
||||
use WC_Order;
|
||||
use WC_Order_Item_Product;
|
||||
use WC_Order_Item_Fee;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\ProductPresets;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\DiscountPresets;
|
||||
|
||||
class OrderFactory
|
||||
{
|
||||
private array $created_order_ids = [];
|
||||
private ProductFactory $product_factory;
|
||||
private CouponFactory $coupon_factory;
|
||||
|
||||
public function __construct(
|
||||
ProductFactory $product_factory = null,
|
||||
CouponFactory $coupon_factory = null
|
||||
)
|
||||
{
|
||||
$this->product_factory = $product_factory ?? new ProductFactory();
|
||||
$this->coupon_factory = $coupon_factory ?? new CouponFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $customer_id
|
||||
* @param string $payment_method
|
||||
* @param array $product_presets
|
||||
* @param array $discount_presets
|
||||
* @param bool $set_paid
|
||||
* @return WC_Order
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function create(
|
||||
int $customer_id,
|
||||
string $payment_method,
|
||||
array $product_presets,
|
||||
array $discount_presets = [],
|
||||
bool $set_paid = true
|
||||
): WC_Order
|
||||
{
|
||||
$products = $this->resolveProductPresets($product_presets);
|
||||
$discounts = $this->resolveDiscountPresets($discount_presets);
|
||||
|
||||
$order = wc_create_order([
|
||||
'customer_id' => $customer_id,
|
||||
'set_paid' => $set_paid,
|
||||
]);
|
||||
|
||||
if (is_wp_error($order)) {
|
||||
throw new \WC_Data_Exception('order_creation_failed', 'Failed to create order');
|
||||
}
|
||||
|
||||
$this->setBillingAddress($order);
|
||||
$this->addProductsToOrder($order, $products);
|
||||
$this->applyDiscountsToOrder($order, $discounts);
|
||||
|
||||
$order->set_payment_method($payment_method);
|
||||
$order->calculate_totals();
|
||||
$order->save();
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
*/
|
||||
private function setBillingAddress(WC_Order $order): void
|
||||
{
|
||||
$order->set_billing_first_name('John');
|
||||
$order->set_billing_last_name('Doe');
|
||||
$order->set_billing_address_1('969 Market');
|
||||
$order->set_billing_city('San Francisco');
|
||||
$order->set_billing_state('CA');
|
||||
$order->set_billing_postcode('94103');
|
||||
$order->set_billing_country('US');
|
||||
$order->set_billing_email('john.doe@example.com');
|
||||
$order->set_billing_phone('(555) 555-5555');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
* @param array $products
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
private function addProductsToOrder(WC_Order $order, array $products): void
|
||||
{
|
||||
foreach ($products as $product_data) {
|
||||
$product_id = null;
|
||||
|
||||
if (!empty($product_data['sku'])) {
|
||||
$product_sku = $product_data['sku'];
|
||||
$product_id = wc_get_product_id_by_sku($product_sku);
|
||||
}
|
||||
|
||||
if (!$product_id && isset($product_data['id'])) {
|
||||
$product_id = $product_data['id'];
|
||||
}
|
||||
|
||||
if (!$product_id) {
|
||||
throw new \WC_Data_Exception('invalid_product', "Product not found - no valid SKU or ID provided");
|
||||
}
|
||||
|
||||
$variation_id = $product_data['variation_id'] ?? 0;
|
||||
$product_type = $product_data['type'] ?? 'simple';
|
||||
|
||||
$product = wc_get_product($variation_id ?: $product_id);
|
||||
|
||||
if (!$product) {
|
||||
throw new \WC_Data_Exception('invalid_product', "Product {$product_id} not found");
|
||||
}
|
||||
|
||||
// Use appropriate item class based on product type
|
||||
$item = $this->createOrderItem($product_type, $product_data, $product);
|
||||
|
||||
if ($variation_id && $product->is_type('variation')) {
|
||||
$item->set_variation_data($product->get_variation_attributes());
|
||||
}
|
||||
|
||||
$order->add_item($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
* @param array $discounts
|
||||
*/
|
||||
private function applyDiscountsToOrder(WC_Order $order, array $discounts): void
|
||||
{
|
||||
foreach ($discounts as $discount) {
|
||||
if (isset($discount['coupon_code'])) {
|
||||
$order->apply_coupon($discount['coupon_code']);
|
||||
}
|
||||
|
||||
if (isset($discount['fee'])) {
|
||||
$fee = new WC_Order_Item_Fee();
|
||||
$fee->set_props([
|
||||
'name' => $discount['fee']['name'],
|
||||
'amount' => -abs($discount['fee']['amount']),
|
||||
'total' => -abs($discount['fee']['amount']),
|
||||
]);
|
||||
$order->add_item($fee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $product_presets
|
||||
* @return array
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
private function resolveProductPresets(array $product_presets): array
|
||||
{
|
||||
$available_presets = ProductPresets::get();
|
||||
$products = [];
|
||||
|
||||
foreach ($product_presets as $preset) {
|
||||
if (is_string($preset)) {
|
||||
if (!isset($available_presets[$preset])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Product preset '{$preset}' not found");
|
||||
}
|
||||
$products[] = $available_presets[$preset];
|
||||
} elseif (is_array($preset)) {
|
||||
$preset_name = $preset['preset'];
|
||||
$quantity = $preset['quantity'] ?? 1;
|
||||
|
||||
if (!isset($available_presets[$preset_name])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Product preset '{$preset_name}' not found");
|
||||
}
|
||||
|
||||
$product_data = $available_presets[$preset_name];
|
||||
$product_data['quantity'] = $quantity;
|
||||
$products[] = $product_data;
|
||||
}
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $discount_presets
|
||||
* @return array
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
private function resolveDiscountPresets(array $discount_presets): array
|
||||
{
|
||||
$available_presets = DiscountPresets::get();
|
||||
$discounts = [];
|
||||
|
||||
foreach ($discount_presets as $preset) {
|
||||
if (!isset($available_presets[$preset])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Discount preset '{$preset}' not found");
|
||||
}
|
||||
$discounts[] = $available_presets[$preset];
|
||||
}
|
||||
|
||||
return $discounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all created orders
|
||||
*/
|
||||
public function cleanup(): void
|
||||
{
|
||||
foreach ($this->created_order_ids as $order_id) {
|
||||
wp_delete_post($order_id, true);
|
||||
}
|
||||
|
||||
$this->created_order_ids = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCreatedIds(): array
|
||||
{
|
||||
return $this->created_order_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $product_type
|
||||
* @param array $product_data
|
||||
* @param \WC_Product $product
|
||||
* @return \WC_Order_Item_Product
|
||||
*/
|
||||
private function createOrderItem(string $product_type, array $product_data, \WC_Product $product): \WC_Order_Item_Product
|
||||
{
|
||||
$item = new \WC_Order_Item_Product();
|
||||
|
||||
$item->set_props([
|
||||
'product_id' => $product->get_id(),
|
||||
'variation_id' => $product_data['variation_id'] ?? 0,
|
||||
'quantity' => $product_data['quantity'],
|
||||
'subtotal' => $product->get_price() * $product_data['quantity'],
|
||||
'total' => $product->get_price() * $product_data['quantity'],
|
||||
]);
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
166
tests/integration/PHPUnit/Factories/ProductFactory.php
Normal file
166
tests/integration/PHPUnit/Factories/ProductFactory.php
Normal file
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Factories;
|
||||
|
||||
use WC_Product_Simple;
|
||||
use WC_Product_Variable;
|
||||
use WC_Product_Variation;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\ProductPresets;
|
||||
|
||||
class ProductFactory
|
||||
{
|
||||
private array $created_product_ids = [];
|
||||
|
||||
/**
|
||||
* @param string $preset_name
|
||||
* @return \WC_Product
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function createFromPreset(string $preset_name): \WC_Product
|
||||
{
|
||||
$presets = ProductPresets::get();
|
||||
|
||||
if (!isset($presets[$preset_name])) {
|
||||
throw new \WC_Data_Exception('invalid_preset', "Product preset '{$preset_name}' not found");
|
||||
}
|
||||
|
||||
$preset = $presets[$preset_name];
|
||||
|
||||
if (isset($preset['sku'])) {
|
||||
$existing_product_id = wc_get_product_id_by_sku($preset['sku']);
|
||||
if ($existing_product_id) {
|
||||
$existing_product = wc_get_product($existing_product_id);
|
||||
if ($existing_product) {
|
||||
return $existing_product;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($preset['product_id'])) {
|
||||
$existing_product = wc_get_product($preset['product_id']);
|
||||
if ($existing_product) {
|
||||
return $existing_product;
|
||||
}
|
||||
}
|
||||
|
||||
if ($preset['type'] === 'subscription' && !class_exists('\WC_Product_Subscription')) {
|
||||
return $this->createSimpleProduct($preset);
|
||||
}
|
||||
switch ($preset['type']) {
|
||||
case 'variable':
|
||||
return $this->createVariableProduct($preset);
|
||||
case 'subscription':
|
||||
return $this->createSubscriptionProduct($preset);
|
||||
case 'simple':
|
||||
default:
|
||||
return $this->createSimpleProduct($preset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $preset
|
||||
* @return WC_Product_Simple
|
||||
*/
|
||||
private function createSimpleProduct(array $preset): WC_Product_Simple
|
||||
{
|
||||
$product = new WC_Product_Simple();
|
||||
$product_id = wc_get_product_id_by_sku($preset['sku']);
|
||||
$product->set_sku($preset['sku']);
|
||||
$product->set_id($product_id);
|
||||
$product->set_name($preset['name']);
|
||||
$product->set_regular_price($preset['price']);
|
||||
$product->set_status('publish');
|
||||
$product->save();
|
||||
|
||||
$this->created_product_ids[] = $product_id;
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $preset
|
||||
* @return WC_Product_Variation
|
||||
*/
|
||||
private function createVariableProduct(array $preset): WC_Product_Variation
|
||||
{
|
||||
// Create parent variable product
|
||||
$parent = new WC_Product_Variable();
|
||||
$product_id = wc_get_product_id_by_sku($preset['sku']);
|
||||
$parent->set_sku($preset['sku']);
|
||||
$parent->set_id($product_id);
|
||||
$parent->set_name($preset['name']);
|
||||
$parent->set_status('publish');
|
||||
$parent->save();
|
||||
|
||||
// Create variation
|
||||
$variation = new WC_Product_Variation();
|
||||
$variation->set_id($preset['variation_id']);
|
||||
$variation->set_parent_id($preset['product_id']);
|
||||
$variation->set_regular_price($preset['price']);
|
||||
$variation->set_attributes(['color' => 'red']);
|
||||
$variation->set_status('publish');
|
||||
$variation->save();
|
||||
|
||||
$this->created_product_ids[] = $product_id;
|
||||
$this->created_product_ids[] = $preset['variation_id'];
|
||||
|
||||
return $variation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $preset
|
||||
* @return \WC_Product_Subscription
|
||||
*/
|
||||
private function createSubscriptionProduct(array $preset): \WC_Product_Subscription
|
||||
{
|
||||
$product = new \WC_Product_Subscription();
|
||||
$product->set_props([
|
||||
'name' => $preset['name'],
|
||||
'regular_price' => $preset['price'],
|
||||
'price' => $preset['price'],
|
||||
'sku' => $preset['sku'],
|
||||
'manage_stock' => false,
|
||||
'tax_status' => 'taxable',
|
||||
'downloadable' => false,
|
||||
'virtual' => false,
|
||||
'stock_status' => 'instock',
|
||||
'weight' => '1.1',
|
||||
'subscription_period' => $preset['subscription_period'],
|
||||
'subscription_period_interval' => $preset['subscription_period_interval'],
|
||||
'subscription_length' => $preset['subscription_length'],
|
||||
'subscription_trial_period' => $preset['subscription_trial_period'],
|
||||
'subscription_trial_length' => $preset['subscription_trial_length'],
|
||||
'subscription_price' => $preset['subscription_price'],
|
||||
'subscription_sign_up_fee' => $preset['subscription_sign_up_fee'],
|
||||
]);
|
||||
|
||||
$product->set_status('publish');
|
||||
$product->save();
|
||||
|
||||
$this->created_product_ids[] = $product->get_id();
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sku
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $sku): bool
|
||||
{
|
||||
$existing_product_id = wc_get_product_id_by_sku($sku);
|
||||
return (bool)$existing_product_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all created products
|
||||
*/
|
||||
public function cleanup(): void
|
||||
{
|
||||
foreach ($this->created_product_ids as $product_id) {
|
||||
wp_delete_post($product_id, true);
|
||||
}
|
||||
|
||||
$this->created_product_ids = [];
|
||||
}
|
||||
}
|
26
tests/integration/PHPUnit/Fixtures/DiscountPresets.php
Normal file
26
tests/integration/PHPUnit/Fixtures/DiscountPresets.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Fixtures;
|
||||
|
||||
class DiscountPresets
|
||||
{
|
||||
public static function get(): array
|
||||
{
|
||||
return [
|
||||
'percentage_10' => [
|
||||
'coupon_code' => 'TEST10PERCENT',
|
||||
'type' => 'percent',
|
||||
'amount' => '10'
|
||||
],
|
||||
'fixed_5' => [
|
||||
'coupon_code' => 'TEST5FIXED',
|
||||
'type' => 'fixed_cart',
|
||||
'amount' => '5.00'
|
||||
],
|
||||
'manual_discount' => [
|
||||
'fee' => ['name' => 'Test Discount', 'amount' => 3.50]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
49
tests/integration/PHPUnit/Fixtures/ProductPresets.php
Normal file
49
tests/integration/PHPUnit/Fixtures/ProductPresets.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Fixtures;
|
||||
|
||||
class ProductPresets
|
||||
{
|
||||
public static function get(): array
|
||||
{
|
||||
return [
|
||||
'simple' => [
|
||||
'sku' => 'DUMMY_SIMPLE_SKU_01',
|
||||
'name' => 'Test Simple Product',
|
||||
'price' => '10.00',
|
||||
'quantity' => 1,
|
||||
'type' => 'simple'
|
||||
],
|
||||
'simple_expensive' => [
|
||||
'sku' => 'DUMMY_SIMPLE_SKU_02',
|
||||
'name' => 'Test Expensive Product',
|
||||
'price' => '199.99',
|
||||
'quantity' => 1,
|
||||
'type' => 'simple'
|
||||
],
|
||||
/*'variable' => [
|
||||
'sku' => 'DUMMY_VARIABLE_SKU_01',
|
||||
'variation_id' => 20002,
|
||||
'name' => 'Test Variable Product',
|
||||
'price' => '25.00',
|
||||
'quantity' => 1,
|
||||
'type' => 'variable'
|
||||
],*/
|
||||
'subscription' => [
|
||||
'name' => 'Dummy Subscription Product',
|
||||
'price' => '10.00',
|
||||
'quantity' => 1,
|
||||
'type' => 'subscription',
|
||||
'sku' => 'DUMMY SUB SKU',
|
||||
'subscription_period' => 'day',
|
||||
'subscription_period_interval' => 1,
|
||||
'subscription_length' => 0,
|
||||
'subscription_trial_period' => '',
|
||||
'subscription_trial_length' => 0,
|
||||
'subscription_price' => 10,
|
||||
'subscription_sign_up_fee' => 0,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
|||
use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
use WooCommerce\PayPalCommerce\PPCP;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Traits\CreateTestOrders;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Traits\CreateTestProducts;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
|
@ -25,311 +27,249 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
|||
|
||||
class IntegrationMockedTestCase extends TestCase
|
||||
{
|
||||
use MockeryPHPUnitIntegration;
|
||||
use MockeryPHPUnitIntegration, CreateTestOrders, CreateTestProducts;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->default_product_id = $this->createAProductIfNotProvided();
|
||||
}
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->customer_id = $this->createCustomerIfNotExists();
|
||||
$this->createTestProducts();
|
||||
$this->createTestCoupons();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $customer_id
|
||||
* @param string $payment_method
|
||||
* @param int $product_id
|
||||
* @param bool $set_paid
|
||||
* @return \WC_Order|\WP_Error
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function getMockedOrder(int $customer_id, string $payment_method, int $product_id, bool $set_paid = true)
|
||||
{
|
||||
$order = wc_create_order([
|
||||
'customer_id' => $customer_id,
|
||||
'set_paid' => $set_paid,
|
||||
'billing' => [
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'address_1' => '969 Market',
|
||||
'address_2' => '',
|
||||
'city' => 'San Francisco',
|
||||
'state' => 'CA',
|
||||
'postcode' => '94103',
|
||||
'country' => 'US',
|
||||
'email' => 'john.doe@example.com',
|
||||
'phone' => '(555) 555-5555'
|
||||
],
|
||||
'line_items' => [
|
||||
[
|
||||
'product_id' => $product_id,
|
||||
'quantity' => 1
|
||||
]
|
||||
],
|
||||
]);
|
||||
$order->set_payment_method($payment_method);
|
||||
// Make sure the order is properly saved
|
||||
$order->save();
|
||||
public function tearDown(): void
|
||||
{
|
||||
// This cleans up everything created during tests
|
||||
//$this->cleanupTestData();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
// Add the product to the order
|
||||
$item = new WC_Order_Item_Product();
|
||||
$item->set_props([
|
||||
'product_id' => $product_id,
|
||||
'quantity' => 1,
|
||||
'subtotal' => 10,
|
||||
'total' => 10,
|
||||
]);
|
||||
$order->add_item($item);
|
||||
$order->calculate_totals();
|
||||
$order->save();
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sku
|
||||
* @return int
|
||||
*/
|
||||
public function createAProductIfNotProvided(string $sku = 'DUMMY SUB SKU'): int
|
||||
{
|
||||
$product_id = wc_get_product_id_by_sku($sku);
|
||||
if (!$product_id) {
|
||||
$product = new \WC_Product_Subscription();
|
||||
$product->set_props([
|
||||
'name' => 'Dummy Subscription Product',
|
||||
'regular_price' => 10,
|
||||
'price' => 10,
|
||||
'sku' => 'DUMMY SUB SKU',
|
||||
'manage_stock' => false,
|
||||
'tax_status' => 'taxable',
|
||||
'downloadable' => false,
|
||||
'virtual' => false,
|
||||
'stock_status' => 'instock',
|
||||
'weight' => '1.1',
|
||||
// Subscription-specific properties
|
||||
'subscription_period' => 'month',
|
||||
'subscription_period_interval' => 1,
|
||||
'subscription_length' => 0, // 0 means unlimited
|
||||
'subscription_trial_period' => '',
|
||||
'subscription_trial_length' => 0,
|
||||
'subscription_price' => 10,
|
||||
'subscription_sign_up_fee' => 0,
|
||||
]);
|
||||
$product->save();
|
||||
$product_id = $product->get_id();
|
||||
}
|
||||
return $product_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, callable> $overriddenServices
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
protected function bootstrapModule(array $overriddenServices = []): ContainerInterface
|
||||
{
|
||||
$overriddenServices = array_merge([
|
||||
'http.redirector' => function () {
|
||||
return new RedirectorStub();
|
||||
}
|
||||
], $overriddenServices);
|
||||
/**
|
||||
* @param array<string, callable> $overriddenServices
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
protected function bootstrapModule(array $overriddenServices = []): ContainerInterface
|
||||
{
|
||||
$overriddenServices = array_merge([
|
||||
'http.redirector' => function () {
|
||||
return new RedirectorStub();
|
||||
}
|
||||
], $overriddenServices);
|
||||
|
||||
|
||||
$module = new class ($overriddenServices) implements ServiceModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
$module = new class ($overriddenServices) implements ServiceModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
public function __construct(array $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
public function __construct(array $services)
|
||||
{
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
public function services(): array
|
||||
{
|
||||
return $this->services;
|
||||
}
|
||||
public function services(): array
|
||||
{
|
||||
return $this->services;
|
||||
}
|
||||
|
||||
public function run(ContainerInterface $c): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
public function run(ContainerInterface $c): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$rootDir = ROOT_DIR;
|
||||
$bootstrap = require("$rootDir/bootstrap.php");
|
||||
$appContainer = $bootstrap($rootDir, [], [$module]);
|
||||
$rootDir = ROOT_DIR;
|
||||
$bootstrap = require("$rootDir/bootstrap.php");
|
||||
$appContainer = $bootstrap($rootDir, [], [$module]);
|
||||
|
||||
PPCP::init($appContainer);
|
||||
PPCP::init($appContainer);
|
||||
|
||||
return $appContainer;
|
||||
}
|
||||
return $appContainer;
|
||||
}
|
||||
|
||||
public function createCustomerIfNotExists(int $customer_id= 1): int
|
||||
{
|
||||
$customer = new \WC_Customer($customer_id);
|
||||
if ( empty($customer->get_email() )) {
|
||||
$customer->set_email('customer'. $customer_id. '@example.com');
|
||||
public function createCustomerIfNotExists(int $customer_id = 1): int
|
||||
{
|
||||
$customer = new \WC_Customer($customer_id);
|
||||
if (empty($customer->get_email())) {
|
||||
$customer->set_email('customer' . $customer_id . '@example.com');
|
||||
$customer->set_first_name('John');
|
||||
$customer->set_last_name('Doe');
|
||||
$customer->save();
|
||||
}
|
||||
return $customer->get_id();
|
||||
}
|
||||
return $customer->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a payment token for a customer.
|
||||
*
|
||||
* @param int $customer_id The customer ID.
|
||||
* @return WC_Payment_Token_CC The created payment token.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createAPaymentTokenForTheCustomer(int $customer_id = 1, $gateway_id = 'ppcp-gateway'): WC_Payment_Token_CC
|
||||
{
|
||||
$this->createCustomerIfNotExists($customer_id);
|
||||
/**
|
||||
* Creates a payment token for a customer.
|
||||
*
|
||||
* @param int $customer_id The customer ID.
|
||||
* @return WC_Payment_Token_CC The created payment token.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createAPaymentTokenForTheCustomer(int $customer_id = 1, $gateway_id = 'ppcp-gateway'): WC_Payment_Token_CC
|
||||
{
|
||||
$this->createCustomerIfNotExists($customer_id);
|
||||
|
||||
$token = new WC_Payment_Token_CC();
|
||||
$token->set_token('test_token_' . uniqid()); // Unique token ID
|
||||
$token->set_gateway_id($gateway_id);
|
||||
$token->set_user_id($customer_id);
|
||||
$token = new WC_Payment_Token_CC();
|
||||
$token->set_token('test_token_' . uniqid()); // Unique token ID
|
||||
$token->set_gateway_id($gateway_id);
|
||||
$token->set_user_id($customer_id);
|
||||
|
||||
// These fields are required for WC_Payment_Token_CC
|
||||
$token->set_card_type('visa'); // lowercase is often expected
|
||||
$token->set_last4('1234');
|
||||
$token->set_expiry_month('12');
|
||||
$token->set_expiry_year('2030'); // Missing expiry year in your original code
|
||||
// These fields are required for WC_Payment_Token_CC
|
||||
$token->set_card_type('visa'); // lowercase is often expected
|
||||
$token->set_last4('1234');
|
||||
$token->set_expiry_month('12');
|
||||
$token->set_expiry_year('2030'); // Missing expiry year in your original code
|
||||
|
||||
$result = $token->save();
|
||||
$result = $token->save();
|
||||
|
||||
if (!$result || is_wp_error($result)) {
|
||||
throw new \Exception('Failed to save payment token: ' .
|
||||
(is_wp_error($result) ? $result->get_error_message() : 'Unknown error'));
|
||||
}
|
||||
if (!$result || is_wp_error($result)) {
|
||||
throw new \Exception('Failed to save payment token: ' .
|
||||
(is_wp_error($result) ? $result->get_error_message() : 'Unknown error'));
|
||||
}
|
||||
|
||||
$saved_token = \WC_Payment_Tokens::get($token->get_id());
|
||||
if (!$saved_token || $saved_token->get_id() !== $token->get_id()) {
|
||||
throw new \Exception('Token was not saved correctly');
|
||||
}
|
||||
$saved_token = \WC_Payment_Tokens::get($token->get_id());
|
||||
if (!$saved_token || $saved_token->get_id() !== $token->get_id()) {
|
||||
throw new \Exception('Token was not saved correctly');
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a subscription for testing.
|
||||
*
|
||||
* @param int $customer_id The customer ID
|
||||
* @param string $payment_method The payment method
|
||||
* @param string $sku
|
||||
* @return WC_Subscription
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function createSubscription(int $customer_id = 1, string $payment_method = 'ppcp-gateway', $sku = 'DUMMY SUB SKU'): WC_Subscription
|
||||
{
|
||||
// Create a product if not provided
|
||||
$product_id = $this->createAProductIfNotProvided($sku);
|
||||
/**
|
||||
* Helper method to create a subscription for testing.
|
||||
*
|
||||
* @param int $customer_id The customer ID
|
||||
* @param string $payment_method The payment method
|
||||
* @param string $sku
|
||||
* @return WC_Subscription
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function createSubscription(int $customer_id = 1, string $payment_method = 'ppcp-gateway', $sku = 'DUMMY SUB SKU'): WC_Subscription
|
||||
{
|
||||
$product_id = wc_get_product_id_by_sku($sku);
|
||||
|
||||
$order = $this->getMockedOrder($customer_id, $payment_method, $product_id, $set_paid = true);
|
||||
$order = $this->getConfiguredOrder(
|
||||
$this->customer_id,
|
||||
$payment_method,
|
||||
['subscription']
|
||||
);
|
||||
$subscription = new WC_Subscription();
|
||||
$subscription->set_customer_id($customer_id);
|
||||
$subscription->set_payment_method($payment_method);
|
||||
$subscription->set_status('active');
|
||||
$subscription->set_parent_id($order->get_id());
|
||||
$subscription->set_billing_period('month');
|
||||
$subscription->set_billing_interval(1);
|
||||
|
||||
$subscription = new WC_Subscription();
|
||||
$subscription->set_customer_id($customer_id);
|
||||
$subscription->set_payment_method($payment_method);
|
||||
$subscription->set_status('active');
|
||||
$subscription->set_parent_id($order->get_id());
|
||||
$subscription->set_billing_period('month');
|
||||
$subscription->set_billing_interval(1);
|
||||
// Add a product to the subscription
|
||||
$subscription_item = new WC_Order_Item_Product();
|
||||
$subscription_item->set_props([
|
||||
'product_id' => $product_id,
|
||||
'quantity' => 1,
|
||||
'subtotal' => 10,
|
||||
'total' => 10,
|
||||
]);
|
||||
$subscription->add_item($subscription_item);
|
||||
$subscription->set_date_created(current_time('mysql'));
|
||||
$subscription->set_start_date(current_time('mysql'));
|
||||
$subscription->set_next_payment_date(date('Y-m-d H:i:s', strtotime('+1 month', current_time('timestamp'))));
|
||||
$subscription->save();
|
||||
|
||||
// Add a product to the subscription
|
||||
$subscription_item = new WC_Order_Item_Product();
|
||||
$subscription_item->set_props([
|
||||
'product_id' => $product_id,
|
||||
'quantity' => 1,
|
||||
'subtotal' => 10,
|
||||
'total' => 10,
|
||||
]);
|
||||
$subscription->add_item($subscription_item);
|
||||
$subscription->set_date_created(current_time('mysql'));
|
||||
$subscription->set_start_date(current_time('mysql'));
|
||||
$subscription->set_next_payment_date(date('Y-m-d H:i:s', strtotime('+1 month', current_time('timestamp'))));
|
||||
$subscription->save();
|
||||
return $subscription;
|
||||
}
|
||||
|
||||
return $subscription;
|
||||
}
|
||||
/**
|
||||
* Creates a renewal order for testing
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @param string $gateway_id
|
||||
* @param int $subscription_id
|
||||
* @return WC_Order
|
||||
*/
|
||||
protected function createRenewalOrder(int $customer_id, string $gateway_id, int $subscription_id): WC_Order
|
||||
{
|
||||
$renewal_order = $this->getConfiguredOrder(
|
||||
$customer_id,
|
||||
$gateway_id,
|
||||
['subscription'],
|
||||
[],
|
||||
false
|
||||
);
|
||||
$renewal_order->update_meta_data('_subscription_renewal', $subscription_id);
|
||||
$renewal_order->update_meta_data('_subscription_renewal', $subscription_id);
|
||||
$renewal_order->save();
|
||||
|
||||
/**
|
||||
* Creates a renewal order for testing
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @param string $gateway_id
|
||||
* @param int $subscription_id
|
||||
* @return WC_Order
|
||||
*/
|
||||
protected function createRenewalOrder(int $customer_id, string $gateway_id, int $subscription_id): WC_Order
|
||||
{
|
||||
$renewal_order = $this->getMockedOrder($customer_id, $gateway_id, $this->default_product_id, false);
|
||||
$renewal_order->update_meta_data('_subscription_renewal', $subscription_id);
|
||||
$renewal_order->save();
|
||||
return $renewal_order;
|
||||
}
|
||||
|
||||
return $renewal_order;
|
||||
}
|
||||
/**
|
||||
* Mocks the OrderEndpoint to return a successful/failed order.
|
||||
*
|
||||
* @param string $intent The order intent (CAPTURE or AUTHORIZE)
|
||||
* @param bool $success Whether the order was successful
|
||||
* @return object The mocked OrderEndpoint
|
||||
*/
|
||||
public function mockOrderEndpoint(string $intent = 'CAPTURE', bool $order_success = true, bool $capture_success = true): object
|
||||
{
|
||||
$order_endpoint = \Mockery::mock(OrderEndpoint::class);
|
||||
$order = \Mockery::mock(Order::class)->shouldIgnoreMissing();
|
||||
|
||||
/**
|
||||
* Mocks the OrderEndpoint to return a successful/failed order.
|
||||
*
|
||||
* @param string $intent The order intent (CAPTURE or AUTHORIZE)
|
||||
* @param bool $success Whether the order was successful
|
||||
* @return object The mocked OrderEndpoint
|
||||
*/
|
||||
public function mockOrderEndpoint(string $intent = 'CAPTURE', bool $success = true): object
|
||||
{
|
||||
$order_endpoint = \Mockery::mock(OrderEndpoint::class)->shouldIgnoreMissing();
|
||||
$order = \Mockery::mock(Order::class)->shouldIgnoreMissing();
|
||||
$order->shouldReceive('id')->andReturn('TEST-ORDER-' . uniqid());
|
||||
$order->shouldReceive('intent')->andReturn($intent);
|
||||
|
||||
$order->shouldReceive('id')->andReturn('TEST-ORDER-' . uniqid());
|
||||
$order->shouldReceive('intent')->andReturn($intent);
|
||||
$order_status = \Mockery::mock(OrderStatus::class);
|
||||
$order_status->shouldReceive('is')->andReturn($order_success);
|
||||
$order_status->shouldReceive('name')->andReturn($order_success ? 'COMPLETED' : 'FAILED');
|
||||
$order->shouldReceive('status')->andReturn($order_status);
|
||||
$card_properties = new \stdClass();
|
||||
$card_properties->brand = 'VISA';
|
||||
$card_properties->last_digits = '1234';
|
||||
$card_properties->expiry = '2026-12';
|
||||
$payment_source = \Mockery::mock(PaymentSource::class);
|
||||
$payment_source->shouldReceive('name')->andReturn('card');
|
||||
$payment_source->shouldReceive('properties')->andReturn($card_properties);
|
||||
$order->shouldReceive('payment_source')->andReturn($payment_source);
|
||||
|
||||
$order_status = \Mockery::mock(OrderStatus::class)->shouldIgnoreMissing();
|
||||
$order_status->shouldReceive('is')->andReturn($success);
|
||||
$order_status->shouldReceive('name')->andReturn($success ? 'COMPLETED' : 'FAILED');
|
||||
$order->shouldReceive('status')->andReturn($order_status);
|
||||
$purchase_unit = \Mockery::mock(PurchaseUnit::class)->shouldIgnoreMissing();
|
||||
$payments = \Mockery::mock(Payments::class)->shouldIgnoreMissing();
|
||||
$capture = \Mockery::mock(Capture::class)->shouldIgnoreMissing();
|
||||
|
||||
$payment_source = \Mockery::mock(PaymentSource::class)->shouldIgnoreMissing();
|
||||
$payment_source->shouldReceive('name')->andReturn('card');
|
||||
$order->shouldReceive('payment_source')->andReturn($payment_source);
|
||||
$capture->shouldReceive('id')->andReturn('TEST-CAPTURE-' . uniqid());
|
||||
$capture_status = \Mockery::mock(CaptureStatus::class)->shouldIgnoreMissing();
|
||||
|
||||
$purchase_unit = \Mockery::mock(PurchaseUnit::class)->shouldIgnoreMissing();
|
||||
$payments = \Mockery::mock(Payments::class)->shouldIgnoreMissing();
|
||||
$capture = \Mockery::mock(Capture::class)->shouldIgnoreMissing();
|
||||
$capture_status->shouldReceive('name')->andReturn($capture_success ? 'COMPLETED' : 'DECLINED');
|
||||
$capture_status->shouldReceive('details')->andReturn(null);
|
||||
$capture->shouldReceive('status')->andReturn($capture_status);
|
||||
|
||||
$capture->shouldReceive('id')->andReturn('TEST-CAPTURE-' . uniqid());
|
||||
$capture_status = \Mockery::mock(CaptureStatus::class)->shouldIgnoreMissing();
|
||||
// Mock authorizations for AUTHORIZE intent
|
||||
if ($intent === 'AUTHORIZE') {
|
||||
$authorization = \Mockery::mock(\WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization::class)->shouldIgnoreMissing();
|
||||
|
||||
$capture_status->shouldReceive('name')->andReturn($success ? 'COMPLETED' : 'DECLINED');
|
||||
$capture->shouldReceive('status')->andReturn($capture_status);
|
||||
$authorization->shouldReceive('id')->andReturn('TEST-AUTH-' . uniqid());
|
||||
$auth_status = \Mockery::mock(\WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus::class)->shouldIgnoreMissing();
|
||||
|
||||
// Mock authorizations for AUTHORIZE intent
|
||||
if ($intent === 'AUTHORIZE') {
|
||||
$authorization = \Mockery::mock(\WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization::class)->shouldIgnoreMissing();
|
||||
$auth_status->shouldReceive('name')->andReturn($capture_success ? 'CREATED' : 'DENIED');
|
||||
$auth_status->shouldReceive('is')->andReturn($capture_success);
|
||||
$authorization->shouldReceive('status')->andReturn($auth_status);
|
||||
$payments->shouldReceive('authorizations')->andReturn([$authorization]);
|
||||
$payments->shouldReceive('captures')->andReturn([]);
|
||||
} else {
|
||||
// For CAPTURE intent, set up captures but no authorizations
|
||||
$payments->shouldReceive('captures')->andReturn([$capture]);
|
||||
$payments->shouldReceive('authorizations')->andReturn([]);
|
||||
}
|
||||
|
||||
$authorization->shouldReceive('id')->andReturn('TEST-AUTH-' . uniqid());
|
||||
$auth_status = \Mockery::mock(\WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus::class)->shouldIgnoreMissing();
|
||||
$purchase_unit->shouldReceive('payments')->andReturn($payments);
|
||||
$order->shouldReceive('purchase_units')->andReturn([$purchase_unit]);
|
||||
|
||||
$auth_status->shouldReceive('name')->andReturn($success ? 'CREATED' : 'DENIED');
|
||||
$auth_status->shouldReceive('is')->andReturn($success);
|
||||
$authorization->shouldReceive('status')->andReturn($auth_status);
|
||||
$payments->shouldReceive('authorizations')->andReturn([$authorization]);
|
||||
$payments->shouldReceive('captures')->andReturn([]);
|
||||
} else {
|
||||
// For CAPTURE intent, set up captures but no authorizations
|
||||
$payments->shouldReceive('captures')->andReturn([$capture]);
|
||||
$payments->shouldReceive('authorizations')->andReturn([]);
|
||||
}
|
||||
|
||||
$purchase_unit->shouldReceive('payments')->andReturn($payments);
|
||||
$order->shouldReceive('purchase_units')->andReturn([$purchase_unit]);
|
||||
|
||||
// Set up the order endpoint methods
|
||||
$order_endpoint->shouldReceive('create')->andReturn($order);
|
||||
if ($intent === 'AUTHORIZE') {
|
||||
$order_endpoint->shouldReceive('authorize')->andReturn($order);
|
||||
} else {
|
||||
$order_endpoint->shouldReceive('capture')->andReturn($order);
|
||||
}
|
||||
$order_endpoint->shouldReceive('order')->andReturn($order);
|
||||
|
||||
return $order_endpoint;
|
||||
}
|
||||
// Set up the order endpoint methods
|
||||
$order_endpoint->shouldReceive('create')->andReturn($order);
|
||||
if ($intent === 'AUTHORIZE') {
|
||||
$order_endpoint->shouldReceive('authorize')->andReturn($order);
|
||||
} else {
|
||||
$order_endpoint->shouldReceive('capture')->andReturn($order);
|
||||
}
|
||||
$order_endpoint->shouldReceive('order')->andReturn($order);
|
||||
$order_endpoint->shouldReceive('patch_order_with')->andReturn($order);
|
||||
return $order_endpoint;
|
||||
}
|
||||
}
|
||||
|
|
25
tests/integration/PHPUnit/Traits/CleansTestData.php
Normal file
25
tests/integration/PHPUnit/Traits/CleansTestData.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Traits;
|
||||
|
||||
trait CleansTestData
|
||||
{
|
||||
/**
|
||||
* Clean all test data created by factories
|
||||
*/
|
||||
protected function cleanupTestData(): void
|
||||
{
|
||||
if (isset($this->order_factory)) {
|
||||
$this->order_factory->cleanup();
|
||||
}
|
||||
|
||||
if (isset($this->product_factory)) {
|
||||
$this->product_factory->cleanup();
|
||||
}
|
||||
|
||||
if (isset($this->coupon_factory)) {
|
||||
$this->coupon_factory->cleanup();
|
||||
}
|
||||
}
|
||||
}
|
50
tests/integration/PHPUnit/Traits/CreateTestOrders.php
Normal file
50
tests/integration/PHPUnit/Traits/CreateTestOrders.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Traits;
|
||||
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Factories\OrderFactory;
|
||||
|
||||
trait CreateTestOrders
|
||||
{
|
||||
use CreateTestProducts;
|
||||
|
||||
private OrderFactory $order_factory;
|
||||
|
||||
/**
|
||||
* Initialize order factory
|
||||
*/
|
||||
protected function initializeOrderFactory(): void
|
||||
{
|
||||
if (!isset($this->product_factory)) {
|
||||
$this->initializeFactories();
|
||||
}
|
||||
|
||||
$this->order_factory = new OrderFactory($this->product_factory, $this->coupon_factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configured order using presets
|
||||
*/
|
||||
protected function getConfiguredOrder(
|
||||
int $customer_id,
|
||||
string $payment_method,
|
||||
array $product_presets,
|
||||
array $discount_presets = [],
|
||||
bool $set_paid = true
|
||||
): WC_Order
|
||||
{
|
||||
if (!isset($this->order_factory)) {
|
||||
$this->initializeOrderFactory();
|
||||
}
|
||||
|
||||
return $this->order_factory->create(
|
||||
$customer_id,
|
||||
$payment_method,
|
||||
$product_presets,
|
||||
$discount_presets,
|
||||
$set_paid
|
||||
);
|
||||
}
|
||||
}
|
60
tests/integration/PHPUnit/Traits/CreateTestProducts.php
Normal file
60
tests/integration/PHPUnit/Traits/CreateTestProducts.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Traits;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Factories\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Factories\CouponFactory;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\ProductPresets;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\Fixtures\DiscountPresets;
|
||||
|
||||
trait CreateTestProducts
|
||||
{
|
||||
private ProductFactory $product_factory;
|
||||
private CouponFactory $coupon_factory;
|
||||
|
||||
/**
|
||||
* Initialize factories
|
||||
*/
|
||||
protected function initializeFactories(): void
|
||||
{
|
||||
$this->product_factory = new ProductFactory();
|
||||
$this->coupon_factory = new CouponFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all test products from presets
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
protected function createTestProducts(): void
|
||||
{
|
||||
if (!isset($this->product_factory)) {
|
||||
$this->initializeFactories();
|
||||
}
|
||||
|
||||
foreach (array_keys(ProductPresets::get()) as $preset_name) {
|
||||
$preset = ProductPresets::get()[$preset_name];
|
||||
// Only create if doesn't exist
|
||||
if (!$this->product_factory->exists($preset['sku'])) {
|
||||
$this->product_factory->createFromPreset($preset_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all test coupons from presets
|
||||
*/
|
||||
protected function createTestCoupons(): void
|
||||
{
|
||||
if (!isset($this->coupon_factory)) {
|
||||
$this->initializeFactories();
|
||||
}
|
||||
|
||||
foreach (DiscountPresets::get() as $preset_name => $preset) {
|
||||
// Only create coupons (skip manual fees)
|
||||
if (isset($preset['coupon_code']) && !$this->coupon_factory->exists($preset['coupon_code'])) {
|
||||
$this->coupon_factory->createFromPreset($preset_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration\Transaction_tests;
|
||||
|
||||
use WC_Payment_Token;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\Tests\Integration\IntegrationMockedTestCase;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @group transactions
|
||||
* @group skip-ci
|
||||
*/
|
||||
class CreditcardTransactionTest extends IntegrationMockedTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->mockPaymentTokensEndpoint = \Mockery::mock(PaymentTokensEndpoint::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a test container with common mocks
|
||||
*
|
||||
* @param OrderEndpoint $orderEndpoint
|
||||
* @param array $additionalServices Additional services to override
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
protected function setupTestContainer(OrderEndpoint $orderEndpoint, array $additionalServices = []): ContainerInterface
|
||||
{
|
||||
$services = [
|
||||
'api.endpoint.order' => function () use ($orderEndpoint) {
|
||||
return $orderEndpoint;
|
||||
},
|
||||
];
|
||||
|
||||
return $this->bootstrapModule(array_merge($services, $additionalServices));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a payment token and configures the mock endpoint to return it
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @param string $gateway_id
|
||||
* @return WC_Payment_Token
|
||||
*/
|
||||
protected function setupPaymentToken(int $customer_id, string $gateway_id = PayPalGateway::ID): WC_Payment_Token
|
||||
{
|
||||
$paymentToken = $this->createAPaymentTokenForTheCustomer($customer_id, $gateway_id);
|
||||
|
||||
$this->mockPaymentTokensEndpoint->shouldReceive('payment_tokens_for_customer')
|
||||
->andReturn([
|
||||
[
|
||||
'id' => $paymentToken->get_token(),
|
||||
'payment_source' => new PaymentSource(
|
||||
'card',
|
||||
(object)[
|
||||
'last_digits' => $paymentToken->get_last4(),
|
||||
'brand' => $paymentToken->get_card_type(),
|
||||
'expiry' => $paymentToken->get_expiry_year() . '-' . $paymentToken->get_expiry_month()
|
||||
]
|
||||
)
|
||||
]
|
||||
]);
|
||||
|
||||
return $paymentToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for different product and discount combinations
|
||||
*/
|
||||
public function paymentProcessingDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple product only' => [
|
||||
'products' => ['simple'],
|
||||
'discounts' => [],
|
||||
'expected_status' => 'processing'
|
||||
],
|
||||
'expensive product' => [
|
||||
'products' => ['simple_expensive'],
|
||||
'discounts' => [],
|
||||
'expected_status' => 'processing'
|
||||
],
|
||||
'multiple products' => [
|
||||
'products' => [
|
||||
['preset' => 'simple', 'quantity' => 2],
|
||||
'simple_expensive'
|
||||
],
|
||||
'discounts' => [],
|
||||
'expected_status' => 'processing'
|
||||
],//TODO fix the discount logic is failing due to taxes
|
||||
/*'simple product with percentage discount' => [
|
||||
'products' => ['simple'],
|
||||
'discounts' => ['percentage_10'],
|
||||
'expected_status' => 'processing'
|
||||
],
|
||||
'simple product with fixed discount' => [
|
||||
'products' => ['simple'],
|
||||
'discounts' => ['fixed_5'],
|
||||
'expected_status' => 'processing'
|
||||
],*/
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests credit card payment processing with different product combinations.
|
||||
*
|
||||
* GIVEN a WooCommerce order with various product and discount combinations
|
||||
* AND valid PayPal order ID in POST data
|
||||
* AND valid credit card form data
|
||||
* WHEN the payment is processed through the credit card gateway
|
||||
* THEN the payment should be successfully captured
|
||||
* AND the order status should change to the expected status
|
||||
* AND a transaction ID should be set on the order
|
||||
*
|
||||
* @dataProvider paymentProcessingDataProvider
|
||||
*/
|
||||
public function testProcessPayment(array $products, array $discounts, string $expected_status)
|
||||
{
|
||||
// Mock successful PayPal API response
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('CAPTURE', false, true);
|
||||
$this->setupTestContainer($mockOrderEndpoint);
|
||||
|
||||
// Create order with provided products and discounts
|
||||
$order = $this->getConfiguredOrder(
|
||||
$this->customer_id,
|
||||
'ppcp-credit-card-gateway',
|
||||
$products,
|
||||
$discounts,
|
||||
false
|
||||
);
|
||||
|
||||
$paypal_order_id = 'TEST-PAYPAL-ORDER-' . uniqid();
|
||||
|
||||
// Set the PayPal order ID in POST data (simulating frontend submission)
|
||||
$_POST['paypal_order_id'] = $paypal_order_id;
|
||||
$order->update_meta_data('_paypal_order_id', $paypal_order_id);
|
||||
$order->save();
|
||||
|
||||
// Mock the session handler to return null (forcing fallback to POST/meta)
|
||||
$sessionHandler = \Mockery::mock(\WooCommerce\PayPalCommerce\Session\SessionHandler::class);
|
||||
$sessionHandler->shouldReceive('order')->andReturn(null);
|
||||
$sessionHandler->shouldReceive('destroy_session_data')->once();
|
||||
|
||||
// Add session handler to container overrides
|
||||
$additionalServices = [
|
||||
'session.handler' => function() use ($sessionHandler) {
|
||||
return $sessionHandler;
|
||||
}
|
||||
];
|
||||
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint, $additionalServices);
|
||||
|
||||
// Simulate credit card form data
|
||||
$_POST['ppcp-credit-card-gateway-card-number'] = '4111111111111111';
|
||||
$_POST['ppcp-credit-card-gateway-card-expiry'] = '12/25';
|
||||
$_POST['ppcp-credit-card-gateway-card-cvc'] = '123';
|
||||
|
||||
// Get the gateway instance
|
||||
$gateway = $c->get('wcgateway.credit-card-gateway');
|
||||
|
||||
// Process payment
|
||||
$result = $gateway->process_payment($order->get_id());
|
||||
|
||||
// Assertions
|
||||
$this->assertEquals('success', $result['result']);
|
||||
$this->assertArrayHasKey('redirect', $result);
|
||||
|
||||
// Verify order status changed
|
||||
$order = wc_get_order($order->get_id()); // Refresh order
|
||||
$this->assertEquals($expected_status, $order->get_status());
|
||||
$this->assertNotEmpty($order->get_transaction_id());
|
||||
|
||||
// Clean up POST data
|
||||
unset($_POST['paypal_order_id']);
|
||||
unset($_POST['ppcp-credit-card-gateway-card-number']);
|
||||
unset($_POST['ppcp-credit-card-gateway-card-expiry']);
|
||||
unset($_POST['ppcp-credit-card-gateway-card-cvc']);
|
||||
}
|
||||
}
|
|
@ -21,78 +21,73 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
|||
*/
|
||||
class VaultingSubscriptionsTest extends IntegrationMockedTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Common mock setup
|
||||
$this->mockPaymentTokensEndpoint = \Mockery::mock(PaymentTokensEndpoint::class);
|
||||
}
|
||||
|
||||
// Create customer and default product that can be reused
|
||||
$this->customer_id = $this->createCustomerIfNotExists();
|
||||
$this->default_product_id = $this->createAProductIfNotProvided();
|
||||
}
|
||||
/**
|
||||
* Sets up a test container with common mocks
|
||||
*
|
||||
* @param OrderEndpoint $orderEndpoint
|
||||
* @param array $additionalServices Additional services to override
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
protected function setupTestContainer(OrderEndpoint $orderEndpoint, array $additionalServices = []): ContainerInterface
|
||||
{
|
||||
$services = [
|
||||
'api.endpoint.order' => function () use ($orderEndpoint) {
|
||||
return $orderEndpoint;
|
||||
},
|
||||
'api.endpoint.payment-tokens' => function () {
|
||||
return $this->mockPaymentTokensEndpoint;
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets up a test container with common mocks
|
||||
*
|
||||
* @param OrderEndpoint $orderEndpoint
|
||||
* @param array $additionalServices Additional services to override
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
protected function setupTestContainer(OrderEndpoint $orderEndpoint, array $additionalServices = []): ContainerInterface
|
||||
{
|
||||
$services = [
|
||||
'api.endpoint.order' => function () use ($orderEndpoint) {
|
||||
return $orderEndpoint;
|
||||
},
|
||||
'api.endpoint.payment-tokens' => function () {
|
||||
return $this->mockPaymentTokensEndpoint;
|
||||
}
|
||||
];
|
||||
return $this->bootstrapModule(array_merge($services, $additionalServices));
|
||||
}
|
||||
|
||||
return $this->bootstrapModule(array_merge($services, $additionalServices));
|
||||
}
|
||||
/**
|
||||
* Creates a payment token and configures the mock endpoint to return it
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @param string $gateway_id
|
||||
* @return WC_Payment_Token
|
||||
*/
|
||||
protected function setupPaymentToken(int $customer_id, string $gateway_id = PayPalGateway::ID): WC_Payment_Token
|
||||
{
|
||||
$paymentToken = $this->createAPaymentTokenForTheCustomer($customer_id, $gateway_id);
|
||||
|
||||
/**
|
||||
* Creates a payment token and configures the mock endpoint to return it
|
||||
*
|
||||
* @param int $customer_id
|
||||
* @param string $gateway_id
|
||||
* @return WC_Payment_Token
|
||||
*/
|
||||
protected function setupPaymentToken(int $customer_id, string $gateway_id = PayPalGateway::ID): WC_Payment_Token
|
||||
{
|
||||
$paymentToken = $this->createAPaymentTokenForTheCustomer($customer_id, $gateway_id);
|
||||
$this->mockPaymentTokensEndpoint->shouldReceive('payment_tokens_for_customer')
|
||||
->andReturn([
|
||||
[
|
||||
'id' => $paymentToken->get_token(),
|
||||
'payment_source' => new PaymentSource(
|
||||
'card',
|
||||
(object)[
|
||||
'last_digits' => $paymentToken->get_last4(),
|
||||
'brand' => $paymentToken->get_card_type(),
|
||||
'expiry' => $paymentToken->get_expiry_year() . '-' . $paymentToken->get_expiry_month()
|
||||
]
|
||||
)
|
||||
]
|
||||
]);
|
||||
|
||||
$this->mockPaymentTokensEndpoint->shouldReceive('payment_tokens_for_customer')
|
||||
->andReturn([
|
||||
[
|
||||
'id' => $paymentToken->get_token(),
|
||||
'payment_source' => new PaymentSource(
|
||||
'card',
|
||||
(object)[
|
||||
'last_digits' => $paymentToken->get_last4(),
|
||||
'brand' => $paymentToken->get_card_type(),
|
||||
'expiry' => $paymentToken->get_expiry_year() . '-' . $paymentToken->get_expiry_month()
|
||||
]
|
||||
)
|
||||
]
|
||||
]);
|
||||
|
||||
return $paymentToken;
|
||||
}
|
||||
return $paymentToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that vaulting is automatically enabled when subscription mode is set to vaulting_api.
|
||||
*
|
||||
* GIVEN a PayPal account with Reference Transactions enabled
|
||||
* WHEN the subscription mode is set to "vaulting_api"
|
||||
* THEN vaulting should be automatically enabled for the PayPal gateway
|
||||
*/
|
||||
public function test_vaulting_is_enabled_when_subscription_mode_is_vaulting_api()
|
||||
{
|
||||
/**
|
||||
* Tests that vaulting is automatically enabled when subscription mode is set to vaulting_api.
|
||||
*
|
||||
* GIVEN a PayPal account with Reference Transactions enabled
|
||||
* WHEN the subscription mode is set to "vaulting_api"
|
||||
* THEN vaulting should be automatically enabled for the PayPal gateway
|
||||
*/
|
||||
public function test_vaulting_is_enabled_when_subscription_mode_is_vaulting_api()
|
||||
{
|
||||
$user_has_cap_callback = function ($allcaps, $caps, $args) {
|
||||
if (isset($args[0]) && $args[0] === 'manage_woocommerce') {
|
||||
$allcaps['manage_woocommerce'] = true;
|
||||
|
@ -164,100 +159,102 @@ class VaultingSubscriptionsTest extends IntegrationMockedTestCase
|
|||
remove_filter('user_has_cap', $user_has_cap_callback, 10);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Data provider for payment gateway tests
|
||||
*/
|
||||
public function paymentGatewayProvider(): array
|
||||
{
|
||||
return [
|
||||
'PayPal Gateway' => [PayPalGateway::ID],
|
||||
'Credit Card Gateway' => [CreditCardGateway::ID]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests PayPal renewal payment processing.
|
||||
*
|
||||
* GIVEN a subscription with a saved PayPal payment token due for renewal
|
||||
* WHEN the renewal process is triggered
|
||||
* THEN a new PayPal order should be created using the customer token
|
||||
*
|
||||
* @dataProvider paymentGatewayProvider
|
||||
*/
|
||||
public function test_renewal_payment_processing(string $gateway_id)
|
||||
{
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('CAPTURE', true);
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
/**
|
||||
* Data provider for payment gateway tests
|
||||
*/
|
||||
public function paymentGatewayProvider(): array
|
||||
{
|
||||
return [
|
||||
'PayPal Gateway' => [PayPalGateway::ID],
|
||||
'Credit Card Gateway' => [CreditCardGateway::ID]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests PayPal renewal payment processing.
|
||||
*
|
||||
* GIVEN a subscription with a saved PayPal payment token due for renewal
|
||||
* WHEN the renewal process is triggered
|
||||
* THEN a new PayPal order should be created using the customer token
|
||||
*
|
||||
* @dataProvider paymentGatewayProvider
|
||||
*/
|
||||
public function test_renewal_payment_processing(string $gateway_id)
|
||||
{
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint();
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
$this->setupPaymentToken($this->customer_id, $gateway_id);
|
||||
$subscription = $this->createSubscription($this->customer_id, $gateway_id);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, $gateway_id, $subscription->get_id());
|
||||
$subscription = $this->createSubscription($this->customer_id, $gateway_id);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, $gateway_id, $subscription->get_id());
|
||||
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
|
||||
// Check that the order was processed
|
||||
$this->assertEquals('processing', $renewal_order->get_status(), 'The renewal order should be processing after successful payment');
|
||||
$this->assertNotEmpty($renewal_order->get_transaction_id(), 'The renewal order should have a transaction ID');
|
||||
}
|
||||
/**
|
||||
* Tests that renewal processing handles failed payments correctly.
|
||||
*
|
||||
* GIVEN a subscription due for renewal
|
||||
* WHEN the payment process fails with an exception
|
||||
* THEN the renewal order should be marked as failed
|
||||
*/
|
||||
public function test_renewal_handles_failed_payment()
|
||||
{
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('CAPTURE', false);
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
// Check that the order was processed
|
||||
$this->assertEquals('processing', $renewal_order->get_status(), 'The renewal order should be processing after successful payment');
|
||||
$this->assertNotEmpty($renewal_order->get_transaction_id(), 'The renewal order should have a transaction ID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that renewal processing handles failed payments correctly.
|
||||
*
|
||||
* GIVEN a subscription due for renewal
|
||||
* WHEN the payment process fails with an exception
|
||||
* THEN the renewal order should be marked as failed
|
||||
*/
|
||||
public function test_renewal_handles_failed_payment()
|
||||
{
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('CAPTURE', false, false);
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
$this->setupPaymentToken($this->customer_id);
|
||||
$subscription = $this->createSubscription($this->customer_id, PayPalGateway::ID);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, PayPalGateway::ID, $subscription->get_id());
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
$subscription = $this->createSubscription($this->customer_id, PayPalGateway::ID);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, PayPalGateway::ID, $subscription->get_id());
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
|
||||
// Check that the order status is failed
|
||||
$this->assertEquals('failed', $renewal_order->get_status(), 'The renewal order should be marked as failed when payment fails');
|
||||
}
|
||||
// Check that the order status is failed
|
||||
$this->assertEquals('failed', $renewal_order->get_status(), 'The renewal order should be marked as failed when payment fails');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests authorization-only subscription renewals.
|
||||
*
|
||||
* GIVEN the payment intent is set to "AUTHORIZE"
|
||||
* WHEN a subscription renewal payment is processed
|
||||
* THEN the payment should be authorized but not captured
|
||||
*/
|
||||
public function test_authorize_only_subscription_renewal()
|
||||
{
|
||||
// Mock the OrderEndpoint with AUTHORIZE intent
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('AUTHORIZE', true);
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
/**
|
||||
* Tests authorization-only subscription renewals.
|
||||
*
|
||||
* GIVEN the payment intent is set to "AUTHORIZE"
|
||||
* WHEN a subscription renewal payment is processed
|
||||
* THEN the payment should be authorized but not captured
|
||||
*/
|
||||
public function test_authorize_only_subscription_renewal()
|
||||
{
|
||||
// Mock the OrderEndpoint with AUTHORIZE intent
|
||||
$mockOrderEndpoint = $this->mockOrderEndpoint('AUTHORIZE', false, true);
|
||||
$c = $this->setupTestContainer($mockOrderEndpoint);
|
||||
|
||||
// Setup payment token and subscription
|
||||
// Setup payment token and subscription
|
||||
$this->setupPaymentToken($this->customer_id);
|
||||
$subscription = $this->createSubscription($this->customer_id, PayPalGateway::ID);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, PayPalGateway::ID, $subscription->get_id());
|
||||
$subscription = $this->createSubscription($this->customer_id, PayPalGateway::ID);
|
||||
$renewal_order = $this->createRenewalOrder($this->customer_id, PayPalGateway::ID, $subscription->get_id());
|
||||
|
||||
// Override the intent setting to ensure it's set to AUTHORIZE
|
||||
$settings = $c->get('wcgateway.settings');
|
||||
$original_intent = $settings->get('intent');
|
||||
$settings->set('intent', 'authorize');
|
||||
$settings->persist();
|
||||
// Override the intent setting to ensure it's set to AUTHORIZE
|
||||
$settings = $c->get('wcgateway.settings');
|
||||
$original_intent = $settings->get('intent');
|
||||
$settings->set('intent', 'authorize');
|
||||
$settings->persist();
|
||||
|
||||
try {
|
||||
// Process the renewal
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
try {
|
||||
// Process the renewal
|
||||
$renewal_handler = $c->get('wc-subscriptions.renewal-handler');
|
||||
$renewal_handler->renew($renewal_order);
|
||||
|
||||
// Check that the order was processed with authorization
|
||||
$this->assertEquals('on-hold', $renewal_order->get_status(), 'The renewal order should be on-hold after successful authorization');
|
||||
$this->assertNotEmpty($renewal_order->get_transaction_id(), 'The renewal order should have a transaction ID');
|
||||
$this->assertEquals('AUTHORIZE', $mockOrderEndpoint->order('')->intent(), 'The order intent should be AUTHORIZE');
|
||||
} finally {
|
||||
// Restore original settings
|
||||
$settings->set('intent', $original_intent);
|
||||
$settings->persist();
|
||||
}
|
||||
}
|
||||
// Check that the order was processed with authorization
|
||||
$this->assertEquals('on-hold', $renewal_order->get_status(), 'The renewal order should be on-hold after successful authorization');
|
||||
$this->assertNotEmpty($renewal_order->get_transaction_id(), 'The renewal order should have a transaction ID');
|
||||
$this->assertEquals('AUTHORIZE', $mockOrderEndpoint->order('')->intent(), 'The order intent should be AUTHORIZE');
|
||||
} finally {
|
||||
// Restore original settings
|
||||
$settings->set('intent', $original_intent);
|
||||
$settings->persist();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: 3.0.6
|
||||
* Version: 3.0.7
|
||||
* Author: PayPal
|
||||
* Author URI: https://paypal.com/
|
||||
* License: GPL-2.0
|
||||
|
@ -11,7 +11,7 @@
|
|||
* Requires Plugins: woocommerce
|
||||
* Requires at least: 6.5
|
||||
* WC requires at least: 9.6
|
||||
* WC tested up to: 9.8
|
||||
* WC tested up to: 9.9
|
||||
* Text Domain: woocommerce-paypal-payments
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce
|
||||
|
@ -27,7 +27,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
|
|||
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
||||
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
||||
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2025-05-14' );
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2025-06-25' );
|
||||
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
||||
|
||||
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue