mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-1486-paylater-block
This commit is contained in:
commit
c83975f293
54 changed files with 2315 additions and 1600 deletions
|
@ -1,5 +1,18 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 2.4.3 - xxxx-xx-xx =
|
||||
* Fix - PayPal Subscription initiated without a WooCommerce order #1907
|
||||
* Fix - Block Checkout reloads when submitting order with empty fields #1904
|
||||
* Fix - "Send checkout billing and shipping data to Apple Pay" displayed when Apple Pay is disabled #1883
|
||||
* Fix - "Order does not contain intent" error for ACDC renewals when triggering 3D Secure #1888
|
||||
* Enhancement - Add button to reload feature eligibility status from Connection tab #1902
|
||||
* Enhancement - Apple Pay validation message improvements #1901
|
||||
* Enhancement - Improve support for Classic Cart & Classic Checkout blocks #1894
|
||||
* Enhancement - Ensure uniform button appearance for PayPal, Google Pay, and Apple Pay buttons #1900
|
||||
* Enhancement - remove string translations for package tracking carriers from repository #1885
|
||||
* Enhancement - Incorrect margins when PayPal buttons are rendered as separate gateways. #1908
|
||||
* Feature preview - Save payment methods (Vault v3) integration #1779
|
||||
|
||||
= 2.4.2 - 2023-12-04 =
|
||||
* Fix - Action callback arguments count in ShipStation tracking integration #1841
|
||||
* Fix - Google Pay scripts loading on unrelated admin pages #1834
|
||||
|
|
|
@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
|
@ -174,14 +175,15 @@ class OrderEndpoint {
|
|||
/**
|
||||
* Creates an order.
|
||||
*
|
||||
* @param PurchaseUnit[] $items The purchase unit items for the order.
|
||||
* @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values.
|
||||
* @param Payer|null $payer The payer off the order.
|
||||
* @param PaymentToken|null $payment_token The payment token.
|
||||
* @param string $paypal_request_id The PayPal request id.
|
||||
* @param string $user_action The user action.
|
||||
* @param string $payment_method WC payment method.
|
||||
* @param array $request_data Request data.
|
||||
* @param PurchaseUnit[] $items The purchase unit items for the order.
|
||||
* @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values.
|
||||
* @param Payer|null $payer The payer off the order.
|
||||
* @param PaymentToken|null $payment_token The payment token.
|
||||
* @param string $paypal_request_id The PayPal request id.
|
||||
* @param string $user_action The user action.
|
||||
* @param string $payment_method WC payment method.
|
||||
* @param array $request_data Request data.
|
||||
* @param PaymentSource|null $payment_source The payment source.
|
||||
*
|
||||
* @return Order
|
||||
* @throws RuntimeException If the request fails.
|
||||
|
@ -194,7 +196,8 @@ class OrderEndpoint {
|
|||
string $paypal_request_id = '',
|
||||
string $user_action = ApplicationContext::USER_ACTION_CONTINUE,
|
||||
string $payment_method = '',
|
||||
array $request_data = array()
|
||||
array $request_data = array(),
|
||||
PaymentSource $payment_source = null
|
||||
): Order {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$data = array(
|
||||
|
@ -221,6 +224,11 @@ class OrderEndpoint {
|
|||
if ( $payment_token ) {
|
||||
$data['payment_source']['token'] = $payment_token->to_array();
|
||||
}
|
||||
if ( $payment_source ) {
|
||||
$data['payment_source'] = array(
|
||||
$payment_source->name() => $payment_source->properties(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter can be used to modify the order creation request body data.
|
||||
|
|
|
@ -26,17 +26,17 @@ class PaymentSource {
|
|||
/**
|
||||
* Payment source properties.
|
||||
*
|
||||
* @var stdClass
|
||||
* @var object
|
||||
*/
|
||||
private $properties;
|
||||
|
||||
/**
|
||||
* PaymentSource constructor.
|
||||
*
|
||||
* @param string $name Payment source name.
|
||||
* @param stdClass $properties Payment source properties.
|
||||
* @param string $name Payment source name.
|
||||
* @param object $properties Payment source properties.
|
||||
*/
|
||||
public function __construct( string $name, stdClass $properties ) {
|
||||
public function __construct( string $name, object $properties ) {
|
||||
$this->name = $name;
|
||||
$this->properties = $properties;
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ class PaymentSource {
|
|||
/**
|
||||
* Payment source properties.
|
||||
*
|
||||
* @return stdClass
|
||||
* @return object
|
||||
*/
|
||||
public function properties(): stdClass {
|
||||
public function properties(): object {
|
||||
return $this->properties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@ return array(
|
|||
|
||||
// Domain validation.
|
||||
$domain_validation_text = __( 'Status: Domain validation failed ❌', 'woocommerce-paypal-payments' );
|
||||
if ( $container->get( 'applepay.is_validated' ) ) {
|
||||
if ( ! $container->get( 'applepay.has_validated' ) ) {
|
||||
$domain_validation_text = __( 'The domain has not yet been validated. Use the Apple Pay button to validate the domain ❌', 'woocommerce-paypal-payments' );
|
||||
} elseif ( $container->get( 'applepay.is_validated' ) ) {
|
||||
$domain_validation_text = __( 'Status: Domain successfully validated ✔️', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
|
@ -157,6 +159,7 @@ return array(
|
|||
->action_visible( 'applepay_button_color' )
|
||||
->action_visible( 'applepay_button_type' )
|
||||
->action_visible( 'applepay_button_language' )
|
||||
->action_visible( 'applepay_checkout_data_mode' )
|
||||
->to_array(),
|
||||
)
|
||||
),
|
||||
|
|
|
@ -1,58 +1,44 @@
|
|||
#applepay-container, .ppcp-button-applepay {
|
||||
.ppcp-button-applepay {
|
||||
// Should replicate apm-button.scss sizes.
|
||||
--apple-pay-button-height: 45px;
|
||||
--apple-pay-button-min-height: 40px;
|
||||
--apple-pay-button-min-height: 35px;
|
||||
--apple-pay-button-width: 100%;
|
||||
--apple-pay-button-max-width: 750px;
|
||||
--apple-pay-button-border-radius: 4px;
|
||||
--apple-pay-button-overflow: hidden;
|
||||
margin:7px 0;
|
||||
|
||||
.ppcp-width-min & {
|
||||
--apple-pay-button-height: 35px;
|
||||
}
|
||||
.ppcp-width-300 & {
|
||||
--apple-pay-button-height: 45px;
|
||||
}
|
||||
.ppcp-width-500 & {
|
||||
--apple-pay-button-height: 55px;
|
||||
}
|
||||
|
||||
&.ppcp-button-pill {
|
||||
--apple-pay-button-border-radius: 50px;
|
||||
}
|
||||
|
||||
&.ppcp-button-minicart {
|
||||
--apple-pay-button-display: block;
|
||||
--apple-pay-button-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-checkout {
|
||||
#applepay-container, .ppcp-button-applepay {
|
||||
margin-top: 0;
|
||||
--apple-pay-button-border-radius: 4px;
|
||||
--apple-pay-button-height: 45px;
|
||||
&.ppcp-button-pill {
|
||||
--apple-pay-button-border-radius: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wp-block-woocommerce-checkout, .wp-block-woocommerce-cart {
|
||||
.ppcp-button-applepay {
|
||||
--apple-pay-button-margin: 0;
|
||||
|
||||
.ppcp-has-applepay-block {
|
||||
|
||||
.wp-block-woocommerce-checkout {
|
||||
#applepay-container, .ppcp-button-applepay {
|
||||
margin: 0;
|
||||
--apple-pay-button-margin: 0;
|
||||
--apple-pay-button-height: 48px;
|
||||
&.ppcp-button-pill {
|
||||
--apple-pay-button-border-radius: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-cart {
|
||||
#applepay-container, .ppcp-button-applepay {
|
||||
margin: 0;
|
||||
--apple-pay-button-margin: 0;
|
||||
--apple-pay-button-height: 48px;
|
||||
apple-pay-button {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
--apple-pay-button-width-default: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wp-admin {
|
||||
.ppcp-button-applepay {
|
||||
pointer-events: none;
|
||||
}
|
||||
&.ppcp-non-ios-device {
|
||||
.ppcp-button-applepay {
|
||||
apple-pay-button {
|
||||
|
|
|
@ -5,10 +5,13 @@ import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/Butto
|
|||
import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator";
|
||||
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
|
||||
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
|
||||
import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons";
|
||||
|
||||
class ApplepayButton {
|
||||
|
||||
constructor(context, externalHandler, buttonConfig, ppcpConfig) {
|
||||
apmButtonsInit(ppcpConfig);
|
||||
|
||||
this.isInitialized = false;
|
||||
|
||||
this.context = context;
|
||||
|
@ -60,7 +63,7 @@ class ApplepayButton {
|
|||
this.initEventHandlers();
|
||||
this.isInitialized = true;
|
||||
this.applePayConfig = config;
|
||||
const isEligible = this.applePayConfig.isEligible;
|
||||
const isEligible = (this.applePayConfig.isEligible && window.ApplePaySession) || this.buttonConfig.is_admin;
|
||||
|
||||
if (isEligible) {
|
||||
this.fetchTransactionInfo().then(() => {
|
||||
|
@ -84,6 +87,10 @@ class ApplepayButton {
|
|||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
jQuery('#' + this.buttonConfig.button.wrapper).hide();
|
||||
jQuery('#' + this.buttonConfig.button.mini_cart_wrapper).hide();
|
||||
jQuery('#express-payment-method-ppcp-applepay').hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,13 +186,13 @@ class ApplepayButton {
|
|||
appleContainer.innerHTML = `<apple-pay-button id="${id}" buttonstyle="${color}" type="${type}" locale="${language}">`;
|
||||
}
|
||||
|
||||
jQuery('#' + wrapper).addClass('ppcp-button-' + ppcpStyle.shape);
|
||||
const $wrapper = jQuery('#' + wrapper);
|
||||
$wrapper.addClass('ppcp-button-' + ppcpStyle.shape);
|
||||
|
||||
if (ppcpStyle.height) {
|
||||
jQuery('#' + wrapper).css('--apple-pay-button-height', `${ppcpStyle.height}px`)
|
||||
$wrapper.css('--apple-pay-button-height', `${ppcpStyle.height}px`)
|
||||
$wrapper.css('height', `${ppcpStyle.height}px`)
|
||||
}
|
||||
|
||||
jQuery(wrapper).append(appleContainer);
|
||||
}
|
||||
|
||||
//------------------------
|
||||
|
|
|
@ -65,7 +65,7 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi
|
|||
buttonConfig.button.wrapper = selector.replace('#', '');
|
||||
applyConfigOptions(buttonConfig);
|
||||
|
||||
const wrapperElement = `<div id="${selector.replace('#', '')}" class="ppcp-button-applepay"></div>`;
|
||||
const wrapperElement = `<div id="${selector.replace('#', '')}" class="ppcp-button-apm ppcp-button-applepay"></div>`;
|
||||
|
||||
if (!jQuery(selector).length) {
|
||||
jQuery(ppcpConfig.button.wrapper).after(wrapperElement);
|
||||
|
|
|
@ -23,12 +23,6 @@ const ApplePayComponent = () => {
|
|||
const manager = new ApplepayManager(buttonConfig, ppcpConfig);
|
||||
manager.init();
|
||||
};
|
||||
useEffect(() => {
|
||||
const bodyClass = 'ppcp-has-applepay-block';
|
||||
if (!document.body.classList.contains(bodyClass)) {
|
||||
document.body.classList.add(bodyClass);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Load ApplePay SDK
|
||||
|
@ -50,7 +44,7 @@ const ApplePayComponent = () => {
|
|||
}, [paypalLoaded, applePayLoaded]);
|
||||
|
||||
return (
|
||||
<div id={buttonConfig.button.wrapper.replace('#', '')}></div>
|
||||
<div id={buttonConfig.button.wrapper.replace('#', '')} className="ppcp-button-apm ppcp-button-applepay"></div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,11 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'applepay.has_validated' => static function ( ContainerInterface $container ): bool {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return $settings->has( 'applepay_validated' );
|
||||
},
|
||||
|
||||
'applepay.is_validated' => static function ( ContainerInterface $container ): bool {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false;
|
||||
|
|
|
@ -968,7 +968,7 @@ class ApplePayButton implements ButtonInterface {
|
|||
add_action(
|
||||
$render_placeholder,
|
||||
function () {
|
||||
echo '<span id="applepay-container-minicart" class="ppcp-button-applepay ppcp-button-minicart"></span>';
|
||||
echo '<span id="applepay-container-minicart" class="ppcp-button-apm ppcp-button-applepay ppcp-button-minicart"></span>';
|
||||
},
|
||||
21
|
||||
);
|
||||
|
@ -981,7 +981,7 @@ class ApplePayButton implements ButtonInterface {
|
|||
*/
|
||||
protected function applepay_button(): void {
|
||||
?>
|
||||
<div id="applepay-container">
|
||||
<div id="applepay-container" class="ppcp-button-apm ppcp-button-applepay">
|
||||
<?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?>
|
||||
</div>
|
||||
<?php
|
||||
|
|
|
@ -149,6 +149,7 @@ class DataToAppleButtonScripts {
|
|||
return array(
|
||||
'sdk_url' => $this->sdk_url,
|
||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
|
||||
'is_admin' => false,
|
||||
'preferences' => array(
|
||||
'checkout_data_mode' => $checkout_data_mode,
|
||||
),
|
||||
|
@ -204,6 +205,7 @@ class DataToAppleButtonScripts {
|
|||
return array(
|
||||
'sdk_url' => $this->sdk_url,
|
||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
|
||||
'is_admin' => false,
|
||||
'preferences' => array(
|
||||
'checkout_data_mode' => $checkout_data_mode,
|
||||
),
|
||||
|
@ -252,6 +254,7 @@ class DataToAppleButtonScripts {
|
|||
return array(
|
||||
'sdk_url' => $this->sdk_url,
|
||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
|
||||
'is_admin' => true,
|
||||
'preferences' => array(
|
||||
'checkout_data_mode' => $checkout_data_mode,
|
||||
),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
/**
|
||||
* ApmApplies helper.
|
||||
* Checks if ApplePay is available for a given country and currency.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApplePay\Helper
|
||||
*/
|
||||
|
@ -15,7 +16,7 @@ namespace WooCommerce\PayPalCommerce\Applepay\Helper;
|
|||
class ApmApplies {
|
||||
|
||||
/**
|
||||
* The matrix which countries and currency combinations can be used for DCC.
|
||||
* The matrix which countries and currency combinations can be used for ApplePay.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -38,7 +39,7 @@ class ApmApplies {
|
|||
/**
|
||||
* ApmApplies constructor.
|
||||
*
|
||||
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for DCC.
|
||||
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for ApplePay.
|
||||
* @param string $currency 3-letter currency code of the shop.
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
*/
|
||||
|
|
15
modules/ppcp-blocks/resources/css/gateway.scss
Normal file
15
modules/ppcp-blocks/resources/css/gateway.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@use "../../../ppcp-button/resources/css/mixins/apm-button" as apm-button;
|
||||
|
||||
li[id^="express-payment-method-ppcp-"] {
|
||||
line-height: 0;
|
||||
|
||||
// Set min-width to 0 as the buttons need to fit in a tight grid.
|
||||
.paypal-buttons {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-button-apm {
|
||||
@include apm-button.button;
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ const PayPalComponent = ({
|
|||
const {responseTypes} = emitResponse;
|
||||
|
||||
const [paypalOrder, setPaypalOrder] = useState(null);
|
||||
const [gotoContinuationOnError, setGotoContinuationOnError] = useState(false);
|
||||
|
||||
const [paypalScriptLoaded, setPaypalScriptLoaded] = useState(false);
|
||||
|
||||
|
@ -165,6 +166,7 @@ const PayPalComponent = ({
|
|||
if (config.finalReviewEnabled) {
|
||||
location.href = getCheckoutRedirectUrl();
|
||||
} else {
|
||||
setGotoContinuationOnError(true);
|
||||
onSubmit();
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -183,7 +185,7 @@ const PayPalComponent = ({
|
|||
if (config.scriptData.continuation) {
|
||||
return true;
|
||||
}
|
||||
if (wp.data.select('wc/store/validation').hasValidationErrors()) {
|
||||
if (gotoContinuationOnError && wp.data.select('wc/store/validation').hasValidationErrors()) {
|
||||
location.href = getCheckoutRedirectUrl();
|
||||
return { type: responseTypes.ERROR };
|
||||
}
|
||||
|
@ -191,7 +193,7 @@ const PayPalComponent = ({
|
|||
return true;
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [onCheckoutValidation] );
|
||||
}, [onCheckoutValidation, gotoContinuationOnError] );
|
||||
|
||||
const handleClick = (data, actions) => {
|
||||
if (isEditing) {
|
||||
|
|
|
@ -88,6 +88,28 @@ class BlocksModule implements ModuleInterface {
|
|||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
// Enqueue frontend scripts.
|
||||
add_action(
|
||||
'wp_enqueue_scripts',
|
||||
static function () use ( $c ) {
|
||||
if ( ! has_block( 'woocommerce/checkout' ) && ! has_block( 'woocommerce/cart' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$module_url = $c->get( 'blocks.url' );
|
||||
$asset_version = $c->get( 'ppcp.asset-version' );
|
||||
|
||||
wp_register_style(
|
||||
'wc-ppcp-blocks',
|
||||
untrailingslashit( $module_url ) . '/assets/css/gateway.css',
|
||||
array(),
|
||||
$asset_version
|
||||
);
|
||||
wp_enqueue_style( 'wc-ppcp-blocks' );
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,8 @@ module.exports = {
|
|||
target: 'web',
|
||||
plugins: [ new DependencyExtractionWebpackPlugin() ],
|
||||
entry: {
|
||||
'checkout-block': path.resolve('./resources/js/checkout-block.js')
|
||||
'checkout-block': path.resolve('./resources/js/checkout-block.js'),
|
||||
"gateway": path.resolve('./resources/css/gateway.scss')
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@use "mixins/apm-button" as apm-button;
|
||||
|
||||
#place_order.ppcp-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -15,3 +17,24 @@
|
|||
.ppc-button-wrapper #ppcp-messages:first-child {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
// Prevents spacing after button group.
|
||||
#ppc-button-ppcp-gateway {
|
||||
line-height: 0;
|
||||
|
||||
div[class^="item-"] {
|
||||
margin-top: 14px;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ppc-button-minicart {
|
||||
line-height: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ppcp-button-apm {
|
||||
@include apm-button.button;
|
||||
}
|
||||
|
|
42
modules/ppcp-button/resources/css/mixins/apm-button.scss
Normal file
42
modules/ppcp-button/resources/css/mixins/apm-button.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
@mixin button {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
max-width: 750px;
|
||||
line-height: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
// Defaults
|
||||
height: 45px;
|
||||
margin-top: 14px;
|
||||
|
||||
&.ppcp-button-pill {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
&.ppcp-button-minicart {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ppcp-width-min & {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.ppcp-width-300 & {
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.ppcp-width-500 & {
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
// No margin on block layout.
|
||||
.wp-block-woocommerce-checkout &, .wp-block-woocommerce-cart & {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wp-admin & {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import FormValidator from "./modules/Helper/FormValidator";
|
|||
import {loadPaypalScript} from "./modules/Helper/ScriptLoading";
|
||||
import buttonModuleWatcher from "./modules/ButtonModuleWatcher";
|
||||
import MessagesBootstrap from "./modules/ContextBootstrap/MessagesBootstap";
|
||||
import {apmButtonsInit} from "./modules/Helper/ApmButtons";
|
||||
|
||||
// TODO: could be a good idea to have a separate spinner for each gateway,
|
||||
// but I think we care mainly about the script loading, so one spinner should be enough.
|
||||
|
@ -145,6 +146,7 @@ const bootstrap = () => {
|
|||
};
|
||||
|
||||
const onSmartButtonsInit = () => {
|
||||
jQuery(document).trigger('ppcp-smart-buttons-init', this);
|
||||
buttonsSpinner.unblock();
|
||||
};
|
||||
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit);
|
||||
|
@ -217,6 +219,8 @@ const bootstrap = () => {
|
|||
messageRenderer,
|
||||
);
|
||||
messagesBootstrap.init();
|
||||
|
||||
apmButtonsInit(PayPalCommerceGateway);
|
||||
};
|
||||
|
||||
document.addEventListener(
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'formdata-polyfill';
|
|||
import onApprove from '../OnApproveHandler/onApproveForPayNow.js';
|
||||
import {payerData} from "../Helper/PayerData";
|
||||
import {getCurrentPaymentMethod} from "../Helper/CheckoutMethodState";
|
||||
import validateCheckoutForm from "../Helper/CheckoutFormValidation";
|
||||
|
||||
class CheckoutActionHandler {
|
||||
|
||||
|
@ -13,7 +14,13 @@ class CheckoutActionHandler {
|
|||
|
||||
subscriptionsConfiguration() {
|
||||
return {
|
||||
createSubscription: (data, actions) => {
|
||||
createSubscription: async (data, actions) => {
|
||||
try {
|
||||
await validateCheckoutForm(this.config);
|
||||
} catch (error) {
|
||||
throw {type: 'form-validation-error'};
|
||||
}
|
||||
|
||||
return actions.subscription.create({
|
||||
'plan_id': this.config.subscription_plan_id
|
||||
});
|
||||
|
|
114
modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js
Normal file
114
modules/ppcp-button/resources/js/modules/Helper/ApmButtons.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
|
||||
export const apmButtonsInit = (config, selector = '.ppcp-button-apm') => {
|
||||
let selectorInContainer = selector;
|
||||
|
||||
if (window.ppcpApmButtons) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config && config.button) {
|
||||
|
||||
// If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs.
|
||||
const wrapper = config.button.wrapper;
|
||||
const isSeparateGateways = jQuery(wrapper).children('div[class^="item-"]').length > 0;
|
||||
|
||||
if (isSeparateGateways) {
|
||||
selector += `, ${wrapper} div[class^="item-"]`;
|
||||
selectorInContainer += `, div[class^="item-"]`;
|
||||
}
|
||||
}
|
||||
|
||||
window.ppcpApmButtons = new ApmButtons(selector, selectorInContainer);
|
||||
}
|
||||
|
||||
export class ApmButtons {
|
||||
|
||||
constructor(selector, selectorInContainer) {
|
||||
this.selector = selector;
|
||||
this.selectorInContainer = selectorInContainer;
|
||||
this.containers = [];
|
||||
|
||||
// Reloads button containers.
|
||||
this.reloadContainers();
|
||||
|
||||
// Refresh button layout.
|
||||
jQuery(window).resize(() => {
|
||||
this.refresh();
|
||||
}).resize();
|
||||
|
||||
jQuery(document).on('ppcp-smart-buttons-init', () => {
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
// Observes for new buttons.
|
||||
(new MutationObserver(this.observeElementsCallback.bind(this)))
|
||||
.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
observeElementsCallback(mutationsList, observer) {
|
||||
const observeSelector = this.selector + ', .widget_shopping_cart, .widget_shopping_cart_content';
|
||||
|
||||
let shouldReload = false;
|
||||
for (let mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.matches && node.matches(observeSelector)) {
|
||||
shouldReload = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldReload) {
|
||||
this.reloadContainers();
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
reloadContainers() {
|
||||
jQuery(this.selector).each((index, el) => {
|
||||
const parent = jQuery(el).parent();
|
||||
if (!this.containers.some($el => $el.is(parent))) {
|
||||
this.containers.push(parent);
|
||||
}
|
||||
});
|
||||
console.log('this.containers', this.containers);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
for (const container of this.containers) {
|
||||
const $container = jQuery(container);
|
||||
|
||||
// Check width and add classes
|
||||
const width = $container.width();
|
||||
|
||||
$container.removeClass('ppcp-width-500 ppcp-width-300 ppcp-width-min');
|
||||
|
||||
if (width >= 500) {
|
||||
$container.addClass('ppcp-width-500');
|
||||
} else if (width >= 300) {
|
||||
$container.addClass('ppcp-width-300');
|
||||
} else {
|
||||
$container.addClass('ppcp-width-min');
|
||||
}
|
||||
|
||||
// Check first apm button
|
||||
const $firstElement = $container.children(':visible').first();
|
||||
|
||||
// Assign margins to buttons
|
||||
$container.find(this.selectorInContainer).each((index, el) => {
|
||||
const $el = jQuery(el);
|
||||
|
||||
if ($el.is($firstElement)) {
|
||||
$el.css('margin-top', `0px`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const height = $el.height();
|
||||
$el.css('margin-top', `${Math.round(height * 0.3)}px`);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
export const cardFieldStyles = (field) => {
|
||||
const allowedProperties = [
|
||||
'appearance',
|
||||
'color',
|
||||
'direction',
|
||||
'font',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-size-adjust',
|
||||
'font-stretch',
|
||||
'font-style',
|
||||
'font-variant',
|
||||
'font-variant-alternates',
|
||||
'font-variant-caps',
|
||||
'font-variant-east-asian',
|
||||
'font-variant-ligatures',
|
||||
'font-variant-numeric',
|
||||
'font-weight',
|
||||
'letter-spacing',
|
||||
'line-height',
|
||||
'opacity',
|
||||
'outline',
|
||||
'padding',
|
||||
'padding-bottom',
|
||||
'padding-left',
|
||||
'padding-right',
|
||||
'padding-top',
|
||||
'text-shadow',
|
||||
'transition',
|
||||
'-moz-appearance',
|
||||
'-moz-osx-font-smoothing',
|
||||
'-moz-tap-highlight-color',
|
||||
'-moz-transition',
|
||||
'-webkit-appearance',
|
||||
'-webkit-osx-font-smoothing',
|
||||
'-webkit-tap-highlight-color',
|
||||
'-webkit-transition',
|
||||
];
|
||||
|
||||
const stylesRaw = window.getComputedStyle(field);
|
||||
const styles = {};
|
||||
Object.values(stylesRaw).forEach((prop) => {
|
||||
if (!stylesRaw[prop] || !allowedProperties.includes(prop)) {
|
||||
return;
|
||||
}
|
||||
styles[prop] = '' + stylesRaw[prop];
|
||||
});
|
||||
|
||||
return styles;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import Spinner from "./Spinner";
|
||||
import FormValidator from "./FormValidator";
|
||||
import ErrorHandler from "../ErrorHandler";
|
||||
|
||||
const validateCheckoutForm = function (config) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const spinner = new Spinner();
|
||||
const errorHandler = new ErrorHandler(
|
||||
config.labels.error.generic,
|
||||
document.querySelector('.woocommerce-notices-wrapper')
|
||||
);
|
||||
|
||||
const formSelector = config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
|
||||
const formValidator = config.early_checkout_validation_enabled ?
|
||||
new FormValidator(
|
||||
config.ajax.validate_checkout.endpoint,
|
||||
config.ajax.validate_checkout.nonce,
|
||||
) : null;
|
||||
|
||||
if (!formValidator) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
formValidator.validate(document.querySelector(formSelector)).then((errors) => {
|
||||
if (errors.length > 0) {
|
||||
spinner.unblock();
|
||||
errorHandler.clear();
|
||||
errorHandler.messages(errors);
|
||||
|
||||
// fire WC event for other plugins
|
||||
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
|
||||
|
||||
reject();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default validateCheckoutForm;
|
|
@ -89,3 +89,11 @@ export const loadPaypalJsScript = (options, buttons, container) => {
|
|||
paypal.Buttons(buttons).render(container);
|
||||
});
|
||||
}
|
||||
|
||||
export const loadPaypalJsScriptPromise = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
loadScript(options)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {show} from "../Helper/Hiding";
|
||||
import {cardFieldStyles} from "../Helper/CardFieldsHelper";
|
||||
|
||||
class CardFieldsRenderer {
|
||||
|
||||
|
@ -53,28 +54,28 @@ class CardFieldsRenderer {
|
|||
if (cardField.isEligible()) {
|
||||
const nameField = document.getElementById('ppcp-credit-card-gateway-card-name');
|
||||
if (nameField) {
|
||||
let styles = this.cardFieldStyles(nameField);
|
||||
let styles = cardFieldStyles(nameField);
|
||||
cardField.NameField({style: {'input': styles}}).render(nameField.parentNode);
|
||||
nameField.remove();
|
||||
}
|
||||
|
||||
const numberField = document.getElementById('ppcp-credit-card-gateway-card-number');
|
||||
if (numberField) {
|
||||
let styles = this.cardFieldStyles(numberField);
|
||||
let styles = cardFieldStyles(numberField);
|
||||
cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode);
|
||||
numberField.remove();
|
||||
}
|
||||
|
||||
const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry');
|
||||
if (expiryField) {
|
||||
let styles = this.cardFieldStyles(expiryField);
|
||||
let styles = cardFieldStyles(expiryField);
|
||||
cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode);
|
||||
expiryField.remove();
|
||||
}
|
||||
|
||||
const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc');
|
||||
if (cvvField) {
|
||||
let styles = this.cardFieldStyles(cvvField);
|
||||
let styles = cardFieldStyles(cvvField);
|
||||
cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode);
|
||||
cvvField.remove();
|
||||
}
|
||||
|
@ -91,8 +92,8 @@ class CardFieldsRenderer {
|
|||
this.spinner.block();
|
||||
this.errorHandler.clear();
|
||||
|
||||
const paymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked').value
|
||||
if(paymentToken !== 'new') {
|
||||
const paymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked')?.value
|
||||
if(paymentToken && paymentToken !== 'new') {
|
||||
fetch(this.defaultConfig.ajax.capture_card_payment.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
|
@ -118,57 +119,6 @@ class CardFieldsRenderer {
|
|||
});
|
||||
}
|
||||
|
||||
cardFieldStyles(field) {
|
||||
const allowedProperties = [
|
||||
'appearance',
|
||||
'color',
|
||||
'direction',
|
||||
'font',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-size-adjust',
|
||||
'font-stretch',
|
||||
'font-style',
|
||||
'font-variant',
|
||||
'font-variant-alternates',
|
||||
'font-variant-caps',
|
||||
'font-variant-east-asian',
|
||||
'font-variant-ligatures',
|
||||
'font-variant-numeric',
|
||||
'font-weight',
|
||||
'letter-spacing',
|
||||
'line-height',
|
||||
'opacity',
|
||||
'outline',
|
||||
'padding',
|
||||
'padding-bottom',
|
||||
'padding-left',
|
||||
'padding-right',
|
||||
'padding-top',
|
||||
'text-shadow',
|
||||
'transition',
|
||||
'-moz-appearance',
|
||||
'-moz-osx-font-smoothing',
|
||||
'-moz-tap-highlight-color',
|
||||
'-moz-transition',
|
||||
'-webkit-appearance',
|
||||
'-webkit-osx-font-smoothing',
|
||||
'-webkit-tap-highlight-color',
|
||||
'-webkit-transition',
|
||||
];
|
||||
|
||||
const stylesRaw = window.getComputedStyle(field);
|
||||
const styles = {};
|
||||
Object.values(stylesRaw).forEach((prop) => {
|
||||
if (!stylesRaw[prop] || !allowedProperties.includes(prop)) {
|
||||
return;
|
||||
}
|
||||
styles[prop] = '' + stylesRaw[prop];
|
||||
});
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
disableFields() {}
|
||||
enableFields() {}
|
||||
}
|
||||
|
|
|
@ -273,6 +273,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @return bool
|
||||
*/
|
||||
public function render_wrapper(): bool {
|
||||
$this->init_context();
|
||||
|
||||
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
|
||||
$this->render_button_wrapper_registrar();
|
||||
$this->render_message_wrapper_registrar();
|
||||
|
|
|
@ -12,6 +12,39 @@ namespace WooCommerce\PayPalCommerce\Button\Helper;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
|
||||
trait ContextTrait {
|
||||
/**
|
||||
* Initializes context preconditions like is_cart() and is_checkout().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init_context(): void {
|
||||
if ( ! apply_filters( 'woocommerce_paypal_payments_block_classic_compat', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate is_checkout() on woocommerce/classic-shortcode checkout blocks.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
add_filter(
|
||||
'woocommerce_is_checkout',
|
||||
function ( $is_checkout ) {
|
||||
if ( $is_checkout ) {
|
||||
return $is_checkout;
|
||||
}
|
||||
return has_block( 'woocommerce/classic-shortcode {"shortcode":"checkout"}' );
|
||||
}
|
||||
);
|
||||
|
||||
// Activate is_cart() on woocommerce/classic-shortcode cart blocks.
|
||||
if ( ! is_cart() && is_callable( 'wc_maybe_define_constant' ) ) {
|
||||
if ( has_block( 'woocommerce/classic-shortcode' ) && ! has_block( 'woocommerce/classic-shortcode {"shortcode":"checkout"}' ) ) {
|
||||
wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks WC is_checkout() + WC checkout ajax requests.
|
||||
*/
|
||||
|
|
|
@ -1,45 +1,9 @@
|
|||
.ppcp-button-googlepay {
|
||||
margin: 7px 0;
|
||||
overflow: hidden;
|
||||
min-height: 40px;
|
||||
height: 45px;
|
||||
|
||||
&.ppcp-button-pill {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
&.ppcp-button-minicart {
|
||||
display: block;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-checkout {
|
||||
.ppcp-button-googlepay {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-has-googlepay-block {
|
||||
|
||||
.wp-block-woocommerce-checkout {
|
||||
.ppcp-button-googlepay {
|
||||
margin: 0;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-cart {
|
||||
.ppcp-button-googlepay {
|
||||
margin: 0;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wp-admin {
|
||||
.ppcp-button-googlepay {
|
||||
pointer-events: none;
|
||||
.wp-block-woocommerce-checkout, .wp-block-woocommerce-cart {
|
||||
.gpay-button {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@ import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hidin
|
|||
import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
|
||||
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
|
||||
import UpdatePaymentData from "./Helper/UpdatePaymentData";
|
||||
import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons";
|
||||
|
||||
class GooglepayButton {
|
||||
|
||||
constructor(context, externalHandler, buttonConfig, ppcpConfig) {
|
||||
apmButtonsInit(ppcpConfig);
|
||||
|
||||
this.isInitialized = false;
|
||||
|
||||
this.context = context;
|
||||
|
|
|
@ -67,7 +67,7 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi
|
|||
buttonConfig.button.wrapper = selector;
|
||||
applyConfigOptions(buttonConfig);
|
||||
|
||||
const wrapperElement = `<div id="${selector.replace('#', '')}" class="ppcp-button-googlepay"></div>`;
|
||||
const wrapperElement = `<div id="${selector.replace('#', '')}" class="ppcp-button-apm ppcp-button-googlepay"></div>`;
|
||||
|
||||
if (!jQuery(selector).length) {
|
||||
jQuery(ppcpConfig.button.wrapper).after(wrapperElement);
|
||||
|
|
|
@ -24,13 +24,6 @@ const GooglePayComponent = () => {
|
|||
manager.init();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const bodyClass = 'ppcp-has-googlepay-block';
|
||||
if (!document.body.classList.contains(bodyClass)) {
|
||||
document.body.classList.add(bodyClass);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Load GooglePay SDK
|
||||
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
|
||||
|
@ -51,7 +44,7 @@ const GooglePayComponent = () => {
|
|||
}, [paypalLoaded, googlePayLoaded]);
|
||||
|
||||
return (
|
||||
<div id={buttonConfig.button.wrapper.replace('#', '')} className="ppcp-button-googlepay"></div>
|
||||
<div id={buttonConfig.button.wrapper.replace('#', '')} className="ppcp-button-apm ppcp-button-googlepay"></div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ class Button implements ButtonInterface {
|
|||
add_action(
|
||||
$render_placeholder,
|
||||
function () {
|
||||
echo '<span id="ppc-button-googlepay-container-minicart" class="ppcp-button-googlepay ppcp-button-minicart"></span>';
|
||||
echo '<span id="ppc-button-googlepay-container-minicart" class="ppcp-button-apm ppcp-button-googlepay ppcp-button-minicart"></span>';
|
||||
},
|
||||
21
|
||||
);
|
||||
|
@ -325,7 +325,7 @@ class Button implements ButtonInterface {
|
|||
*/
|
||||
private function googlepay_button(): void {
|
||||
?>
|
||||
<div id="ppc-button-googlepay-container" class="ppcp-button-googlepay">
|
||||
<div id="ppc-button-googlepay-container" class="ppcp-button-apm ppcp-button-googlepay">
|
||||
<?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?>
|
||||
</div>
|
||||
<?php
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
/**
|
||||
* Properties of the GooglePay module.
|
||||
* ApmApplies helper.
|
||||
* Checks if GooglePay is available for a given country and currency.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Googlepay\Helper
|
||||
*/
|
||||
|
@ -15,7 +16,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
|
|||
class ApmApplies {
|
||||
|
||||
/**
|
||||
* The matrix which countries and currency combinations can be used for DCC.
|
||||
* The matrix which countries and currency combinations can be used for GooglePay.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -38,7 +39,7 @@ class ApmApplies {
|
|||
/**
|
||||
* DccApplies constructor.
|
||||
*
|
||||
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for DCC.
|
||||
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for GooglePay.
|
||||
* @param string $currency 3-letter currency code of the shop.
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,8 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.25.0",
|
||||
"@paypal/paypal-js": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
|
|
|
@ -3,59 +3,17 @@ import {
|
|||
ORDER_BUTTON_SELECTOR,
|
||||
PaymentMethods
|
||||
} from "../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState";
|
||||
|
||||
import {loadScript} from "@paypal/paypal-js";
|
||||
import {
|
||||
setVisible,
|
||||
setVisibleByClass
|
||||
} from "../../../ppcp-button/resources/js/modules/Helper/Hiding";
|
||||
import {loadPaypalJsScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
|
||||
import ErrorHandler from "../../../ppcp-button/resources/js/modules/ErrorHandler";
|
||||
import {cardFieldStyles} from "../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper";
|
||||
|
||||
loadPaypalJsScript(
|
||||
{
|
||||
clientId: ppcp_add_payment_method.client_id,
|
||||
merchantId: ppcp_add_payment_method.merchant_id,
|
||||
dataUserIdToken: ppcp_add_payment_method.id_token,
|
||||
},
|
||||
{
|
||||
createVaultSetupToken: async () => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce,
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if(result.data.id) {
|
||||
return result.data.id
|
||||
}
|
||||
},
|
||||
onApprove: async ({ vaultSetupToken }) => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce,
|
||||
vault_setup_token: vaultSetupToken,
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log(result)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`
|
||||
const errorHandler = new ErrorHandler(
|
||||
PayPalCommerceGateway.labels.error.generic,
|
||||
document.querySelector('.woocommerce-notices-wrapper')
|
||||
);
|
||||
|
||||
const init = () => {
|
||||
|
@ -69,6 +27,156 @@ document.addEventListener(
|
|||
jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () {
|
||||
init()
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
loadScript({
|
||||
clientId: ppcp_add_payment_method.client_id,
|
||||
merchantId: ppcp_add_payment_method.merchant_id,
|
||||
dataUserIdToken: ppcp_add_payment_method.id_token,
|
||||
components: 'buttons,card-fields',
|
||||
})
|
||||
.then((paypal) => {
|
||||
errorHandler.clear();
|
||||
|
||||
paypal.Buttons(
|
||||
{
|
||||
createVaultSetupToken: async () => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce,
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
if (result.data.id) {
|
||||
return result.data.id
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onApprove: async ({vaultSetupToken}) => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce,
|
||||
vault_setup_token: vaultSetupToken,
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json();
|
||||
if(result.success === true) {
|
||||
window.location.href = ppcp_add_payment_method.payment_methods_page;
|
||||
return;
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
}
|
||||
},
|
||||
).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`);
|
||||
|
||||
const cardField = paypal.CardFields({
|
||||
createVaultSetupToken: async () => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce,
|
||||
payment_method: PaymentMethods.CARDS,
|
||||
verification_method: ppcp_add_payment_method.verification_method
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
if (result.data.id) {
|
||||
return result.data.id
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onApprove: async ({vaultSetupToken}) => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce,
|
||||
vault_setup_token: vaultSetupToken,
|
||||
payment_method: PaymentMethods.CARDS
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json();
|
||||
if(result.success === true) {
|
||||
window.location.href = ppcp_add_payment_method.payment_methods_page;
|
||||
return;
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
}
|
||||
});
|
||||
|
||||
if (cardField.isEligible()) {
|
||||
const nameField = document.getElementById('ppcp-credit-card-gateway-card-name');
|
||||
if (nameField) {
|
||||
let styles = cardFieldStyles(nameField);
|
||||
cardField.NameField({style: {'input': styles}}).render(nameField.parentNode);
|
||||
nameField.hidden = true;
|
||||
}
|
||||
|
||||
const numberField = document.getElementById('ppcp-credit-card-gateway-card-number');
|
||||
if (numberField) {
|
||||
let styles = cardFieldStyles(numberField);
|
||||
cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode);
|
||||
numberField.hidden = true;
|
||||
}
|
||||
|
||||
const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry');
|
||||
if (expiryField) {
|
||||
let styles = cardFieldStyles(expiryField);
|
||||
cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode);
|
||||
expiryField.hidden = true;
|
||||
}
|
||||
|
||||
const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc');
|
||||
if (cvvField) {
|
||||
let styles = cardFieldStyles(cvvField);
|
||||
cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode);
|
||||
cvvField.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('#place_order').addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
cardField.submit()
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
});
|
||||
});
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\SavePaymentMethods\WooCommercePaymentTokens;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
/**
|
||||
* Class CreatePaymentToken
|
||||
|
@ -98,16 +100,37 @@ class CreatePaymentToken implements EndpointInterface {
|
|||
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
|
||||
update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id );
|
||||
|
||||
$email = '';
|
||||
if ( isset( $result->payment_source->paypal->email_address ) ) {
|
||||
$email = $result->payment_source->paypal->email_address;
|
||||
if ( isset( $result->payment_source->paypal ) ) {
|
||||
$email = '';
|
||||
if ( isset( $result->payment_source->paypal->email_address ) ) {
|
||||
$email = $result->payment_source->paypal->email_address;
|
||||
}
|
||||
|
||||
$this->wc_payment_tokens->create_payment_token_paypal(
|
||||
get_current_user_id(),
|
||||
$result->id,
|
||||
$email
|
||||
);
|
||||
}
|
||||
|
||||
$this->wc_payment_tokens->create_payment_token_paypal(
|
||||
get_current_user_id(),
|
||||
$result->id,
|
||||
$email
|
||||
);
|
||||
if ( isset( $result->payment_source->card ) ) {
|
||||
$token = new \WC_Payment_Token_CC();
|
||||
$token->set_token( $result->id );
|
||||
$token->set_user_id( get_current_user_id() );
|
||||
$token->set_gateway_id( CreditCardGateway::ID );
|
||||
|
||||
$token->set_last4( $result->payment_source->card->last_digits ?? '' );
|
||||
$expiry = explode( '-', $result->payment_source->card->expiry ?? '' );
|
||||
$token->set_expiry_year( $expiry[0] ?? '' );
|
||||
$token->set_expiry_month( $expiry[1] ?? '' );
|
||||
|
||||
$brand = $result->payment_source->card->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
||||
if ( $brand ) {
|
||||
$token->set_card_type( $brand );
|
||||
}
|
||||
|
||||
$token->save();
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
|
|
|
@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
|
||||
/**
|
||||
* Class CreateSetupToken
|
||||
|
@ -67,13 +68,8 @@ class CreateSetupToken implements EndpointInterface {
|
|||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
$this->request_data->read_request( $this->nonce() );
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
|
||||
/**
|
||||
* Suppress ArgumentTypeCoercion
|
||||
*
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
*/
|
||||
$payment_source = new PaymentSource(
|
||||
'paypal',
|
||||
(object) array(
|
||||
|
@ -85,6 +81,28 @@ class CreateSetupToken implements EndpointInterface {
|
|||
)
|
||||
);
|
||||
|
||||
$payment_method = $data['payment_method'] ?? '';
|
||||
if ( $payment_method === CreditCardGateway::ID ) {
|
||||
$properties = (object) array();
|
||||
|
||||
$verification_method = $data['verification_method'] ?? '';
|
||||
if ( $verification_method === 'SCA_WHEN_REQUIRED' || $verification_method === 'SCA_ALWAYS' ) {
|
||||
$properties = (object) array(
|
||||
'verification_method' => $verification_method,
|
||||
'usage_type' => 'MERCHANT',
|
||||
'experience_context' => (object) array(
|
||||
'return_url' => esc_url( wc_get_account_endpoint_url( 'payment-methods' ) ),
|
||||
'cancel_url' => esc_url( wc_get_account_endpoint_url( 'add-payment-method' ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$payment_source = new PaymentSource(
|
||||
'card',
|
||||
$properties
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source );
|
||||
|
||||
wp_send_json_success( $result );
|
||||
|
|
|
@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface
|
|||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class SavePaymentMethodsModule
|
||||
|
@ -218,14 +219,21 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
|||
|
||||
$id_token = $api->id_token( $target_customer_id );
|
||||
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
$verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : '';
|
||||
|
||||
wp_localize_script(
|
||||
'ppcp-add-payment-method',
|
||||
'ppcp_add_payment_method',
|
||||
array(
|
||||
'client_id' => $c->get( 'button.client_id' ),
|
||||
'merchant_id' => $c->get( 'api.merchant_id' ),
|
||||
'id_token' => $id_token,
|
||||
'ajax' => array(
|
||||
'client_id' => $c->get( 'button.client_id' ),
|
||||
'merchant_id' => $c->get( 'api.merchant_id' ),
|
||||
'id_token' => $id_token,
|
||||
'payment_methods_page' => wc_get_account_endpoint_url( 'payment-methods' ),
|
||||
'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ),
|
||||
'verification_method' => $verification_method,
|
||||
'ajax' => array(
|
||||
'create_setup_token' => array(
|
||||
'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( CreateSetupToken::nonce() ),
|
||||
|
|
|
@ -1031,6 +1031,13 @@
|
|||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@paypal/paypal-js@^6.0.0":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.1.tgz#5d68d5863a5176383fee9424bc944231668fcffd"
|
||||
integrity sha512-bvYetmkg2GEC6onsUJQx1E9hdAJWff2bS3IPeiZ9Sh9U7h26/fIgMKm240cq/908sbSoDjHys75XXd8at9OpQA==
|
||||
dependencies:
|
||||
promise-polyfill "^8.3.0"
|
||||
|
||||
"@types/eslint-scope@^3.7.3":
|
||||
version "3.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e"
|
||||
|
@ -1868,6 +1875,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
|||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
promise-polyfill@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
|
||||
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@use "../../../ppcp-button/resources/css/mixins/apm-button" as apm-button;
|
||||
|
||||
.ppcp-field-hidden {
|
||||
display: none !important;
|
||||
|
@ -15,3 +16,12 @@
|
|||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents spacing after button group.
|
||||
.ppcp-button-preview-inner {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.ppcp-button-apm {
|
||||
@include apm-button.button;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,6 @@ document.addEventListener(
|
|||
|
||||
jQuery( '*[data-ppcp-display]' ).each( (index, el) => {
|
||||
const rules = jQuery(el).data('ppcpDisplay');
|
||||
|
||||
// console.log('rules', rules);
|
||||
|
||||
for (const rule of rules) {
|
||||
displayManager.addRule(rule);
|
||||
}
|
||||
|
|
|
@ -353,5 +353,41 @@ document.addEventListener(
|
|||
}, 'card'));
|
||||
});
|
||||
}
|
||||
|
||||
// Logic to handle the "Check available features" button.
|
||||
((props) => {
|
||||
const $btn = jQuery(props.button);
|
||||
|
||||
$btn.click(async () => {
|
||||
$btn.prop('disabled', true);
|
||||
|
||||
const response = await fetch(
|
||||
props.endpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
nonce: props.nonce,
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
if (!responseData.success) {
|
||||
alert(responseData.data.message);
|
||||
$btn.prop('disabled', false);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
})(PayPalCommerceGatewaySettings.ajax.refresh_feature_status);
|
||||
|
||||
}
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
|
||||
|
@ -1003,6 +1004,13 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'wcgateway.endpoint.refresh-feature-status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint {
|
||||
return new RefreshFeatureStatusEndpoint(
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
new Cache( 'ppcp-timeout' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
||||
'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string {
|
||||
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
|
||||
|
|
|
@ -10,9 +10,11 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
|
||||
|
||||
/**
|
||||
* Class SettingsPageAssets
|
||||
|
@ -237,6 +239,13 @@ class SettingsPageAssets {
|
|||
'disabled_sources' => $this->disabled_sources,
|
||||
'all_funding_sources' => $this->all_funding_sources,
|
||||
'components' => array( 'buttons', 'funding-eligibility', 'messages' ),
|
||||
'ajax' => array(
|
||||
'refresh_feature_status' => array(
|
||||
'endpoint' => \WC_AJAX::get_endpoint( RefreshFeatureStatusEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( RefreshFeatureStatusEndpoint::nonce() ),
|
||||
'button' => '.ppcp-refresh-feature-status',
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
/**
|
||||
* Controls the endpoint for refreshing feature status.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcGateway\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class RefreshFeatureStatusEndpoint
|
||||
*/
|
||||
class RefreshFeatureStatusEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-refresh-feature-status';
|
||||
|
||||
const CACHE_KEY = 'refresh_feature_status_timeout';
|
||||
const TIMEOUT = 60;
|
||||
|
||||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* The cache.
|
||||
*
|
||||
* @var Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* RefreshFeatureStatusEndpoint constructor.
|
||||
*
|
||||
* @param ContainerInterface $settings The settings.
|
||||
* @param Cache $cache The cache.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
ContainerInterface $settings,
|
||||
Cache $cache,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->cache = $cache;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the incoming request.
|
||||
*/
|
||||
public function handle_request(): void {
|
||||
$now = time();
|
||||
$last_request_time = $this->cache->get( self::CACHE_KEY ) ?: 0;
|
||||
$seconds_missing = $last_request_time + self::TIMEOUT - $now;
|
||||
|
||||
if ( ! $this->verify_nonce() ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'Expired request.', 'woocommerce-paypal-payments' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $seconds_missing > 0 ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => sprintf(
|
||||
// translators: %1$s is the number of seconds remaining.
|
||||
__( 'Wait %1$s seconds before trying again.', 'woocommerce-paypal-payments' ),
|
||||
$seconds_missing
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->cache->set( self::CACHE_KEY, $now, self::TIMEOUT );
|
||||
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings );
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the nonce.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function verify_nonce(): bool {
|
||||
$json = json_decode( file_get_contents( 'php://input' ) ?: '', true );
|
||||
return wp_verify_nonce( $json['nonce'] ?? '', self::nonce() ) !== false;
|
||||
}
|
||||
}
|
|
@ -390,6 +390,16 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
'</a>'
|
||||
),
|
||||
),
|
||||
'refresh_feature_status' => array(
|
||||
'title' => __( 'Refresh feature availability status', 'woocommerce-paypal-payments' ),
|
||||
'type' => 'ppcp-text',
|
||||
'text' => '<button type="button" class="button ppcp-refresh-feature-status">' . esc_html__( 'Check available features', 'woocommerce-paypal-payments' ) . '</button>',
|
||||
'screens' => array(
|
||||
State::STATE_ONBOARDED,
|
||||
),
|
||||
'requirements' => array(),
|
||||
'gateway' => Settings::CONNECTION_TAB_ID,
|
||||
),
|
||||
'ppcp_dcc_status' => array(
|
||||
'title' => __( 'Advanced Credit and Debit Card Payments', 'woocommerce-paypal-payments' ),
|
||||
'type' => 'ppcp-text',
|
||||
|
|
|
@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface;
|
|||
use Throwable;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
@ -255,6 +256,16 @@ class WCGatewayModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . RefreshFeatureStatusEndpoint::ENDPOINT,
|
||||
static function () use ( $c ) {
|
||||
$endpoint = $c->get( 'wcgateway.endpoint.refresh-feature-status' );
|
||||
assert( $endpoint instanceof RefreshFeatureStatusEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_paypal_payments_gateway_migrate',
|
||||
static function () use ( $c ) {
|
||||
|
|
|
@ -11,7 +11,9 @@ namespace WooCommerce\PayPalCommerce\WcSubscriptions;
|
|||
|
||||
use WC_Subscription;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||
|
@ -21,6 +23,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
|||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
|
||||
|
@ -193,6 +196,54 @@ class RenewalHandler {
|
|||
|
||||
$token = $this->get_token_for_customer( $customer, $wc_order );
|
||||
if ( $token ) {
|
||||
if ( $wc_order->get_payment_method() === CreditCardGateway::ID ) {
|
||||
$stored_credentials = array(
|
||||
'payment_initiator' => 'MERCHANT',
|
||||
'payment_type' => 'RECURRING',
|
||||
'usage' => 'SUBSEQUENT',
|
||||
);
|
||||
|
||||
$subscriptions = wcs_get_subscriptions_for_renewal_order( $wc_order );
|
||||
foreach ( $subscriptions as $post_id => $subscription ) {
|
||||
$previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
|
||||
if ( $previous_transaction_reference ) {
|
||||
$stored_credentials['previous_transaction_reference'] = $previous_transaction_reference;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$payment_source = new PaymentSource(
|
||||
'card',
|
||||
(object) array(
|
||||
'vault_id' => $token->id(),
|
||||
'stored_credential' => $stored_credentials,
|
||||
)
|
||||
);
|
||||
|
||||
$order = $this->order_endpoint->create(
|
||||
array( $purchase_unit ),
|
||||
$shipping_preference,
|
||||
$payer,
|
||||
null,
|
||||
'',
|
||||
ApplicationContext::USER_ACTION_CONTINUE,
|
||||
'',
|
||||
array(),
|
||||
$payment_source
|
||||
);
|
||||
|
||||
$this->handle_paypal_order( $wc_order, $order );
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Renewal for order %d is completed.',
|
||||
$wc_order->get_id()
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$order = $this->order_endpoint->create(
|
||||
array( $purchase_unit ),
|
||||
$shipping_preference,
|
||||
|
|
|
@ -142,48 +142,6 @@ class WcSubscriptionsModule implements ModuleInterface {
|
|||
20,
|
||||
2
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'ppcp_create_order_request_body_data',
|
||||
function( array $data ) use ( $c ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) );
|
||||
if ( ! $subscription_id ) {
|
||||
return $data;
|
||||
}
|
||||
$subscription = wc_get_order( $subscription_id );
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (
|
||||
$wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID
|
||||
&& isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN'
|
||||
&& isset( $data['payment_source']['token']['source']->card )
|
||||
) {
|
||||
$data['payment_source'] = array(
|
||||
'card' => array(
|
||||
'vault_id' => $data['payment_source']['token']['id'],
|
||||
'stored_credential' => array(
|
||||
'payment_initiator' => 'MERCHANT',
|
||||
'payment_type' => 'RECURRING',
|
||||
'usage' => 'SUBSEQUENT',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
|
||||
if ( $previous_transaction_reference ) {
|
||||
$data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "woocommerce-paypal-payments",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"description": "WooCommerce PayPal Payments",
|
||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||
"license": "GPL-2.0",
|
||||
|
|
15
readme.txt
15
readme.txt
|
@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple
|
|||
Requires at least: 5.3
|
||||
Tested up to: 6.4
|
||||
Requires PHP: 7.2
|
||||
Stable tag: 2.4.2
|
||||
Stable tag: 2.4.3
|
||||
License: GPLv2
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
|
@ -179,6 +179,19 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 2.4.3 - xxxx-xx-xx =
|
||||
* Fix - PayPal Subscription initiated without a WooCommerce order #1907
|
||||
* Fix - Block Checkout reloads when submitting order with empty fields #1904
|
||||
* Fix - "Send checkout billing and shipping data to Apple Pay" displayed when Apple Pay is disabled #1883
|
||||
* Fix - "Order does not contain intent" error for ACDC renewals when triggering 3D Secure #1888
|
||||
* Enhancement - Add button to reload feature eligibility status from Connection tab #1902
|
||||
* Enhancement - Apple Pay validation message improvements #1901
|
||||
* Enhancement - Improve support for Classic Cart & Classic Checkout blocks #1894
|
||||
* Enhancement - Ensure uniform button appearance for PayPal, Google Pay, and Apple Pay buttons #1900
|
||||
* Enhancement - remove string translations for package tracking carriers from repository #1885
|
||||
* Enhancement - Incorrect margins when PayPal buttons are rendered as separate gateways. #1908
|
||||
* Feature preview - Save payment methods (Vault v3) integration #1779
|
||||
|
||||
= 2.4.2 - 2023-12-04 =
|
||||
* Fix - Action callback arguments count in ShipStation tracking integration #1841
|
||||
* Fix - Google Pay scripts loading on unrelated admin pages #1834
|
||||
|
|
|
@ -116,6 +116,9 @@ class RenewalHandlerTest extends TestCase
|
|||
->shouldReceive('payment_source')
|
||||
->andReturn(null);
|
||||
|
||||
$wcOrder
|
||||
->shouldReceive('get_payment_method')
|
||||
->andReturn('');
|
||||
$wcOrder
|
||||
->shouldReceive('get_meta')
|
||||
->andReturn('');
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: WooCommerce PayPal Payments
|
||||
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
||||
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
||||
* Version: 2.4.2
|
||||
* Version: 2.4.3
|
||||
* Author: WooCommerce
|
||||
* Author URI: https://woocommerce.com/
|
||||
* License: GPL-2.0
|
||||
|
@ -25,7 +25,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', '2023-11-28' );
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2023-12-18' );
|
||||
|
||||
! 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_' );
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue