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

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

View file

@ -29,6 +29,7 @@ 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;
@ -48,7 +49,7 @@ 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;
@ -59,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' );
@ -72,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' );
@ -81,7 +79,6 @@ return array(
$settings_renderer,
$funding_source_renderer,
$order_processor,
$authorized_payments,
$settings,
$session_handler,
$refund_processor,
@ -91,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' );
@ -137,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 ), 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 '';
}
@ -168,36 +178,47 @@ 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' )
);
},
'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus {
'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' );
@ -217,7 +238,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' );
@ -239,7 +260,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' );
@ -264,13 +285,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' );
@ -286,23 +307,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 );
@ -865,6 +886,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(
@ -2078,7 +2133,7 @@ return array(
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' ),
@ -2096,28 +2151,28 @@ return array(
);
},
'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset {
'wcgateway.checkout.address-preset' => static function( ContainerInterface $container ): CheckoutPayPalAddressPreset {
return new CheckoutPayPalAddressPreset(
$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' );
@ -2128,43 +2183,43 @@ return array(
);
},
'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string {
'wcgateway.transaction-url-sandbox' => static function ( ContainerInterface $container ): string {
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
},
'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.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper();
},
'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint {
'wcgateway.pay-upon-invoice-order-endpoint' => static function ( ContainerInterface $container ): PayUponInvoiceOrderEndpoint {
return new PayUponInvoiceOrderEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -2173,10 +2228,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' ),
@ -2188,13 +2243,13 @@ return array(
$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(
@ -2202,18 +2257,18 @@ return array(
(string) $source_website_id()
);
},
'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper {
'wcgateway.pay-upon-invoice-helper' => static function( ContainerInterface $container ): PayUponInvoiceHelper {
return new PayUponInvoiceHelper(
$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' ),
@ -2231,14 +2286,14 @@ return array(
$container->get( 'api.factory.capture' )
);
},
'wcgateway.oxxo' => static function( ContainerInterface $container ): OXXO {
'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 {
'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway {
return new OXXOGateway(
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.purchase-unit' ),
@ -2246,7 +2301,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint {
'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint {
return new OXXOEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
@ -2255,7 +2310,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' );
/**
@ -2267,7 +2322,7 @@ return array(
);
},
'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool {
'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool {
try {
$token = $container->get( 'api.bearer' )->bearer();
return $token->vaulting_available();
@ -2276,7 +2331,7 @@ return array(
}
},
'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string {
'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string {
$vaulting_label = __( 'Enable saved cards and subscription features on your store.', 'woocommerce-paypal-payments' );
if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) {
@ -2298,7 +2353,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' );
@ -2306,4 +2361,28 @@ 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 {
$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

@ -52,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'] ) ) {

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

@ -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,118 @@ 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 ) {
if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) {
$wc_order->update_status(
'failed',
__( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? ''
);
$this->session_handler->increment_insufficient_funding_tries();
if ( $this->session_handler->insufficient_funding_tries() >= 3 ) {
return $this->handle_payment_failure(
null,
new Exception(
__( 'Please use a different payment method.', 'woocommerce-paypal-payments' ),
$error->getCode(),
$error
)
);
}
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
return array(
'result' => 'success',
'redirect' => $url,
);
}
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.
*
@ -503,11 +551,11 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
/**
* Returns the environment.
* Returns the settings renderer.
*
* @return Environment
* @return SettingsRenderer
*/
protected function environment(): Environment {
return $this->environment;
protected function settings_renderer(): SettingsRenderer {
return $this->settings_renderer;
}
}

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

@ -98,6 +98,9 @@ class CheckoutHelper {
if ( $date_time && time() < strtotime( '+18 years', $date_time ) ) {
return false;
}
if ( $date_time < strtotime( '-100 years', time() ) ) {
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

@ -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\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
@ -58,7 +59,7 @@ class SectionsRenderer {
/**
* Renders the Sections tab.
*/
public function render() {
public function render(): void {
if ( ! $this->should_render() ) {
return;
}
@ -66,6 +67,7 @@ class SectionsRenderer {
$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' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ),
);
@ -80,8 +82,8 @@ class SectionsRenderer {
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' );
if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $id );
}
echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
}

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' );
@ -294,6 +298,10 @@ 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' );
}