Merge branch 'trunk' into PCP-726-add-oxxo-apm-alternative-payment

This commit is contained in:
Alex P 2022-07-26 16:59:27 +03:00
commit 1049fda586
49 changed files with 1586 additions and 668 deletions

View file

@ -1,14 +1,14 @@
*** Changelog *** *** Changelog ***
= 1.9.1 - TBD = = 1.9.1 - 2022-07-25 =
* Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721 * Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721
* Fix - Unable to purchase a product with Credit card button in pay for order page #718 * Fix - Unable to purchase a product with Credit card button in pay for order page #718
* Fix - Pay Later messaging only displayed when smart button is active on the same page #283 * Fix - Pay Later messaging only displayed when smart button is active on the same page #283
* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667 * Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
* Fix - Placeholders and card type detection not working for PayPal Card Processing (260) #685 * Fix - Placeholders and card type detection not working for PayPal Card Processing (260) #685
* Fix - PUI gateway is displayed with unsupported store currency #711 * Fix - PUI gateway is displayed with unsupported store currency #711
* Fix - Wrong PUI locale sent causing error PAYMENT_SOURCE_CANNOT_BE_USED #741 * Fix - Wrong PUI locale sent causing error PAYMENT_SOURCE_CANNOT_BE_USED #741
* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 * Enhancement - Missing PayPal fee in WC order details for PUI purchase #714
* Enhancement - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723 * Enhancement - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723
* Enhancement - PUI feature capitalization not consistent #724 * Enhancement - PUI feature capitalization not consistent #724

View file

@ -400,6 +400,60 @@ return array(
); );
}, },
'api.shop.is-latin-america' => static function ( ContainerInterface $container ): bool {
return in_array(
$container->get( 'api.shop.country' ),
array(
'AI',
'AG',
'AR',
'AW',
'BS',
'BB',
'BZ',
'BM',
'BO',
'BR',
'VG',
'KY',
'CL',
'CO',
'CR',
'DM',
'DO',
'EC',
'SV',
'FK',
'GF',
'GD',
'GP',
'GT',
'GY',
'HN',
'JM',
'MQ',
'MX',
'MS',
'AN',
'NI',
'PA',
'PY',
'PE',
'KN',
'LC',
'PM',
'VC',
'SR',
'TT',
'TC',
'UY',
'VE',
),
true
);
},
/** /**
* Currencies supported by PayPal. * Currencies supported by PayPal.
* *

View file

@ -106,7 +106,7 @@ class IdentityToken {
&& defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION
) { ) {
$customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) ); $customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) );
update_user_meta( $user_id, 'ppcp_customer_id', $customer_id );
$args['body'] = wp_json_encode( $args['body'] = wp_json_encode(
array( array(
'customer_id' => $customer_id, 'customer_id' => $customer_id,

View file

@ -194,7 +194,7 @@ class OrderEndpoint {
'application_context' => $this->application_context_repository 'application_context' => $this->application_context_repository
->current_context( $shipping_preference )->to_array(), ->current_context( $shipping_preference )->to_array(),
); );
if ( $payer && ! empty( $payer->email_address() ) && ! empty( $payer->name() ) ) { if ( $payer && ! empty( $payer->email_address() ) ) {
$data['payer'] = $payer->to_array(); $data['payer'] = $payer->to_array();
} }
if ( $payment_token ) { if ( $payment_token ) {

View file

@ -18,7 +18,7 @@ class Payer {
/** /**
* The name. * The name.
* *
* @var PayerName * @var PayerName|null
*/ */
private $name; private $name;
@ -46,7 +46,7 @@ class Payer {
/** /**
* The address. * The address.
* *
* @var Address * @var Address|null
*/ */
private $address; private $address;
@ -67,7 +67,7 @@ class Payer {
/** /**
* Payer constructor. * Payer constructor.
* *
* @param PayerName $name The name. * @param PayerName|null $name The name.
* @param string $email_address The email. * @param string $email_address The email.
* @param string $payer_id The payer id. * @param string $payer_id The payer id.
* @param Address|null $address The address. * @param Address|null $address The address.
@ -76,7 +76,7 @@ class Payer {
* @param PayerTaxInfo|null $tax_info The tax info. * @param PayerTaxInfo|null $tax_info The tax info.
*/ */
public function __construct( public function __construct(
PayerName $name, ?PayerName $name,
string $email_address, string $email_address,
string $payer_id, string $payer_id,
Address $address = null, Address $address = null,
@ -97,12 +97,21 @@ class Payer {
/** /**
* Returns the name. * Returns the name.
* *
* @return PayerName * @return PayerName|null
*/ */
public function name(): PayerName { public function name(): ?PayerName {
return $this->name; return $this->name;
} }
/**
* Sets the name.
*
* @param PayerName|null $name The value.
*/
public function set_name( ?PayerName $name ): void {
$this->name = $name;
}
/** /**
* Returns the email address. * Returns the email address.
* *
@ -139,6 +148,15 @@ class Payer {
return $this->address; return $this->address;
} }
/**
* Sets the address.
*
* @param Address|null $address The value.
*/
public function set_address( ?Address $address ): void {
$this->address = $address;
}
/** /**
* Returns the phone. * Returns the phone.
* *
@ -164,27 +182,26 @@ class Payer {
*/ */
public function to_array() { public function to_array() {
$payer = array( $payer = array(
'name' => $this->name()->to_array(),
'email_address' => $this->email_address(), 'email_address' => $this->email_address(),
); );
if ( $this->address() ) { if ( $this->name ) {
$payer['address'] = $this->address->to_array(); $payer['name'] = $this->name->to_array();
if ( 2 !== strlen( $this->address()->country_code() ) ) {
unset( $payer['address'] );
}
} }
if ( $this->payer_id() ) { if ( $this->address && 2 === strlen( $this->address->country_code() ) ) {
$payer['payer_id'] = $this->payer_id(); $payer['address'] = $this->address->to_array();
}
if ( $this->payer_id ) {
$payer['payer_id'] = $this->payer_id;
} }
if ( $this->phone() ) { if ( $this->phone ) {
$payer['phone'] = $this->phone()->to_array(); $payer['phone'] = $this->phone->to_array();
} }
if ( $this->tax_info() ) { if ( $this->tax_info ) {
$payer['tax_info'] = $this->tax_info()->to_array(); $payer['tax_info'] = $this->tax_info->to_array();
} }
if ( $this->birthdate() ) { if ( $this->birthdate ) {
$payer['birth_date'] = $this->birthdate()->format( 'Y-m-d' ); $payer['birth_date'] = $this->birthdate->format( 'Y-m-d' );
} }
return $payer; return $payer;
} }

View file

@ -111,15 +111,6 @@ class PayPalApiException extends RuntimeException {
return false; return false;
} }
/**
* Returns response issues.
*
* @return array
*/
public function issues(): array {
return $this->response->issues ?? array();
}
/** /**
* The HTTP status code. * The HTTP status code.
* *

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -132,7 +133,7 @@ class AmountFactory {
$total_value = (float) $order->get_total(); $total_value = (float) $order->get_total();
if ( ( if ( (
CreditCardGateway::ID === $order->get_payment_method() in_array( $order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true )
|| ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) )
) )
&& $this->is_free_trial_order( $order ) && $this->is_free_trial_order( $order )

View file

@ -57,6 +57,6 @@ class CustomerRepository {
return $guest_customer_id; return $guest_customer_id;
} }
return $this->prefix . (string) $user_id; return get_user_meta( $user_id, 'ppcp_customer_id', true ) ?: $this->prefix . (string) $user_id;
} }
} }

View file

@ -3,6 +3,9 @@
"version": "1.0.0", "version": "1.0.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"main": "resources/js/button.js", "main": "resources/js/button.js",
"dependencies": {
"deepmerge": "^4.2.2"
},
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5", "@babel/preset-env": "^7.9.5",

View file

@ -18,7 +18,9 @@ import {hide, setVisible} from "./modules/Helper/Hiding";
import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
const buttonsSpinner = new Spinner('.ppc-button-wrapper'); // 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.
const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper'));
const cardsSpinner = new Spinner('#ppcp-hosted-fields'); const cardsSpinner = new Spinner('#ppcp-hosted-fields');
const bootstrap = () => { const bootstrap = () => {
@ -38,9 +40,36 @@ const bootstrap = () => {
requiredFields.each((i, input) => { requiredFields.each((i, input) => {
jQuery(input).trigger('validate'); jQuery(input).trigger('validate');
}); });
if (jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible').length) { const invalidFields = Array.from(jQuery('form.woocommerce-checkout .validate-required.woocommerce-invalid:visible'));
if (invalidFields.length) {
const billingFieldsContainer = document.querySelector('.woocommerce-billing-fields');
const shippingFieldsContainer = document.querySelector('.woocommerce-shipping-fields');
const nameMessageMap = PayPalCommerceGateway.labels.error.required.elements;
const messages = invalidFields.map(el => {
const name = el.querySelector('[name]')?.getAttribute('name');
if (name && name in nameMessageMap) {
return nameMessageMap[name];
}
let label = el.querySelector('label').textContent
.replaceAll('*', '')
.trim();
if (billingFieldsContainer?.contains(el)) {
label = PayPalCommerceGateway.labels.billing_field.replace('%s', label);
}
if (shippingFieldsContainer?.contains(el)) {
label = PayPalCommerceGateway.labels.shipping_field.replace('%s', label);
}
return PayPalCommerceGateway.labels.error.required.field
.replace('%s', `<strong>${label}</strong>`)
}).filter(s => s.length > 2);
errorHandler.clear(); errorHandler.clear();
errorHandler.message(PayPalCommerceGateway.labels.error.js_validation); if (messages.length) {
messages.forEach(s => errorHandler.message(s));
} else {
errorHandler.message(PayPalCommerceGateway.labels.error.required.generic);
}
return actions.reject(); return actions.reject();
} }
@ -83,7 +112,7 @@ const bootstrap = () => {
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,
messageRenderer, messageRenderer,
); );w
singleProductBootstrap.init(); singleProductBootstrap.init();
} }
@ -138,6 +167,11 @@ document.addEventListener(
return; return;
} }
const paypalButtonGatewayIds = [
PaymentMethods.PAYPAL,
...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id),
]
// Sometimes PayPal script takes long time to load, // Sometimes PayPal script takes long time to load,
// so we additionally hide the standard order button here to avoid failed orders. // so we additionally hide the standard order button here to avoid failed orders.
// Normally it is hidden later after the script load. // Normally it is hidden later after the script load.
@ -153,12 +187,12 @@ document.addEventListener(
} }
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod);
const isCards = currentPaymentMethod === PaymentMethods.CARDS; const isCards = currentPaymentMethod === PaymentMethods.CARDS;
setVisible(ORDER_BUTTON_SELECTOR, !isPaypal && !isCards, true); setVisible(ORDER_BUTTON_SELECTOR, !isPaypalButton && !isCards, true);
if (isPaypal) { if (isPaypalButton) {
// stopped after the first rendering of the buttons, in onInit // stopped after the first rendering of the buttons, in onInit
buttonsSpinner.block(); buttonsSpinner.block();
} else { } else {

View file

@ -26,6 +26,9 @@ class CheckoutActionHandler {
const createaccount = jQuery('#createaccount').is(":checked") ? true : false; const createaccount = jQuery('#createaccount').is(":checked") ? true : false;
const paymentMethod = getCurrentPaymentMethod();
const fundingSource = window.ppcpFundingSource;
return fetch(this.config.ajax.create_order.endpoint, { return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -34,8 +37,8 @@ class CheckoutActionHandler {
bn_code:bnCode, bn_code:bnCode,
context:this.config.context, context:this.config.context,
order_id:this.config.order_id, order_id:this.config.order_id,
payment_method: getCurrentPaymentMethod(), payment_method: paymentMethod,
funding_source: window.ppcpFundingSource, funding_source: fundingSource,
form: formJsonObj, form: formJsonObj,
createaccount: createaccount createaccount: createaccount
}) })

View file

@ -32,9 +32,7 @@ class CartBootstrap {
); );
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, actionHandler.configuration()
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(),
); );
} }
} }

View file

@ -69,9 +69,7 @@ class CheckoutBootstap {
); );
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, actionHandler.configuration()
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(),
); );
this.buttonChangeObserver.observe( this.buttonChangeObserver.observe(
@ -84,16 +82,27 @@ class CheckoutBootstap {
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS; const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod);
const isSavedCard = isCard && isSavedCardSelected(); const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard; const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== '';
const paypalButtonWrappers = {
...Object.entries(PayPalCommerceGateway.separate_buttons)
.reduce((result, [k, data]) => {
return {...result, [data.id]: data.wrapper}
}, {}),
};
setVisible(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, true); setVisible(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, true);
setVisible('.ppcp-vaulted-paypal-details', isPaypal); setVisible('.ppcp-vaulted-paypal-details', isPaypal);
setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal));
setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial);
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard);
for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) {
setVisible(wrapper, gatewayId === currentPaymentMethod);
}
if (isPaypal && !isFreeTrial) { if (isPaypal && !isFreeTrial) {
this.messages.render(); this.messages.render();

View file

@ -32,9 +32,13 @@ class MiniCartBootstap {
} }
this.renderer.render( this.renderer.render(
this.gateway.button.mini_cart_wrapper, this.actionHandler.configuration(),
this.gateway.hosted_fields.mini_cart_wrapper, {
this.actionHandler.configuration() button: {
wrapper: this.gateway.button.mini_cart_wrapper,
style: this.gateway.button.mini_cart_style,
},
}
); );
} }
} }

View file

@ -85,9 +85,7 @@ class SingleProductBootstap {
); );
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, actionHandler.configuration()
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(),
); );
} }
} }

View file

@ -2,6 +2,7 @@ export const PaymentMethods = {
PAYPAL: 'ppcp-gateway', PAYPAL: 'ppcp-gateway',
CARDS: 'ppcp-credit-card-gateway', CARDS: 'ppcp-credit-card-gateway',
OXXO: 'ppcp-oxxo-gateway', OXXO: 'ppcp-oxxo-gateway',
CARD_BUTTON: 'ppcp-card-button-gateway',
}; };
export const ORDER_BUTTON_SELECTOR = '#place_order'; export const ORDER_BUTTON_SELECTOR = '#place_order';

View file

@ -1,33 +1,86 @@
import merge from "deepmerge";
class Renderer { class Renderer {
constructor(creditCardRenderer, defaultConfig, onSmartButtonClick, onSmartButtonsInit) { constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
this.defaultConfig = defaultConfig; this.defaultSettings = defaultSettings;
this.creditCardRenderer = creditCardRenderer; this.creditCardRenderer = creditCardRenderer;
this.onSmartButtonClick = onSmartButtonClick; this.onSmartButtonClick = onSmartButtonClick;
this.onSmartButtonsInit = onSmartButtonsInit; this.onSmartButtonsInit = onSmartButtonsInit;
this.renderedSources = new Set();
} }
render(wrapper, hostedFieldsWrapper, contextConfig) { render(contextConfig, settingsOverride = {}) {
const settings = merge(this.defaultSettings, settingsOverride);
this.renderButtons(wrapper, contextConfig); const enabledSeparateGateways = Object.fromEntries(Object.entries(
this.creditCardRenderer.render(hostedFieldsWrapper, contextConfig); settings.separate_buttons).filter(([s, data]) => document.querySelector(data.wrapper)
));
const hasEnabledSeparateGateways = Object.keys(enabledSeparateGateways).length !== 0;
if (!hasEnabledSeparateGateways) {
this.renderButtons(
settings.button.wrapper,
settings.button.style,
contextConfig
);
} else {
// render each button separately
for (const fundingSource of paypal.getFundingSources().filter(s => !(s in enabledSeparateGateways))) {
let style = settings.button.style;
if (fundingSource !== 'paypal') {
style = {
shape: style.shape,
};
}
this.renderButtons(
settings.button.wrapper,
style,
contextConfig,
fundingSource
);
}
}
this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig);
for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) {
this.renderButtons(
data.wrapper,
data.style,
contextConfig,
fundingSource
);
}
} }
renderButtons(wrapper, contextConfig) { renderButtons(wrapper, style, contextConfig, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper) || 'undefined' === typeof paypal.Buttons ) { if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource) || 'undefined' === typeof paypal.Buttons ) {
return; return;
} }
const style = wrapper === this.defaultConfig.button.wrapper ? this.defaultConfig.button.style : this.defaultConfig.button.mini_cart_style; if (fundingSource) {
paypal.Buttons({ contextConfig.fundingSource = fundingSource;
}
const btn = paypal.Buttons({
style, style,
...contextConfig, ...contextConfig,
onClick: this.onSmartButtonClick, onClick: this.onSmartButtonClick,
onInit: this.onSmartButtonsInit, onInit: this.onSmartButtonsInit,
}).render(wrapper); });
if (!btn.isEligible()) {
return;
}
btn.render(wrapper);
this.renderedSources.add(wrapper + fundingSource ?? '');
} }
isAlreadyRendered(wrapper) { isAlreadyRendered(wrapper, fundingSource) {
return document.querySelector(wrapper).hasChildNodes(); return this.renderedSources.has(wrapper + fundingSource ?? '');
} }
hideButtons(element) { hideButtons(element) {

View file

@ -133,6 +133,7 @@ return array(
$settings, $settings,
$early_order_handler, $early_order_handler,
$registration_needed, $registration_needed,
$container->get( 'wcgateway.settings.card_billing_data_mode' ),
$logger $logger
); );
}, },

View file

@ -28,7 +28,9 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -421,15 +423,20 @@ class SmartButton implements SmartButtonInterface {
) { ) {
add_action( add_action(
$this->single_product_renderer_hook(), $this->single_product_renderer_hook(),
array( function () {
$this, $this->button_renderer( PayPalGateway::ID );
'button_renderer', },
),
31 31
); );
} }
add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 ); add_action(
$this->pay_order_renderer_hook(),
function (): void {
$this->button_renderer( PayPalGateway::ID );
$this->button_renderer( CardButtonGateway::ID );
}
);
$not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) && $not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) &&
! $this->settings->get( 'button_mini_cart_enabled' ); ! $this->settings->get( 'button_mini_cart_enabled' );
@ -457,7 +464,13 @@ class SmartButton implements SmartButtonInterface {
); );
} }
add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); add_action(
$this->checkout_button_renderer_hook(),
function (): void {
$this->button_renderer( PayPalGateway::ID );
$this->button_renderer( CardButtonGateway::ID );
}
);
$not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) &&
! $this->settings->get( 'button_cart_enabled' ); ! $this->settings->get( 'button_cart_enabled' );
@ -468,7 +481,7 @@ class SmartButton implements SmartButtonInterface {
return; return;
} }
$this->button_renderer(); $this->button_renderer( PayPalGateway::ID );
}, },
20 20
); );
@ -524,8 +537,10 @@ class SmartButton implements SmartButtonInterface {
/** /**
* Renders the HTML for the buttons. * Renders the HTML for the buttons.
*
* @param string $gateway_id The gateway ID, like 'ppcp-gateway'.
*/ */
public function button_renderer() { public function button_renderer( string $gateway_id ) {
if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
return; return;
@ -543,13 +558,13 @@ class SmartButton implements SmartButtonInterface {
$available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
if ( ! isset( $available_gateways['ppcp-gateway'] ) ) { if ( ! isset( $available_gateways[ $gateway_id ] ) ) {
return; return;
} }
// The wrapper is needed for the loading spinner, // The wrapper is needed for the loading spinner,
// otherwise jQuery block() prevents buttons rendering. // otherwise jQuery block() prevents buttons rendering.
echo '<div class="ppc-button-wrapper"><div id="ppc-button"></div></div>'; echo '<div class="ppc-button-wrapper"><div id="ppc-button-' . esc_attr( $gateway_id ) . '"></div></div>';
} }
/** /**
@ -810,7 +825,7 @@ class SmartButton implements SmartButtonInterface {
'bn_codes' => $this->bn_codes(), 'bn_codes' => $this->bn_codes(),
'payer' => $this->payerData(), 'payer' => $this->payerData(),
'button' => array( 'button' => array(
'wrapper' => '#ppc-button', 'wrapper' => '#ppc-button-' . PayPalGateway::ID,
'mini_cart_wrapper' => '#ppc-button-minicart', 'mini_cart_wrapper' => '#ppc-button-minicart',
'cancel_wrapper' => '#ppcp-cancel', 'cancel_wrapper' => '#ppcp-cancel',
'url' => $this->url(), 'url' => $this->url(),
@ -830,10 +845,19 @@ class SmartButton implements SmartButtonInterface {
'tagline' => $this->style_for_context( 'tagline', $this->context() ), 'tagline' => $this->style_for_context( 'tagline', $this->context() ),
), ),
), ),
'separate_buttons' => array(
'card' => array(
'id' => CardButtonGateway::ID,
'wrapper' => '#ppc-button-' . CardButtonGateway::ID,
'style' => array(
'shape' => $this->style_for_context( 'shape', $this->context() ),
// TODO: color black, white from the gateway settings.
),
),
),
'hosted_fields' => array( 'hosted_fields' => array(
'wrapper' => '#ppcp-hosted-fields', 'wrapper' => '#ppcp-hosted-fields',
'mini_cart_wrapper' => '#ppcp-hosted-fields-mini-cart', 'labels' => array(
'labels' => array(
'credit_card_number' => '', 'credit_card_number' => '',
'cvv' => '', 'cvv' => '',
'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ), 'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ),
@ -847,21 +871,36 @@ class SmartButton implements SmartButtonInterface {
), ),
'cardholder_name_required' => __( 'Cardholder\'s first and last name are required, please fill the checkout form required fields.', 'woocommerce-paypal-payments' ), 'cardholder_name_required' => __( 'Cardholder\'s first and last name are required, please fill the checkout form required fields.', 'woocommerce-paypal-payments' ),
), ),
'valid_cards' => $this->dcc_applies->valid_cards(), 'valid_cards' => $this->dcc_applies->valid_cards(),
'contingency' => $this->get_3ds_contingency(), 'contingency' => $this->get_3ds_contingency(),
), ),
'messages' => $this->message_values(), 'messages' => $this->message_values(),
'labels' => array( 'labels' => array(
'error' => array( 'error' => array(
'generic' => __( 'generic' => __(
'Something went wrong. Please try again or choose another payment source.', 'Something went wrong. Please try again or choose another payment source.',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
'js_validation' => __( 'required' => array(
'Required form fields are not filled or invalid.', 'generic' => __(
'woocommerce-paypal-payments' 'Required form fields are not filled.',
'woocommerce-paypal-payments'
),
// phpcs:ignore WordPress.WP.I18n
'field' => __( '%s is a required field.', 'woocommerce' ),
'elements' => array( // Map <form element name> => text for error messages.
'terms' => __(
'Please read and accept the terms and conditions to proceed with your order.',
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
'woocommerce'
),
),
), ),
), ),
// phpcs:ignore WordPress.WP.I18n
'billing_field' => _x( 'Billing %s', 'checkout-validation', 'woocommerce' ),
// phpcs:ignore WordPress.WP.I18n
'shipping_field' => _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ),
), ),
'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0, 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0,
'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ), 'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ),
@ -933,7 +972,10 @@ class SmartButton implements SmartButtonInterface {
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ); $is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' );
if ( is_checkout() && $is_dcc_enabled ) { $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] );
if ( is_checkout() && ( $is_dcc_enabled || $is_separate_card_enabled ) ) {
$key = array_search( 'card', $disable_funding, true ); $key = array_search( 'card', $disable_funding, true );
if ( false !== $key ) { if ( false !== $key ) {
unset( $disable_funding[ $key ] ); unset( $disable_funding[ $key ] );
@ -1018,6 +1060,7 @@ class SmartButton implements SmartButtonInterface {
if ( $this->load_button_component() ) { if ( $this->load_button_component() ) {
$components[] = 'buttons'; $components[] = 'buttons';
$components[] = 'funding-eligibility';
} }
if ( if (
$this->messages_apply->for_country() $this->messages_apply->for_country()
@ -1112,6 +1155,9 @@ class SmartButton implements SmartButtonInterface {
if ( $source && $source->card() ) { if ( $source && $source->card() ) {
return false; // Ignore for DCC. return false; // Ignore for DCC.
} }
if ( 'card' === $this->session_handler->funding_source() ) {
return false; // Ignore for card buttons.
}
return true; return true;
} }

View file

@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface;
use stdClass; use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount; use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
@ -27,7 +28,9 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -118,6 +121,13 @@ class CreateOrderEndpoint implements EndpointInterface {
*/ */
private $registration_needed; private $registration_needed;
/**
* The value of card_billing_data_mode from the settings.
*
* @var string
*/
protected $card_billing_data_mode;
/** /**
* The logger. * The logger.
* *
@ -137,6 +147,7 @@ class CreateOrderEndpoint implements EndpointInterface {
* @param Settings $settings The Settings object. * @param Settings $settings The Settings object.
* @param EarlyOrderHandler $early_order_handler The EarlyOrderHandler object. * @param EarlyOrderHandler $early_order_handler The EarlyOrderHandler object.
* @param bool $registration_needed Whether a new user must be registered during checkout. * @param bool $registration_needed Whether a new user must be registered during checkout.
* @param string $card_billing_data_mode The value of card_billing_data_mode from the settings.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
@ -149,6 +160,7 @@ class CreateOrderEndpoint implements EndpointInterface {
Settings $settings, Settings $settings,
EarlyOrderHandler $early_order_handler, EarlyOrderHandler $early_order_handler,
bool $registration_needed, bool $registration_needed,
string $card_billing_data_mode,
LoggerInterface $logger LoggerInterface $logger
) { ) {
@ -161,6 +173,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->settings = $settings; $this->settings = $settings;
$this->early_order_handler = $early_order_handler; $this->early_order_handler = $early_order_handler;
$this->registration_needed = $registration_needed; $this->registration_needed = $registration_needed;
$this->card_billing_data_mode = $card_billing_data_mode;
$this->logger = $logger; $this->logger = $logger;
} }
@ -204,7 +217,7 @@ class CreateOrderEndpoint implements EndpointInterface {
// The cart does not have any info about payment method, so we must handle free trial here. // The cart does not have any info about payment method, so we must handle free trial here.
if ( ( if ( (
CreditCardGateway::ID === $payment_method in_array( $payment_method, array( CreditCardGateway::ID, CardButtonGateway::ID ), true )
|| ( PayPalGateway::ID === $payment_method && 'card' === $funding_source ) || ( PayPalGateway::ID === $payment_method && 'card' === $funding_source )
) )
&& $this->is_free_trial_cart() && $this->is_free_trial_cart()
@ -331,18 +344,40 @@ class CreateOrderEndpoint implements EndpointInterface {
private function create_paypal_order( \WC_Order $wc_order = null ): Order { private function create_paypal_order( \WC_Order $wc_order = null ): Order {
assert( $this->purchase_unit instanceof PurchaseUnit ); assert( $this->purchase_unit instanceof PurchaseUnit );
$funding_source = $this->parsed_request_data['funding_source'] ?? '';
$payer = $this->payer( $this->parsed_request_data, $wc_order );
$shipping_preference = $this->shipping_preference_factory->from_state( $shipping_preference = $this->shipping_preference_factory->from_state(
$this->purchase_unit, $this->purchase_unit,
$this->parsed_request_data['context'], $this->parsed_request_data['context'],
WC()->cart, WC()->cart,
$this->parsed_request_data['funding_source'] ?? '' $funding_source
); );
if ( 'card' === $funding_source ) {
if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) {
if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) {
if ( $payer ) {
$payer->set_address( null );
}
}
if ( ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) {
if ( $payer ) {
$payer->set_name( null );
}
}
}
if ( CardBillingMode::NO_WC === $this->card_billing_data_mode ) {
$payer = null;
}
}
try { try {
return $this->api_endpoint->create( return $this->api_endpoint->create(
array( $this->purchase_unit ), array( $this->purchase_unit ),
$shipping_preference, $shipping_preference,
$this->payer( $this->parsed_request_data, $wc_order ), $payer,
null, null,
$this->payment_method() $this->payment_method()
); );
@ -364,7 +399,7 @@ class CreateOrderEndpoint implements EndpointInterface {
return $this->api_endpoint->create( return $this->api_endpoint->create(
array( $this->purchase_unit ), array( $this->purchase_unit ),
$shipping_preference, $shipping_preference,
$this->payer( $this->parsed_request_data, $wc_order ), $payer,
null, null,
$this->payment_method() $this->payment_method()
); );

View file

@ -1353,6 +1353,11 @@ debug@^4.1.0, debug@^4.1.1:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
define-properties@^1.1.3: define-properties@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"

View file

@ -70,6 +70,10 @@ class CancelController {
return; // Ignore for DCC. return; // Ignore for DCC.
} }
if ( 'card' === $this->session_handler->funding_source() ) {
return; // Ignore for card buttons.
}
$url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() ); $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() );
add_action( add_action(
'woocommerce_review_order_after_submit', 'woocommerce_review_order_after_submit',

View file

@ -24,6 +24,8 @@ return array(
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
$payer_factory = $container->get( 'api.factory.payer' ); $payer_factory = $container->get( 'api.factory.payer' );
$environment = $container->get( 'onboarding.environment' ); $environment = $container->get( 'onboarding.environment' );
$settings = $container->get( 'wcgateway.settings' );
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
return new RenewalHandler( return new RenewalHandler(
$logger, $logger,
$repository, $repository,
@ -31,7 +33,9 @@ return array(
$purchase_unit_factory, $purchase_unit_factory,
$container->get( 'api.factory.shipping-preference' ), $container->get( 'api.factory.shipping-preference' ),
$payer_factory, $payer_factory,
$environment $environment,
$settings,
$authorized_payments_processor
); );
}, },
'subscription.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { 'subscription.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription; namespace WooCommerce\PayPalCommerce\Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
@ -17,10 +18,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
* Class RenewalHandler * Class RenewalHandler
@ -80,16 +83,32 @@ class RenewalHandler {
*/ */
protected $environment; protected $environment;
/**
* The settings
*
* @var Settings
*/
protected $settings;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/** /**
* RenewalHandler constructor. * RenewalHandler constructor.
* *
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param PaymentTokenRepository $repository The payment token repository. * @param PaymentTokenRepository $repository The payment token repository.
* @param OrderEndpoint $order_endpoint The order endpoint. * @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @param PayerFactory $payer_factory The payer factory. * @param PayerFactory $payer_factory The payer factory.
* @param Environment $environment The environment. * @param Environment $environment The environment.
* @param Settings $settings The Settings.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
*/ */
public function __construct( public function __construct(
LoggerInterface $logger, LoggerInterface $logger,
@ -98,16 +117,20 @@ class RenewalHandler {
PurchaseUnitFactory $purchase_unit_factory, PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory, ShippingPreferenceFactory $shipping_preference_factory,
PayerFactory $payer_factory, PayerFactory $payer_factory,
Environment $environment Environment $environment,
Settings $settings,
AuthorizedPaymentsProcessor $authorized_payments_processor
) { ) {
$this->logger = $logger; $this->logger = $logger;
$this->repository = $repository; $this->repository = $repository;
$this->order_endpoint = $order_endpoint; $this->order_endpoint = $order_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory; $this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory; $this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory; $this->payer_factory = $payer_factory;
$this->environment = $environment; $this->environment = $environment;
$this->settings = $settings;
$this->authorized_payments_processor = $authorized_payments_processor;
} }
/** /**
@ -179,6 +202,14 @@ class RenewalHandler {
} }
$this->handle_new_order_status( $order, $wc_order ); $this->handle_new_order_status( $order, $wc_order );
if ( $this->capture_authorized_downloads( $order ) && AuthorizedPaymentsProcessor::SUCCESSFUL === $this->authorized_payments_processor->process( $wc_order ) ) {
$wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
);
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' );
$wc_order->update_status( 'completed' );
}
} }
/** /**
@ -229,4 +260,39 @@ class RenewalHandler {
return current( $tokens ); return current( $tokens );
} }
/**
* Returns if an order should be captured immediately.
*
* @param Order $order The PayPal order.
*
* @return bool
* @throws NotFoundException When a setting was not found.
*/
protected function capture_authorized_downloads( Order $order ): bool {
if (
! $this->settings->has( 'capture_for_virtual_only' )
|| ! $this->settings->get( 'capture_for_virtual_only' )
) {
return false;
}
if ( $order->intent() === 'CAPTURE' ) {
return false;
}
/**
* We fetch the order again as the authorize endpoint (from which the Order derives)
* drops the item's category, making it impossible to check, if purchase units contain
* physical goods.
*/
$order = $this->order_endpoint->order( $order->id() );
foreach ( $order->purchase_units() as $unit ) {
if ( $unit->contains_physical_goods() ) {
return false;
}
}
return true;
}
} }

View file

@ -16,6 +16,7 @@ use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
@ -118,7 +119,7 @@ class PaymentTokenChecker {
if ( $tokens ) { if ( $tokens ) {
try { try {
if ( $this->is_free_trial_order( $wc_order ) ) { if ( $this->is_free_trial_order( $wc_order ) ) {
if ( CreditCardGateway::ID === $wc_order->get_payment_method() if ( in_array( $wc_order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true )
|| ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) )
) { ) {
$order = $this->order_repository->for_wc_order( $wc_order ); $order = $this->order_repository->for_wc_order( $wc_order );

View file

@ -29,6 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO; use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOEndpoint;
@ -48,7 +49,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\DccWithoutPayPalAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
@ -59,11 +60,10 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
return array( return array(
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
$order_processor = $container->get( 'wcgateway.order-processor' ); $order_processor = $container->get( 'wcgateway.order-processor' );
$settings_renderer = $container->get( 'wcgateway.settings.render' ); $settings_renderer = $container->get( 'wcgateway.settings.render' );
$funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' ); $funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' );
$authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$refund_processor = $container->get( 'wcgateway.processor.refunds' ); $refund_processor = $container->get( 'wcgateway.processor.refunds' );
@ -72,8 +72,6 @@ return array(
$subscription_helper = $container->get( 'subscription.helper' ); $subscription_helper = $container->get( 'subscription.helper' );
$page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' ); $page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); $payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$environment = $container->get( 'onboarding.environment' ); $environment = $container->get( 'onboarding.environment' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
$api_shop_country = $container->get( 'api.shop.country' ); $api_shop_country = $container->get( 'api.shop.country' );
@ -81,7 +79,6 @@ return array(
$settings_renderer, $settings_renderer,
$funding_source_renderer, $funding_source_renderer,
$order_processor, $order_processor,
$authorized_payments,
$settings, $settings,
$session_handler, $session_handler,
$refund_processor, $refund_processor,
@ -91,14 +88,11 @@ return array(
$page_id, $page_id,
$environment, $environment,
$payment_token_repository, $payment_token_repository,
$container->get( 'api.factory.shipping-preference' ),
$logger, $logger,
$payments_endpoint,
$order_endpoint,
$api_shop_country $api_shop_country
); );
}, },
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway { 'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
$order_processor = $container->get( 'wcgateway.order-processor' ); $order_processor = $container->get( 'wcgateway.order-processor' );
$settings_renderer = $container->get( 'wcgateway.settings.render' ); $settings_renderer = $container->get( 'wcgateway.settings.render' );
$authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' ); $authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' );
@ -137,27 +131,43 @@ return array(
$payments_endpoint $payments_endpoint
); );
}, },
'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway {
return new CardButtonGateway(
$container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.order-processor' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'session.handler' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'subscription.helper' ),
$container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
$container->get( 'onboarding.environment' ),
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways {
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new DisableGateways( $session_handler, $settings ); return new DisableGateways( $session_handler, $settings );
}, },
'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool { 'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool {
$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
$tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : ''; $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : '';
return 'wc-settings' === $page && 'checkout' === $tab; return 'wc-settings' === $page && 'checkout' === $tab;
}, },
'wcgateway.is-ppcp-settings-page' => static function ( ContainerInterface $container ): bool { 'wcgateway.is-ppcp-settings-page' => static function ( ContainerInterface $container ): bool {
if ( ! $container->get( 'wcgateway.is-wc-payments-page' ) ) { if ( ! $container->get( 'wcgateway.is-wc-payments-page' ) ) {
return false; return false;
} }
$section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : ''; $section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : '';
return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID ), true ); return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true );
}, },
'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string {
if ( ! $container->get( 'wcgateway.is-ppcp-settings-page' ) ) { if ( ! $container->get( 'wcgateway.is-ppcp-settings-page' ) ) {
return ''; return '';
} }
@ -168,36 +178,47 @@ return array(
return $ppcp_tab ? $ppcp_tab : $section; return $ppcp_tab ? $ppcp_tab : $section;
}, },
'wcgateway.settings' => static function ( ContainerInterface $container ): Settings { 'wcgateway.settings' => static function ( ContainerInterface $container ): Settings {
return new Settings(); return new Settings();
}, },
'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice { 'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice {
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new ConnectAdminNotice( $state, $settings ); return new ConnectAdminNotice( $state, $settings );
}, },
'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): DccWithoutPayPalAdminNotice { 'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
$state = $container->get( 'onboarding.state' ); return new GatewayWithoutPayPalAdminNotice(
$settings = $container->get( 'wcgateway.settings' ); CreditCardGateway::ID,
$is_payments_page = $container->get( 'wcgateway.is-wc-payments-page' ); $container->get( 'onboarding.state' ),
$is_ppcp_settings_page = $container->get( 'wcgateway.is-ppcp-settings-page' ); $container->get( 'wcgateway.settings' ),
return new DccWithoutPayPalAdminNotice( $state, $settings, $is_payments_page, $is_ppcp_settings_page ); $container->get( 'wcgateway.is-wc-payments-page' ),
$container->get( 'wcgateway.is-ppcp-settings-page' )
);
}, },
'wcgateway.notice.authorize-order-action' => 'wcgateway.notice.card-button-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
return new GatewayWithoutPayPalAdminNotice(
CardButtonGateway::ID,
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ),
$container->get( 'wcgateway.is-ppcp-settings-page' )
);
},
'wcgateway.notice.authorize-order-action' =>
static function ( ContainerInterface $container ): AuthorizeOrderActionNotice { static function ( ContainerInterface $container ): AuthorizeOrderActionNotice {
return new AuthorizeOrderActionNotice(); return new AuthorizeOrderActionNotice();
}, },
'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer { 'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer {
return new SectionsRenderer( return new SectionsRenderer(
$container->get( 'wcgateway.current-ppcp-settings-page-id' ), $container->get( 'wcgateway.current-ppcp-settings-page-id' ),
$container->get( 'api.shop.country' ) $container->get( 'api.shop.country' )
); );
}, },
'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus { 'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new SettingsStatus( $settings ); return new SettingsStatus( $settings );
}, },
'wcgateway.settings.render' => static function ( ContainerInterface $container ): SettingsRenderer { 'wcgateway.settings.render' => static function ( ContainerInterface $container ): SettingsRenderer {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
$fields = $container->get( 'wcgateway.settings.fields' ); $fields = $container->get( 'wcgateway.settings.fields' );
@ -217,7 +238,7 @@ return array(
$page_id $page_id
); );
}, },
'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener { 'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$fields = $container->get( 'wcgateway.settings.fields' ); $fields = $container->get( 'wcgateway.settings.fields' );
$webhook_registrar = $container->get( 'webhook.registrar' ); $webhook_registrar = $container->get( 'webhook.registrar' );
@ -239,7 +260,7 @@ return array(
$signup_link_ids $signup_link_ids
); );
}, },
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$order_endpoint = $container->get( 'api.endpoint.order' ); $order_endpoint = $container->get( 'api.endpoint.order' );
@ -264,13 +285,13 @@ return array(
$order_helper $order_helper
); );
}, },
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor { 'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {
$order_endpoint = $container->get( 'api.endpoint.order' ); $order_endpoint = $container->get( 'api.endpoint.order' );
$payments_endpoint = $container->get( 'api.endpoint.payments' ); $payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
return new RefundProcessor( $order_endpoint, $payments_endpoint, $logger ); return new RefundProcessor( $order_endpoint, $payments_endpoint, $logger );
}, },
'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor { 'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor {
$order_endpoint = $container->get( 'api.endpoint.order' ); $order_endpoint = $container->get( 'api.endpoint.order' );
$payments_endpoint = $container->get( 'api.endpoint.payments' ); $payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
@ -286,23 +307,23 @@ return array(
$subscription_helper $subscription_helper
); );
}, },
'wcgateway.admin.render-authorize-action' => static function ( ContainerInterface $container ): RenderAuthorizeAction { 'wcgateway.admin.render-authorize-action' => static function ( ContainerInterface $container ): RenderAuthorizeAction {
$column = $container->get( 'wcgateway.admin.orders-payment-status-column' ); $column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
return new RenderAuthorizeAction( $column ); return new RenderAuthorizeAction( $column );
}, },
'wcgateway.admin.order-payment-status' => static function ( ContainerInterface $container ): PaymentStatusOrderDetail { 'wcgateway.admin.order-payment-status' => static function ( ContainerInterface $container ): PaymentStatusOrderDetail {
$column = $container->get( 'wcgateway.admin.orders-payment-status-column' ); $column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
return new PaymentStatusOrderDetail( $column ); return new PaymentStatusOrderDetail( $column );
}, },
'wcgateway.admin.orders-payment-status-column' => static function ( ContainerInterface $container ): OrderTablePaymentStatusColumn { 'wcgateway.admin.orders-payment-status-column' => static function ( ContainerInterface $container ): OrderTablePaymentStatusColumn {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new OrderTablePaymentStatusColumn( $settings ); return new OrderTablePaymentStatusColumn( $settings );
}, },
'wcgateway.admin.fees-renderer' => static function ( ContainerInterface $container ): FeesRenderer { 'wcgateway.admin.fees-renderer' => static function ( ContainerInterface $container ): FeesRenderer {
return new FeesRenderer(); return new FeesRenderer();
}, },
'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array { 'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array {
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
assert( $state instanceof State ); assert( $state instanceof State );
@ -865,6 +886,40 @@ return array(
'requirements' => array(), 'requirements' => array(),
'gateway' => 'paypal', 'gateway' => 'paypal',
), ),
'card_billing_data_mode' => array(
'title' => __( 'Card billing data handling', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
'desc_tip' => true,
'description' => __( 'Using the WC form data increases convenience for the customers, but can cause issues if card details do not match the billing data in the checkout form.', 'woocommerce-paypal-payments' ),
'default' => $container->get( 'wcgateway.settings.card_billing_data_mode.default' ),
'options' => array(
CardBillingMode::USE_WC => __( 'Use WC checkout form data (do not show any address fields)', 'woocommerce-paypal-payments' ),
CardBillingMode::MINIMAL_INPUT => __( 'Request only name and postal code', 'woocommerce-paypal-payments' ),
CardBillingMode::NO_WC => __( 'Do not use WC checkout form data (request all address fields)', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => array( 'paypal', CardButtonGateway::ID ),
),
'allow_card_button_gateway' => array(
'title' => __( 'Separate Card Button from PayPal gateway', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
'label' => __( 'Enable a separate payment gateway for the branded PayPal Debit or Credit Card button.', 'woocommerce-paypal-payments' ),
'description' => __( 'By default, the Debit or Credit Card button is displayed in the PayPal Checkout payment gateway. This setting creates a second gateway for the Card button.', 'woocommerce-paypal-payments' ),
'default' => $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'paypal',
),
// General button styles. // General button styles.
'button_style_heading' => array( 'button_style_heading' => array(
@ -2078,7 +2133,7 @@ return array(
return $fields; return $fields;
}, },
'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array { 'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array {
return array( return array(
'card' => _x( 'Credit or debit cards', 'Name of payment method', 'woocommerce-paypal-payments' ), 'card' => _x( 'Credit or debit cards', 'Name of payment method', 'woocommerce-paypal-payments' ),
'credit' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ), 'credit' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
@ -2096,28 +2151,28 @@ return array(
); );
}, },
'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset { 'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset {
return new CheckoutPayPalAddressPreset( return new CheckoutPayPalAddressPreset(
$container->get( 'session.handler' ) $container->get( 'session.handler' )
); );
}, },
'wcgateway.url' => static function ( ContainerInterface $container ): string { 'wcgateway.url' => static function ( ContainerInterface $container ): string {
return plugins_url( return plugins_url(
$container->get( 'wcgateway.relative-path' ), $container->get( 'wcgateway.relative-path' ),
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
); );
}, },
'wcgateway.relative-path' => static function( ContainerInterface $container ): string { 'wcgateway.relative-path' => static function( ContainerInterface $container ): string {
return 'modules/ppcp-wc-gateway/'; return 'modules/ppcp-wc-gateway/';
}, },
'wcgateway.absolute-path' => static function( ContainerInterface $container ): string { 'wcgateway.absolute-path' => static function( ContainerInterface $container ): string {
return plugin_dir_path( return plugin_dir_path(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
) . ) .
$container->get( 'wcgateway.relative-path' ); $container->get( 'wcgateway.relative-path' );
}, },
'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint { 'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint {
$gateway = $container->get( 'wcgateway.paypal-gateway' ); $gateway = $container->get( 'wcgateway.paypal-gateway' );
$endpoint = $container->get( 'api.endpoint.order' ); $endpoint = $container->get( 'api.endpoint.order' );
$prefix = $container->get( 'api.prefix' ); $prefix = $container->get( 'api.prefix' );
@ -2128,43 +2183,43 @@ return array(
); );
}, },
'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string { 'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string {
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s'; return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
}, },
'wcgateway.transaction-url-live' => static function ( ContainerInterface $container ): string { 'wcgateway.transaction-url-live' => static function ( ContainerInterface $container ): string {
return 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s'; return 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
}, },
'wcgateway.transaction-url-provider' => static function ( ContainerInterface $container ): TransactionUrlProvider { 'wcgateway.transaction-url-provider' => static function ( ContainerInterface $container ): TransactionUrlProvider {
$sandbox_url_base = $container->get( 'wcgateway.transaction-url-sandbox' ); $sandbox_url_base = $container->get( 'wcgateway.transaction-url-sandbox' );
$live_url_base = $container->get( 'wcgateway.transaction-url-live' ); $live_url_base = $container->get( 'wcgateway.transaction-url-live' );
return new TransactionUrlProvider( $sandbox_url_base, $live_url_base ); return new TransactionUrlProvider( $sandbox_url_base, $live_url_base );
}, },
'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus { 'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$partner_endpoint = $container->get( 'api.endpoint.partners' ); $partner_endpoint = $container->get( 'api.endpoint.partners' );
return new DCCProductStatus( $settings, $partner_endpoint ); return new DCCProductStatus( $settings, $partner_endpoint );
}, },
'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers { 'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers {
return new MessagesDisclaimers( return new MessagesDisclaimers(
$container->get( 'api.shop.country' ) $container->get( 'api.shop.country' )
); );
}, },
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer { 'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new FundingSourceRenderer( return new FundingSourceRenderer(
$container->get( 'wcgateway.settings' ) $container->get( 'wcgateway.settings' )
); );
}, },
'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper { 'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper(); return new CheckoutHelper();
}, },
'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint { 'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint {
return new PayUponInvoiceOrderEndpoint( return new PayUponInvoiceOrderEndpoint(
$container->get( 'api.host' ), $container->get( 'api.host' ),
$container->get( 'api.bearer' ), $container->get( 'api.bearer' ),
@ -2173,10 +2228,10 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
'wcgateway.pay-upon-invoice-payment-source-factory' => static function ( ContainerInterface $container ): PaymentSourceFactory { 'wcgateway.pay-upon-invoice-payment-source-factory' => static function ( ContainerInterface $container ): PaymentSourceFactory {
return new PaymentSourceFactory(); return new PaymentSourceFactory();
}, },
'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway { 'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway {
return new PayUponInvoiceGateway( return new PayUponInvoiceGateway(
$container->get( 'wcgateway.pay-upon-invoice-order-endpoint' ), $container->get( 'wcgateway.pay-upon-invoice-order-endpoint' ),
$container->get( 'api.factory.purchase-unit' ), $container->get( 'api.factory.purchase-unit' ),
@ -2188,13 +2243,13 @@ return array(
$container->get( 'wcgateway.checkout-helper' ) $container->get( 'wcgateway.checkout-helper' )
); );
}, },
'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId { 'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId {
return new FraudNetSessionId(); return new FraudNetSessionId();
}, },
'wcgateway.pay-upon-invoice-fraudnet-source-website-id' => static function ( ContainerInterface $container ): FraudNetSourceWebsiteId { 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' => static function ( ContainerInterface $container ): FraudNetSourceWebsiteId {
return new FraudNetSourceWebsiteId( $container->get( 'api.merchant_id' ) ); return new FraudNetSourceWebsiteId( $container->get( 'api.merchant_id' ) );
}, },
'wcgateway.pay-upon-invoice-fraudnet' => static function ( ContainerInterface $container ): FraudNet { 'wcgateway.pay-upon-invoice-fraudnet' => static function ( ContainerInterface $container ): FraudNet {
$session_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-session-id' ); $session_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-session-id' );
$source_website_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' ); $source_website_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' );
return new FraudNet( return new FraudNet(
@ -2202,18 +2257,18 @@ return array(
(string) $source_website_id() (string) $source_website_id()
); );
}, },
'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper { 'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper {
return new PayUponInvoiceHelper( return new PayUponInvoiceHelper(
$container->get( 'wcgateway.checkout-helper' ) $container->get( 'wcgateway.checkout-helper' )
); );
}, },
'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus { 'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus {
return new PayUponInvoiceProductStatus( return new PayUponInvoiceProductStatus(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ) $container->get( 'api.endpoint.partners' )
); );
}, },
'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice { 'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice {
return new PayUponInvoice( return new PayUponInvoice(
$container->get( 'wcgateway.url' ), $container->get( 'wcgateway.url' ),
$container->get( 'wcgateway.pay-upon-invoice-fraudnet' ), $container->get( 'wcgateway.pay-upon-invoice-fraudnet' ),
@ -2231,14 +2286,14 @@ return array(
$container->get( 'api.factory.capture' ) $container->get( 'api.factory.capture' )
); );
}, },
'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO { 'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO {
return new OXXO( return new OXXO(
$container->get( 'wcgateway.checkout-helper' ), $container->get( 'wcgateway.checkout-helper' ),
$container->get( 'wcgateway.url' ), $container->get( 'wcgateway.url' ),
$container->get( 'ppcp.asset-version' ) $container->get( 'ppcp.asset-version' )
); );
}, },
'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway { 'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway {
return new OXXOGateway( return new OXXOGateway(
$container->get( 'api.endpoint.order' ), $container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.purchase-unit' ), $container->get( 'api.factory.purchase-unit' ),
@ -2246,7 +2301,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint { 'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint {
return new OXXOEndpoint( return new OXXOEndpoint(
$container->get( 'button.request-data' ), $container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ), $container->get( 'api.endpoint.order' ),
@ -2255,7 +2310,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { 'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
/** /**
@ -2267,7 +2322,7 @@ return array(
); );
}, },
'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool { 'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool {
try { try {
$token = $container->get( 'api.bearer' )->bearer(); $token = $container->get( 'api.bearer' )->bearer();
return $token->vaulting_available(); return $token->vaulting_available();
@ -2276,7 +2331,7 @@ return array(
} }
}, },
'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string { 'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string {
$vaulting_label = __( 'Enable saved cards and subscription features on your store.', 'woocommerce-paypal-payments' ); $vaulting_label = __( 'Enable saved cards and subscription features on your store.', 'woocommerce-paypal-payments' );
if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) { if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) {
@ -2298,7 +2353,7 @@ return array(
return $vaulting_label; return $vaulting_label;
}, },
'wcgateway.settings.fields.pay-later-label' => static function ( ContainerInterface $container ): string { 'wcgateway.settings.fields.pay-later-label' => static function ( ContainerInterface $container ): string {
$pay_later_label = '<span class="ppcp-pay-later-enabled-label">%s</span>'; $pay_later_label = '<span class="ppcp-pay-later-enabled-label">%s</span>';
$pay_later_label .= '<span class="ppcp-pay-later-disabled-label">'; $pay_later_label .= '<span class="ppcp-pay-later-disabled-label">';
$pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); $pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' );
@ -2306,4 +2361,28 @@ return array(
return $pay_later_label; return $pay_later_label;
}, },
'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string {
return $container->get( 'api.shop.is-latin-america' ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC;
},
'wcgateway.settings.card_billing_data_mode' => static function ( ContainerInterface $container ): string {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface );
return $settings->has( 'card_billing_data_mode' ) ?
(string) $settings->get( 'card_billing_data_mode' ) :
$container->get( 'wcgateway.settings.card_billing_data_mode.default' );
},
'wcgateway.settings.allow_card_button_gateway.default' => static function ( ContainerInterface $container ): bool {
return $container->get( 'api.shop.is-latin-america' );
},
'wcgateway.settings.allow_card_button_gateway' => static function ( ContainerInterface $container ): bool {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface );
return $settings->has( 'allow_card_button_gateway' ) ?
(bool) $settings->get( 'allow_card_button_gateway' ) :
$container->get( 'wcgateway.settings.allow_card_button_gateway.default' );
},
); );

View file

@ -0,0 +1,19 @@
<?php
/**
* Possible values of card_billing_data_mode.
*
* @package WooCommerce\PayPalCommerce\WcGateway
*/
declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway;
/**
* Class CardBillingMode
*/
interface CardBillingMode {
public const USE_WC = 'use_wc';
public const MINIMAL_INPUT = 'minimal_input';
public const NO_WC = 'no_wc';
}

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Checkout; namespace WooCommerce\PayPalCommerce\WcGateway\Checkout;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -59,9 +60,10 @@ class DisableGateways {
if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) { if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) {
return $methods; return $methods;
} }
if ( $this->disable_both_gateways() ) { if ( $this->disable_all_gateways() ) {
unset( $methods[ PayPalGateway::ID ] ); unset( $methods[ PayPalGateway::ID ] );
unset( $methods[ CreditCardGateway::ID ] ); unset( $methods[ CreditCardGateway::ID ] );
unset( $methods[ CardButtonGateway::ID ] );
return $methods; return $methods;
} }
@ -77,21 +79,15 @@ class DisableGateways {
return $methods; return $methods;
} }
if ( $this->is_credit_card() ) {
return array(
CreditCardGateway::ID => $methods[ CreditCardGateway::ID ],
PayPalGateway::ID => $methods[ PayPalGateway::ID ],
);
}
return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] ); return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] );
} }
/** /**
* Whether both gateways should be disabled or not. * Whether all gateways should be disabled or not.
* *
* @return bool * @return bool
*/ */
private function disable_both_gateways() : bool { private function disable_all_gateways() : bool {
if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) { if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) {
return true; return true;
} }
@ -110,22 +106,20 @@ class DisableGateways {
* @return bool * @return bool
*/ */
private function needs_to_disable_gateways(): bool { private function needs_to_disable_gateways(): bool {
return $this->session_handler->order() !== null;
}
/**
* Whether the current PayPal session is done via DCC payment.
*
* @return bool
*/
private function is_credit_card(): bool {
$order = $this->session_handler->order(); $order = $this->session_handler->order();
if ( ! $order ) { if ( ! $order ) {
return false; return false;
} }
if ( ! $order->payment_source() || ! $order->payment_source()->card() ) {
return false; $source = $order->payment_source();
if ( $source && $source->card() ) {
return false; // DCC.
} }
if ( 'card' === $this->session_handler->funding_source() ) {
return false; // Card buttons.
}
return true; return true;
} }
} }

View file

@ -52,7 +52,7 @@ class ReturnUrlEndpoint {
/** /**
* Handles the incoming request. * Handles the incoming request.
*/ */
public function handle_request() { public function handle_request(): void {
// phpcs:disable WordPress.Security.NonceVerification.Recommended // phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['token'] ) ) { if ( ! isset( $_GET['token'] ) ) {

View file

@ -0,0 +1,32 @@
<?php
/**
* Wrapper for more detailed gateway error.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Exception
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Exception;
use Exception;
use Throwable;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages;
/**
* Class GatewayGenericException
*/
class GatewayGenericException extends Exception {
/**
* GatewayGenericException constructor.
*
* @param Throwable|null $inner The exception.
*/
public function __construct( ?Throwable $inner = null ) {
parent::__construct(
Messages::generic_payment_error_message(),
$inner ? (int) $inner->getCode() : 0,
$inner
);
}
}

View file

@ -0,0 +1,364 @@
<?php
/**
* The PayPal Card Button Gateway
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
/**
* Class CardButtonGateway
*/
class CardButtonGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait;
const ID = 'ppcp-card-button-gateway';
/**
* The Settings Renderer.
*
* @var SettingsRenderer
*/
protected $settings_renderer;
/**
* The processor for orders.
*
* @var OrderProcessor
*/
protected $order_processor;
/**
* The settings.
*
* @var ContainerInterface
*/
protected $config;
/**
* The Session Handler.
*
* @var SessionHandler
*/
protected $session_handler;
/**
* The Refund Processor.
*
* @var RefundProcessor
*/
private $refund_processor;
/**
* The state.
*
* @var State
*/
protected $state;
/**
* Service able to provide transaction url for an order.
*
* @var TransactionUrlProvider
*/
protected $transaction_url_provider;
/**
* The subscription helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The payment token repository.
*
* @var PaymentTokenRepository
*/
protected $payment_token_repository;
/**
* Whether the plugin is in onboarded state.
*
* @var bool
*/
private $onboarded;
/**
* Whether the gateway should be enabled by default.
*
* @var bool
*/
private $default_enabled;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* CardButtonGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $default_enabled Whether the gateway should be enabled by default.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
SettingsRenderer $settings_renderer,
OrderProcessor $order_processor,
ContainerInterface $config,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
State $state,
TransactionUrlProvider $transaction_url_provider,
SubscriptionHelper $subscription_helper,
bool $default_enabled,
Environment $environment,
PaymentTokenRepository $payment_token_repository,
LoggerInterface $logger
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->order_processor = $order_processor;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->state = $state;
$this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->default_enabled = $default_enabled;
$this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
if ( $this->onboarded ) {
$this->supports = array( 'refunds' );
}
if (
defined( 'PPCP_FLAG_SUBSCRIPTION' )
&& PPCP_FLAG_SUBSCRIPTION
&& $this->gateways_enabled()
&& $this->vault_setting_enabled()
) {
$this->supports = array(
'refunds',
'products',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change',
'subscription_payment_method_change_customer',
'subscription_payment_method_change_admin',
'multiple_subscriptions',
);
}
$this->method_title = __( 'PayPal Card Button', 'woocommerce-paypal-payments' );
$this->method_description = __( 'The separate payment gateway with the card button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' );
$this->title = $this->get_option( 'title', __( 'Debit & Credit Cards', 'woocommerce-paypal-payments' ) );
$this->description = $this->get_option( 'description', '' );
$this->init_form_fields();
$this->init_settings();
add_action(
'woocommerce_update_options_payment_gateways_' . $this->id,
array(
$this,
'process_admin_options',
)
);
}
/**
* Whether the Gateway needs to be setup.
*
* @return bool
*/
public function needs_setup(): bool {
return ! $this->onboarded;
}
/**
* Initializes the form fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal Card Button', 'woocommerce-paypal-payments' ),
'default' => $this->default_enabled ? 'yes' : 'no',
'desc_tip' => true,
'description' => __( 'Enable/Disable the separate payment gateway with the card button.', 'woocommerce-paypal-payments' ),
),
'title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => $this->title,
'desc_tip' => true,
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ),
),
'description' => array(
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => $this->description,
'desc_tip' => true,
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ),
),
'ppcp' => array(
'type' => 'ppcp',
),
);
}
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
$saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING );
if ( $saved_paypal_payment ) {
update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.
*
* If the gateway declares 'refunds' support, this will allow it to refund.
* a passed in amount.
*
* @param int $order_id Order ID.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
* @return boolean True or false based on success, or a WP_Error object.
*/
public function process_refund( $order_id, $amount = null, $reason = '' ) {
$order = wc_get_order( $order_id );
if ( ! is_a( $order, \WC_Order::class ) ) {
return false;
}
return $this->refund_processor->process( $order, (float) $amount, (string) $reason );
}
/**
* Return transaction url for this gateway and given order.
*
* @param \WC_Order $order WC order to get transaction url by.
*
* @return string
*/
public function get_transaction_url( $order ): string {
$this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order );
return parent::get_transaction_url( $order );
}
/**
* Returns the settings renderer.
*
* @return SettingsRenderer
*/
protected function settings_renderer(): SettingsRenderer {
return $this->settings_renderer;
}
}

View file

@ -9,20 +9,30 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -31,7 +41,8 @@ use Psr\Container\ContainerInterface;
*/ */
class CreditCardGateway extends \WC_Payment_Gateway_CC { class CreditCardGateway extends \WC_Payment_Gateway_CC {
use ProcessPaymentTrait; use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait,
GatewaySettingsRendererTrait;
const ID = 'ppcp-credit-card-gateway'; const ID = 'ppcp-credit-card-gateway';
@ -203,15 +214,25 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
Environment $environment, Environment $environment,
PaymentsEndpoint $payments_endpoint PaymentsEndpoint $payments_endpoint
) { ) {
$this->id = self::ID; $this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->order_processor = $order_processor; $this->order_processor = $order_processor;
$this->authorized_payments_processor = $authorized_payments_processor; $this->authorized_payments_processor = $authorized_payments_processor;
$this->settings_renderer = $settings_renderer;
$this->config = $config; $this->config = $config;
$this->module_url = $module_url;
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
$this->refund_processor = $refund_processor; $this->refund_processor = $refund_processor;
$this->state = $state;
$this->transaction_url_provider = $transaction_url_provider;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->environment = $environment; $this->environment = $environment;
$this->payments_endpoint = $payments_endpoint;
if ( $state->current_state() === State::STATE_ONBOARDED ) { if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' ); $this->supports = array( 'refunds' );
@ -261,18 +282,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
'process_admin_options', 'process_admin_options',
) )
); );
$this->module_url = $module_url;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->state = $state;
} }
/** /**
@ -295,20 +304,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
remove_action( 'gettext', 'replace_credit_card_cvv_label' ); remove_action( 'gettext', 'replace_credit_card_cvv_label' );
} }
/**
* Renders the settings.
*
* @return string
*/
public function generate_ppcp_html(): string {
ob_start();
$this->settings_renderer->render();
$content = ob_get_contents();
ob_end_clean();
return $content;
}
/** /**
* Replace WooCommerce credit card field label. * Replace WooCommerce credit card field label.
* *
@ -409,6 +404,158 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
return $this->is_enabled(); return $this->is_enabled();
} }
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
/**
* If customer has chosen a saved credit card payment.
*/
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id );
$tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( 'Saved card token not found.' ) )
);
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
''
);
try {
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
);
$this->add_paypal_meta( $wc_order, $order, $this->environment );
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) )
);
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) )
);
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->is_free_trial_order( $wc_order ) ) {
$this->authorized_payments_processor->void_authorizations( $order );
$wc_order->payment_complete();
} elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
return $this->handle_payment_success( $wc_order );
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
if ( $saved_credit_card ) {
update_post_meta( $order_id, 'payment_token_id', $saved_credit_card );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/** /**
* Process refund. * Process refund.
@ -500,11 +647,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
} }
/** /**
* Returns the environment. * Returns the settings renderer.
* *
* @return Environment * @return SettingsRenderer
*/ */
protected function environment(): Environment { protected function settings_renderer(): SettingsRenderer {
return $this->environment; return $this->settings_renderer;
} }
} }

View file

@ -0,0 +1,37 @@
<?php
/**
* Adds generate_ppcp_html method for rendering settings.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
/**
* Trait GatewaySettingsRendererTrait
*/
trait GatewaySettingsRendererTrait {
/**
* Renders the settings.
*
* @return string
*/
public function generate_ppcp_html(): string {
ob_start();
$this->settings_renderer()->render();
$content = ob_get_contents();
ob_end_clean();
return $content;
}
/**
* Returns the settings renderer.
*
* @return SettingsRenderer
*/
abstract protected function settings_renderer(): SettingsRenderer;
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Common messages.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
/**
* Class Messages
*/
class Messages {
/**
* The generic payment failure message.
*
* @return string
*/
public static function generic_payment_error_message(): string {
return apply_filters(
'woocommerce_paypal_payments_generic_payment_error_message',
__( 'Failed to process the payment. Please try again or contact the shop admin.', 'woocommerce-paypal-payments' )
);
}
}

View file

@ -9,18 +9,21 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
@ -32,7 +35,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
*/ */
class PayPalGateway extends \WC_Payment_Gateway { class PayPalGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait; use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait;
const ID = 'ppcp-gateway'; const ID = 'ppcp-gateway';
const INTENT_META_KEY = '_ppcp_paypal_intent'; const INTENT_META_KEY = '_ppcp_paypal_intent';
@ -63,13 +66,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/ */
protected $order_processor; protected $order_processor;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/** /**
* The settings. * The settings.
* *
@ -119,27 +115,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/ */
protected $payment_token_repository; protected $payment_token_repository;
/**
* The shipping_preference factory.
*
* @var ShippingPreferenceFactory
*/
private $shipping_preference_factory;
/**
* The payments endpoint
*
* @var PaymentsEndpoint
*/
protected $payments_endpoint;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
protected $order_endpoint;
/** /**
* Whether the plugin is in onboarded state. * Whether the plugin is in onboarded state.
* *
@ -178,30 +153,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
/** /**
* PayPalGateway constructor. * PayPalGateway constructor.
* *
* @param SettingsRenderer $settings_renderer The Settings Renderer. * @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer. * @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor. * @param OrderProcessor $order_processor The Order Processor.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor. * @param ContainerInterface $config The settings.
* @param ContainerInterface $config The settings. * @param SessionHandler $session_handler The Session Handler.
* @param SessionHandler $session_handler The Session Handler. * @param RefundProcessor $refund_processor The Refund Processor.
* @param RefundProcessor $refund_processor The Refund Processor. * @param State $state The state.
* @param State $state The state. * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param SubscriptionHelper $subscription_helper The subscription helper. * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. * @param Environment $environment The environment.
* @param Environment $environment The environment. * @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param LoggerInterface $logger The logger.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. * @param string $api_shop_country The api shop country.
* @param LoggerInterface $logger The logger.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param string $api_shop_country The api shop country.
*/ */
public function __construct( public function __construct(
SettingsRenderer $settings_renderer, SettingsRenderer $settings_renderer,
FundingSourceRenderer $funding_source_renderer, FundingSourceRenderer $funding_source_renderer,
OrderProcessor $order_processor, OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorized_payments_processor,
ContainerInterface $config, ContainerInterface $config,
SessionHandler $session_handler, SessionHandler $session_handler,
RefundProcessor $refund_processor, RefundProcessor $refund_processor,
@ -211,37 +181,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $page_id, string $page_id,
Environment $environment, Environment $environment,
PaymentTokenRepository $payment_token_repository, PaymentTokenRepository $payment_token_repository,
ShippingPreferenceFactory $shipping_preference_factory,
LoggerInterface $logger, LoggerInterface $logger,
PaymentsEndpoint $payments_endpoint,
OrderEndpoint $order_endpoint,
string $api_shop_country string $api_shop_country
) { ) {
$this->id = self::ID;
$this->id = self::ID; $this->settings_renderer = $settings_renderer;
$this->order_processor = $order_processor; $this->funding_source_renderer = $funding_source_renderer;
$this->authorized_payments_processor = $authorized_payments_processor; $this->order_processor = $order_processor;
$this->settings_renderer = $settings_renderer; $this->config = $config;
$this->funding_source_renderer = $funding_source_renderer; $this->session_handler = $session_handler;
$this->config = $config; $this->refund_processor = $refund_processor;
$this->session_handler = $session_handler; $this->state = $state;
$this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider;
$this->transaction_url_provider = $transaction_url_provider; $this->subscription_helper = $subscription_helper;
$this->page_id = $page_id; $this->page_id = $page_id;
$this->environment = $environment; $this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED; $this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->id = self::ID; $this->payment_token_repository = $payment_token_repository;
$this->order_processor = $order_processor; $this->logger = $logger;
$this->authorized_payments = $authorized_payments_processor; $this->api_shop_country = $api_shop_country;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->settings_renderer = $settings_renderer;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->page_id = $page_id;
$this->environment = $environment;
$this->logger = $logger;
if ( $this->onboarded ) { if ( $this->onboarded ) {
$this->supports = array( 'refunds' ); $this->supports = array( 'refunds' );
@ -291,13 +249,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
'process_admin_options', 'process_admin_options',
) )
); );
$this->subscription_helper = $subscription_helper;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->order_endpoint = $order_endpoint;
$this->state = $state;
$this->api_shop_country = $api_shop_country;
} }
/** /**
@ -306,7 +257,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @return bool * @return bool
*/ */
public function needs_setup(): bool { public function needs_setup(): bool {
return ! $this->onboarded; return ! $this->onboarded;
} }
@ -334,20 +284,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
} }
} }
/**
* Renders the settings.
*
* @return string
*/
public function generate_ppcp_html(): string {
ob_start();
$this->settings_renderer->render( false );
$content = ob_get_contents();
ob_end_clean();
return $content;
}
/** /**
* Defines the method title. If we are on the credit card tab in the settings, we want to change this. * Defines the method title. If we are on the credit card tab in the settings, we want to change this.
* *
@ -450,6 +386,118 @@ class PayPalGateway extends \WC_Payment_Gateway {
} }
// phpcs:enable WordPress.Security.NonceVerification.Recommended // phpcs:enable WordPress.Security.NonceVerification.Recommended
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
$funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING );
if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
$tokens,
function ( PaymentToken $token ): bool {
return isset( $token->source()->paypal );
}
) ) {
return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) );
}
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
$saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING );
if ( $saved_paypal_payment ) {
update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) {
$wc_order->update_status(
'failed',
__( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? ''
);
$this->session_handler->increment_insufficient_funding_tries();
if ( $this->session_handler->insufficient_funding_tries() >= 3 ) {
return $this->handle_payment_failure(
null,
new Exception(
__( 'Please use a different payment method.', 'woocommerce-paypal-payments' ),
$error->getCode(),
$error
)
);
}
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
return array(
'result' => 'success',
'redirect' => $url,
);
}
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/** /**
* Process refund. * Process refund.
* *
@ -503,11 +551,11 @@ class PayPalGateway extends \WC_Payment_Gateway {
} }
/** /**
* Returns the environment. * Returns the settings renderer.
* *
* @return Environment * @return SettingsRenderer
*/ */
protected function environment(): Environment { protected function settings_renderer(): SettingsRenderer {
return $this->environment; return $this->settings_renderer;
} }
} }

View file

@ -10,279 +10,14 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception; use Exception;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/** /**
* Trait ProcessPaymentTrait * Trait ProcessPaymentTrait
*/ */
trait ProcessPaymentTrait { trait ProcessPaymentTrait {
use OrderMetaTrait, PaymentsStatusHandlingTrait, TransactionIdHandlingTrait, FreeTrialHandlerTrait;
/**
* Process a payment for an WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*
* @throws RuntimeException When processing payment fails.
*/
public function process_payment( $order_id ) {
$failure_data = array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
);
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
wc_add_notice(
__( 'Couldn\'t find order to process', 'woocommerce-paypal-payments' ),
'error'
);
return $failure_data;
}
$payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING );
$funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING );
/**
* If customer has chosen a saved credit card payment.
*/
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( CreditCardGateway::ID === $payment_method && $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id );
$tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
return null;
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
''
);
try {
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
);
$this->add_paypal_meta( $wc_order, $order, $this->environment() );
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
$this->logger->warning( "Unexpected status for order {$order->id()} using a saved credit card: " . $order->status()->name() );
return null;
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
$this->logger->warning( "Could neither capture nor authorize order {$order->id()} using a saved credit card:" . 'Status: ' . $order->status()->name() . ' Intent: ' . $order->intent() );
return null;
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->is_free_trial_order( $wc_order ) ) {
$this->authorized_payments_processor->void_authorizations( $order );
$wc_order->payment_complete();
} elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
} catch ( RuntimeException $error ) {
$this->handle_failure( $wc_order, $error );
return null;
}
}
if ( PayPalGateway::ID === $payment_method && 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
$tokens,
function ( PaymentToken $token ): bool {
return isset( $token->source()->paypal );
}
) ) {
$this->handle_failure( $wc_order, new Exception( 'No saved PayPal account.' ) );
return null;
}
$wc_order->payment_complete();
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
if ( 'ppcp-credit-card-gateway' === $this->id && $saved_credit_card ) {
update_post_meta( $order_id, 'payment_token_id', $saved_credit_card );
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
$saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING );
if ( 'ppcp-gateway' === $this->id && $saved_paypal_payment ) {
update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment );
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
}
/**
* If the WC_Order is payed through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( $this->order_processor->process( $wc_order ) ) {
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
as_schedule_single_action(
time() + ( 1 * MINUTE_IN_SECONDS ),
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $order_id,
'customer_id' => $wc_order->get_customer_id(),
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
WC()->cart->empty_cart();
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
} catch ( PayPalApiException $error ) {
if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) {
$wc_order->update_status(
'failed',
__( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? ''
);
$this->session_handler->increment_insufficient_funding_tries();
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
if ( $this->session_handler->insufficient_funding_tries() >= 3 ) {
$this->session_handler->destroy_session_data();
wc_add_notice(
__( 'Please use a different payment method.', 'woocommerce-paypal-payments' ),
'error'
);
return $failure_data;
}
return array(
'result' => 'success',
'redirect' => $url,
);
}
$error_message = $error->getMessage();
if ( $error->issues() ) {
$error_message = implode(
array_map(
function( $issue ) {
return $issue->issue . ' ' . $issue->description . '<br/>';
},
$error->issues()
)
);
}
wc_add_notice( $error_message, 'error' );
$this->session_handler->destroy_session_data();
} catch ( RuntimeException $error ) {
$this->handle_failure( $wc_order, $error );
return $failure_data;
}
wc_add_notice(
$this->order_processor->last_error(),
'error'
);
$wc_order->update_status(
'failed',
__( 'Could not process order. ', 'woocommerce-paypal-payments' ) . $this->order_processor->last_error()
);
return $failure_data;
}
/** /**
* Checks if PayPal or Credit Card gateways are enabled. * Checks if PayPal or Credit Card gateways are enabled.
* *
@ -311,29 +46,86 @@ trait ProcessPaymentTrait {
return false; return false;
} }
/**
* Scheduled the vaulted payment check.
*
* @param int $wc_order_id The WC order ID.
* @param int $customer_id The customer ID.
*/
protected function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void {
as_schedule_single_action(
time() + ( 1 * MINUTE_IN_SECONDS ),
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $wc_order_id,
'customer_id' => $customer_id,
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
/** /**
* Handles the payment failure. * Handles the payment failure.
* *
* @param \WC_Order $wc_order The order. * @param WC_Order|null $wc_order The order.
* @param Exception $error The error causing the failure. * @param Exception $error The error causing the failure.
* @return array The data that can be returned by the gateway process_payment method.
*/ */
protected function handle_failure( \WC_Order $wc_order, Exception $error ): void { protected function handle_payment_failure( ?WC_Order $wc_order, Exception $error ): array {
$this->logger->error( 'Payment failed: ' . $error->getMessage() ); $this->logger->error( 'Payment failed: ' . $this->format_exception( $error ) );
$wc_order->update_status( if ( $wc_order ) {
'failed', $wc_order->update_status(
__( 'Could not process order. ', 'woocommerce-paypal-payments' ) . $error->getMessage() 'failed',
); $this->format_exception( $error )
);
}
$this->session_handler->destroy_session_data(); $this->session_handler->destroy_session_data();
wc_add_notice( $error->getMessage(), 'error' ); wc_add_notice( $error->getMessage(), 'error' );
return array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
);
} }
/** /**
* Returns the environment. * Handles the payment completion.
* *
* @return Environment * @param WC_Order|null $wc_order The order.
* @param string|null $url The redirect URL.
* @return array The data that can be returned by the gateway process_payment method.
*/ */
abstract protected function environment(): Environment; protected function handle_payment_success( ?WC_Order $wc_order, string $url = null ): array {
if ( ! $url ) {
$url = $this->get_return_url( $wc_order );
}
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $url,
);
}
/**
* Outputs the exception, including the inner exception.
*
* @param Throwable $exception The exception to format.
* @return string
*/
protected function format_exception( Throwable $exception ): string {
$output = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine();
$prev = $exception->getPrevious();
if ( ! $prev ) {
return $output;
}
if ( $exception instanceof GatewayGenericException ) {
$output = '';
}
return $output . ' ' . $this->format_exception( $prev );
}
} }

View file

@ -98,6 +98,9 @@ class CheckoutHelper {
if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) { if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) {
return false; return false;
} }
if ( $date_time < strtotime( '-100 years', time() ) ) {
return false;
}
return true; return true;
} }

View file

@ -1,6 +1,6 @@
<?php <?php
/** /**
* Creates the admin message about the DCC gateway being enabled without the PayPal gateway. * Creates the admin message about the gateway being enabled without the PayPal gateway.
* *
* @package WooCommerce\PayPalCommerce\WcGateway\Notice * @package WooCommerce\PayPalCommerce\WcGateway\Notice
*/ */
@ -9,14 +9,21 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Notice; namespace WooCommerce\PayPalCommerce\WcGateway\Notice;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/** /**
* Creates the admin message about the DCC gateway being enabled without the PayPal gateway. * Creates the admin message about the gateway being enabled without the PayPal gateway.
*/ */
class DccWithoutPayPalAdminNotice { class GatewayWithoutPayPalAdminNotice {
/**
* The gateway ID.
*
* @var string
*/
private $id;
/** /**
* The state. * The state.
@ -49,17 +56,20 @@ class DccWithoutPayPalAdminNotice {
/** /**
* ConnectAdminNotice constructor. * ConnectAdminNotice constructor.
* *
* @param string $id The gateway ID.
* @param State $state The state. * @param State $state The state.
* @param ContainerInterface $settings The settings. * @param ContainerInterface $settings The settings.
* @param bool $is_payments_page Whether the current page is the WC payment page. * @param bool $is_payments_page Whether the current page is the WC payment page.
* @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page. * @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
*/ */
public function __construct( public function __construct(
string $id,
State $state, State $state,
ContainerInterface $settings, ContainerInterface $settings,
bool $is_payments_page, bool $is_payments_page,
bool $is_ppcp_settings_page bool $is_ppcp_settings_page
) { ) {
$this->id = $id;
$this->state = $state; $this->state = $state;
$this->settings = $settings; $this->settings = $settings;
$this->is_payments_page = $is_payments_page; $this->is_payments_page = $is_payments_page;
@ -76,12 +86,20 @@ class DccWithoutPayPalAdminNotice {
return null; return null;
} }
$gateway = $this->get_gateway();
if ( ! $gateway ) {
return null;
}
$name = $gateway->get_method_title();
$message = sprintf( $message = sprintf(
/* translators: %1$s the gateway name. */ /* translators: %1$s the gateway name, %2$s URL. */
__( __(
'PayPal Card Processing cannot be used without the PayPal gateway. <a href="%1$s">Enable the PayPal Gateway</a>.', '%1$s cannot be used without the PayPal gateway. <a href="%2$s">Enable the PayPal gateway</a>.',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
$name,
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ) admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' )
); );
return new Message( $message, 'warning' ); return new Message( $message, 'warning' );
@ -93,9 +111,29 @@ class DccWithoutPayPalAdminNotice {
* @return bool * @return bool
*/ */
protected function should_display(): bool { protected function should_display(): bool {
return State::STATE_ONBOARDED === $this->state->current_state() if ( State::STATE_ONBOARDED !== $this->state->current_state() ||
&& ( $this->is_payments_page || $this->is_ppcp_settings_page ) ( ! $this->is_payments_page && ! $this->is_ppcp_settings_page ) ) {
&& ( $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) return false;
&& ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ); }
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
return false;
}
$gateway = $this->get_gateway();
return $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
}
/**
* Returns the gateway object or null.
*
* @return WC_Payment_Gateway|null
*/
protected function get_gateway(): ?WC_Payment_Gateway {
$gateways = WC()->payment_gateways->payment_gateways();
if ( ! isset( $gateways[ $this->id ] ) ) {
return null;
}
return $gateways[ $this->id ];
} }
} }

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings; namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
@ -34,6 +35,7 @@ trait PageMatcherTrait {
$gateway_page_id_map = array( $gateway_page_id_map = array(
PayPalGateway::ID => 'paypal', PayPalGateway::ID => 'paypal',
CreditCardGateway::ID => 'dcc', // TODO: consider using just the gateway ID for PayPal and DCC too. CreditCardGateway::ID => 'dcc', // TODO: consider using just the gateway ID for PayPal and DCC too.
CardButtonGateway::ID => CardButtonGateway::ID,
WebhooksStatusPage::ID => WebhooksStatusPage::ID, WebhooksStatusPage::ID => WebhooksStatusPage::ID,
); );
return array_key_exists( $current_page_id, $gateway_page_id_map ) return array_key_exists( $current_page_id, $gateway_page_id_map )

View file

@ -9,6 +9,7 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Settings; namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
@ -58,7 +59,7 @@ class SectionsRenderer {
/** /**
* Renders the Sections tab. * Renders the Sections tab.
*/ */
public function render() { public function render(): void {
if ( ! $this->should_render() ) { if ( ! $this->should_render() ) {
return; return;
} }
@ -66,6 +67,7 @@ class SectionsRenderer {
$sections = array( $sections = array(
PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ),
CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ),
); );
@ -80,8 +82,8 @@ class SectionsRenderer {
foreach ( $sections as $id => $label ) { foreach ( $sections as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id );
if ( PayUponInvoiceGateway::ID === $id ) { if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-pay-upon-invoice-gateway' ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $id );
} }
echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>'; echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
} }

View file

@ -29,7 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\DccWithoutPayPalAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -164,11 +164,15 @@ class WCGatewayModule implements ModuleInterface {
$notices[] = $connect_message; $notices[] = $connect_message;
} }
$dcc_without_paypal_notice = $c->get( 'wcgateway.notice.dcc-without-paypal' ); foreach ( array(
assert( $dcc_without_paypal_notice instanceof DccWithoutPayPalAdminNotice ); $c->get( 'wcgateway.notice.dcc-without-paypal' ),
$dcc_without_paypal_message = $dcc_without_paypal_notice->message(); $c->get( 'wcgateway.notice.card-button-without-paypal' ),
if ( $dcc_without_paypal_message ) { ) as $gateway_without_paypal_notice ) {
$notices[] = $dcc_without_paypal_message; assert( $gateway_without_paypal_notice instanceof GatewayWithoutPayPalAdminNotice );
$message = $gateway_without_paypal_notice->message();
if ( $message ) {
$notices[] = $message;
}
} }
$authorize_order_action = $c->get( 'wcgateway.notice.authorize-order-action' ); $authorize_order_action = $c->get( 'wcgateway.notice.authorize-order-action' );
@ -294,6 +298,10 @@ class WCGatewayModule implements ModuleInterface {
$methods[] = $container->get( 'wcgateway.credit-card-gateway' ); $methods[] = $container->get( 'wcgateway.credit-card-gateway' );
} }
if ( $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) {
$methods[] = $container->get( 'wcgateway.card-button-gateway' );
}
if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) {
$methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' );
} }

View file

@ -46,7 +46,7 @@ class PaymentCaptureRefunded implements RequestHandler {
* @return string[] * @return string[]
*/ */
public function event_types(): array { public function event_types(): array {
return array( 'PAYMENT.CAPTURE.REFUNDED' ); return array( 'PAYMENT.CAPTURE.REFUNDED', 'PAYMENT.AUTHORIZATION.VOIDED' );
} }
/** /**

View file

@ -46,6 +46,7 @@ class IdentityTokenTest extends TestCase
public function testGenerateForCustomerReturnsToken() public function testGenerateForCustomerReturnsToken()
{ {
$id = 1;
define( 'PPCP_FLAG_SUBSCRIPTION', true ); define( 'PPCP_FLAG_SUBSCRIPTION', true );
$token = Mockery::mock(Token::class); $token = Mockery::mock(Token::class);
$token $token
@ -60,6 +61,7 @@ class IdentityTokenTest extends TestCase
$this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true);
$this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1'); $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1');
expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1');
$rawResponse = [ $rawResponse = [
'body' => '{"client_token":"abc123", "expires_in":3600}', 'body' => '{"client_token":"abc123", "expires_in":3600}',
@ -97,6 +99,7 @@ class IdentityTokenTest extends TestCase
public function testGenerateForCustomerFailsBecauseWpError() public function testGenerateForCustomerFailsBecauseWpError()
{ {
$id = 1;
$token = Mockery::mock(Token::class); $token = Mockery::mock(Token::class);
$token $token
->expects('token')->andReturn('bearer'); ->expects('token')->andReturn('bearer');
@ -111,7 +114,8 @@ class IdentityTokenTest extends TestCase
$this->logger->shouldReceive('debug'); $this->logger->shouldReceive('debug');
$this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true);
$this->customer_repository->shouldReceive('customer_id_for_user'); $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1');
expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1');
$this->expectException(RuntimeException::class); $this->expectException(RuntimeException::class);
$this->sut->generate_for_user(1); $this->sut->generate_for_user(1);
@ -119,6 +123,7 @@ class IdentityTokenTest extends TestCase
public function testGenerateForCustomerFailsBecauseResponseCodeIsNot200() public function testGenerateForCustomerFailsBecauseResponseCodeIsNot200()
{ {
$id = 1;
$token = Mockery::mock(Token::class); $token = Mockery::mock(Token::class);
$token $token
->expects('token')->andReturn('bearer'); ->expects('token')->andReturn('bearer');
@ -137,7 +142,8 @@ class IdentityTokenTest extends TestCase
$this->logger->shouldReceive('debug'); $this->logger->shouldReceive('debug');
$this->settings->shouldReceive('has')->andReturn(true); $this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true); $this->settings->shouldReceive('get')->andReturn(true);
$this->customer_repository->shouldReceive('customer_id_for_user'); $this->customer_repository->shouldReceive('customer_id_for_user')->andReturn('prefix1');
expect('update_user_meta')->with($id, 'ppcp_customer_id', 'prefix1');
$this->expectException(PayPalApiException::class); $this->expectException(PayPalApiException::class);
$this->sut->generate_for_user(1); $this->sut->generate_for_user(1);

View file

@ -1046,8 +1046,6 @@ class OrderEndpointTest extends TestCase
$payer = Mockery::mock(Payer::class); $payer = Mockery::mock(Payer::class);
$payer->expects('email_address')->andReturn('email@email.com'); $payer->expects('email_address')->andReturn('email@email.com');
$payerName = Mockery::mock(PayerName::class);
$payer->expects('name')->andReturn($payerName);
$payer->expects('to_array')->andReturn(['payer']); $payer->expects('to_array')->andReturn(['payer']);
$result = $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); $result = $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer);
$this->assertEquals($expectedOrder, $result); $this->assertEquals($expectedOrder, $result);
@ -1138,8 +1136,6 @@ class OrderEndpointTest extends TestCase
$payer = Mockery::mock(Payer::class); $payer = Mockery::mock(Payer::class);
$payer->expects('email_address')->andReturn('email@email.com'); $payer->expects('email_address')->andReturn('email@email.com');
$payerName = Mockery::mock(PayerName::class);
$payer->expects('name')->andReturn($payerName);
$payer->expects('to_array')->andReturn(['payer']); $payer->expects('to_array')->andReturn(['payer']);
$testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, $payer); $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, $payer);
} }
@ -1229,8 +1225,6 @@ class OrderEndpointTest extends TestCase
$this->expectException(RuntimeException::class); $this->expectException(RuntimeException::class);
$payer = Mockery::mock(Payer::class); $payer = Mockery::mock(Payer::class);
$payer->expects('email_address')->andReturn('email@email.com'); $payer->expects('email_address')->andReturn('email@email.com');
$payerName = Mockery::mock(PayerName::class);
$payer->expects('name')->andReturn($payerName);
$payer->expects('to_array')->andReturn(['payer']); $payer->expects('to_array')->andReturn(['payer']);
$testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer); $testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer);
} }

View file

@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger; use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
@ -152,6 +153,7 @@ class CreateOrderEndpointTest extends TestCase
$session_handler = Mockery::mock(SessionHandler::class); $session_handler = Mockery::mock(SessionHandler::class);
$settings = Mockery::mock(Settings::class); $settings = Mockery::mock(Settings::class);
$early_order_handler = Mockery::mock(EarlyOrderHandler::class); $early_order_handler = Mockery::mock(EarlyOrderHandler::class);
$settings->shouldReceive('has')->andReturnFalse();
$testee = new CreateOrderEndpoint( $testee = new CreateOrderEndpoint(
$request_data, $request_data,
@ -163,6 +165,7 @@ class CreateOrderEndpointTest extends TestCase
$settings, $settings,
$early_order_handler, $early_order_handler,
false, false,
CardBillingMode::MINIMAL_INPUT,
new NullLogger() new NullLogger()
); );
return array($payer_factory, $testee); return array($payer_factory, $testee);

View file

@ -23,6 +23,8 @@ use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
class RenewalHandlerTest extends TestCase class RenewalHandlerTest extends TestCase
{ {
@ -48,6 +50,11 @@ class RenewalHandlerTest extends TestCase
$this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); $this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class);
$this->payerFactory = Mockery::mock(PayerFactory::class); $this->payerFactory = Mockery::mock(PayerFactory::class);
$this->environment = new Environment(new Dictionary([])); $this->environment = new Environment(new Dictionary([]));
$authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$settings = Mockery::mock(Settings::class);
$settings
->shouldReceive('has')
->andReturnFalse();
$this->logger->shouldReceive('error')->andReturnUsing(function ($msg) { $this->logger->shouldReceive('error')->andReturnUsing(function ($msg) {
throw new Exception($msg); throw new Exception($msg);
@ -61,7 +68,9 @@ class RenewalHandlerTest extends TestCase
$this->purchaseUnitFactory, $this->purchaseUnitFactory,
$this->shippingPreferenceFactory, $this->shippingPreferenceFactory,
$this->payerFactory, $this->payerFactory,
$this->environment $this->environment,
$settings,
$authorizedPaymentProcessor
); );
} }

View file

@ -3,14 +3,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Psr\Log\NullLogger;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -37,7 +30,6 @@ class WcGatewayTest extends TestCase
private $settingsRenderer; private $settingsRenderer;
private $funding_source_renderer; private $funding_source_renderer;
private $orderProcessor; private $orderProcessor;
private $authorizedOrdersProcessor;
private $settings; private $settings;
private $refundProcessor; private $refundProcessor;
private $onboardingState; private $onboardingState;
@ -45,10 +37,7 @@ class WcGatewayTest extends TestCase
private $subscriptionHelper; private $subscriptionHelper;
private $environment; private $environment;
private $paymentTokenRepository; private $paymentTokenRepository;
private $shipping_preference_factory;
private $logger; private $logger;
private $paymentsEndpoint;
private $orderEndpoint;
private $apiShopCountry; private $apiShopCountry;
public function setUp(): void { public function setUp(): void {
@ -60,7 +49,6 @@ class WcGatewayTest extends TestCase
$this->settingsRenderer = Mockery::mock(SettingsRenderer::class); $this->settingsRenderer = Mockery::mock(SettingsRenderer::class);
$this->orderProcessor = Mockery::mock(OrderProcessor::class); $this->orderProcessor = Mockery::mock(OrderProcessor::class);
$this->authorizedOrdersProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$this->settings = Mockery::mock(Settings::class); $this->settings = Mockery::mock(Settings::class);
$this->sessionHandler = Mockery::mock(SessionHandler::class); $this->sessionHandler = Mockery::mock(SessionHandler::class);
$this->refundProcessor = Mockery::mock(RefundProcessor::class); $this->refundProcessor = Mockery::mock(RefundProcessor::class);
@ -69,10 +57,7 @@ class WcGatewayTest extends TestCase
$this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class); $this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class);
$this->environment = Mockery::mock(Environment::class); $this->environment = Mockery::mock(Environment::class);
$this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class); $this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class);
$this->shipping_preference_factory = Mockery::mock(ShippingPreferenceFactory::class);
$this->logger = Mockery::mock(LoggerInterface::class); $this->logger = Mockery::mock(LoggerInterface::class);
$this->paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class);
$this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->funding_source_renderer = new FundingSourceRenderer($this->settings); $this->funding_source_renderer = new FundingSourceRenderer($this->settings);
$this->apiShopCountry = 'DE'; $this->apiShopCountry = 'DE';
@ -87,6 +72,7 @@ class WcGatewayTest extends TestCase
$this->settings->shouldReceive('has')->andReturnFalse(); $this->settings->shouldReceive('has')->andReturnFalse();
$this->logger->shouldReceive('info'); $this->logger->shouldReceive('info');
$this->logger->shouldReceive('error');
} }
private function createGateway() private function createGateway()
@ -95,7 +81,6 @@ class WcGatewayTest extends TestCase
$this->settingsRenderer, $this->settingsRenderer,
$this->funding_source_renderer, $this->funding_source_renderer,
$this->orderProcessor, $this->orderProcessor,
$this->authorizedOrdersProcessor,
$this->settings, $this->settings,
$this->sessionHandler, $this->sessionHandler,
$this->refundProcessor, $this->refundProcessor,
@ -105,10 +90,7 @@ class WcGatewayTest extends TestCase
PayPalGateway::ID, PayPalGateway::ID,
$this->environment, $this->environment,
$this->paymentTokenRepository, $this->paymentTokenRepository,
$this->shipping_preference_factory,
$this->logger, $this->logger,
$this->paymentsEndpoint,
$this->orderEndpoint,
$this->apiShopCountry $this->apiShopCountry
); );
} }
@ -173,8 +155,10 @@ class WcGatewayTest extends TestCase
when('wc_get_checkout_url') when('wc_get_checkout_url')
->justReturn($redirectUrl); ->justReturn($redirectUrl);
expect('wc_add_notice') $this->sessionHandler
->with('Couldn\'t find order to process','error'); ->shouldReceive('destroy_session_data');
expect('wc_add_notice');
$this->assertEquals( $this->assertEquals(
[ [
@ -195,7 +179,6 @@ class WcGatewayTest extends TestCase
->andReturnFalse(); ->andReturnFalse();
$this->orderProcessor $this->orderProcessor
->expects('last_error') ->expects('last_error')
->twice()
->andReturn($lastError); ->andReturn($lastError);
$this->subscriptionHelper->shouldReceive('has_subscription')->with($orderId)->andReturn(true); $this->subscriptionHelper->shouldReceive('has_subscription')->with($orderId)->andReturn(true);
$this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true); $this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true);
@ -206,6 +189,8 @@ class WcGatewayTest extends TestCase
expect('wc_get_order') expect('wc_get_order')
->with($orderId) ->with($orderId)
->andReturn($wcOrder); ->andReturn($wcOrder);
$this->sessionHandler
->shouldReceive('destroy_session_data');
expect('wc_add_notice') expect('wc_add_notice')
->with($lastError, 'error'); ->with($lastError, 'error');

View file

@ -27,6 +27,7 @@ class PayUponInvoiceHelperTest extends TestCase
['1942-02-31', false], ['1942-02-31', false],
['01-01-1942', false], ['01-01-1942', false],
['1942-01-01', true], ['1942-01-01', true],
['0001-01-01', false],
]; ];
} }

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
class WC_Payment_Gateway class WC_Payment_Gateway
{ {
protected function get_option(string $key) : string { public function get_option(string $key, $empty_value = null) {
return $key; return $key;
} }
@ -19,4 +19,4 @@ class WC_Payment_Gateway
public function process_admin_options() { public function process_admin_options() {
} }
} }