Merge remote-tracking branch 'origin/trunk' into PCP-155-tracking-api

# Conflicts:
#	modules/ppcp-wc-gateway/services.php
#	modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php
#	tests/PHPUnit/WcGateway/Helper/PayUponInvoiceHelperTest.php
This commit is contained in:
Narek Zakarian 2022-08-17 17:11:20 +04:00
commit 9b63ab0b91
81 changed files with 3707 additions and 1031 deletions

View file

@ -14,3 +14,6 @@ trim_trailing_whitespace = false
[*.{js,json,yml}] [*.{js,json,yml}]
indent_style = space indent_style = space
[*.yml]
indent_size = 2

57
.github/workflows/package.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Build package
on:
workflow_dispatch:
inputs:
packageVersion:
description: 'Package version'
required: false
type: string
filePrefix:
description: 'File prefix'
required: false
type: string
jobs:
package:
runs-on: ubuntu-latest
env:
PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }}
FILENAME: woocommerce-paypal-payments
name: Build package
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.1
tools: composer:v1
- name: Fix plugin version input # Add the version number if only suffix entered
run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV
if: env.PACKAGE_VERSION && !contains(env.PACKAGE_VERSION, '.')
- name: Set plugin version header
run: 'sed -Ei "s/Version: .*/Version: ${PACKAGE_VERSION}/g" woocommerce-paypal-payments.php'
if: env.PACKAGE_VERSION
- name: Build
run: yarn build
- name: Unzip # GH currently always zips, so if we upload a zip we get a zip inside a zip
run: unzip woocommerce-paypal-payments.zip -d dist
- name: Set file name
env:
FILE_PREFIX: ${{ github.event.inputs.filePrefix }}
run: echo "FILENAME=$FILE_PREFIX-$FILENAME" >> $GITHUB_ENV
if: github.event.inputs.filePrefix
- name: Upload
uses: actions/upload-artifact@v3
with:
name: ${{ env.FILENAME }}
path: dist/

View file

@ -1,13 +1,27 @@
*** Changelog *** *** Changelog ***
= 1.9.1 - TBD = = 1.9.2 - 2022-08-09 =
* Fix - Do not allow birth date older than 100 years for PUI. #743
* Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698
* Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626
* Fix - Voiding authorization at PayPal did not update the status/order notes. #712
* Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750
* Fix - Do not show links for unavailable gateways settings pages. #753
* Fix - The smart buttons were not loaded on single product page if a subscription product exists in the cart. #703
* Fix - DCC was causing other gateways to disappear after checkout validation error. #757
* Fix - Buttons not loading on single product page with default settings when product is in cart. #777
* Enhancement - Improve Checkout Field Validation Message. #739
* Enhancement - Handle PAYER_ACTION_REQUIRED error. #759
= 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
* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714 * 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 - 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

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint; namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
@ -28,6 +29,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WP_Error;
/** /**
* Class OrderEndpoint * Class OrderEndpoint
@ -192,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 ) {
@ -564,4 +566,49 @@ class OrderEndpoint {
$new_order = $this->order( $order_to_update->id() ); $new_order = $this->order( $order_to_update->id() );
return $new_order; return $new_order;
} }
/**
* Confirms payment source.
*
* @param string $id The PayPal order ID.
* @param array $payment_source The payment source.
* @return stdClass
* @throws PayPalApiException If the request fails.
* @throws RuntimeException If something unexpected happens.
*/
public function confirm_payment_source( string $id, array $payment_source ): stdClass {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id . '/confirm-payment-source';
$data = array(
'payment_source' => $payment_source,
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
'application_context' => array(
'locale' => 'es-MX',
),
);
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
),
'body' => wp_json_encode( $data ),
);
$response = $this->request( $url, $args );
if ( $response instanceof WP_Error ) {
throw new RuntimeException( $response->get_error_message() );
}
$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 200 !== $status_code ) {
throw new PayPalApiException( $json, $status_code );
}
return $json;
}
} }

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();
} }
@ -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

@ -1,6 +1,8 @@
export const PaymentMethods = { export const PaymentMethods = {
PAYPAL: 'ppcp-gateway', PAYPAL: 'ppcp-gateway',
CARDS: 'ppcp-credit-card-gateway', CARDS: 'ppcp-credit-card-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,96 @@
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,
hasEnabledSeparateGateways
);
} 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,
hasEnabledSeparateGateways,
fundingSource
);
}
}
this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig);
for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) {
this.renderButtons(
data.wrapper,
data.style,
contextConfig,
hasEnabledSeparateGateways,
fundingSource
);
}
} }
renderButtons(wrapper, contextConfig) { renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper) || 'undefined' === typeof paypal.Buttons ) { if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) || '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, hasEnabledSeparateGateways) {
return document.querySelector(wrapper).hasChildNodes(); // Simply check that has child nodes when we do not need to render buttons separately,
// this will reduce the risk of breaking with different themes/plugins
// and on the cart page (where we also do not need to render separately), which may fully reload this part of the page.
// Ideally we should also find a way to detect such full reloads and remove the corresponding keys from the set.
if (!hasEnabledSeparateGateways) {
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,16 +423,23 @@ class SmartButton implements SmartButtonInterface {
) { ) {
add_action( add_action(
$this->single_product_renderer_hook(), $this->single_product_renderer_hook(),
array( function () {
$this, $product = wc_get_product();
'button_renderer',
), if (
is_a( $product, WC_Product::class )
&& ! $this->product_supports_payment( $product )
) {
return;
}
$this->button_renderer( PayPalGateway::ID );
},
31 31
); );
} }
add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 );
$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' );
if ( if (
@ -457,21 +466,38 @@ class SmartButton implements SmartButtonInterface {
); );
} }
add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && if ( isset( $available_gateways['ppcp-gateway'] ) ) {
! $this->settings->get( 'button_cart_enabled' ); add_action(
add_action( $this->pay_order_renderer_hook(),
$this->proceed_to_checkout_button_renderer_hook(), function (): void {
function() use ( $not_enabled_on_cart ) { $this->button_renderer( PayPalGateway::ID );
if ( ! is_cart() || $not_enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() ) { $this->button_renderer( CardButtonGateway::ID );
return;
} }
);
add_action(
$this->checkout_button_renderer_hook(),
function (): void {
$this->button_renderer( PayPalGateway::ID );
$this->button_renderer( CardButtonGateway::ID );
}
);
$this->button_renderer(); $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) &&
}, ! $this->settings->get( 'button_cart_enabled' );
20 add_action(
); $this->proceed_to_checkout_button_renderer_hook(),
function() use ( $not_enabled_on_cart ) {
if ( ! is_cart() || $not_enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() ) {
return;
}
$this->button_renderer( PayPalGateway::ID );
},
20
);
}
return true; return true;
} }
@ -524,32 +550,24 @@ 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;
} }
$product = wc_get_product();
if (
! is_checkout() && is_a( $product, WC_Product::class )
&& ! $this->product_supports_payment( $product )
) {
return;
}
$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 +828,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 +848,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 +874,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' ),
@ -870,10 +912,10 @@ class SmartButton implements SmartButtonInterface {
); );
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
unset( $localize['button']['mini_cart_style']['tagline'] ); $localize['button']['mini_cart_style']['tagline'] = false;
} }
if ( $this->style_for_context( 'layout', $this->context() ) !== 'horizontal' ) { if ( $this->style_for_context( 'layout', $this->context() ) !== 'horizontal' ) {
unset( $localize['button']['style']['tagline'] ); $localize['button']['style']['tagline'] = false;
} }
$this->request_data->dequeue_nonce_fix(); $this->request_data->dequeue_nonce_fix();
@ -901,7 +943,9 @@ class SmartButton implements SmartButtonInterface {
* @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting was not found. * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting was not found.
*/ */
private function url(): string { private function url(): string {
$intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture';
$product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent;
$other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent;
$params = array( $params = array(
'client-id' => $this->client_id, 'client-id' => $this->client_id,
@ -910,9 +954,7 @@ class SmartButton implements SmartButtonInterface {
'components' => implode( ',', $this->components() ), 'components' => implode( ',', $this->components() ),
'vault' => $this->can_save_vault_token() ? 'true' : 'false', 'vault' => $this->can_save_vault_token() ? 'true' : 'false',
'commit' => is_checkout() ? 'true' : 'false', 'commit' => is_checkout() ? 'true' : 'false',
'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) 'intent' => $this->context() === 'product' ? $product_intent : $other_context_intent,
? 'authorize'
: $intent,
); );
if ( if (
$this->environment->current_environment_is( Environment::SANDBOX ) $this->environment->current_environment_is( Environment::SANDBOX )
@ -933,7 +975,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 ] );
@ -942,7 +987,7 @@ class SmartButton implements SmartButtonInterface {
if ( $this->is_free_trial_cart() ) { if ( $this->is_free_trial_cart() ) {
$all_sources = array_keys( $this->all_funding_sources ); $all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled ) { if ( $is_dcc_enabled || $is_separate_card_enabled ) {
$all_sources = array_diff( $all_sources, array( 'card' ) ); $all_sources = array_diff( $all_sources, array( 'card' ) );
} }
$disable_funding = $all_sources; $disable_funding = $all_sources;
@ -1018,6 +1063,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()
@ -1049,9 +1095,10 @@ class SmartButton implements SmartButtonInterface {
} }
if ( if (
$this->context() === 'product' $this->context() === 'product'
&& $this->settings->has( 'button_product_enabled' ) && (
&& $this->settings->get( 'button_product_enabled' ) ( $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ) ) ||
|| $this->settings->has( 'message_product_enabled' ) ( $this->settings->has( 'message_product_enabled' ) && $this->settings->get( 'message_product_enabled' ) )
)
) { ) {
$load_buttons = true; $load_buttons = true;
} }
@ -1061,14 +1108,17 @@ class SmartButton implements SmartButtonInterface {
) { ) {
$load_buttons = true; $load_buttons = true;
} }
if ( if (
$this->context() === 'cart' $this->context() === 'cart'
&& $this->settings->has( 'button_cart_enabled' ) && (
&& $this->settings->get( 'button_cart_enabled' ) ( $this->settings->has( 'button_cart_enabled' ) && $this->settings->get( 'button_cart_enabled' ) ) ||
|| $this->settings->has( 'message_product_enabled' ) ( $this->settings->has( 'message_cart_enabled' ) && $this->settings->get( 'message_cart_enabled' ) )
)
) { ) {
$load_buttons = true; $load_buttons = true;
} }
if ( $this->context() === 'pay-now' ) { if ( $this->context() === 'pay-now' ) {
$load_buttons = true; $load_buttons = true;
} }
@ -1112,6 +1162,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

@ -873,6 +873,46 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@types/eslint-scope@^3.7.0": "@types/eslint-scope@^3.7.0":
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e"
@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.4.1: acorn@^8.4.1, acorn@^8.5.0:
version "8.5.0" version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
ajv-keywords@^3.5.2: ajv-keywords@^3.5.2:
version "3.5.2" version "3.5.2"
@ -1313,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"
@ -2017,9 +2062,9 @@ signal-exit@^3.0.3:
integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==
source-map-support@~0.5.20: source-map-support@~0.5.20:
version "0.5.20" version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies: dependencies:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
@ -2034,11 +2079,6 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
strip-final-newline@^2.0.0: strip-final-newline@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
@ -2076,12 +2116,13 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2" terser "^5.7.2"
terser@^5.7.2: terser@^5.7.2:
version "5.9.0" version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0" commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.20" source-map-support "~0.5.20"
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:

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

@ -57,4 +57,17 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
'vaulting.credit-card-handler' => function( ContainerInterface $container ): VaultedCreditCardHandler {
return new VaultedCreditCardHandler(
$container->get( 'subscription.helper' ),
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.payer' ),
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.processor.authorized-payments' ),
$container->get( 'wcgateway.settings' )
);
},
); );

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

@ -0,0 +1,223 @@
<?php
/**
* Handles payment through saved credit card.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use Psr\Container\ContainerInterface;
use WC_Customer;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/**
* Class VaultedCreditCardHandler
*/
class VaultedCreditCardHandler {
use OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait;
/**
* The subscription helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The payment token repository.
*
* @var PaymentTokenRepository
*/
private $payment_token_repository;
/**
* The purchase unit factory.
*
* @var PurchaseUnitFactory
*/
private $purchase_unit_factory;
/**
* The payer factory.
*
* @var PayerFactory
*/
private $payer_factory;
/**
* The shipping_preference factory.
*
* @var ShippingPreferenceFactory
*/
private $shipping_preference_factory;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/**
* The settings.
*
* @var ContainerInterface
*/
protected $config;
/**
* VaultedCreditCardHandler constructor
*
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param PayerFactory $payer_factory The payer factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param Environment $environment The environment.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The processor for authorized payments.
* @param ContainerInterface $config The settings.
*/
public function __construct(
SubscriptionHelper $subscription_helper,
PaymentTokenRepository $payment_token_repository,
PurchaseUnitFactory $purchase_unit_factory,
PayerFactory $payer_factory,
ShippingPreferenceFactory $shipping_preference_factory,
OrderEndpoint $order_endpoint,
Environment $environment,
AuthorizedPaymentsProcessor $authorized_payments_processor,
ContainerInterface $config
) {
$this->subscription_helper = $subscription_helper;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->payer_factory = $payer_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->order_endpoint = $order_endpoint;
$this->environment = $environment;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->config = $config;
}
/**
* Handles the saved credit card payment.
*
* @param string $saved_credit_card The saved credit card.
* @param WC_Order $wc_order The WC order.
* @return WC_Order
* @throws RuntimeException When something went wrong with the payment process.
*/
public function handle_payment(
string $saved_credit_card,
WC_Order $wc_order
): WC_Order {
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if (
$change_payment
&& $this->subscription_helper->has_subscription( $wc_order->get_id() )
&& $this->subscription_helper->is_subscription_change_payment()
&& $saved_credit_card
) {
update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card );
return $wc_order;
}
$tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
throw new RuntimeException( 'Saved card token not found.' );
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_wc_order( $wc_order );
$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 ) ) {
throw new RuntimeException( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." );
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
throw new RuntimeException( "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 $wc_order;
} catch ( RuntimeException $error ) {
throw new RuntimeException( $error->getMessage() );
}
}
}

View file

@ -873,6 +873,46 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@types/eslint-scope@^3.7.0": "@types/eslint-scope@^3.7.0":
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e"
@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.4.1: acorn@^8.4.1, acorn@^8.5.0:
version "8.5.0" version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
ajv-keywords@^3.5.2: ajv-keywords@^3.5.2:
version "3.5.2" version "3.5.2"
@ -2017,9 +2057,9 @@ signal-exit@^3.0.3:
integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==
source-map-support@~0.5.20: source-map-support@~0.5.20:
version "0.5.20" version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies: dependencies:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
@ -2034,11 +2074,6 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
strip-final-newline@^2.0.0: strip-final-newline@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
@ -2076,12 +2111,13 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2" terser "^5.7.2"
terser@^5.7.2: terser@^5.7.2:
version "5.9.0" version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0" commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.20" source-map-support "~0.5.20"
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="42px" height="20px" viewBox="0 0 42 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>logo OXXO</title>
<desc>Created with Sketch.</desc>
<defs/>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="SPB_&amp;_AltPay_NewAssets" transform="translate(-100.000000, -159.000000)">
<g id="logo-OXXO" transform="translate(100.000000, 159.000000)">
<path d="M0.142456528,1.48437917 C0.142456528,0.77043992 0.728159303,0.186243119 1.44446761,0.186243119 L40.6503931,0.186243119 C41.3667014,0.186243119 41.9524042,0.77043992 41.9524042,1.48437917 L41.9524042,18.1011373 C41.9524042,18.8150765 41.3667014,19.3990362 40.6503931,19.3990362 L1.44446761,19.3990362 C0.728159303,19.3990362 0.142456528,18.8150765 0.142456528,18.1011373 L0.142456528,1.48437917 Z" id="Fill-2" fill="#EDA42D"/>
<polygon id="Fill-4" fill="#FEFEFE" points="0.142480318 17.5124813 41.952428 17.5124813 41.952428 2.07265562 0.142480318 2.07265562"/>
<path d="M35.5752619,6.08262231 C33.662331,6.08262231 32.1029152,7.63763417 32.1029152,9.54463469 C32.1029152,11.4511608 33.662331,13.0064099 35.5752619,13.0064099 C37.4877171,13.0064099 39.0471329,11.4511608 39.0471329,9.54463469 C39.0471329,7.63763417 37.4877171,6.08262231 35.5752619,6.08262231" id="Fill-6" fill="#EC1D24"/>
<path d="M6.95585459,6.08262231 C5.04268574,6.08262231 3.48326994,7.63763417 3.48326994,9.54463469 C3.48326994,11.4511608 5.04268574,13.0064099 6.95585459,13.0064099 C8.86807185,13.0064099 10.4277255,11.4511608 10.4277255,9.54463469 C10.4277255,7.63763417 8.86807185,6.08262231 6.95585459,6.08262231" id="Fill-7" fill="#EC1D24"/>
<path d="M35.5752619,15.0141446 C32.5537303,15.0141446 30.0893537,12.5573397 30.0893537,9.54480072 C30.0893537,6.53155015 32.5537303,4.07521964 35.5752619,4.07521964 C38.5970315,4.07521964 41.0609322,6.53155015 41.0609322,9.54480072 C41.0609322,12.5573397 38.5970315,15.0141446 35.5752619,15.0141446 Z M12.4411918,9.54480072 C12.4411918,12.5573397 9.97729109,15.0141446 6.95575943,15.0141446 C3.93351408,15.0141446 1.46985124,12.5573397 1.46985124,9.54480072 C1.46985124,6.53155015 3.93351408,4.07521964 6.95575943,4.07521964 C9.97729109,4.07521964 12.4411918,6.53155015 12.4411918,9.54480072 Z M35.3028697,3.03585692 C32.0884035,2.9620911 30.5772808,5.01709763 28.384107,7.55170056 L26.3151155,9.94232969 L29.591435,13.8526295 C30.3719756,15.0542296 28.8822636,16.2465793 27.9580332,15.1472077 L24.9288888,11.5447794 L21.9772989,14.9562705 C21.0373673,16.0421223 19.5645461,14.8288999 20.3617394,13.6386849 L23.5659761,9.92382894 L21.4667717,7.42693908 L22.8173138,5.75949957 L24.9522028,8.31639828 L26.7923372,6.18217058 C27.6953948,5.13569219 28.6162946,3.74884741 29.8098246,3.03585692 L0.142385159,3.03585692 L0.142385159,16.549707 L7.07875226,16.549707 C10.2934564,16.549707 11.7529554,14.6332189 13.8866549,12.0492806 L15.8999784,9.61097649 L12.5334959,5.77752594 C11.726073,4.59418943 13.1874752,3.36815887 14.1371606,4.44594623 L17.2483795,7.9779294 L20.1209875,4.49931378 C21.0354641,3.39164059 22.5356435,4.57118208 21.7662842,5.77942346 L18.6486421,9.56757088 L20.8051797,12.0153626 L19.4463112,13.6197098 L17.2997653,11.2058361 L15.5095892,13.3813347 C14.6310351,14.4484486 13.7415376,15.8094397 12.5646605,16.549707 L41.9523328,16.549707 L41.9523328,3.03585692 L35.3028697,3.03585692 Z" id="Fill-8" fill="#EC1D24"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,18 @@
document.addEventListener(
'DOMContentLoaded',
function() {
jQuery('form.checkout').on('checkout_place_order_success', function(type, data) {
if(data.payer_action && data.payer_action !== '') {
const width = screen.width / 2;
const height = screen.height / 2;
const left = width - (width / 2);
const top = height - (height / 2);
window.open(
data.payer_action,
'_blank',
'popup, width=' + width + ', height=' + height + ', top=' + top + ', left=' + left
);
}
});
}
);

View file

@ -30,7 +30,11 @@ 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\OXXOEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNetSessionId; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNetSessionId;
@ -39,13 +43,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFac
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; 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;
@ -56,11 +61,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' );
@ -69,8 +73,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' );
@ -78,7 +80,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,
@ -88,73 +89,75 @@ 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' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$module_url = $container->get( 'wcgateway.url' ); $module_url = $container->get( 'wcgateway.url' );
$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' );
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' ); $transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
$payer_factory = $container->get( 'api.factory.payer' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$subscription_helper = $container->get( 'subscription.helper' ); $subscription_helper = $container->get( 'subscription.helper' );
$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' );
$environment = $container->get( 'onboarding.environment' ); $vaulted_credit_card_handler = $container->get( 'vaulting.credit-card-handler' );
return new CreditCardGateway( return new CreditCardGateway(
$settings_renderer, $settings_renderer,
$order_processor, $order_processor,
$authorized_payments,
$settings, $settings,
$module_url, $module_url,
$session_handler, $session_handler,
$refund_processor, $refund_processor,
$state, $state,
$transaction_url_provider, $transaction_url_provider,
$payment_token_repository,
$purchase_unit_factory,
$container->get( 'api.factory.shipping-preference' ),
$payer_factory,
$order_endpoint,
$subscription_helper, $subscription_helper,
$logger, $logger,
$environment, $payments_endpoint,
$payments_endpoint $vaulted_credit_card_handler
); );
}, },
'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, OXXOGateway::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 '';
} }
@ -165,36 +168,70 @@ 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( 'wcgateway.settings.sections' )
); );
}, },
'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus { 'wcgateway.settings.sections' => static function ( ContainerInterface $container ): array {
$sections = array(
PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ),
CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ),
OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ),
);
// Remove for all not registered in WC gateways that cannot render anything in this case.
$gateways = WC()->payment_gateways->payment_gateways();
foreach ( array_diff(
array_keys( $sections ),
array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID )
) as $id ) {
if ( ! isset( $gateways[ $id ] ) ) {
unset( $sections[ $id ] );
}
}
return $sections;
},
'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' );
@ -214,7 +251,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' );
@ -236,7 +273,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' );
@ -261,13 +298,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' );
@ -283,23 +320,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 );
@ -862,6 +899,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',
),
'tracking_enabled' => array( 'tracking_enabled' => array(
'title' => __( 'Tracking', 'woocommerce-paypal-payments' ), 'title' => __( 'Tracking', 'woocommerce-paypal-payments' ),
'type' => 'checkbox', 'type' => 'checkbox',
@ -2086,10 +2157,14 @@ return array(
$fields['disable_cards']['options'] = $card_options; $fields['disable_cards']['options'] = $card_options;
$fields['card_icons']['options'] = array_merge( $dark_versions, $card_options ); $fields['card_icons']['options'] = array_merge( $dark_versions, $card_options );
if ( defined( 'PPCP_FLAG_SEPARATE_APM_BUTTONS' ) && PPCP_FLAG_SEPARATE_APM_BUTTONS === false ) {
unset( $fields['allow_card_button_gateway'] );
}
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' ),
@ -2107,28 +2182,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' );
@ -2139,40 +2214,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.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint { 'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper();
},
'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' ),
@ -2181,10 +2259,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' ),
@ -2192,16 +2270,17 @@ return array(
$container->get( 'onboarding.environment' ), $container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'wcgateway.pay-upon-invoice-helper' ) $container->get( 'wcgateway.pay-upon-invoice-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(
@ -2209,19 +2288,20 @@ 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( 'api.shop.country' ), $container->get( 'api.shop.country' ),
$container->get( 'wcgateway.pay-upon-invoice-product-status' ) $container->get( 'wcgateway.pay-upon-invoice-product-status' )
); );
}, },
'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' ),
@ -2235,10 +2315,36 @@ return array(
$container->get( 'wcgateway.current-ppcp-settings-page-id' ), $container->get( 'wcgateway.current-ppcp-settings-page-id' ),
$container->get( 'wcgateway.pay-upon-invoice-product-status' ), $container->get( 'wcgateway.pay-upon-invoice-product-status' ),
$container->get( 'wcgateway.pay-upon-invoice-helper' ), $container->get( 'wcgateway.pay-upon-invoice-helper' ),
$container->get( 'wcgateway.checkout-helper' ),
$container->get( 'api.factory.capture' ) $container->get( 'api.factory.capture' )
); );
}, },
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool { 'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO {
return new OXXO(
$container->get( 'wcgateway.checkout-helper' ),
$container->get( 'wcgateway.url' ),
$container->get( 'ppcp.asset-version' )
);
},
'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway {
return new OXXOGateway(
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'wcgateway.url' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint {
return new OXXOEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
/** /**
@ -2250,7 +2356,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();
@ -2259,7 +2365,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' ) ) {
@ -2281,7 +2387,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' );
@ -2289,7 +2395,35 @@ return array(
return $pay_later_label; return $pay_later_label;
}, },
'order-tracking.is-tracking-available' => static function ( ContainerInterface $container ): bool {
'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 {
if ( defined( 'PPCP_FLAG_SEPARATE_APM_BUTTONS' ) && PPCP_FLAG_SEPARATE_APM_BUTTONS === false ) {
return false;
}
$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' );
},
'order-tracking.is-tracking-available' => static function ( ContainerInterface $container ): bool {
try { try {
/* @var Bearer $bearer The bearer. */ /* @var Bearer $bearer The bearer. */
$bearer = $container->get( 'api.bearer' ); $bearer = $container->get( 'api.bearer' );
@ -2299,7 +2433,7 @@ return array(
return false; return false;
} }
}, },
'wcgateway.settings.should-disable-tracking-checkbox' => static function ( ContainerInterface $container ): bool { 'wcgateway.settings.should-disable-tracking-checkbox' => static function ( ContainerInterface $container ): bool {
/** /**
* The PUI helper. * The PUI helper.
* *

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

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint; namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait; use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
@ -51,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'] ) ) {
@ -68,7 +69,12 @@ class ReturnUrlEndpoint {
} }
$wc_order = wc_get_order( $wc_order_id ); $wc_order = wc_get_order( $wc_order_id );
if ( ! $wc_order ) { if ( ! is_a( $wc_order, \WC_Order::class ) ) {
exit();
}
if ( $wc_order->get_payment_method() === OXXOGateway::ID ) {
wp_safe_redirect( wc_get_checkout_url() );
exit(); exit();
} }

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,18 +9,19 @@ 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_Customer;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
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\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
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;
@ -31,7 +32,7 @@ use Psr\Container\ContainerInterface;
*/ */
class CreditCardGateway extends \WC_Payment_Gateway_CC { class CreditCardGateway extends \WC_Payment_Gateway_CC {
use ProcessPaymentTrait; use ProcessPaymentTrait, GatewaySettingsRendererTrait;
const ID = 'ppcp-credit-card-gateway'; const ID = 'ppcp-credit-card-gateway';
@ -49,13 +50,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/ */
protected $order_processor; protected $order_processor;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/** /**
* The settings. * The settings.
* *
@ -63,6 +57,13 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/ */
protected $config; protected $config;
/**
* The vaulted credit card handler.
*
* @var VaultedCreditCardHandler
*/
protected $vaulted_credit_card_handler;
/** /**
* The URL to the module. * The URL to the module.
* *
@ -105,34 +106,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/ */
private $payment_token_repository; private $payment_token_repository;
/**
* The purchase unit factory.
*
* @var PurchaseUnitFactory
*/
private $purchase_unit_factory;
/**
* The shipping_preference factory.
*
* @var ShippingPreferenceFactory
*/
private $shipping_preference_factory;
/**
* The payer factory.
*
* @var PayerFactory
*/
private $payer_factory;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/** /**
* The subscription helper. * The subscription helper.
* *
@ -147,13 +120,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/ */
protected $logger; protected $logger;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/** /**
* The payments endpoint * The payments endpoint
* *
@ -164,54 +130,46 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
/** /**
* CreditCardGateway constructor. * CreditCardGateway constructor.
* *
* @param SettingsRenderer $settings_renderer The Settings Renderer. * @param SettingsRenderer $settings_renderer The Settings 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 string $module_url The URL to the module.
* @param string $module_url The URL to the module. * @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 able to provide view transaction url base.
* @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param LoggerInterface $logger The logger.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler.
* @param PayerFactory $payer_factory The payer factory.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param LoggerInterface $logger The logger.
* @param Environment $environment The environment.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
*/ */
public function __construct( public function __construct(
SettingsRenderer $settings_renderer, SettingsRenderer $settings_renderer,
OrderProcessor $order_processor, OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorized_payments_processor,
ContainerInterface $config, ContainerInterface $config,
string $module_url, string $module_url,
SessionHandler $session_handler, SessionHandler $session_handler,
RefundProcessor $refund_processor, RefundProcessor $refund_processor,
State $state, State $state,
TransactionUrlProvider $transaction_url_provider, TransactionUrlProvider $transaction_url_provider,
PaymentTokenRepository $payment_token_repository,
PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory,
PayerFactory $payer_factory,
OrderEndpoint $order_endpoint,
SubscriptionHelper $subscription_helper, SubscriptionHelper $subscription_helper,
LoggerInterface $logger, LoggerInterface $logger,
Environment $environment, PaymentsEndpoint $payments_endpoint,
PaymentsEndpoint $payments_endpoint VaultedCreditCardHandler $vaulted_credit_card_handler
) { ) {
$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->config = $config;
$this->settings_renderer = $settings_renderer; $this->module_url = $module_url;
$this->config = $config; $this->session_handler = $session_handler;
$this->session_handler = $session_handler; $this->refund_processor = $refund_processor;
$this->refund_processor = $refund_processor; $this->state = $state;
$this->environment = $environment; $this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->vaulted_credit_card_handler = $vaulted_credit_card_handler;
if ( $state->current_state() === State::STATE_ONBOARDED ) { if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' ); $this->supports = array( 'refunds' );
@ -261,18 +219,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 +241,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 +341,77 @@ 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 );
if ( $saved_credit_card ) {
try {
$wc_order = $this->vaulted_credit_card_handler->handle_payment(
$saved_credit_card,
$wc_order
);
return $this->handle_payment_success( $wc_order );
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* 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 +503,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

@ -0,0 +1,223 @@
<?php
/**
* OXXO integration.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
use WC_Order;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
/**
* Class OXXO.
*/
class OXXO {
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/**
* The module URL.
*
* @var string
*/
protected $module_url;
/**
* The asset version.
*
* @var string
*/
protected $asset_version;
/**
* OXXO constructor.
*
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param string $module_url The module URL.
* @param string $asset_version The asset version.
*/
public function __construct(
CheckoutHelper $checkout_helper,
string $module_url,
string $asset_version
) {
$this->checkout_helper = $checkout_helper;
$this->module_url = $module_url;
$this->asset_version = $asset_version;
}
/**
* Initializes OXXO integration.
*/
public function init(): void {
add_filter(
'woocommerce_available_payment_gateways',
function ( array $methods ): array {
if ( ! $this->checkout_allowed_for_oxxo() ) {
unset( $methods[ OXXOGateway::ID ] );
}
return $methods;
}
);
add_action(
'wp_enqueue_scripts',
array( $this, 'register_assets' )
);
add_filter(
'woocommerce_thankyou_order_received_text',
function( string $message, WC_Order $order ) {
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? '';
$button = '';
if ( $payer_action ) {
$button = '<p><a id="ppcp-oxxo-payer-action" class="button" href="' . $payer_action . '" target="_blank">' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '</a></p>';
}
return $message . ' ' . $button;
},
10,
2
);
add_action(
'woocommerce_email_before_order_table',
function ( WC_Order $order, bool $sent_to_admin ) {
if (
! $sent_to_admin
&& $order->get_payment_method() === OXXOGateway::ID
&& $order->has_status( 'on-hold' )
) {
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ) ?? '';
if ( $payer_action ) {
echo '<p><a class="button" href="' . esc_url( $payer_action ) . '" target="_blank">' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '</a></p>';
}
}
},
10,
2
);
add_filter(
'ppcp_payment_capture_reversed_webhook_update_status_note',
function( string $note, WC_Order $wc_order, string $event_type ): string {
if ( $wc_order->get_payment_method() === OXXOGateway::ID && $event_type === 'PAYMENT.CAPTURE.DENIED' ) {
$note = __( 'OXXO voucher has expired or the buyer didn\'t complete the payment successfully.', 'woocommerce-paypal-payments' );
}
return $note;
},
10,
3
);
add_action(
'add_meta_boxes',
function( string $post_type ) {
if ( $post_type === 'shop_order' ) {
$post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING );
$order = wc_get_order( $post_id );
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) {
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' );
if ( $payer_action ) {
add_meta_box(
'ppcp_oxxo_payer_action',
__( 'OXXO Voucher/Ticket', 'woocommerce-paypal-payments' ),
function() use ( $payer_action ) {
echo '<p><a class="button" href="' . esc_url( $payer_action ) . '" target="_blank">' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '</a></p>';
},
$post_type,
'side',
'high'
);
}
}
}
}
);
add_action(
'woocommerce_order_details_before_order_table_items',
function( WC_Order $order ) {
if ( $order->get_payment_method() === OXXOGateway::ID ) {
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' );
if ( $payer_action ) {
echo '<p><a class="button" href="' . esc_url( $payer_action ) . '" target="_blank">' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '</a></p>';
}
}
}
);
}
/**
* Checks if checkout is allowed for OXXO.
*
* @return bool
*/
private function checkout_allowed_for_oxxo(): bool {
if ( 'MXN' !== get_woocommerce_currency() ) {
return false;
}
$billing_country = filter_input( INPUT_POST, 'country', FILTER_SANITIZE_STRING ) ?? null;
if ( $billing_country && 'MX' !== $billing_country ) {
return false;
}
if ( ! $this->checkout_helper->is_checkout_amount_allowed( 0, 10000 ) ) {
return false;
}
return true;
}
/**
* Register OXXO assets.
*/
public function register_assets(): void {
$gateway_settings = get_option( 'woocommerce_ppcp-oxxo-gateway_settings' );
$gateway_enabled = $gateway_settings['enabled'] ?? '';
if ( $gateway_enabled === 'yes' && is_checkout() ) {
wp_enqueue_script(
'ppcp-oxxo',
trailingslashit( $this->module_url ) . 'assets/js/oxxo.js',
array(),
$this->asset_version,
true
);
}
wp_localize_script(
'ppcp-oxxo',
'OXXOConfig',
array(
'oxxo_endpoint' => \WC_AJAX::get_endpoint( 'ppc-oxxo' ),
'oxxo_nonce' => wp_create_nonce( 'ppc-oxxo' ),
'error' => array(
'generic' => __(
'Something went wrong. Please try again or choose another payment source.',
'woocommerce-paypal-payments'
),
'js_validation' => __(
'Required form fields are not filled or invalid.',
'woocommerce-paypal-payments'
),
),
)
);
}
}

View file

@ -0,0 +1,156 @@
<?php
/**
* Handles OXXO payer action.
*
* @package WooCommerce\PayPalCommerce\Onboarding\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
/**
* OXXOEndpoint constructor.
*/
class OXXOEndpoint implements EndpointInterface {
/**
* The request data
*
* @var RequestData
*/
protected $request_data;
/**
* The purchase unit factory.
*
* @var PurchaseUnitFactory
*/
protected $purchase_unit_factory;
/**
* The shipping preference factory.
*
* @var ShippingPreferenceFactory
*/
protected $shipping_preference_factory;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
protected $order_endpoint;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* OXXOEndpoint constructor
*
* @param RequestData $request_data The request data.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory,
LoggerInterface $logger
) {
$this->request_data = $request_data;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->order_endpoint = $order_endpoint;
$this->logger = $logger;
}
/**
* The nonce
*
* @return string
*/
public static function nonce(): string {
return 'ppc-oxxo';
}
/**
* Handles the request.
*
* @return bool
*/
public function handle_request(): bool {
$purchase_unit = $this->purchase_unit_factory->from_wc_cart();
$payer_action = '';
try {
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
'checkout'
);
$order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference );
$payment_source = array(
'oxxo' => array(
'name' => 'John Doe',
'email' => 'foo@bar.com',
'country_code' => 'MX',
),
);
$payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source );
foreach ( $payment_method->links as $link ) {
if ( $link->rel === 'payer-action' ) {
$payer_action = $link->href;
}
}
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) {
$details = '';
foreach ( $exception->details() as $detail ) {
$issue = $detail->issue ?? '';
$field = $detail->field ?? '';
$description = $detail->description ?? '';
$details .= $issue . ' ' . $field . ' ' . $description . '<br>';
}
$error = $details;
}
$this->logger->error( $error );
wc_add_notice( $error, 'error' );
wp_send_json_error( 'Could not get OXXO payer action.' );
return false;
}
WC()->session->set( 'ppcp_payer_action', $payer_action );
wp_send_json_success(
array( 'payer_action' => $payer_action )
);
return true;
}
}

View file

@ -0,0 +1,210 @@
<?php
/**
* The OXXO Gateway
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
use Psr\Log\LoggerInterface;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
/**
* Class OXXOGateway.
*/
class OXXOGateway extends WC_Payment_Gateway {
const ID = 'ppcp-oxxo-gateway';
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
protected $order_endpoint;
/**
* The purchase unit factory.
*
* @var PurchaseUnitFactory
*/
protected $purchase_unit_factory;
/**
* The shipping preference factory.
*
* @var ShippingPreferenceFactory
*/
protected $shipping_preference_factory;
/**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* OXXOGateway constructor.
*
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory.
* @param string $module_url The URL to the module.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
OrderEndpoint $order_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory,
string $module_url,
LoggerInterface $logger
) {
$this->id = self::ID;
$this->method_title = __( 'OXXO', 'woocommerce-paypal-payments' );
$this->method_description = __( 'OXXO is a Mexican chain of convenience stores.', 'woocommerce-paypal-payments' );
$this->title = $this->get_option( 'title', $this->method_title );
$this->description = $this->get_option( 'description', __( 'OXXO allows you to pay bills and online purchases in-store with cash.', 'woocommerce-paypal-payments' ) );
$this->init_form_fields();
$this->init_settings();
add_action(
'woocommerce_update_options_payment_gateways_' . $this->id,
array(
$this,
'process_admin_options',
)
);
$this->order_endpoint = $order_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->module_url = $module_url;
$this->logger = $logger;
$this->icon = esc_url( $this->module_url ) . 'assets/images/oxxo.svg';
}
/**
* Initialize the form fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'OXXO', 'woocommerce-paypal-payments' ),
'default' => 'no',
'desc_tip' => true,
'description' => __( 'Enable/Disable OXXO payment gateway.', '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' ),
),
);
}
/**
* Processes the order.
*
* @param int $order_id The WC order ID.
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer_action = '';
try {
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
'checkout'
);
$order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference );
$payment_source = array(
'oxxo' => array(
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
),
);
$payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source );
foreach ( $payment_method->links as $link ) {
if ( $link->rel === 'payer-action' ) {
$payer_action = $link->href;
$wc_order->add_meta_data( 'ppcp_oxxo_payer_action', $payer_action );
$wc_order->save_meta_data();
}
}
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) {
$details = '';
foreach ( $exception->details() as $detail ) {
$issue = $detail->issue ?? '';
$field = $detail->field ?? '';
$description = $detail->description ?? '';
$details .= $issue . ' ' . $field . ' ' . $description . '<br>';
}
$error = $details;
}
$this->logger->error( $error );
wc_add_notice( $error, 'error' );
$wc_order->update_status(
'failed',
$error
);
return array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
);
}
WC()->cart->empty_cart();
$result = array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
if ( $payer_action ) {
$result['payer_action'] = $payer_action;
}
return $result;
}
}

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,130 @@ 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 ) {
$retry_keys_messages = array(
'INSTRUMENT_DECLINED' => __( 'Instrument declined.', 'woocommerce-paypal-payments' ),
'PAYER_ACTION_REQUIRED' => __( 'Payer action required, possibly overcharge.', 'woocommerce-paypal-payments' ),
);
$retry_errors = array_filter(
array_keys( $retry_keys_messages ),
function ( string $key ) use ( $error ): bool {
return $error->has_detail( $key );
}
);
if ( $retry_errors ) {
$retry_error_key = $retry_errors[0];
$wc_order->update_status(
'failed',
$retry_keys_messages[ $retry_error_key ] . ' ' . $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 +563,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

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
@ -115,6 +116,13 @@ class PayUponInvoice {
*/ */
protected $pui_product_status; protected $pui_product_status;
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/** /**
* The capture factory. * The capture factory.
* *
@ -137,6 +145,7 @@ class PayUponInvoice {
* @param string $current_ppcp_settings_page_id Current PayPal settings page id. * @param string $current_ppcp_settings_page_id Current PayPal settings page id.
* @param PayUponInvoiceProductStatus $pui_product_status The PUI product status. * @param PayUponInvoiceProductStatus $pui_product_status The PUI product status.
* @param PayUponInvoiceHelper $pui_helper The PUI helper. * @param PayUponInvoiceHelper $pui_helper The PUI helper.
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param CaptureFactory $capture_factory The capture factory. * @param CaptureFactory $capture_factory The capture factory.
*/ */
public function __construct( public function __construct(
@ -152,6 +161,7 @@ class PayUponInvoice {
string $current_ppcp_settings_page_id, string $current_ppcp_settings_page_id,
PayUponInvoiceProductStatus $pui_product_status, PayUponInvoiceProductStatus $pui_product_status,
PayUponInvoiceHelper $pui_helper, PayUponInvoiceHelper $pui_helper,
CheckoutHelper $checkout_helper,
CaptureFactory $capture_factory CaptureFactory $capture_factory
) { ) {
$this->module_url = $module_url; $this->module_url = $module_url;
@ -166,6 +176,7 @@ class PayUponInvoice {
$this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id; $this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id;
$this->pui_product_status = $pui_product_status; $this->pui_product_status = $pui_product_status;
$this->pui_helper = $pui_helper; $this->pui_helper = $pui_helper;
$this->checkout_helper = $checkout_helper;
$this->capture_factory = $capture_factory; $this->capture_factory = $capture_factory;
} }
@ -298,7 +309,7 @@ class PayUponInvoice {
} }
}, },
10, 10,
3 2
); );
add_filter( add_filter(
@ -322,6 +333,23 @@ class PayUponInvoice {
) )
); );
$checkout_fields = WC()->checkout()->get_checkout_fields();
$checkout_phone_required = $checkout_fields['billing']['billing_phone']['required'] ?? false;
if ( ! array_key_exists( 'billing_phone', $checkout_fields['billing'] ) || $checkout_phone_required === false ) {
woocommerce_form_field(
'billing_phone',
array(
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
'label' => __( 'Phone', 'woocommerce' ),
'type' => 'tel',
'class' => array( 'form-row-wide' ),
'validate' => array( 'phone' ),
'autocomplete' => 'tel',
'required' => true,
)
);
}
echo '</div><div>'; echo '</div><div>';
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
@ -360,11 +388,14 @@ class PayUponInvoice {
} }
$birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING ); $birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING );
if ( ( $birth_date && ! $this->pui_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) {
$errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) );
} }
$national_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ); $national_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING );
if ( ! $national_number ) {
$errors->add( 'validation', __( 'Phone field cannot be empty.', 'woocommerce-paypal-payments' ) );
}
if ( $national_number ) { if ( $national_number ) {
$numeric_phone_number = preg_replace( '/[^0-9]/', '', $national_number ); $numeric_phone_number = preg_replace( '/[^0-9]/', '', $national_number );
if ( $numeric_phone_number && ! preg_match( '/^[0-9]{1,14}?$/', $numeric_phone_number ) ) { if ( $numeric_phone_number && ! preg_match( '/^[0-9]{1,14}?$/', $numeric_phone_number ) ) {
@ -477,7 +508,7 @@ class PayUponInvoice {
if ( $post_type === 'shop_order' ) { if ( $post_type === 'shop_order' ) {
$post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING ); $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING );
$order = wc_get_order( $post_id ); $order = wc_get_order( $post_id );
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === 'ppcp-pay-upon-invoice-gateway' ) { if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) {
$instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' ); $instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' );
if ( $instructions ) { if ( $instructions ) {
add_meta_box( add_meta_box(

View file

@ -12,14 +12,13 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
use WC_Order; use WC_Order;
use WC_Order_Item_Product;
use WC_Payment_Gateway; use WC_Payment_Gateway;
use WC_Product;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
@ -81,6 +80,13 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
*/ */
protected $pui_helper; protected $pui_helper;
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/** /**
* PayUponInvoiceGateway constructor. * PayUponInvoiceGateway constructor.
* *
@ -91,6 +97,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
* @param TransactionUrlProvider $transaction_url_provider The transaction URL provider. * @param TransactionUrlProvider $transaction_url_provider The transaction URL provider.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param PayUponInvoiceHelper $pui_helper The PUI helper. * @param PayUponInvoiceHelper $pui_helper The PUI helper.
* @param CheckoutHelper $checkout_helper The checkout helper.
*/ */
public function __construct( public function __construct(
PayUponInvoiceOrderEndpoint $order_endpoint, PayUponInvoiceOrderEndpoint $order_endpoint,
@ -99,7 +106,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
Environment $environment, Environment $environment,
TransactionUrlProvider $transaction_url_provider, TransactionUrlProvider $transaction_url_provider,
LoggerInterface $logger, LoggerInterface $logger,
PayUponInvoiceHelper $pui_helper PayUponInvoiceHelper $pui_helper,
CheckoutHelper $checkout_helper
) { ) {
$this->id = self::ID; $this->id = self::ID;
@ -128,6 +136,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
$this->environment = $environment; $this->environment = $environment;
$this->transaction_url_provider = $transaction_url_provider; $this->transaction_url_provider = $transaction_url_provider;
$this->pui_helper = $pui_helper; $this->pui_helper = $pui_helper;
$this->checkout_helper = $checkout_helper;
} }
/** /**
@ -198,7 +207,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
$pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); $pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING );
if ( 'true' === $pay_for_order ) { if ( 'true' === $pay_for_order ) {
if ( ! $this->pui_helper->validate_birth_date( $birth_date ) ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) {
wc_add_notice( 'Invalid birth date.', 'error' ); wc_add_notice( 'Invalid birth date.', 'error' );
return array( return array(
'result' => 'failure', 'result' => 'failure',
@ -206,6 +215,12 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
} }
} }
$phone_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? '';
if ( $phone_number ) {
$wc_order->set_billing_phone( $phone_number );
$wc_order->save();
}
$wc_order->update_status( 'on-hold', __( 'Awaiting Pay upon Invoice payment.', 'woocommerce-paypal-payments' ) ); $wc_order->update_status( 'on-hold', __( 'Awaiting Pay upon Invoice payment.', 'woocommerce-paypal-payments' ) );
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payment_source = $this->payment_source_factory->from_wc_order( $wc_order, $birth_date ); $payment_source = $this->payment_source_factory->from_wc_order( $wc_order, $birth_date );

View file

@ -24,8 +24,8 @@ class PaymentSourceFactory {
* @return PaymentSource * @return PaymentSource
*/ */
public function from_wc_order( WC_Order $order, string $birth_date ) { public function from_wc_order( WC_Order $order, string $birth_date ) {
$address = $order->get_address(); $address = $order->get_address();
$phone = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? $address['phone'] ?: '';
$phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] );
$phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code;
if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) { if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) {
@ -44,13 +44,13 @@ class PaymentSourceFactory {
$address['last_name'] ?? '', $address['last_name'] ?? '',
$address['email'] ?? '', $address['email'] ?? '',
$birth_date, $birth_date,
preg_replace( '/[^0-9]/', '', $address['phone'] ) ?? '', preg_replace( '/[^0-9]/', '', $phone ) ?? '',
$phone_country_code, $phone_country_code,
$address['address_1'] ?? '', $address['address_1'] ?? '',
$address['city'] ?? '', $address['city'] ?? '',
$address['postcode'] ?? '', $address['postcode'] ?? '',
$address['country'] ?? '', $address['country'] ?? '',
'en-DE', 'de-DE',
$merchant_name, $merchant_name,
$logo_url, $logo_url,
array( $customer_service_instructions ) array( $customer_service_instructions )

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() . ' ' . basename( $exception->getFile() ) . ':' . $exception->getLine();
$prev = $exception->getPrevious();
if ( ! $prev ) {
return $output;
}
if ( $exception instanceof GatewayGenericException ) {
$output = '';
}
return $output . ' ' . $this->format_exception( $prev );
}
} }

View file

@ -0,0 +1,131 @@
<?php
/**
* The Checkout helper.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use DateTime;
use WC_Order;
use WC_Order_Item_Product;
use WC_Product;
use WC_Product_Variable;
use WC_Product_Variation;
/**
* CheckoutHelper class.
*/
class CheckoutHelper {
/**
* Checks if amount is allowed within the given range.
*
* @param float $minimum Minimum amount.
* @param float $maximum Maximum amount.
* @return bool
*/
public function is_checkout_amount_allowed( float $minimum, float $maximum ): bool {
$cart = WC()->cart ?? null;
if ( $cart && ! is_checkout_pay_page() ) {
$cart_total = (float) $cart->get_total( 'numeric' );
if ( $cart_total < $minimum || $cart_total > $maximum ) {
return false;
}
$items = $cart->get_cart_contents();
foreach ( $items as $item ) {
$product = wc_get_product( $item['product_id'] );
if ( is_a( $product, WC_Product::class ) && ! $this->is_physical_product( $product ) ) {
return false;
}
}
}
if ( is_wc_endpoint_url( 'order-pay' ) ) {
/**
* Needed for WordPress `query_vars`.
*
* @psalm-suppress InvalidGlobal
*/
global $wp;
if ( isset( $wp->query_vars['order-pay'] ) && absint( $wp->query_vars['order-pay'] ) > 0 ) {
$order_id = absint( $wp->query_vars['order-pay'] );
$order = wc_get_order( $order_id );
if ( is_a( $order, WC_Order::class ) ) {
$order_total = (float) $order->get_total();
if ( $order_total < $minimum || $order_total > $maximum ) {
return false;
}
foreach ( $order->get_items() as $item_id => $item ) {
if ( is_a( $item, WC_Order_Item_Product::class ) ) {
$product = wc_get_product( $item->get_product_id() );
if ( is_a( $product, WC_Product::class ) && ! $this->is_physical_product( $product ) ) {
return false;
}
}
}
}
}
}
return true;
}
/**
* Ensures date is valid and at least 18 years back.
*
* @param string $date The date.
* @param string $format The date format.
* @return bool
*/
public function validate_birth_date( string $date, string $format = 'Y-m-d' ): bool {
$d = DateTime::createFromFormat( $format, $date );
if ( false === $d ) {
return false;
}
if ( $date !== $d->format( $format ) ) {
return false;
}
$date_time = strtotime( $date );
if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) {
return false;
}
if ( $date_time < strtotime( '-100 years', time() ) ) {
return false;
}
return true;
}
/**
* Ensures product is neither downloadable nor virtual.
*
* @param WC_Product $product WC product.
* @return bool
*/
public function is_physical_product( WC_Product $product ):bool {
if ( $product->is_downloadable() || $product->is_virtual() ) {
return false;
}
if ( is_a( $product, WC_Product_Variable::class ) ) {
foreach ( $product->get_available_variations( 'object' ) as $variation ) {
if ( is_a( $variation, WC_Product_Variation::class ) ) {
if ( true === $variation->is_downloadable() || true === $variation->is_virtual() ) {
return false;
}
}
}
}
return true;
}
}

View file

@ -9,18 +9,20 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Helper; namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use DateTime;
use WC_Order; use WC_Order;
use WC_Order_Item_Product;
use WC_Product;
use WC_Product_Variable;
use WC_Product_Variation;
/** /**
* Class PayUponInvoiceHelper * Class PayUponInvoiceHelper
*/ */
class PayUponInvoiceHelper { class PayUponInvoiceHelper {
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/** /**
* The selected shop country. * The selected shop country.
* *
@ -38,64 +40,17 @@ class PayUponInvoiceHelper {
/** /**
* PayUponInvoiceHelper constructor. * PayUponInvoiceHelper constructor.
* *
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param string $shop_country The selected shop country. * @param string $shop_country The selected shop country.
* @param PayUponInvoiceProductStatus $pui_product_status The PUI seller product status. * @param PayUponInvoiceProductStatus $pui_product_status The PUI seller product status.
*/ */
public function __construct( string $shop_country, PayUponInvoiceProductStatus $pui_product_status ) { public function __construct( CheckoutHelper $checkout_helper, string $shop_country, PayUponInvoiceProductStatus $pui_product_status ) {
$this->checkout_helper = $checkout_helper;
$this->shop_country = $shop_country; $this->shop_country = $shop_country;
$this->pui_product_status = $pui_product_status; $this->pui_product_status = $pui_product_status;
} }
/**
* Ensures date is valid and at least 18 years back.
*
* @param string $date The date.
* @param string $format The date format.
* @return bool
*/
public function validate_birth_date( string $date, string $format = 'Y-m-d' ): bool {
$d = DateTime::createFromFormat( $format, $date );
if ( false === $d ) {
return false;
}
if ( $date !== $d->format( $format ) ) {
return false;
}
$date_time = strtotime( $date );
if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) {
return false;
}
return true;
}
/**
* Ensures product is ready for PUI.
*
* @param WC_Product $product WC product.
* @return bool
*/
public function product_ready_for_pui( WC_Product $product ):bool {
if ( $product->is_downloadable() || $product->is_virtual() ) {
return false;
}
if ( is_a( $product, WC_Product_Variable::class ) ) {
foreach ( $product->get_available_variations( 'object' ) as $variation ) {
if ( is_a( $variation, WC_Product_Variation::class ) ) {
if ( true === $variation->is_downloadable() || true === $variation->is_virtual() ) {
return false;
}
}
}
}
return true;
}
/** /**
* Checks whether checkout is ready for PUI. * Checks whether checkout is ready for PUI.
* *
@ -112,58 +67,37 @@ class PayUponInvoiceHelper {
return false; return false;
} }
if ( 'EUR' !== get_woocommerce_currency() ) { if ( ! $this->is_valid_currency() ) {
return false; return false;
} }
$cart = WC()->cart ?? null; if ( ! $this->checkout_helper->is_checkout_amount_allowed( 5, 2500 ) ) {
if ( $cart && ! is_checkout_pay_page() ) { return false;
$cart_total = (float) $cart->get_total( 'numeric' );
if ( $cart_total < 5 || $cart_total > 2500 ) {
return false;
}
$items = $cart->get_cart_contents();
foreach ( $items as $item ) {
$product = wc_get_product( $item['product_id'] );
if ( is_a( $product, WC_Product::class ) && ! $this->product_ready_for_pui( $product ) ) {
return false;
}
}
}
if ( is_wc_endpoint_url( 'order-pay' ) ) {
/**
* Needed for WordPress `query_vars`.
*
* @psalm-suppress InvalidGlobal
*/
global $wp;
if ( isset( $wp->query_vars['order-pay'] ) && absint( $wp->query_vars['order-pay'] ) > 0 ) {
$order_id = absint( $wp->query_vars['order-pay'] );
$order = wc_get_order( $order_id );
if ( is_a( $order, WC_Order::class ) ) {
$order_total = (float) $order->get_total();
if ( $order_total < 5 || $order_total > 2500 ) {
return false;
}
foreach ( $order->get_items() as $item_id => $item ) {
if ( is_a( $item, WC_Order_Item_Product::class ) ) {
$product = wc_get_product( $item->get_product_id() );
if ( is_a( $product, WC_Product::class ) && ! $this->product_ready_for_pui( $product ) ) {
return false;
}
}
}
}
}
} }
return true; return true;
} }
/**
* Checks if currency is allowed for PUI.
*
* @return bool
*/
private function is_valid_currency(): bool {
global $wp;
$order_id = isset( $wp->query_vars['order-pay'] ) ? (int) $wp->query_vars['order-pay'] : 0;
if ( 0 === $order_id ) {
return 'EUR' === get_woocommerce_currency();
}
$order = wc_get_order( $order_id );
if ( is_a( $order, WC_Order::class ) ) {
return 'EUR' === $order->get_currency();
}
return false;
}
/** /**
* Checks whether PUI is ready in admin screen. * Checks whether PUI is ready in admin screen.
* *

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

@ -31,7 +31,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
*/ */
class AuthorizedPaymentsProcessor { class AuthorizedPaymentsProcessor {
use PaymentsStatusHandlingTrait; use PaymentsStatusHandlingTrait, TransactionIdHandlingTrait;
const SUCCESSFUL = 'SUCCESSFUL'; const SUCCESSFUL = 'SUCCESSFUL';
const ALREADY_CAPTURED = 'ALREADY_CAPTURED'; const ALREADY_CAPTURED = 'ALREADY_CAPTURED';
@ -200,6 +200,9 @@ class AuthorizedPaymentsProcessor {
$this->handle_capture_status( $capture, $wc_order ); $this->handle_capture_status( $capture, $wc_order );
$transaction_id = $capture->id();
$this->update_transaction_id( $transaction_id, $wc_order );
if ( self::SUCCESSFUL === $result_status ) { if ( self::SUCCESSFUL === $result_status ) {
if ( $capture->status()->is( CaptureStatus::COMPLETED ) ) { if ( $capture->status()->is( CaptureStatus::COMPLETED ) ) {
$wc_order->add_order_note( $wc_order->add_order_note(

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

@ -10,8 +10,6 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Settings; namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage; use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
/** /**
@ -29,21 +27,21 @@ class SectionsRenderer {
protected $page_id; protected $page_id;
/** /**
* The api shop country. * Key - page/gateway ID, value - displayed text.
* *
* @var string * @var array<string, string>
*/ */
protected $api_shop_country; protected $sections;
/** /**
* SectionsRenderer constructor. * SectionsRenderer constructor.
* *
* @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 string $api_shop_country The api shop country. * @param array<string, string> $sections Key - page/gateway ID, value - displayed text.
*/ */
public function __construct( string $page_id, string $api_shop_country ) { public function __construct( string $page_id, array $sections ) {
$this->page_id = $page_id; $this->page_id = $page_id;
$this->api_shop_country = $api_shop_country; $this->sections = $sections;
} }
/** /**
@ -58,34 +56,26 @@ class SectionsRenderer {
/** /**
* Renders the Sections tab. * Renders the Sections tab.
*/ */
public function render() { public function render(): string {
if ( ! $this->should_render() ) { if ( ! $this->should_render() ) {
return; return '';
} }
$sections = array( $html = '<nav class="nav-tab-wrapper woo-nav-tab-wrapper">';
PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ),
);
if ( 'DE' !== $this->api_shop_country ) { foreach ( $this->sections as $id => $label ) {
unset( $sections[ PayUponInvoiceGateway::ID ] ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $id );
} if ( in_array( $id, array( CreditCardGateway::ID, WebhooksStatusPage::ID ), true ) ) {
// We need section=ppcp-gateway for the webhooks page because it is not a gateway,
echo '<ul class="subsubsub">'; // and for DCC because otherwise it will not render the page if gateway is not available (country/currency).
// Other gateways render fields differently, and their pages are not expected to work when gateway is not available.
$array_keys = array_keys( $sections ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id );
foreach ( $sections as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id );
if ( PayUponInvoiceGateway::ID === $id ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-pay-upon-invoice-gateway' );
} }
echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>'; $html .= '<a href="' . esc_url( $url ) . '" class="nav-tab ' . ( $this->page_id === $id ? 'nav-tab-active' : '' ) . '">' . esc_html( $label ) . '</a> ';
} }
echo '</ul><br class="clear" />'; $html .= '</nav>';
return $html;
} }
} }

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;
@ -66,13 +66,12 @@ class WCGatewayModule implements ModuleInterface {
'woocommerce_sections_checkout', 'woocommerce_sections_checkout',
function() use ( $c ) { function() use ( $c ) {
$section_renderer = $c->get( 'wcgateway.settings.sections-renderer' ); $section_renderer = $c->get( 'wcgateway.settings.sections-renderer' );
/** assert( $section_renderer instanceof SectionsRenderer );
* The Section Renderer.
* // phpcs:ignore WordPress.Security.EscapeOutput
* @var SectionsRenderer $section_renderer echo $section_renderer->render();
*/ },
$section_renderer->render(); 20
}
); );
add_action( add_action(
@ -164,11 +163,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' );
@ -228,9 +231,13 @@ class WCGatewayModule implements ModuleInterface {
add_action( add_action(
'init', 'init',
function () use ( $c ) { function () use ( $c ) {
if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) { if ( 'DE' === $c->get( 'api.shop.country' ) ) {
( $c->get( 'wcgateway.pay-upon-invoice' ) )->init(); ( $c->get( 'wcgateway.pay-upon-invoice' ) )->init();
} }
if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === true ) {
( $c->get( 'wcgateway.oxxo' ) )->init();
}
} }
); );
@ -260,6 +267,18 @@ class WCGatewayModule implements ModuleInterface {
10, 10,
2 2
); );
add_action(
'wc_ajax_ppc-oxxo',
static function () use ( $c ) {
if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === false ) {
return;
}
$endpoint = $c->get( 'wcgateway.endpoint.oxxo' );
$endpoint->handle_request();
}
);
} }
/** /**
@ -272,7 +291,12 @@ class WCGatewayModule implements ModuleInterface {
add_filter( add_filter(
'woocommerce_payment_gateways', 'woocommerce_payment_gateways',
static function ( $methods ) use ( $container ): array { static function ( $methods ) use ( $container ): array {
$methods[] = $container->get( 'wcgateway.paypal-gateway' ); $paypal_gateway = $container->get( 'wcgateway.paypal-gateway' );
assert( $paypal_gateway instanceof \WC_Payment_Gateway );
$paypal_gateway_enabled = wc_string_to_bool( $paypal_gateway->get_option( 'enabled' ) );
$methods[] = $paypal_gateway;
$dcc_applies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
/** /**
@ -284,10 +308,18 @@ class WCGatewayModule implements ModuleInterface {
$methods[] = $container->get( 'wcgateway.credit-card-gateway' ); $methods[] = $container->get( 'wcgateway.credit-card-gateway' );
} }
if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { if ( $paypal_gateway_enabled && $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) {
$methods[] = $container->get( 'wcgateway.card-button-gateway' );
}
if ( 'DE' === $container->get( 'api.shop.country' ) ) {
$methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' );
} }
if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === true ) {
$methods[] = $container->get( 'wcgateway.oxxo-gateway' );
}
return (array) $methods; return (array) $methods;
} }
); );

View file

@ -8,6 +8,7 @@ module.exports = {
entry: { entry: {
'gateway-settings': path.resolve('./resources/js/gateway-settings.js'), 'gateway-settings': path.resolve('./resources/js/gateway-settings.js'),
'pay-upon-invoice': path.resolve('./resources/js/pay-upon-invoice.js'), 'pay-upon-invoice': path.resolve('./resources/js/pay-upon-invoice.js'),
'oxxo': path.resolve('./resources/js/oxxo.js'),
}, },
output: { output: {
path: path.resolve(__dirname, 'assets/'), path: path.resolve(__dirname, 'assets/'),

View file

@ -873,6 +873,46 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@types/eslint-scope@^3.7.0": "@types/eslint-scope@^3.7.0":
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e"
@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.4.1: acorn@^8.4.1, acorn@^8.5.0:
version "8.5.0" version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
ajv-keywords@^3.5.2: ajv-keywords@^3.5.2:
version "3.5.2" version "3.5.2"
@ -1902,9 +1942,9 @@ signal-exit@^3.0.3:
integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==
source-map-support@~0.5.20: source-map-support@~0.5.20:
version "0.5.20" version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies: dependencies:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
@ -1919,11 +1959,6 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
strip-final-newline@^2.0.0: strip-final-newline@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
@ -1961,12 +1996,13 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2" terser "^5.7.2"
terser@^5.7.2: terser@^5.7.2:
version "5.9.0" version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0" commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.20" source-map-support "~0.5.20"
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:

View file

@ -20,6 +20,8 @@ use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved;
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted; use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureDenied;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCapturePending;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed; use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -78,6 +80,7 @@ return array(
new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ), new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ),
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ), new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ),
new VaultCreditCardCreated( $logger, $prefix ), new VaultCreditCardCreated( $logger, $prefix ),
new PaymentCapturePending( $logger ),
); );
}, },

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
/** /**
@ -189,7 +190,7 @@ class CheckoutOrderApproved implements RequestHandler {
} }
foreach ( $wc_orders as $wc_order ) { foreach ( $wc_orders as $wc_order ) {
if ( PayUponInvoiceGateway::ID === $wc_order->get_payment_method() ) { if ( PayUponInvoiceGateway::ID === $wc_order->get_payment_method() || OXXOGateway::ID === $wc_order->get_payment_method() ) {
continue; continue;
} }

View file

@ -0,0 +1,122 @@
<?php
/**
* Handles the Webhook PAYMENT.CAPTURE.PENDING
*
* @package WooCommerce\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
use WP_REST_Request;
use WP_REST_Response;
/**
* Class PaymentCaptureCompleted
*/
class PaymentCapturePending implements RequestHandler {
use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* PaymentCaptureCompleted constructor.
*
* @param LoggerInterface $logger The logger.
*/
public function __construct(
LoggerInterface $logger
) {
$this->logger = $logger;
}
/**
* The event types a handler handles.
*
* @return string[]
*/
public function event_types(): array {
return array( 'PAYMENT.CAPTURE.PENDING' );
}
/**
* Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
}
/**
* Responsible for handling the request.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
$response = array( 'success' => false );
$order_id = $request['resource'] !== null && isset( $request['resource']['custom_id'] )
? $this->sanitize_custom_id( $request['resource']['custom_id'] )
: 0;
if ( ! $order_id ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
__(
'No order for webhook event %s was found.',
'woocommerce-paypal-payments'
),
$request['id'] !== null && isset( $request['id'] ) ? $request['id'] : ''
);
$this->logger->log(
'warning',
$message,
array(
'request' => $request,
)
);
$response['message'] = $message;
return new WP_REST_Response( $response );
}
$resource = $request['resource'];
if ( ! is_array( $resource ) ) {
$message = 'Resource data not found in webhook request.';
$this->logger->warning( $message, array( 'request' => $request ) );
$response['message'] = $message;
return new WP_REST_Response( $response );
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf(
'WC order for PayPal ID %s not found.',
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
);
$this->logger->warning( $message );
$response['message'] = $message;
return new WP_REST_Response( $response );
}
if ( $wc_order->get_status() === 'pending' ) {
$wc_order->update_status( 'on-hold', __( 'Payment initiation was successful, and is waiting for the buyer to complete the payment.', 'woocommerce-paypal-payments' ) );
}
$response['success'] = true;
return new WP_REST_Response( $response );
}
}

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

@ -112,12 +112,17 @@ class PaymentCaptureReversed implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
/**
* Allows adding an update status note.
*/
$note = apply_filters( 'ppcp_payment_capture_reversed_webhook_update_status_note', '', $wc_order, $request['event_type'] );
/** /**
* The WooCommerce order. * The WooCommerce order.
* *
* @var \WC_Order $wc_order * @var \WC_Order $wc_order
*/ */
$response['success'] = (bool) $wc_order->update_status( 'cancelled' ); $response['success'] = (bool) $wc_order->update_status( 'cancelled', $note );
$message = $response['success'] ? sprintf( $message = $response['success'] ? sprintf(
// translators: %1$s is the order id. // translators: %1$s is the order id.

View file

@ -873,6 +873,46 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@types/eslint-scope@^3.7.0": "@types/eslint-scope@^3.7.0":
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e"
@ -1057,10 +1097,10 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.4.1: acorn@^8.4.1, acorn@^8.5.0:
version "8.5.0" version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
ajv-keywords@^3.5.2: ajv-keywords@^3.5.2:
version "3.5.2" version "3.5.2"
@ -2017,9 +2057,9 @@ signal-exit@^3.0.3:
integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==
source-map-support@~0.5.20: source-map-support@~0.5.20:
version "0.5.20" version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies: dependencies:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
@ -2034,11 +2074,6 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
strip-final-newline@^2.0.0: strip-final-newline@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
@ -2076,12 +2111,13 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2" terser "^5.7.2"
terser@^5.7.2: terser@^5.7.2:
version "5.9.0" version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0" commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.20" source-map-support "~0.5.20"
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:

View file

@ -1,6 +1,6 @@
{ {
"name": "woocommerce-paypal-payments", "name": "woocommerce-paypal-payments",
"version": "1.9.1", "version": "1.9.2",
"description": "WooCommerce PayPal Payments", "description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0", "license": "GPL-2.0",

View file

@ -1,5 +1,6 @@
{ {
"redefinable-internals": [ "redefinable-internals": [
"json_decode" "json_decode",
] "filter_input"
]
} }

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell,
Requires at least: 5.3 Requires at least: 5.3
Tested up to: 6.0 Tested up to: 6.0
Requires PHP: 7.1 Requires PHP: 7.1
Stable tag: 1.9.1 Stable tag: 1.9.2
License: GPLv2 License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -81,13 +81,27 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog == == Changelog ==
= 1.9.2 =
* Fix - Do not allow birth date older than 100 years for PUI. #743
* Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698
* Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626
* Fix - Voiding authorization at PayPal did not update the status/order notes. #712
* Fix - PayPal scripts were loading on pages without smart buttons or Pay Later messaging. #750
* Fix - Do not show links for unavailable gateways settings pages. #753
* Fix - The smart buttons were not loaded on single product page if a subscription product exists in the cart. #703
* Fix - DCC was causing other gateways to disappear after checkout validation error. #757
* Fix - Buttons not loading on single product page with default settings when product is in cart. #777
* Enhancement - Improve Checkout Field Validation Message. #739
* Enhancement - Handle PAYER_ACTION_REQUIRED error. #759
= 1.9.1 = = 1.9.1 =
* 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
* 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

@ -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

@ -0,0 +1,146 @@
<?php
namespace PHPUnit\Vaulting;
use Mockery;
use Psr\Container\ContainerInterface;
use WC_Customer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSourceCard;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use function Brain\Monkey\Functions\expect;
use function Brain\Monkey\Functions\when;
class VaultedCreditCardHandlerTest extends TestCase
{
private $subscriptionHelper;
private $paymentTokenRepository;
private $purchaseUnitFactory;
private $payerFactory;
private $shippingPreferenceFactory;
private $orderEndpoint;
private $environment;
private $authorizedPaymentProcessor;
private $config;
private $testee;
public function setUp(): void
{
parent::setUp();
$this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class);
$this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class);
$this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
$this->payerFactory = Mockery::mock(PayerFactory::class);
$this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class);
$this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->environment = Mockery::mock(Environment::class);
$this->authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$this->config = Mockery::mock(ContainerInterface::class);
$this->testee = new VaultedCreditCardHandler(
$this->subscriptionHelper,
$this->paymentTokenRepository,
$this->purchaseUnitFactory,
$this->payerFactory,
$this->shippingPreferenceFactory,
$this->orderEndpoint,
$this->environment,
$this->authorizedPaymentProcessor,
$this->config
);
}
public function testHandlePaymentChangingPayment()
{
when('filter_input')->justReturn(1);
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->shouldReceive('get_id')->andReturn(1);
$this->subscriptionHelper->shouldReceive('has_subscription')->andReturn(true);
$this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true);
expect('update_post_meta')->with(1, 'payment_token_id', 'abc123');
$customer = Mockery::mock(WC_Customer::class);
$result = $this->testee->handle_payment('abc123', $wcOrder, $customer);
$this->assertInstanceOf(\WC_Order::class, $result);
}
public function testHandlePayment()
{
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->shouldReceive('get_id')->andReturn(1);
$wcOrder->shouldReceive('get_customer_id')->andReturn(1);
$wcOrder->shouldReceive('update_meta_data')->andReturn(1);
$wcOrder->shouldReceive('save')->once();
$wcOrder->shouldReceive('payment_complete')->andReturn(true);
$token = Mockery::mock(PaymentToken::class);
$tokenId = 'abc123';
$token->shouldReceive('id')->andReturn($tokenId);
$this->paymentTokenRepository->shouldReceive('all_for_user_id')
->andReturn([$token]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$this->purchaseUnitFactory->shouldReceive('from_wc_order')
->andReturn($purchaseUnit);
$customer = Mockery::mock(WC_Customer::class);
$payer = Mockery::mock(Payer::class);
$this->payerFactory->shouldReceive('from_wc_order')
->andReturn($payer);
$this->shippingPreferenceFactory->shouldReceive('from_state')
->andReturn('some_preference');
$order = Mockery::mock(Order::class);
$order->shouldReceive('id')->andReturn('1');
$order->shouldReceive('intent')->andReturn('CAPTURE');
$paymentSource = Mockery::mock(PaymentSource::class);
$paymentSourceCard = Mockery::mock(PaymentSourceCard::class);
$paymentSource->shouldReceive('card')->andReturn($paymentSourceCard);
$order->shouldReceive('payment_source')->andReturn($paymentSource);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus->shouldReceive('is')->andReturn(true);
$order->shouldReceive('status')->andReturn($orderStatus);
$order->shouldReceive('purchase_units')->andReturn([$purchaseUnit]);
$payments = Mockery::mock(Payments::class);
$capture = Mockery::mock(Capture::class);
$capture->shouldReceive('id')->andReturn('1');
$captureStatus = Mockery::mock(CaptureStatus::class);
$captureStatus->shouldReceive('details')->andReturn(null);
$captureStatus->shouldReceive('name')->andReturn(CaptureStatus::COMPLETED);
$capture->shouldReceive('status')->andReturn($captureStatus);
$payments->shouldReceive('captures')->andReturn([$capture]);
$purchaseUnit->shouldReceive('payments')->andReturn($payments);
$this->orderEndpoint->shouldReceive('create')
->with([$purchaseUnit], 'some_preference', $payer, $token)
->andReturn($order);
$this->environment->shouldReceive('current_environment_is')->andReturn(true);
$this->config->shouldReceive('has')->andReturn(false);
$result = $this->testee->handle_payment($tokenId, $wcOrder, $customer);
$this->assertInstanceOf(\WC_Order::class, $result);
}
}

View file

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Mockery;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use function Brain\Monkey\Functions\when;
class CreditCardGatewayTest extends TestCase
{
private $settingsRenderer;
private $orderProcessor;
private $config;
private $moduleUrl;
private $sessionHandler;
private $refundProcessor;
private $state;
private $transactionUrlProvider;
private $subscriptionHelper;
private $logger;
private $paymentsEndpoint;
private $vaultedCreditCardHandler;
private $testee;
public function setUp(): void
{
parent::setUp();
$this->settingsRenderer = Mockery::mock(SettingsRenderer::class);
$this->orderProcessor = Mockery::mock(OrderProcessor::class);
$this->config = Mockery::mock(ContainerInterface::class);
$this->moduleUrl = '';
$this->sessionHandler = Mockery::mock(SessionHandler::class);
$this->refundProcessor = Mockery::mock(RefundProcessor::class);
$this->state = Mockery::mock(State::class);
$this->transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$this->subscriptionHelper = Mockery::mock(SubscriptionHelper::class);
$this->logger = Mockery::mock(LoggerInterface::class);
$this->paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class);
$this->vaultedCreditCardHandler = Mockery::mock(VaultedCreditCardHandler::class);
$this->state->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$this->config->shouldReceive('has')->andReturn(true);
$this->config->shouldReceive('get')->andReturn('');
$this->testee = new CreditCardGateway(
$this->settingsRenderer,
$this->orderProcessor,
$this->config,
$this->moduleUrl,
$this->sessionHandler,
$this->refundProcessor,
$this->state,
$this->transactionUrlProvider,
$this->subscriptionHelper,
$this->logger,
$this->paymentsEndpoint,
$this->vaultedCreditCardHandler
);
}
public function testProcessPayment()
{
$wc_order = Mockery::mock(WC_Order::class);
when('wc_get_order')->justReturn($wc_order);
$this->orderProcessor->shouldReceive('process')
->with($wc_order)
->andReturn(true);
$this->subscriptionHelper->shouldReceive('has_subscription')
->andReturn(false);
$this->sessionHandler->shouldReceive('destroy_session_data')->once();
$result = $this->testee->process_payment(1);
$this->assertEquals('success', $result['result']);
}
public function testProcessPaymentVaultedCard()
{
$wc_order = Mockery::mock(WC_Order::class);
$wc_order->shouldReceive('get_customer_id')->andReturn(1);
when('wc_get_order')->justReturn($wc_order);
$savedCreditCard = 'abc123';
when('filter_input')->justReturn($savedCreditCard);
$this->vaultedCreditCardHandler
->shouldReceive('handle_payment')
->with($savedCreditCard, $wc_order)
->andReturn($wc_order);
$this->sessionHandler->shouldReceive('destroy_session_data')->once();
$result = $this->testee->process_payment(1);
$this->assertEquals('success', $result['result']);
}
}

View file

@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
use Mockery;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\TestCase;
use function Brain\Monkey\Functions\when;
class OXXOGatewayTest extends TestCase
{
private $orderEndpoint;
private $purchaseUnitFactory;
private $shippingPreferenceFactory;
private $logger;
private $wcOrder;
private $testee;
public function setUp(): void
{
parent::setUp();
$this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
$this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class);
$this->logger = Mockery::mock(LoggerInterface::class);
$this->wcOrder = Mockery::mock(WC_Order::class);
when('wc_get_order')->justReturn($this->wcOrder);
when('get_option')->justReturn([
'title' => 'foo',
'description' => 'bar',
]);
$this->testee = new OXXOGateway(
$this->orderEndpoint,
$this->purchaseUnitFactory,
$this->shippingPreferenceFactory,
'oxxo.svg',
$this->logger
);
}
public function testProcessPaymentSuccess()
{
$this->wcOrder->shouldReceive('get_billing_first_name')->andReturn('John');
$this->wcOrder->shouldReceive('get_billing_last_name')->andReturn('Doe');
$this->wcOrder->shouldReceive('get_billing_email')->andReturn('foo@bar.com');
$this->wcOrder->shouldReceive('get_billing_country')->andReturn('MX');
list($purchaseUnit, $shippingPreference) = $this->setStubs();
$linkHref = 'https://sandbox.paypal.com/payment/oxxo?token=ABC123';
$this->orderEndpoint
->shouldReceive('confirm_payment_source')
->with('1', [
'oxxo' => [
'name' => 'John Doe',
'email' => 'foo@bar.com',
'country_code' => 'MX',
]
]
)->andReturn((object)[
'links' => [
(object)[
'rel' => 'payer-action',
'href' => $linkHref,
],
]
]);
$order = Mockery::mock(Order::class);
$order->shouldReceive('id')->andReturn('1');
$this->orderEndpoint
->shouldReceive('create')
->with([$purchaseUnit], $shippingPreference)
->andReturn($order);
$this->wcOrder
->shouldReceive('add_meta_data')
->with('ppcp_oxxo_payer_action', $linkHref)
->andReturn(true);
$this->wcOrder->shouldReceive('save_meta_data');
$woocommerce = Mockery::mock(\WooCommerce::class);
$cart = Mockery::mock(\WC_Cart::class);
when('WC')->justReturn($woocommerce);
$woocommerce->cart = $cart;
$cart->shouldReceive('empty_cart');
$result = $this->testee->process_payment(1);
$this->assertEquals('success', $result['result']);
}
public function testProcessPaymentFailure()
{
list($purchaseUnit, $shippingPreference) = $this->setStubs();
$this->orderEndpoint
->shouldReceive('create')
->with([$purchaseUnit], $shippingPreference)
->andThrows(RuntimeException::class);
$this->logger->shouldReceive('error');
when('wc_add_notice')->justReturn();
when('wc_get_checkout_url')->justReturn();
$this->wcOrder->shouldReceive('update_status');
$result = $this->testee->process_payment(1);
$this->assertEquals('failure', $result['result']);
}
/**
* @return array
*/
private function setStubs(): array
{
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$this->purchaseUnitFactory
->shouldReceive('from_wc_order')
->with($this->wcOrder)
->andReturn($purchaseUnit);
$shippingPreference = 'SOME_SHIPPING_PREFERENCE';
$this->shippingPreferenceFactory
->shouldReceive('from_state')
->with($purchaseUnit, 'checkout')
->andReturn($shippingPreference);
return array($purchaseUnit, $shippingPreference);
}
}

View file

@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -26,6 +27,7 @@ class PayUponInvoiceGatewayTest extends TestCase
private $logger; private $logger;
private $testee; private $testee;
private $pui_helper; private $pui_helper;
private $checkout_helper;
public function setUp(): void public function setUp(): void
{ {
@ -38,6 +40,7 @@ class PayUponInvoiceGatewayTest extends TestCase
$this->logger = Mockery::mock(LoggerInterface::class); $this->logger = Mockery::mock(LoggerInterface::class);
$this->transaction_url_provider = Mockery::mock(TransactionUrlProvider::class); $this->transaction_url_provider = Mockery::mock(TransactionUrlProvider::class);
$this->pui_helper = Mockery::mock(PayUponInvoiceHelper::class); $this->pui_helper = Mockery::mock(PayUponInvoiceHelper::class);
$this->checkout_helper = Mockery::mock(CheckoutHelper::class);
$this->setInitStubs(); $this->setInitStubs();
@ -48,7 +51,8 @@ class PayUponInvoiceGatewayTest extends TestCase
$this->environment, $this->environment,
$this->transaction_url_provider, $this->transaction_url_provider,
$this->logger, $this->logger,
$this->pui_helper $this->pui_helper,
$this->checkout_helper
); );
} }

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

@ -14,8 +14,7 @@ class PayUponInvoiceHelperTest extends TestCase
*/ */
public function testValidateBirthDate($input, $output) public function testValidateBirthDate($input, $output)
{ {
$pui_product_status = Mockery::mock(PayUponInvoiceProductStatus::class); $this->assertSame((new CheckoutHelper())->validate_birth_date($input), $output);
$this->assertSame((new PayUponInvoiceHelper('DE', $pui_product_status))->validate_birth_date($input), $output);
} }
public function datesProvider(): array{ public function datesProvider(): array{
@ -28,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,6 +4,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor; namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Mockery\MockInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use WC_Order; use WC_Order;
@ -26,7 +27,11 @@ use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
class AuthorizedPaymentsProcessorTest extends TestCase class AuthorizedPaymentsProcessorTest extends TestCase
{ {
/**
* @var WC_Order&MockInterface
*/
private $wcOrder; private $wcOrder;
private $paypalOrderId = 'abc'; private $paypalOrderId = 'abc';
private $authorizationId = 'qwe'; private $authorizationId = 'qwe';
private $amount = 42.0; private $amount = 42.0;
@ -37,7 +42,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
private $paymentsEndpoint; private $paymentsEndpoint;
private $notice; private $notice;
private $config; private $config;
private $subscription_helperauthorization; private $captureId = '123qwe';
private $testee; private $testee;
public function setUp(): void { public function setUp(): void {
@ -79,7 +84,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
$this->paymentsEndpoint $this->paymentsEndpoint
->expects('capture') ->expects('capture')
->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency))) ->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency)))
->andReturn($this->createCapture(CaptureStatus::COMPLETED)); ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED));
$this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder)); $this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder));
} }
@ -99,7 +104,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
$this->paymentsEndpoint $this->paymentsEndpoint
->expects('capture') ->expects('capture')
->with($authorizations[2]->id(), equalTo(new Money($this->amount, $this->currency))) ->with($authorizations[2]->id(), equalTo(new Money($this->amount, $this->currency)))
->andReturn($this->createCapture(CaptureStatus::COMPLETED)); ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED));
$this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder)); $this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder));
} }
@ -150,12 +155,13 @@ class AuthorizedPaymentsProcessorTest extends TestCase
$this->paymentsEndpoint $this->paymentsEndpoint
->expects('capture') ->expects('capture')
->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency))) ->with($this->authorizationId, equalTo(new Money($this->amount, $this->currency)))
->andReturn($this->createCapture(CaptureStatus::COMPLETED)); ->andReturn($this->createCapture($this->captureId, CaptureStatus::COMPLETED));
$this->wcOrder->shouldReceive('payment_complete')->andReturn(true); $this->wcOrder->shouldReceive('payment_complete')->andReturn(true);
$this->wcOrder->expects('add_order_note'); $this->wcOrder->expects('add_order_note')->twice();
$this->wcOrder->expects('update_meta_data'); $this->wcOrder->expects('update_meta_data');
$this->wcOrder->expects('save'); $this->wcOrder->expects('set_transaction_id')->with($this->captureId);
$this->wcOrder->shouldReceive('save')->atLeast()->times(1);
$this->assertTrue( $this->assertTrue(
$this->testee->capture_authorized_payment($this->wcOrder) $this->testee->capture_authorized_payment($this->wcOrder)
@ -248,8 +254,11 @@ class AuthorizedPaymentsProcessorTest extends TestCase
return new Authorization($id, new AuthorizationStatus($status)); return new Authorization($id, new AuthorizationStatus($status));
} }
private function createCapture(string $status): Capture { private function createCapture(string $id, string $status): Capture {
$capture = Mockery::mock(Capture::class); $capture = Mockery::mock(Capture::class);
$capture
->shouldReceive('id')
->andReturn($id);
$capture $capture
->shouldReceive('status') ->shouldReceive('status')
->andReturn(new CaptureStatus($status)); ->andReturn(new CaptureStatus($status));

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() {
} }
} }

View file

@ -3,5 +3,10 @@ declare(strict_types=1);
class WC_Payment_Gateway_CC class WC_Payment_Gateway_CC
{ {
public function init_settings() {}
public function process_admin_options() {}
protected function get_return_url($wcOrder) {
return $wcOrder;
}
} }

View file

@ -3,13 +3,13 @@
* Plugin Name: WooCommerce PayPal Payments * Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 1.9.1 * Version: 1.9.2
* Author: WooCommerce * Author: WooCommerce
* Author URI: https://woocommerce.com/ * Author URI: https://woocommerce.com/
* License: GPL-2.0 * License: GPL-2.0
* Requires PHP: 7.1 * Requires PHP: 7.1
* WC requires at least: 3.9 * WC requires at least: 3.9
* WC tested up to: 6.6 * WC tested up to: 6.7
* Text Domain: woocommerce-paypal-payments * Text Domain: woocommerce-paypal-payments
* *
* @package WooCommerce\PayPalCommerce * @package WooCommerce\PayPalCommerce
@ -24,6 +24,8 @@ define( 'PAYPAL_SANDBOX_API_URL', 'https://api.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2022-04-13' ); define( 'PAYPAL_INTEGRATION_DATE', '2022-04-13' );
define( 'PPCP_FLAG_SUBSCRIPTION', true ); define( 'PPCP_FLAG_SUBSCRIPTION', true );
define( 'PPCP_FLAG_OXXO', apply_filters( 'woocommerce_paypal_payments_enable_oxxo_feature', false ) );
define( 'PPCP_FLAG_SEPARATE_APM_BUTTONS', apply_filters( 'woocommerce_paypal_payments_enable_separate_apm_buttons_feature', false ) );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' ); ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );