Merge branch 'trunk' into PCP-1393-update-to-vault-v-3

This commit is contained in:
Emili Castells Guasch 2023-10-25 11:21:28 +02:00
commit 9f41d5c874
29 changed files with 755 additions and 492 deletions

View file

@ -14,6 +14,14 @@
* Enhancement - Cart simulation improvements #1753
* Enhancement - Billing schedule fields not greyed out when PayPal Subscriptions product is connected #1755
* Enhancement - Check validation errors when submitting in block #1528
* Enhancement - Improve handling of server error when submitting block #1785
* Enhancement - Extend Apple Pay country eligibility #1781
* Enhancement - Apple Pay validation notice improvements #1783
* Enhancement - Apple Pay payment process issues #1789
* Enhancement - Disable the tracking if payment is not captured #1780
* Enhancement - Place order button remains - Could not retrieve order #1786
* Enhancement - Google Pay for variable product greyed out but clickable #1788
* Enhancement - Merchant credential validation & remove PAYEE object #1795
= 2.3.1 - 2023-09-26 =
* Fix - Fatal error when saving product while WooCommerce Subscriptions plugin is not active #1731

View file

@ -308,8 +308,6 @@ return array(
'api.factory.purchase-unit' => static function ( ContainerInterface $container ): PurchaseUnitFactory {
$amount_factory = $container->get( 'api.factory.amount' );
$payee_repository = $container->get( 'api.repository.payee' );
$payee_factory = $container->get( 'api.factory.payee' );
$item_factory = $container->get( 'api.factory.item' );
$shipping_factory = $container->get( 'api.factory.shipping' );
$payments_factory = $container->get( 'api.factory.payments' );
@ -319,8 +317,6 @@ return array(
return new PurchaseUnitFactory(
$amount_factory,
$payee_repository,
$payee_factory,
$item_factory,
$shipping_factory,
$payments_factory,

View file

@ -51,13 +51,6 @@ class PurchaseUnit {
*/
private $description;
/**
* The Payee.
*
* @var Payee|null
*/
private $payee;
/**
* The custom id.
*
@ -108,7 +101,6 @@ class PurchaseUnit {
* @param Shipping|null $shipping The Shipping.
* @param string $reference_id The reference ID.
* @param string $description The description.
* @param Payee|null $payee The Payee.
* @param string $custom_id The custom ID.
* @param string $invoice_id The invoice ID.
* @param string $soft_descriptor The soft descriptor.
@ -120,7 +112,6 @@ class PurchaseUnit {
Shipping $shipping = null,
string $reference_id = 'default',
string $description = '',
Payee $payee = null,
string $custom_id = '',
string $invoice_id = '',
string $soft_descriptor = '',
@ -150,7 +141,6 @@ class PurchaseUnit {
}
)
);
$this->payee = $payee;
$this->custom_id = $custom_id;
$this->invoice_id = $invoice_id;
$this->soft_descriptor = $soft_descriptor;
@ -257,15 +247,6 @@ class PurchaseUnit {
return $this->soft_descriptor;
}
/**
* Returns the Payee.
*
* @return Payee|null
*/
public function payee() {
return $this->payee;
}
/**
* Returns the Payments.
*
@ -314,10 +295,6 @@ class PurchaseUnit {
),
);
if ( $this->payee() ) {
$purchase_unit['payee'] = $this->payee()->to_array();
}
if ( $this->payments() ) {
$purchase_unit['payments'] = $this->payments()->to_array();
}

View file

@ -14,7 +14,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
/**
@ -29,20 +28,6 @@ class PurchaseUnitFactory {
*/
private $amount_factory;
/**
* The payee repository.
*
* @var PayeeRepository
*/
private $payee_repository;
/**
* The payee factory.
*
* @var PayeeFactory
*/
private $payee_factory;
/**
* The item factory.
*
@ -89,8 +74,6 @@ class PurchaseUnitFactory {
* PurchaseUnitFactory constructor.
*
* @param AmountFactory $amount_factory The amount factory.
* @param PayeeRepository $payee_repository The Payee repository.
* @param PayeeFactory $payee_factory The Payee factory.
* @param ItemFactory $item_factory The item factory.
* @param ShippingFactory $shipping_factory The shipping factory.
* @param PaymentsFactory $payments_factory The payments factory.
@ -100,8 +83,6 @@ class PurchaseUnitFactory {
*/
public function __construct(
AmountFactory $amount_factory,
PayeeRepository $payee_repository,
PayeeFactory $payee_factory,
ItemFactory $item_factory,
ShippingFactory $shipping_factory,
PaymentsFactory $payments_factory,
@ -111,8 +92,6 @@ class PurchaseUnitFactory {
) {
$this->amount_factory = $amount_factory;
$this->payee_repository = $payee_repository;
$this->payee_factory = $payee_factory;
$this->item_factory = $item_factory;
$this->shipping_factory = $shipping_factory;
$this->payments_factory = $payments_factory;
@ -146,7 +125,6 @@ class PurchaseUnitFactory {
}
$reference_id = 'default';
$description = '';
$payee = $this->payee_repository->payee();
$custom_id = (string) $order->get_id();
$invoice_id = $this->prefix . $order->get_order_number();
$soft_descriptor = $this->soft_descriptor;
@ -157,7 +135,6 @@ class PurchaseUnitFactory {
$shipping,
$reference_id,
$description,
$payee,
$custom_id,
$invoice_id,
$soft_descriptor
@ -211,8 +188,6 @@ class PurchaseUnitFactory {
$reference_id = 'default';
$description = '';
$payee = $this->payee_repository->payee();
$custom_id = '';
$session = WC()->session;
if ( $session instanceof WC_Session_Handler ) {
@ -229,7 +204,6 @@ class PurchaseUnitFactory {
$shipping,
$reference_id,
$description,
$payee,
$custom_id,
$invoice_id,
$soft_descriptor
@ -269,7 +243,6 @@ class PurchaseUnitFactory {
$data->items
);
}
$payee = isset( $data->payee ) ? $this->payee_factory->from_paypal_response( $data->payee ) : null;
$shipping = null;
try {
if ( isset( $data->shipping ) ) {
@ -293,7 +266,6 @@ class PurchaseUnitFactory {
$shipping,
$data->reference_id,
$description,
$payee,
$custom_id,
$invoice_id,
$soft_descriptor,

View file

@ -24,7 +24,7 @@ return array(
return $fields;
}
$is_available = $container->get( 'applepay.enabled' );
$is_available = $container->get( 'applepay.available' );
$is_referral = $container->get( 'applepay.is_referral' );
$insert_after = function ( array $array, string $key, array $new ): array {

View file

@ -24,9 +24,7 @@ class ApplepayButton {
);
//PRODUCT DETAIL PAGE
if(this.context === 'product') {
this.productQuantity = document.querySelector('input.qty').value
}
this.refreshContextData();
this.updated_contact_info = []
this.selectedShippingMethod = []
@ -43,6 +41,8 @@ class ApplepayButton {
if (this.isInitialized) {
return;
}
this.log('Init', this.context);
this.initEventHandlers();
this.isInitialized = true;
this.applePayConfig = config;
@ -76,6 +76,16 @@ class ApplepayButton {
});
}
}
reinit() {
if (!this.applePayConfig) {
return;
}
this.isInitialized = false;
this.init(this.applePayConfig);
}
async fetchTransactionInfo() {
this.transactionInfo = await this.contextHandler.transactionInfo();
}
@ -123,6 +133,7 @@ class ApplepayButton {
}
applePaySession(paymentRequest) {
this.log('applePaySession', paymentRequest);
const session = new ApplePaySession(4, paymentRequest)
session.begin()
@ -172,6 +183,8 @@ class ApplepayButton {
* Show Apple Pay payment sheet when Apple Pay payment button is clicked
*/
async onButtonClick() {
this.log('onButtonClick', this.context);
const paymentDataRequest = this.paymentDataRequest();
// trigger woocommerce validation if we are in the checkout page
if (this.context === 'checkout') {
@ -183,7 +196,8 @@ class ApplepayButton {
try {
const formData = new FormData(document.querySelector(checkoutFormSelector));
this.form_saved = Object.fromEntries(formData.entries());
this.update_request_data_with_form(paymentDataRequest);
// This line should be reviewed, the paypal.Applepay().confirmOrder fails if we add it.
//this.update_request_data_with_form(paymentDataRequest);
} catch (error) {
console.error(error);
}
@ -241,17 +255,29 @@ class ApplepayButton {
return paymentDataRequest
}
refreshContextData() {
switch (this.context) {
case 'product':
// Refresh product data that makes the price change.
this.productQuantity = document.querySelector('input.qty').value;
break;
}
}
//------------------------
// Payment process
//------------------------
onvalidatemerchant(session) {
this.log('onvalidatemerchant', this.buttonConfig.ajax_url);
return (applePayValidateMerchantEvent) => {
this.log('onvalidatemerchant call');
paypal.Applepay().validateMerchant({
validationUrl: applePayValidateMerchantEvent.validationURL
})
.then(validateResult => {
this.log('onvalidatemerchant ok');
session.completeMerchantValidation(validateResult.merchantSession);
//call backend to update validation to true
jQuery.ajax({
@ -265,6 +291,7 @@ class ApplepayButton {
})
})
.catch(validateError => {
this.log('onvalidatemerchant error', validateError);
console.error(validateError);
//call backend to update validation to false
jQuery.ajax({
@ -276,19 +303,24 @@ class ApplepayButton {
'woocommerce-process-checkout-nonce': this.nonce,
}
})
this.log('onvalidatemerchant session abort');
session.abort();
});
};
}
onshippingmethodselected(session) {
this.log('onshippingmethodselected', this.buttonConfig.ajax_url);
const ajax_url = this.buttonConfig.ajax_url
return (event) => {
this.log('onshippingmethodselected call');
const data = this.getShippingMethodData(event);
jQuery.ajax({
url: ajax_url,
method: 'POST',
data: data,
success: (applePayShippingMethodUpdate, textStatus, jqXHR) => {
this.log('onshippingmethodselected ok');
let response = applePayShippingMethodUpdate.data
if (applePayShippingMethodUpdate.success === false) {
response.errors = createAppleErrors(response.errors)
@ -309,6 +341,7 @@ class ApplepayButton {
session.completeShippingMethodSelection(response)
},
error: (jqXHR, textStatus, errorThrown) => {
this.log('onshippingmethodselected error', textStatus);
console.warn(textStatus, errorThrown)
session.abort()
},
@ -316,14 +349,18 @@ class ApplepayButton {
};
}
onshippingcontactselected(session) {
this.log('onshippingcontactselected', this.buttonConfig.ajax_url);
const ajax_url = this.buttonConfig.ajax_url
return (event) => {
this.log('onshippingcontactselected call');
const data = this.getShippingContactData(event);
jQuery.ajax({
url: ajax_url,
method: 'POST',
data: data,
success: (applePayShippingContactUpdate, textStatus, jqXHR) => {
this.log('onshippingcontactselected ok');
let response = applePayShippingContactUpdate.data
this.updated_contact_info = event.shippingContact
if (applePayShippingContactUpdate.success === false) {
@ -335,6 +372,7 @@ class ApplepayButton {
session.completeShippingContactSelection(response)
},
error: (jqXHR, textStatus, errorThrown) => {
this.log('onshippingcontactselected error', textStatus);
console.warn(textStatus, errorThrown)
session.abort()
},
@ -344,6 +382,8 @@ class ApplepayButton {
getShippingContactData(event) {
const product_id = this.buttonConfig.product.id;
this.refreshContextData();
switch (this.context) {
case 'product':
return {
@ -371,6 +411,9 @@ class ApplepayButton {
}
getShippingMethodData(event) {
const product_id = this.buttonConfig.product.id;
this.refreshContextData();
switch (this.context) {
case 'product': return {
action: 'ppcp_update_shipping_method',
@ -397,7 +440,10 @@ class ApplepayButton {
}
onpaymentauthorized(session) {
this.log('onpaymentauthorized');
return async (event) => {
this.log('onpaymentauthorized call');
function form() {
return document.querySelector('form.cart');
}
@ -420,32 +466,45 @@ class ApplepayButton {
'_wp_http_referer': '/?wc-ajax=update_order_review',
'paypal_order_id': data.paypal_order_id,
};
this.log('onpaymentauthorized request', this.buttonConfig.ajax_url, data);
jQuery.ajax({
url: this.buttonConfig.ajax_url,
method: 'POST',
data: request_data,
complete: (jqXHR, textStatus) => {
this.log('onpaymentauthorized complete');
},
success: (authorizationResult, textStatus, jqXHR) => {
this.log('onpaymentauthorized ok');
resolve(authorizationResult)
},
error: (jqXHR, textStatus, errorThrown) => {
this.log('onpaymentauthorized error', textStatus);
reject(new Error(errorThrown));
},
})
} catch (error) {
this.log('onpaymentauthorized catch', error);
console.log(error) // handle error
}
});
}
let id = await this.contextHandler.createOrder();
this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact);
try {
const confirmOrderResponse = await paypal.Applepay().confirmOrder({
orderId: id,
token: event.payment.token,
billingContact: event.payment.billingContact,
});
this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse);
if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) {
if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") {
try {

View file

@ -35,6 +35,12 @@ class ApplepayManager {
})();
}
reinit() {
for (const button of this.buttons) {
button.reinit();
}
}
/**
* Gets ApplePay configuration of the PayPal merchant.
* @returns {Promise<null>}

View file

@ -8,11 +8,19 @@ import ApplepayManager from "./ApplepayManager";
jQuery
}) {
let manager;
const bootstrap = function () {
const manager = new ApplepayManager(buttonConfig, ppcpConfig);
manager = new ApplepayManager(buttonConfig, ppcpConfig);
manager.init();
};
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
if (manager) {
manager.reinit();
}
});
document.addEventListener(
'DOMContentLoaded',
() => {
@ -20,7 +28,6 @@ import ApplepayManager from "./ApplepayManager";
(typeof (buttonConfig) === 'undefined') ||
(typeof (ppcpConfig) === 'undefined')
) {
console.error('PayPal button could not be configured.');
return;
}
const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;

View file

@ -16,11 +16,10 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
use WooCommerce\PayPalCommerce\Applepay\Assets\DataToAppleButtonScripts;
use WooCommerce\PayPalCommerce\Applepay\Assets\BlocksPaymentMethod;
use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
return array(
'applepay.eligible' => static function ( ContainerInterface $container ): bool {
@ -47,23 +46,37 @@ return array(
return ! $status->has_request_failure();
},
'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice {
$settings = $container->get( 'wcgateway.settings' );
return new AvailabilityNotice(
$container->get( 'applepay.apple-product-status' ),
$container->get( 'wcgateway.is-wc-gateways-list-page' ),
$container->get( 'wcgateway.is-ppcp-settings-page' ),
$container->get( 'applepay.available' ) || ( ! $container->get( 'applepay.is_referral' ) ),
$container->get( 'applepay.server_supported' ),
$settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false,
$container->get( 'applepay.button' )
);
},
'applepay.apple-product-status' => static function( ContainerInterface $container ): AppleProductStatus {
return new AppleProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'applepay.status-cache' ),
$container->get( 'onboarding.state' ),
$container->get( 'api.helper.failure-registry' )
);
},
'applepay.enabled' => static function ( ContainerInterface $container ): bool {
if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', false ) ) {
'applepay.available' => static function ( ContainerInterface $container ): bool {
if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', true ) ) {
$status = $container->get( 'applepay.apple-product-status' );
assert( $status instanceof AppleProductStatus );
/**
* If merchant isn't onboarded via /v1/customer/partner-referrals this returns false as the API call fails.
*/
return apply_filters( 'woocommerce_paypal_payments_applepay_product_status', $status->apple_is_active() );
return apply_filters( 'woocommerce_paypal_payments_applepay_product_status', $status->is_active() );
}
return true;
},
@ -117,6 +130,24 @@ return array(
return apply_filters(
'woocommerce_paypal_payments_applepay_supported_country_currency_matrix',
array(
'GB' => array(
'AUD',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'JPY',
'NOK',
'NZD',
'PLN',
'SEK',
'SGD',
'USD',
),
'US' => array(
'AUD',
'CAD',
@ -125,6 +156,24 @@ return array(
'JPY',
'USD',
),
'CA' => array(
'AUD',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'JPY',
'NOK',
'NZD',
'PLN',
'SEK',
'SGD',
'USD',
),
)
);
},
@ -149,7 +198,7 @@ return array(
$environment = $container->get( 'onboarding.environment' );
assert( $environment instanceof Environment );
$enabled = $product_status->apple_is_active();
$enabled = $product_status->is_active();
$enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );

File diff suppressed because one or more lines are too long

View file

@ -12,23 +12,16 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Cart;
use WC_Checkout;
use WC_Order;
use WC_Session_Handler;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Session\MemoryWcSession;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
/**
* Class PayPalPaymentMethod
* Class ApplePayButton
*/
class ApplePayButton implements ButtonInterface {
use RequestHandlerTrait;
@ -178,21 +171,6 @@ class ApplePayButton implements ButtonInterface {
$data['products'][0] = 'PAYMENT_METHODS';
}
$data['capabilities'][] = 'APPLE_PAY';
$data['operations'][] = array(
'operation' => 'API_INTEGRATION',
'api_integration_preference' => array(
'rest_api_integration' => array(
'integration_method' => 'PAYPAL',
'integration_type' => 'THIRD_PARTY',
'third_party_details' => array(
'features' => array(
'PAYMENT',
'REFUND',
),
),
),
),
);
return $data;
}
@ -422,7 +400,7 @@ class ApplePayButton implements ButtonInterface {
}
$applepay_request_data_object->order_data( $context );
$this->update_posted_data( $applepay_request_data_object );
if ( $context == 'product' ) {
if ( $context === 'product' ) {
$cart_item_key = $this->prepare_cart( $applepay_request_data_object );
$cart = WC()->cart;
$address = $applepay_request_data_object->shipping_address();
@ -902,15 +880,19 @@ class ApplePayButton implements ButtonInterface {
* Renders the Apple Pay button on the page
*
* @return bool
*
* @psalm-suppress RedundantCondition
*/
public function render(): bool {
$is_applepay_button_enabled = $this->settings->has( 'applepay_button_enabled' ) ? $this->settings->get( 'applepay_button_enabled' ) : false;
if ( ! $this->is_enabled() ) {
return true;
}
$button_enabled_product = $is_applepay_button_enabled && $this->settings_status->is_smart_button_enabled_for_location( 'product' );
$button_enabled_cart = $is_applepay_button_enabled && $this->settings_status->is_smart_button_enabled_for_location( 'cart' );
$button_enabled_checkout = $is_applepay_button_enabled;
$button_enabled_payorder = $is_applepay_button_enabled;
$button_enabled_minicart = $is_applepay_button_enabled && $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
$button_enabled_product = $this->settings_status->is_smart_button_enabled_for_location( 'product' );
$button_enabled_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' );
$button_enabled_checkout = true;
$button_enabled_payorder = true;
$button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
@ -1017,13 +999,7 @@ class ApplePayButton implements ButtonInterface {
);
wp_enqueue_script( 'wc-ppcp-applepay' );
wp_register_style(
'wc-ppcp-applepay',
untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
array(),
$this->version
);
wp_enqueue_style( 'wc-ppcp-applepay' );
$this->enqueue_styles();
wp_localize_script(
'wc-ppcp-applepay',
@ -1038,6 +1014,23 @@ class ApplePayButton implements ButtonInterface {
);
}
/**
* Enqueues styles.
*/
public function enqueue_styles(): void {
if ( ! $this->is_enabled() ) {
return;
}
wp_register_style(
'wc-ppcp-applepay',
untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
array(),
$this->version
);
wp_enqueue_style( 'wc-ppcp-applepay' );
}
/**
* Returns the script data.
*
@ -1054,7 +1047,6 @@ class ApplePayButton implements ButtonInterface {
*/
public function is_enabled(): bool {
try {
// todo add also onboarded apple and enabled buttons.
return $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' );
} catch ( Exception $e ) {
return false;

View file

@ -300,6 +300,10 @@ class ApplePayDataObjectHttp {
*/
protected function assign_data_object_values( array $data ): void {
foreach ( $data as $key => $value ) {
// Null values may give origin to type errors. If necessary replace condition this with a specialized field filter.
if ( null === $value ) {
continue;
}
if ( $key === 'woocommerce-process-checkout-nonce' ) {
$key = 'nonce';
}

View file

@ -1,41 +1,37 @@
<?php
/**
* Manage the Seller status.
* Status of the ApplePay merchant connection.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper
* @package WooCommerce\PayPalCommerce\Applepay\Assets
*/
declare( strict_types=1 );
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Applepay\Assets;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class PayUponInvoiceProductStatus
* Class AppleProductStatus
*/
class AppleProductStatus {
const CAPABILITY_NAME = 'APPLE_PAY';
const SETTINGS_KEY = 'products_apple_enabled';
const APPLE_STATUS_CACHE_KEY = 'apple_status_cache';
const SETTINGS_VALUE_ENABLED = 'yes';
const SETTINGS_VALUE_DISABLED = 'no';
const SETTINGS_VALUE_UNDEFINED = '';
/**
* The Cache.
*
* @var Cache
*/
protected $cache;
/**
* Caches the status for the current load.
* The current status stored in memory.
*
* @var bool|null
*/
private $current_status_cache;
private $current_status = null;
/**
* If there was a request failure.
@ -73,24 +69,21 @@ class AppleProductStatus {
private $api_failure_registry;
/**
* PayUponInvoiceProductStatus constructor.
* AppleProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
* @param State $onboarding_state The onboarding state.
* @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
Cache $cache,
State $onboarding_state,
FailureRegistry $api_failure_registry
) {
$this->settings = $settings;
$this->partners_endpoint = $partners_endpoint;
$this->cache = $cache;
$this->onboarding_state = $onboarding_state;
$this->api_failure_registry = $api_failure_registry;
}
@ -100,55 +93,71 @@ class AppleProductStatus {
*
* @return bool
*/
public function apple_is_active() : bool {
if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
public function is_active() : bool {
// If not onboarded then makes no sense to check status.
if ( ! $this->is_onboarded() ) {
return false;
}
if ( $this->cache->has( self::APPLE_STATUS_CACHE_KEY ) ) {
return $this->cache->get( self::APPLE_STATUS_CACHE_KEY ) === 'true';
// If status was already checked on this request return the same result.
if ( null !== $this->current_status ) {
return $this->current_status;
}
if ( $this->current_status_cache === true ) {
return $this->current_status_cache;
}
if ( $this->settings->has( 'products_apple_enabled' ) && $this->settings->get( 'products_apple_enabled' ) === true ) {
$this->current_status_cache = true;
return true;
// Check if status was checked on previous requests.
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
$this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
return $this->current_status;
}
// Check API failure registry to prevent multiple failed API requests.
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) {
$this->has_request_failure = true;
$this->current_status_cache = false;
return $this->current_status_cache;
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
// Request seller status via PayPal API.
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
$this->has_request_failure = true;
$this->current_status_cache = false;
return false;
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
// Check the seller status for the intended capability.
foreach ( $seller_status->products() as $product ) {
if ( $product->name() !== 'PAYMENT_METHODS' ) {
continue;
}
if ( in_array( 'APPLE_PAY', $product->capabilities(), true ) ) {
$this->settings->set( 'products_apple_enabled', true );
if ( in_array( self::CAPABILITY_NAME, $product->capabilities(), true ) ) {
// Capability found, persist status and return true.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
$this->settings->persist();
$this->current_status_cache = true;
$this->cache->set( self::APPLE_STATUS_CACHE_KEY, 'true', 3 * MONTH_IN_SECONDS );
return true;
$this->current_status = true;
return $this->current_status;
}
}
$this->cache->set( self::APPLE_STATUS_CACHE_KEY, 'false', 3 * MONTH_IN_SECONDS );
$this->current_status_cache = false;
return false;
// Capability not found, persist status and return false.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->settings->persist();
$this->current_status = false;
return $this->current_status;
}
/**
* Returns if the seller is onboarded.
*
* @return bool
*/
public function is_onboarded(): bool {
return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED;
}
/**
@ -160,4 +169,24 @@ class AppleProductStatus {
return $this->has_request_failure;
}
/**
* Clears the persisted result to force a recheck.
*
* @param Settings|null $settings The settings object.
* We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
* @return void
*/
public function clear( Settings $settings = null ): void {
if ( null === $settings ) {
$settings = $this->settings;
}
$this->current_status = null;
if ( $settings->has( self::SETTINGS_KEY ) ) {
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist();
}
}
}

View file

@ -0,0 +1,291 @@
<?php
/**
* Adds availability notice if applicable.
*
* @package WooCommerce\PayPalCommerce\Applepay\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Applepay\Helper;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
/**
* Class AvailabilityNotice
*/
class AvailabilityNotice {
/**
* The product status handler.
*
* @var AppleProductStatus
*/
private $product_status;
/**
* Indicates if we're on the WooCommerce gateways list page.
*
* @var bool
*/
private $is_wc_gateways_list_page;
/**
* Indicates if we're on a PPCP Settings page.
*
* @var bool
*/
private $is_ppcp_settings_page;
/**
* Indicates if ApplePay is available to be enabled.
*
* @var bool
*/
private $is_available;
/**
* Indicates if this server is supported for ApplePay.
*
* @var bool
*/
private $is_server_supported;
/**
* Indicates if the merchant is validated for ApplePay.
*
* @var bool
*/
private $is_merchant_validated;
/**
* The button.
*
* @var ApplePayButton
*/
private $button;
/**
* Class ApmProductStatus constructor.
*
* @param AppleProductStatus $product_status The product status handler.
* @param bool $is_wc_gateways_list_page Indicates if we're on the WooCommerce gateways list page.
* @param bool $is_ppcp_settings_page Indicates if we're on a PPCP Settings page.
* @param bool $is_available Indicates if ApplePay is available to be enabled.
* @param bool $is_server_supported Indicates if this server is supported for ApplePay.
* @param bool $is_merchant_validated Indicates if the merchant is validated for ApplePay.
* @param ApplePayButton $button The button.
*/
public function __construct(
AppleProductStatus $product_status,
bool $is_wc_gateways_list_page,
bool $is_ppcp_settings_page,
bool $is_available,
bool $is_server_supported,
bool $is_merchant_validated,
ApplePayButton $button
) {
$this->product_status = $product_status;
$this->is_wc_gateways_list_page = $is_wc_gateways_list_page;
$this->is_ppcp_settings_page = $is_ppcp_settings_page;
$this->is_available = $is_available;
$this->is_server_supported = $is_server_supported;
$this->is_merchant_validated = $is_merchant_validated;
$this->button = $button;
}
/**
* Adds availability notice if applicable.
*
* @return void
*/
public function execute(): void {
if ( ! $this->should_display() ) {
return;
}
// We need to check is active before checking failure requests, otherwise failure status won't be set.
$is_active = $this->product_status->is_active();
if ( $this->product_status->has_request_failure() ) {
$this->add_seller_status_failure_notice();
} elseif ( ! $is_active ) {
$this->add_not_available_notice();
}
if ( ! $this->is_available ) {
return;
}
if ( ! $this->is_server_supported ) {
$this->add_server_not_supported_notice();
}
$button_enabled = $this->button->is_enabled();
// We do this check on $_POST because this is called before settings are saved.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( isset( $_POST['ppcp'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$post_data = wc_clean( (array) wp_unslash( $_POST['ppcp'] ) );
$button_enabled = wc_string_to_bool( $post_data['applepay_button_enabled'] ?? false );
}
if ( ! $button_enabled ) {
return;
}
if ( ! $this->is_merchant_validated ) {
$this->add_merchant_not_validated_notice();
}
}
/**
* Whether the message should display.
*
* @return bool
*/
protected function should_display(): bool {
if ( ! $this->product_status->is_onboarded() ) {
return false;
}
if ( ! $this->is_wc_gateways_list_page && ! $this->is_ppcp_settings_page ) {
return false;
}
return true;
}
/**
* Adds seller status failure notice.
*
* @return void
*/
private function add_seller_status_failure_notice(): void {
add_filter(
Repository::NOTICES_FILTER,
/**
* Adds seller status notice.
*
* @param array $notices The notices.
* @return array
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'<p>Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our %1$sonboarding process%2$s to resolve this.</p><p>Don\'t worry if you cannot use the %1$sonboarding process%2$s; most functionalities available to your account should work.</p>',
'woocommerce-paypal-payments'
),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#connect-paypal-account" target="_blank">',
'</a>'
);
// Name the key so it can be overridden in other modules.
$notices['error_product_status'] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
return $notices;
}
);
}
/**
* Adds not available notice.
*
* @return void
*/
private function add_not_available_notice(): void {
add_filter(
Repository::NOTICES_FILTER,
/**
* Adds ApplePay not available notice.
*
* @param array $notices The notices.
* @return array
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
__(
'Apple Pay is not available on your PayPal seller account.',
'woocommerce-paypal-payments'
)
);
$notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
return $notices;
}
);
}
/**
* Adds ApplePay server not supported notice.
*
* @return void
*/
private function add_server_not_supported_notice(): void {
add_filter(
Repository::NOTICES_FILTER,
/**
* Adds ApplePay server not supported notice.
*
* @param array $notices The notices.
* @return array
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
__(
'Apple Pay is not supported on this server. Please contact your hosting provider to enable it.',
'woocommerce-paypal-payments'
)
);
$notices[] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' );
return $notices;
}
);
}
/**
* Adds ApplePay merchant not validated notice.
*
* @return void
*/
private function add_merchant_not_validated_notice(): void {
add_filter(
Repository::NOTICES_FILTER,
/**
* Adds ApplePay merchant not validated notice.
*
* @param array $notices The notices.
* @return array
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag for the well-known file, %3$s and %4$s are the opening and closing of HTML <a> tag for the help document.
__(
'Apple Pay Validation Error. Please ensure the presentment of the correct %1$sdomain association file%2$s for Apple to validate your domain. %3$sLearn more%4$s about the Apple Pay requirements',
'woocommerce-paypal-payments'
),
'<a href="/.well-known/apple-developer-merchantid-domain-association" target="_blank">',
'</a>',
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#apple-pay" target="_blank">',
'</a>'
);
$notices[] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' );
return $notices;
}
);
}
}

View file

@ -19,7 +19,7 @@ const PayPalComponent = ({
shippingData,
isEditing,
}) => {
const {onPaymentSetup, onCheckoutAfterProcessingWithError, onCheckoutValidation} = eventRegistration;
const {onPaymentSetup, onCheckoutFail, onCheckoutValidation} = eventRegistration;
const {responseTypes} = emitResponse;
const [paypalOrder, setPaypalOrder] = useState(null);
@ -253,21 +253,24 @@ const PayPalComponent = ({
}, [onPaymentSetup, paypalOrder, activePaymentMethod]);
useEffect(() => {
const unsubscribe = onCheckoutAfterProcessingWithError(({ processingResponse }) => {
if (activePaymentMethod !== config.id) {
return;
}
const unsubscribe = onCheckoutFail(({ processingResponse }) => {
console.error(processingResponse)
if (onClose) {
onClose();
}
if (processingResponse?.paymentDetails?.errorMessage) {
return {
type: emitResponse.responseTypes.ERROR,
message: processingResponse.paymentDetails.errorMessage,
messageContext: config.scriptData.continuation ? emitResponse.noticeContexts.PAYMENTS : emitResponse.noticeContexts.EXPRESS_PAYMENTS,
};
if (config.scriptData.continuation) {
return true;
}
if (!config.finalReviewEnabled) {
location.href = getCheckoutRedirectUrl();
}
return true;
});
return unsubscribe;
}, [onCheckoutAfterProcessingWithError, onClose]);
}, [onCheckoutFail, onClose, activePaymentMethod]);
if (config.scriptData.continuation) {
return (

View file

@ -6,6 +6,10 @@
cursor: not-allowed;
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
* {
pointer-events: none;
}
}
.ppc-button-wrapper #ppcp-messages:first-child {

View file

@ -189,7 +189,6 @@ class GooglepayButton {
callback(el);
} else if (timeElapsed > timeout) {
clearInterval(interval);
console.error('Waiting for wrapper timed out.', selector);
}
}, delay);
}

View file

@ -16,7 +16,9 @@ import GooglepayManager from "./GooglepayManager";
};
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
manager.reinit();
if (manager) {
manager.reinit();
}
});
document.addEventListener(

View file

@ -204,21 +204,6 @@ class Button implements ButtonInterface {
}
$data['capabilities'][] = 'GOOGLE_PAY';
$data['operations'][] = array(
'operation' => 'API_INTEGRATION',
'api_integration_preference' => array(
'rest_api_integration' => array(
'integration_method' => 'PAYPAL',
'integration_type' => 'THIRD_PARTY',
'third_party_details' => array(
'features' => array(
'PAYMENT',
'REFUND',
),
),
),
),
);
return $data;
}
@ -363,6 +348,23 @@ class Button implements ButtonInterface {
);
wp_enqueue_script( 'wc-ppcp-googlepay' );
$this->enqueue_styles();
wp_localize_script(
'wc-ppcp-googlepay',
'wc_ppcp_googlepay',
$this->script_data()
);
}
/**
* Enqueues styles.
*/
public function enqueue_styles(): void {
if ( ! $this->is_enabled() ) {
return;
}
wp_register_style(
'wc-ppcp-googlepay',
untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
@ -370,12 +372,6 @@ class Button implements ButtonInterface {
$this->version
);
wp_enqueue_style( 'wc-ppcp-googlepay' );
wp_localize_script(
'wc-ppcp-googlepay',
'wc_ppcp_googlepay',
$this->script_data()
);
}
/**

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice;
@ -45,7 +46,6 @@ class GooglepayModule implements ModuleInterface {
function( Settings $settings = null ) use ( $c ): void {
$apm_status = $c->get( 'googlepay.helpers.apm-product-status' );
assert( $apm_status instanceof ApmProductStatus );
$apm_status->clear( $settings );
}
);
@ -86,7 +86,20 @@ class GooglepayModule implements ModuleInterface {
add_action(
'wp_enqueue_scripts',
static function () use ( $c, $button ) {
$button->enqueue();
$smart_button = $c->get( 'button.smart-button' );
assert( $smart_button instanceof SmartButtonInterface );
if ( $smart_button->should_load_ppcp_script() ) {
$button->enqueue();
}
if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) {
/**
* Should add this to the ButtonInterface.
*
* @psalm-suppress UndefinedInterfaceMethod
*/
$button->enqueue_styles();
}
}
);

View file

@ -107,7 +107,6 @@ class AvailabilityNotice {
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
@ -142,7 +141,6 @@ class AvailabilityNotice {
* @psalm-suppress MissingClosureParamType
*/
static function ( $notices ): array {
$message = sprintf(
__(
'Google Pay is not available on your PayPal seller account.',

View file

@ -9,25 +9,40 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\OrderTracking;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Compat\AdminContextTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
trait TrackingAvailabilityTrait {
use AdminContextTrait;
/**
* Checks if tracking is enabled.
* Checks if tracking should be enabled for current post.
*
* @param Bearer $bearer The Bearer.
* @return bool
*/
protected function is_tracking_enabled( Bearer $bearer ): bool {
$post_id = (int) wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! $post_id ) {
return false;
}
$order = wc_get_order( $post_id );
if ( ! is_a( $order, WC_Order::class ) ) {
return false;
}
$captured = $order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY );
$is_captured = empty( $captured ) || wc_string_to_bool( $captured );
$is_paypal_order_edit_page = $order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) && ! empty( $order->get_transaction_id() );
try {
$token = $bearer->bearer();
return $token->is_tracking_available()
&& $this->is_paypal_order_edit_page()
return $is_paypal_order_edit_page
&& $is_captured
&& $token->is_tracking_available()
&& apply_filters( 'woocommerce_paypal_payments_shipment_tracking_enabled', true );
} catch ( RuntimeException $exception ) {
return false;

View file

@ -318,6 +318,8 @@ return array(
$pui_status_cache,
$dcc_status_cache,
$container->get( 'http.redirector' ),
$container->get( 'api.partner_merchant_id-production' ),
$container->get( 'api.partner_merchant_id-sandbox' ),
$logger
);
},

View file

@ -124,13 +124,6 @@ class SettingsListener {
*/
protected $redirector;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* Max onboarding URL retries.
*
@ -145,6 +138,27 @@ class SettingsListener {
*/
private $onboarding_retry_delay = 2;
/**
* Partner merchant ID production.
*
* @var string
*/
private $partner_merchant_id_production;
/**
* Partner merchant ID sandbox.
*
* @var string
*/
private $partner_merchant_id_sandbox;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* SettingsListener constructor.
*
@ -160,6 +174,8 @@ class SettingsListener {
* @param Cache $pui_status_cache The PUI status cache.
* @param Cache $dcc_status_cache The DCC status cache.
* @param RedirectorInterface $redirector The HTTP redirector.
* @param string $partner_merchant_id_production Partner merchant ID production.
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
* @param ?LoggerInterface $logger The logger.
*/
public function __construct(
@ -175,22 +191,26 @@ class SettingsListener {
Cache $pui_status_cache,
Cache $dcc_status_cache,
RedirectorInterface $redirector,
string $partner_merchant_id_production,
string $partner_merchant_id_sandbox,
LoggerInterface $logger = null
) {
$this->settings = $settings;
$this->setting_fields = $setting_fields;
$this->webhook_registrar = $webhook_registrar;
$this->cache = $cache;
$this->state = $state;
$this->bearer = $bearer;
$this->page_id = $page_id;
$this->signup_link_cache = $signup_link_cache;
$this->signup_link_ids = $signup_link_ids;
$this->pui_status_cache = $pui_status_cache;
$this->dcc_status_cache = $dcc_status_cache;
$this->redirector = $redirector;
$this->logger = $logger ?: new NullLogger();
$this->settings = $settings;
$this->setting_fields = $setting_fields;
$this->webhook_registrar = $webhook_registrar;
$this->cache = $cache;
$this->state = $state;
$this->bearer = $bearer;
$this->page_id = $page_id;
$this->signup_link_cache = $signup_link_cache;
$this->signup_link_ids = $signup_link_ids;
$this->pui_status_cache = $pui_status_cache;
$this->dcc_status_cache = $dcc_status_cache;
$this->redirector = $redirector;
$this->partner_merchant_id_production = $partner_merchant_id_production;
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
$this->logger = $logger ?: new NullLogger();
}
/**
@ -210,7 +230,11 @@ class SettingsListener {
return;
}
$merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) );
$merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) );
if ( $merchant_id === $this->partner_merchant_id_production || $merchant_id === $this->partner_merchant_id_sandbox ) {
return;
}
$merchant_email = $this->sanitize_onboarding_email( sanitize_text_field( wp_unslash( $_GET['merchantId'] ) ) );
$onboarding_token = sanitize_text_field( wp_unslash( $_GET['ppcpToken'] ) );
$retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0;
@ -498,10 +522,18 @@ class SettingsListener {
if ( ! isset( $settings['client_id_sandbox'] ) && ! isset( $settings['client_id_production'] ) ) {
return $settings;
}
$is_sandbox = isset( $settings['sandbox_on'] ) && $settings['sandbox_on'];
$settings['client_id'] = $is_sandbox ? $settings['client_id_sandbox'] : $settings['client_id_production'];
$settings['client_secret'] = $is_sandbox ? $settings['client_secret_sandbox'] : $settings['client_secret_production'];
$settings['merchant_id'] = $is_sandbox ? $settings['merchant_id_sandbox'] : $settings['merchant_id_production'];
$is_sandbox = isset( $settings['sandbox_on'] ) && $settings['sandbox_on'];
$settings['client_id'] = $is_sandbox ? $settings['client_id_sandbox'] : $settings['client_id_production'];
$settings['client_secret'] = $is_sandbox ? $settings['client_secret_sandbox'] : $settings['client_secret_production'];
if ( $settings['merchant_id_sandbox'] === $this->partner_merchant_id_sandbox || $settings['merchant_id_sandbox'] === $this->partner_merchant_id_production ) {
$settings['merchant_id_sandbox'] = '';
}
if ( $settings['merchant_id_production'] === $this->partner_merchant_id_sandbox || $settings['merchant_id_sandbox'] === $this->partner_merchant_id_production ) {
$settings['merchant_id_production'] = '';
}
$settings['merchant_id'] = $is_sandbox ? $settings['merchant_id_sandbox'] : $settings['merchant_id_production'];
$settings['merchant_email'] = $is_sandbox ? $settings['merchant_email_sandbox'] : $settings['merchant_email_production'];
return $settings;
}

View file

@ -180,6 +180,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog ==
= 2.4.0 - xxxx-xx-xx =
* Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735
* Fix - ACDC disappearing after plugin updates #1751
* Fix - Subscription module hooks #1748
@ -193,6 +194,14 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
* Enhancement - Cart simulation improvements #1753
* Enhancement - Billing schedule fields not greyed out when PayPal Subscriptions product is connected #1755
* Enhancement - Check validation errors when submitting in block #1528
* Enhancement - Improve handling of server error when submitting block #1785
* Enhancement - Extend Apple Pay country eligibility #1781
* Enhancement - Apple Pay validation notice improvements #1783
* Enhancement - Apple Pay payment process issues #1789
* Enhancement - Disable the tracking if payment is not captured #1780
* Enhancement - Place order button remains - Could not retrieve order #1786
* Enhancement - Google Pay for variable product greyed out but clickable #1788
* Enhancement - Merchant credential validation & remove PAYEE object #1795
= 2.3.1 - 2023-09-26 =
* Fix - Fatal error when saving product while WooCommerce Subscriptions plugin is not active #1731

View file

@ -45,7 +45,6 @@ class PurchaseUnitTest extends TestCase
$shipping,
'referenceId',
'description',
null,
'customId',
'invoiceId',
'softDescriptor'
@ -54,7 +53,6 @@ class PurchaseUnitTest extends TestCase
$this->assertEquals($amount, $testee->amount());
$this->assertEquals('referenceId', $testee->reference_id());
$this->assertEquals('description', $testee->description());
$this->assertNull($testee->payee());
$this->assertEquals('customId', $testee->custom_id());
$this->assertEquals('invoiceId', $testee->invoice_id());
$this->assertEquals('softDescriptor', $testee->soft_descriptor());
@ -808,46 +806,4 @@ class PurchaseUnitTest extends TestCase
return $values;
}
public function testPayee()
{
$amount = Mockery::mock(Amount::class);
$amount->shouldReceive('breakdown')->andReturnNull();
$amount->shouldReceive('to_array')->andReturn(['amount']);
$item1 = Mockery::mock(Item::class);
$item1->shouldReceive('to_array')->andReturn(['item1']);
$item2 = Mockery::mock(Item::class);
$item2->shouldReceive('to_array')->andReturn(['item2']);
$shipping = Mockery::mock(Shipping::class);
$shipping->shouldReceive('to_array')->andReturn(['shipping']);
$payee = Mockery::mock(Payee::class);
$payee->shouldReceive('to_array')->andReturn(['payee']);
$testee = new PurchaseUnit(
$amount,
[],
$shipping,
'referenceId',
'description',
$payee,
'customId',
'invoiceId',
'softDescriptor'
);
$this->assertEquals($payee, $testee->payee());
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'items' => [],
'shipping' => ['shipping'],
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
'payee' => ['payee'],
];
$this->assertEquals($expected, $testee->to_array());
}
}

View file

@ -7,11 +7,9 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Address;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payee;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@ -45,11 +43,6 @@ class PurchaseUnitFactoryTest extends TestCase
->shouldReceive('from_wc_order')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->shouldReceive('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->shouldReceive('from_wc_order')
@ -75,8 +68,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -84,7 +75,6 @@ class PurchaseUnitFactoryTest extends TestCase
$unit = $testee->from_wc_order($wcOrder);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals($this->wcOrderId, $unit->custom_id());
@ -106,11 +96,6 @@ class PurchaseUnitFactoryTest extends TestCase
->shouldReceive('from_wc_order')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->shouldReceive('payee')->andReturn($payee);
$fee = Mockery::mock(Item::class, [
'category' => Item::DIGITAL_GOODS,
@ -139,8 +124,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -162,11 +145,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_order')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_order')
@ -194,8 +172,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -216,11 +192,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_order')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_order')
@ -243,8 +214,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -267,11 +236,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
@ -299,8 +263,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -308,7 +270,6 @@ class PurchaseUnitFactoryTest extends TestCase
$unit = $testee->from_wc_cart($wcCart);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals('', $unit->custom_id());
@ -331,11 +292,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
@ -346,8 +302,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -369,11 +323,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_cart')
@ -395,8 +344,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -410,15 +357,10 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$rawShipping = (object) ['shipping' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
@ -427,8 +369,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -442,13 +382,11 @@ class PurchaseUnitFactoryTest extends TestCase
'soft_descriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
];
$unit = $testee->from_paypal_response($response);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('description', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals('customId', $unit->custom_id());
@ -459,67 +397,19 @@ class PurchaseUnitFactoryTest extends TestCase
$this->assertEquals($shipping, $unit->shipping());
}
public function testFromPayPalResponsePayeeIsNull()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$rawShipping = (object) ['shipping' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shipping = Mockery::mock(Shipping::class);
$shippingFactory->expects('from_paypal_response')->with($rawShipping)->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$response = (object) [
'reference_id' => 'default',
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'shipping' => $rawShipping,
];
$unit = $testee->from_paypal_response($response);
$this->assertNull($unit->payee());
}
public function testFromPayPalResponseShippingIsNull()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -533,7 +423,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
];
$unit = $testee->from_paypal_response($response);
@ -543,15 +432,11 @@ class PurchaseUnitFactoryTest extends TestCase
public function testFromPayPalResponseNeedsReferenceId()
{
$amountFactory = Mockery::mock(AmountFactory::class);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@ -564,7 +449,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => '',
'items' => [],
'payee' => '',
'shipping' => '',
];
@ -576,17 +460,12 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
$rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item);
@ -600,8 +479,6 @@ class PurchaseUnitFactoryTest extends TestCase
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
@ -615,7 +492,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
'payments' => $rawPayments,
];
@ -628,17 +504,12 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
$rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item);
@ -650,8 +521,6 @@ class PurchaseUnitFactoryTest extends TestCase
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
@ -665,7 +534,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
];

View file

@ -53,7 +53,9 @@ class SettingsListenerTest extends ModularTestCase
$signup_link_ids,
$pui_status_cache,
$dcc_status_cache,
new RedirectorStub()
new RedirectorStub(),
'',
''
);
$_GET['section'] = PayPalGateway::ID;

View file

@ -23,7 +23,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2023-10-18' );
define( 'PAYPAL_INTEGRATION_DATE', '2023-10-25' );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );