mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 13:44:42 +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,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.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue