Merge branch 'trunk' into PCP-808-1-9-1-test-1-improve-pui-gateway-availability-on-pay-for-order-page-with-unsupported-currency

This commit is contained in:
dinamiko 2022-08-09 15:45:55 +02:00
commit 698f560dab
69 changed files with 2948 additions and 830 deletions

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

@ -29,7 +29,11 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
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\PayUponInvoice\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNetSessionId;
@ -38,13 +42,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFac
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
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\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
@ -55,11 +60,10 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
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' );
$settings_renderer = $container->get( 'wcgateway.settings.render' );
$funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' );
$authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' );
$settings = $container->get( 'wcgateway.settings' );
$session_handler = $container->get( 'session.handler' );
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
@ -68,8 +72,6 @@ return array(
$subscription_helper = $container->get( 'subscription.helper' );
$page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' );
$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' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$api_shop_country = $container->get( 'api.shop.country' );
@ -77,7 +79,6 @@ return array(
$settings_renderer,
$funding_source_renderer,
$order_processor,
$authorized_payments,
$settings,
$session_handler,
$refund_processor,
@ -87,14 +88,11 @@ return array(
$page_id,
$environment,
$payment_token_repository,
$container->get( 'api.factory.shipping-preference' ),
$logger,
$payments_endpoint,
$order_endpoint,
$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' );
$settings_renderer = $container->get( 'wcgateway.settings.render' );
$authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' );
@ -133,27 +131,43 @@ return array(
$payments_endpoint
);
},
'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways {
'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway {
return new CardButtonGateway(
$container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.order-processor' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'session.handler' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'subscription.helper' ),
$container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
$container->get( 'onboarding.environment' ),
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways {
$session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.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'] ) ) : '';
$tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['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' ) ) {
return false;
}
$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' ) ) {
return '';
}
@ -164,36 +178,70 @@ return array(
return $ppcp_tab ? $ppcp_tab : $section;
},
'wcgateway.settings' => static function ( ContainerInterface $container ): Settings {
'wcgateway.settings' => static function ( ContainerInterface $container ): 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' );
$settings = $container->get( 'wcgateway.settings' );
return new ConnectAdminNotice( $state, $settings );
},
'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): DccWithoutPayPalAdminNotice {
$state = $container->get( 'onboarding.state' );
$settings = $container->get( 'wcgateway.settings' );
$is_payments_page = $container->get( 'wcgateway.is-wc-payments-page' );
$is_ppcp_settings_page = $container->get( 'wcgateway.is-ppcp-settings-page' );
return new DccWithoutPayPalAdminNotice( $state, $settings, $is_payments_page, $is_ppcp_settings_page );
'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
return new GatewayWithoutPayPalAdminNotice(
CreditCardGateway::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' =>
'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 {
return new AuthorizeOrderActionNotice();
},
'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer {
'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer {
return new SectionsRenderer(
$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' );
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' );
$state = $container->get( 'onboarding.state' );
$fields = $container->get( 'wcgateway.settings.fields' );
@ -213,7 +261,7 @@ return array(
$page_id
);
},
'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener {
'wcgateway.settings.listener' => static function ( ContainerInterface $container ): SettingsListener {
$settings = $container->get( 'wcgateway.settings' );
$fields = $container->get( 'wcgateway.settings.fields' );
$webhook_registrar = $container->get( 'webhook.registrar' );
@ -235,7 +283,7 @@ return array(
$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' );
$order_endpoint = $container->get( 'api.endpoint.order' );
@ -260,13 +308,13 @@ return array(
$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' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
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' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
@ -282,23 +330,23 @@ return array(
$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' );
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' );
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' );
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();
},
'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array {
'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array {
$state = $container->get( 'onboarding.state' );
assert( $state instanceof State );
@ -861,6 +909,40 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'card_billing_data_mode' => array(
'title' => __( 'Card billing data handling', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
'desc_tip' => true,
'description' => __( 'Using the WC form data increases convenience for the customers, but can cause issues if card details do not match the billing data in the checkout form.', 'woocommerce-paypal-payments' ),
'default' => $container->get( 'wcgateway.settings.card_billing_data_mode.default' ),
'options' => array(
CardBillingMode::USE_WC => __( 'Use WC checkout form data (do not show any address fields)', 'woocommerce-paypal-payments' ),
CardBillingMode::MINIMAL_INPUT => __( 'Request only name and postal code', 'woocommerce-paypal-payments' ),
CardBillingMode::NO_WC => __( 'Do not use WC checkout form data (request all address fields)', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => array( 'paypal', CardButtonGateway::ID ),
),
'allow_card_button_gateway' => array(
'title' => __( 'Separate Card Button from PayPal gateway', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
'label' => __( 'Enable a separate payment gateway for the branded PayPal Debit or Credit Card button.', 'woocommerce-paypal-payments' ),
'description' => __( 'By default, the Debit or Credit Card button is displayed in the PayPal Checkout payment gateway. This setting creates a second gateway for the Card button.', 'woocommerce-paypal-payments' ),
'default' => $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'paypal',
),
// General button styles.
'button_style_heading' => array(
@ -2071,10 +2153,14 @@ return array(
$fields['disable_cards']['options'] = $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;
},
'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array {
'wcgateway.all-funding-sources' => static function( ContainerInterface $container ): array {
return array(
'card' => _x( 'Credit or debit cards', 'Name of payment method', 'woocommerce-paypal-payments' ),
'credit' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
@ -2092,28 +2178,28 @@ return array(
);
},
'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset {
'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset {
return new CheckoutPayPalAddressPreset(
$container->get( 'session.handler' )
);
},
'wcgateway.url' => static function ( ContainerInterface $container ): string {
'wcgateway.url' => static function ( ContainerInterface $container ): string {
return plugins_url(
$container->get( 'wcgateway.relative-path' ),
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/';
},
'wcgateway.absolute-path' => static function( ContainerInterface $container ): string {
'wcgateway.absolute-path' => static function( ContainerInterface $container ): string {
return plugin_dir_path(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
) .
$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' );
$endpoint = $container->get( 'api.endpoint.order' );
$prefix = $container->get( 'api.prefix' );
@ -2124,40 +2210,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';
},
'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';
},
'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' );
$live_url_base = $container->get( 'wcgateway.transaction-url-live' );
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' );
$partner_endpoint = $container->get( 'api.endpoint.partners' );
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(
$container->get( 'api.shop.country' )
);
},
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new FundingSourceRenderer(
$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(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -2166,10 +2255,10 @@ return array(
$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();
},
'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway {
'wcgateway.pay-upon-invoice-gateway' => static function ( ContainerInterface $container ): PayUponInvoiceGateway {
return new PayUponInvoiceGateway(
$container->get( 'wcgateway.pay-upon-invoice-order-endpoint' ),
$container->get( 'api.factory.purchase-unit' ),
@ -2177,16 +2266,17 @@ return array(
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$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();
},
'wcgateway.pay-upon-invoice-fraudnet-source-website-id' => static function ( ContainerInterface $container ): FraudNetSourceWebsiteId {
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' );
$source_website_id = $container->get( 'wcgateway.pay-upon-invoice-fraudnet-source-website-id' );
return new FraudNet(
@ -2194,16 +2284,18 @@ return array(
(string) $source_website_id()
);
},
'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper {
return new PayUponInvoiceHelper();
'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper {
return new PayUponInvoiceHelper(
$container->get( 'wcgateway.checkout-helper' )
);
},
'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus {
'wcgateway.pay-upon-invoice-product-status' => static function( ContainerInterface $container ): PayUponInvoiceProductStatus {
return new PayUponInvoiceProductStatus(
$container->get( 'wcgateway.settings' ),
$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(
$container->get( 'wcgateway.url' ),
$container->get( 'wcgateway.pay-upon-invoice-fraudnet' ),
@ -2217,10 +2309,36 @@ return array(
$container->get( 'wcgateway.current-ppcp-settings-page-id' ),
$container->get( 'wcgateway.pay-upon-invoice-product-status' ),
$container->get( 'wcgateway.pay-upon-invoice-helper' ),
$container->get( 'wcgateway.checkout-helper' ),
$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' );
/**
@ -2232,7 +2350,7 @@ return array(
);
},
'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool {
'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool {
try {
$token = $container->get( 'api.bearer' )->bearer();
return $token->vaulting_available();
@ -2241,7 +2359,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' );
if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) {
@ -2263,7 +2381,7 @@ return array(
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-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' );
@ -2271,4 +2389,32 @@ return array(
return $pay_later_label;
},
'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string {
return $container->get( 'api.shop.is-latin-america' ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC;
},
'wcgateway.settings.card_billing_data_mode' => static function ( ContainerInterface $container ): string {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface );
return $settings->has( 'card_billing_data_mode' ) ?
(string) $settings->get( 'card_billing_data_mode' ) :
$container->get( 'wcgateway.settings.card_billing_data_mode.default' );
},
'wcgateway.settings.allow_card_button_gateway.default' => static function ( ContainerInterface $container ): bool {
return $container->get( 'api.shop.is-latin-america' );
},
'wcgateway.settings.allow_card_button_gateway' => static function ( ContainerInterface $container ): bool {
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' );
},
);

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;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Container\ContainerInterface;
@ -59,9 +60,10 @@ class DisableGateways {
if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) {
return $methods;
}
if ( $this->disable_both_gateways() ) {
if ( $this->disable_all_gateways() ) {
unset( $methods[ PayPalGateway::ID ] );
unset( $methods[ CreditCardGateway::ID ] );
unset( $methods[ CardButtonGateway::ID ] );
return $methods;
}
@ -77,21 +79,15 @@ class DisableGateways {
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 ] );
}
/**
* Whether both gateways should be disabled or not.
* Whether all gateways should be disabled or not.
*
* @return bool
*/
private function disable_both_gateways() : bool {
private function disable_all_gateways() : bool {
if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) {
return true;
}
@ -110,22 +106,20 @@ class DisableGateways {
* @return 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();
if ( ! $order ) {
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;
}
}

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
@ -51,7 +52,7 @@ class ReturnUrlEndpoint {
/**
* Handles the incoming request.
*/
public function handle_request() {
public function handle_request(): void {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['token'] ) ) {
@ -68,7 +69,12 @@ class ReturnUrlEndpoint {
}
$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();
}

View file

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

View file

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

View file

@ -9,20 +9,30 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
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\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface;
@ -31,7 +41,8 @@ use Psr\Container\ContainerInterface;
*/
class CreditCardGateway extends \WC_Payment_Gateway_CC {
use ProcessPaymentTrait;
use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait,
GatewaySettingsRendererTrait;
const ID = 'ppcp-credit-card-gateway';
@ -203,15 +214,25 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
Environment $environment,
PaymentsEndpoint $payments_endpoint
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->order_processor = $order_processor;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings_renderer = $settings_renderer;
$this->config = $config;
$this->module_url = $module_url;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->state = $state;
$this->transaction_url_provider = $transaction_url_provider;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->environment = $environment;
$this->payments_endpoint = $payments_endpoint;
if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' );
@ -261,18 +282,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
'process_admin_options',
)
);
$this->module_url = $module_url;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->state = $state;
}
/**
@ -295,20 +304,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
remove_action( 'gettext', 'replace_credit_card_cvv_label' );
}
/**
* 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.
*
@ -409,6 +404,158 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
return $this->is_enabled();
}
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
/**
* If customer has chosen a saved credit card payment.
*/
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id );
$tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( 'Saved card token not found.' ) )
);
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
''
);
try {
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
);
$this->add_paypal_meta( $wc_order, $order, $this->environment );
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) )
);
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) )
);
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->is_free_trial_order( $wc_order ) ) {
$this->authorized_payments_processor->void_authorizations( $order );
$wc_order->payment_complete();
} elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
return $this->handle_payment_success( $wc_order );
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
if ( $saved_credit_card ) {
update_post_meta( $order_id, 'payment_token_id', $saved_credit_card );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.
@ -500,11 +647,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
}
/**
* Returns the environment.
* Returns the settings renderer.
*
* @return Environment
* @return SettingsRenderer
*/
protected function environment(): Environment {
return $this->environment;
protected function settings_renderer(): SettingsRenderer {
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;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
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\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
@ -32,7 +35,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
*/
class PayPalGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait;
use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait;
const ID = 'ppcp-gateway';
const INTENT_META_KEY = '_ppcp_paypal_intent';
@ -63,13 +66,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $order_processor;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/**
* The settings.
*
@ -119,27 +115,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
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.
*
@ -178,30 +153,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
/**
* PayPalGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments 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 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 PaymentTokenRepository $payment_token_repository The payment token repository.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @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.
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source 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 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 PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
* @param string $api_shop_country The api shop country.
*/
public function __construct(
SettingsRenderer $settings_renderer,
FundingSourceRenderer $funding_source_renderer,
OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorized_payments_processor,
ContainerInterface $config,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
@ -211,37 +181,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $page_id,
Environment $environment,
PaymentTokenRepository $payment_token_repository,
ShippingPreferenceFactory $shipping_preference_factory,
LoggerInterface $logger,
PaymentsEndpoint $payments_endpoint,
OrderEndpoint $order_endpoint,
string $api_shop_country
) {
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings_renderer = $settings_renderer;
$this->funding_source_renderer = $funding_source_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->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments = $authorized_payments_processor;
$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;
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->funding_source_renderer = $funding_source_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->page_id = $page_id;
$this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
$this->api_shop_country = $api_shop_country;
if ( $this->onboarded ) {
$this->supports = array( 'refunds' );
@ -291,13 +249,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
'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
*/
public function needs_setup(): bool {
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.
*
@ -450,6 +386,130 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
// 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.
*
@ -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 {
return $this->environment;
protected function settings_renderer(): SettingsRenderer {
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\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
@ -115,6 +116,13 @@ class PayUponInvoice {
*/
protected $pui_product_status;
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/**
* The capture factory.
*
@ -137,6 +145,7 @@ class PayUponInvoice {
* @param string $current_ppcp_settings_page_id Current PayPal settings page id.
* @param PayUponInvoiceProductStatus $pui_product_status The PUI product status.
* @param PayUponInvoiceHelper $pui_helper The PUI helper.
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param CaptureFactory $capture_factory The capture factory.
*/
public function __construct(
@ -152,6 +161,7 @@ class PayUponInvoice {
string $current_ppcp_settings_page_id,
PayUponInvoiceProductStatus $pui_product_status,
PayUponInvoiceHelper $pui_helper,
CheckoutHelper $checkout_helper,
CaptureFactory $capture_factory
) {
$this->module_url = $module_url;
@ -166,6 +176,7 @@ class PayUponInvoice {
$this->current_ppcp_settings_page_id = $current_ppcp_settings_page_id;
$this->pui_product_status = $pui_product_status;
$this->pui_helper = $pui_helper;
$this->checkout_helper = $checkout_helper;
$this->capture_factory = $capture_factory;
}
@ -298,7 +309,7 @@ class PayUponInvoice {
}
},
10,
3
2
);
add_filter(
@ -360,7 +371,7 @@ class PayUponInvoice {
}
$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' ) );
}
@ -477,7 +488,7 @@ class PayUponInvoice {
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() === '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' );
if ( $instructions ) {
add_meta_box(

View file

@ -12,14 +12,13 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice;
use Psr\Log\LoggerInterface;
use RuntimeException;
use WC_Order;
use WC_Order_Item_Product;
use WC_Payment_Gateway;
use WC_Product;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
@ -81,6 +80,13 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
*/
protected $pui_helper;
/**
* The checkout helper.
*
* @var CheckoutHelper
*/
protected $checkout_helper;
/**
* PayUponInvoiceGateway constructor.
*
@ -91,6 +97,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
* @param TransactionUrlProvider $transaction_url_provider The transaction URL provider.
* @param LoggerInterface $logger The logger.
* @param PayUponInvoiceHelper $pui_helper The PUI helper.
* @param CheckoutHelper $checkout_helper The checkout helper.
*/
public function __construct(
PayUponInvoiceOrderEndpoint $order_endpoint,
@ -99,7 +106,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
Environment $environment,
TransactionUrlProvider $transaction_url_provider,
LoggerInterface $logger,
PayUponInvoiceHelper $pui_helper
PayUponInvoiceHelper $pui_helper,
CheckoutHelper $checkout_helper
) {
$this->id = self::ID;
@ -128,6 +136,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
$this->environment = $environment;
$this->transaction_url_provider = $transaction_url_provider;
$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 );
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' );
return array(
'result' => 'failure',

View file

@ -10,279 +10,14 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
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;
use Throwable;
use WC_Order;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
/**
* 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.
*
@ -311,29 +46,86 @@ trait ProcessPaymentTrait {
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.
*
* @param \WC_Order $wc_order The order.
* @param Exception $error The error causing the failure.
* @param WC_Order|null $wc_order The order.
* @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 {
$this->logger->error( 'Payment failed: ' . $error->getMessage() );
protected function handle_payment_failure( ?WC_Order $wc_order, Exception $error ): array {
$this->logger->error( 'Payment failed: ' . $this->format_exception( $error ) );
$wc_order->update_status(
'failed',
__( 'Could not process order. ', 'woocommerce-paypal-payments' ) . $error->getMessage()
);
if ( $wc_order ) {
$wc_order->update_status(
'failed',
$this->format_exception( $error )
);
}
$this->session_handler->destroy_session_data();
wc_add_notice( $error->getMessage(), 'error' );
return array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
);
}
/**
* Returns the environment.
* Handles the payment completion.
*
* @return Environment
* @param WC_Order|null $wc_order The order.
* @param string|null $url The redirect URL.
* @return array The data that can be returned by the gateway process_payment method.
*/
abstract protected function environment(): Environment;
protected function handle_payment_success( ?WC_Order $wc_order, string $url = null ): array {
if ( ! $url ) {
$url = $this->get_return_url( $wc_order );
}
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $url,
);
}
/**
* Outputs the exception, including the inner exception.
*
* @param Throwable $exception The exception to format.
* @return string
*/
protected function format_exception( Throwable $exception ): string {
$output = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine();
$prev = $exception->getPrevious();
if ( ! $prev ) {
return $output;
}
if ( $exception instanceof GatewayGenericException ) {
$output = '';
}
return $output . ' ' . $this->format_exception( $prev );
}
}

View file

@ -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,65 +9,25 @@ 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;
/**
* Class PayUponInvoiceHelper
*/
class PayUponInvoiceHelper {
/**
* Ensures date is valid and at least 18 years back.
* The checkout helper.
*
* @param string $date The date.
* @param string $format The date format.
* @return bool
* @var CheckoutHelper
*/
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;
}
protected $checkout_helper;
/**
* Ensures product is ready for PUI.
* PayUponInvoiceHelper constructor.
*
* @param WC_Product $product WC product.
* @return bool
* @param CheckoutHelper $checkout_helper The checkout helper.
*/
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;
public function __construct( CheckoutHelper $checkout_helper ) {
$this->checkout_helper = $checkout_helper;
}
/**
@ -90,49 +50,8 @@ class PayUponInvoiceHelper {
return false;
}
$cart = WC()->cart ?? null;
if ( $cart && ! is_checkout_pay_page() ) {
$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;
}
}
}
}
}
if ( ! $this->checkout_helper->is_checkout_amount_allowed( 5, 2500 ) ) {
return false;
}
return true;

View file

@ -1,6 +1,6 @@
<?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
*/
@ -9,14 +9,21 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Notice;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\Onboarding\State;
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.
@ -49,17 +56,20 @@ class DccWithoutPayPalAdminNotice {
/**
* ConnectAdminNotice constructor.
*
* @param string $id The gateway ID.
* @param State $state The state.
* @param ContainerInterface $settings The settings.
* @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.
*/
public function __construct(
string $id,
State $state,
ContainerInterface $settings,
bool $is_payments_page,
bool $is_ppcp_settings_page
) {
$this->id = $id;
$this->state = $state;
$this->settings = $settings;
$this->is_payments_page = $is_payments_page;
@ -76,12 +86,20 @@ class DccWithoutPayPalAdminNotice {
return null;
}
$gateway = $this->get_gateway();
if ( ! $gateway ) {
return null;
}
$name = $gateway->get_method_title();
$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'
),
$name,
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' )
);
return new Message( $message, 'warning' );
@ -93,9 +111,29 @@ class DccWithoutPayPalAdminNotice {
* @return bool
*/
protected function should_display(): bool {
return State::STATE_ONBOARDED === $this->state->current_state()
&& ( $this->is_payments_page || $this->is_ppcp_settings_page )
&& ( $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) )
&& ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) );
if ( State::STATE_ONBOARDED !== $this->state->current_state() ||
( ! $this->is_payments_page && ! $this->is_ppcp_settings_page ) ) {
return false;
}
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
return false;
}
$gateway = $this->get_gateway();
return $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
}
/**
* Returns the gateway object or null.
*
* @return WC_Payment_Gateway|null
*/
protected function get_gateway(): ?WC_Payment_Gateway {
$gateways = WC()->payment_gateways->payment_gateways();
if ( ! isset( $gateways[ $this->id ] ) ) {
return null;
}
return $gateways[ $this->id ];
}
}

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
@ -34,6 +35,7 @@ trait PageMatcherTrait {
$gateway_page_id_map = array(
PayPalGateway::ID => 'paypal',
CreditCardGateway::ID => 'dcc', // TODO: consider using just the gateway ID for PayPal and DCC too.
CardButtonGateway::ID => CardButtonGateway::ID,
WebhooksStatusPage::ID => WebhooksStatusPage::ID,
);
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;
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;
/**
@ -29,21 +27,21 @@ class SectionsRenderer {
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.
*
* @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 string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param array<string, string> $sections Key - page/gateway ID, value - displayed text.
*/
public function __construct( string $page_id, string $api_shop_country ) {
$this->page_id = $page_id;
$this->api_shop_country = $api_shop_country;
public function __construct( string $page_id, array $sections ) {
$this->page_id = $page_id;
$this->sections = $sections;
}
/**
@ -58,30 +56,22 @@ class SectionsRenderer {
/**
* Renders the Sections tab.
*/
public function render() {
public function render(): void {
if ( ! $this->should_render() ) {
return;
}
$sections = array(
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 ) {
unset( $sections[ PayUponInvoiceGateway::ID ] );
}
echo '<ul class="subsubsub">';
$array_keys = array_keys( $sections );
$array_keys = array_keys( $this->sections );
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' );
foreach ( $this->sections as $id => $label ) {
$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,
// 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.
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id );
}
echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
}

View file

@ -29,7 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
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\Settings\SectionsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -164,11 +164,15 @@ class WCGatewayModule implements ModuleInterface {
$notices[] = $connect_message;
}
$dcc_without_paypal_notice = $c->get( 'wcgateway.notice.dcc-without-paypal' );
assert( $dcc_without_paypal_notice instanceof DccWithoutPayPalAdminNotice );
$dcc_without_paypal_message = $dcc_without_paypal_notice->message();
if ( $dcc_without_paypal_message ) {
$notices[] = $dcc_without_paypal_message;
foreach ( array(
$c->get( 'wcgateway.notice.dcc-without-paypal' ),
$c->get( 'wcgateway.notice.card-button-without-paypal' ),
) as $gateway_without_paypal_notice ) {
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' );
@ -231,6 +235,10 @@ class WCGatewayModule implements ModuleInterface {
if ( 'DE' === $c->get( 'api.shop.country' ) && 'EUR' === $c->get( 'api.shop.currency' ) ) {
( $c->get( 'wcgateway.pay-upon-invoice' ) )->init();
}
if ( defined( 'PPCP_FLAG_OXXO' ) && PPCP_FLAG_OXXO === true ) {
( $c->get( 'wcgateway.oxxo' ) )->init();
}
}
);
@ -260,6 +268,18 @@ class WCGatewayModule implements ModuleInterface {
10,
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();
}
);
}
/**
@ -284,10 +304,18 @@ class WCGatewayModule implements ModuleInterface {
$methods[] = $container->get( 'wcgateway.credit-card-gateway' );
}
if ( $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) {
$methods[] = $container->get( 'wcgateway.card-button-gateway' );
}
if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) {
$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;
}
);

View file

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