Merge branch 'trunk' into PCP-82-fr-now-has-pay-in-4

This commit is contained in:
Kirill Braslavsky 2021-02-24 08:33:01 +02:00
commit 039a01211e
24 changed files with 800 additions and 109 deletions

View file

@ -2,9 +2,9 @@ name: PHP Composer
on:
push:
branches: [ master ]
branches: [ trunk ]
pull_request:
branches: [ master ]
branches: [ trunk ]
jobs:
build:
@ -14,9 +14,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Packagist.com Auth
run: composer config --global --auth http-basic.repo.packagist.com token 5863d6c7b313709512b6d5b7f93c7fad8807a5aef92b2155019f7f41ccac
- name: Validate composer.json and composer.lock
run: composer validate

View file

@ -1,5 +1,10 @@
*** Changelog ***
= 1.1.0 - 2021-02-01 =
* Add - Buy Now Pay Later for UK. #104
* Add - DE now has 12 month installments. #106
* Fix - Check phone for empty string. #102
= 1.0.4 - 2021-01-18 =
* Fix - Check if WooCommerce is active before initialize. #99
* Fix - Payment buttons only visible on order-pay site when Mini Cart is enabled; payment fails. #96

View file

@ -6,7 +6,6 @@
"require": {
"dhii/module-interface": "0.1",
"psr/container": "^1.0",
"oomphinc/composer-installers-extender": "^1.1",
"container-interop/service-provider": "^0.4.0",
"dhii/containers": "v0.1.0-alpha1",
"dhii/wp-containers": "v0.1.0-alpha1",

View file

@ -74,12 +74,13 @@ class PartnerReferralsData {
return array(
'partner_config_override' => array(
'partner_logo_url' => 'https://connect.woocommerce.com/images/woocommerce_logo.png',
'return_url' => admin_url(
'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway'
'return_url' => apply_filters(
'woocommerce_paypal_payments_partner_config_override_return_url',
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' )
),
'return_url_description' => __(
'Return to your shop.',
'woocommerce-paypal-payments'
'return_url_description' => apply_filters(
'woocommerce_paypal_payments_partner_config_override_return_url_description',
__( 'Return to your shop.', 'woocommerce-paypal-payments' )
),
'show_add_credit_card' => true,
),

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
@ -16,7 +17,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -215,7 +215,11 @@ class CreateOrderEndpoint implements EndpointInterface {
$number = preg_replace( '/[^0-9]/', '', $number );
$number = substr( $number, 0, 14 );
$data['payer']['phone']['phone_number']['national_number'] = $number;
if ( empty( $data['payer']['phone']['phone_number']['national_number'] ) ) {
unset( $data['payer']['phone'] );
}
}
$payer = $this->payer_factory->from_paypal_response( json_decode( wp_json_encode( $data['payer'] ) ) );
}
return $payer;

View file

@ -22,6 +22,8 @@ class MessagesApply {
*/
private $countries = array(
'US',
'DE',
'GB',
'FR',
);

View file

@ -1,22 +1,104 @@
function onboardingCallback(authCode, sharedId) {
const sandboxSwitchElement = document.querySelector('#ppcp-sandbox_on');
fetch(
PayPalCommerceGatewayOnboarding.endpoint,
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(
{
authCode: authCode,
sharedId: sharedId,
nonce: PayPalCommerceGatewayOnboarding.nonce,
env: sandboxSwitchElement && sandboxSwitchElement.checked ? 'sandbox' : 'production'
}
)
// Onboarding.
const ppcp_onboarding = {
BUTTON_SELECTOR: '[data-paypal-onboard-button]',
PAYPAL_JS_ID: 'ppcp-onboarding-paypal-js',
_timeout: false,
init: function() {
document.addEventListener('DOMContentLoaded', this.reload);
},
reload: function() {
const buttons = document.querySelectorAll(ppcp_onboarding.BUTTON_SELECTOR);
if (0 === buttons.length) {
return;
}
);
// Add event listeners to buttons.
buttons.forEach(
(element) => {
if (element.hasAttribute('data-ppcp-button-initialized')) {
return;
}
element.addEventListener(
'click',
(e) => {
if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) {
e.preventDefault();
}
}
);
}
);
// Clear any previous PayPal scripts.
[ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach(
(scriptID) => {
const scriptTag = document.getElementById(scriptID);
if (scriptTag) {
scriptTag.parentNode.removeChild(scriptTag);
}
if ('undefined' !== typeof window.PAYPAL) {
delete window.PAYPAL;
}
}
);
// Load PayPal scripts.
const paypalScriptTag = document.createElement('script');
paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID;
paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url;
document.body.append(paypalScriptTag);
if (ppcp_onboarding._timeout) {
clearTimeout(ppcp_onboarding._timeout);
}
ppcp_onboarding._timeout = setTimeout(
() => {
buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); });
if ('undefined' !== window.PAYPAL.apps.Signup) {
window.PAYPAL.apps.Signup.render();
}
},
1000
);
},
loginSeller: function(env, authCode, sharedId) {
fetch(
PayPalCommerceGatewayOnboarding.endpoint,
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(
{
authCode: authCode,
sharedId: sharedId,
nonce: PayPalCommerceGatewayOnboarding.nonce,
env: env
}
)
}
);
},
};
function ppcp_onboarding_sandboxCallback(...args) {
return ppcp_onboarding.loginSeller('sandbox', ...args);
}
function ppcp_onboarding_productionCallback(...args) {
return ppcp_onboarding.loginSeller('production', ...args);
}
/**
@ -174,21 +256,23 @@ const disconnect = (event) => {
);
// Prevent a possibly dirty form arising from this particular checkbox.
sandboxSwitchElement.addEventListener(
'click',
(event) => {
const value = event.target.checked;
if (sandboxSwitchElement) {
sandboxSwitchElement.addEventListener(
'click',
(event) => {
const value = event.target.checked;
toggleSandboxProduction( ! value );
toggleSandboxProduction( ! value );
event.preventDefault();
event.stopPropagation();
setTimeout( () => {
event.target.checked = value;
}, 1
);
}
);
event.preventDefault();
event.stopPropagation();
setTimeout( () => {
event.target.checked = value;
}, 1
);
}
);
}
// document.querySelectorAll('#mainform input[type="checkbox"]').forEach(
// (checkbox) => {
@ -207,6 +291,8 @@ const disconnect = (event) => {
}
)
}
)
);
// Onboarding buttons.
ppcp_onboarding.init();
})();

View file

@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\Onboarding\Onboarding_REST_Controller;
return array(
'api.sandbox-host' => static function ( $container ): string {
@ -207,4 +208,7 @@ return array(
$partner_referrals_sandbox
);
},
'onboarding.rest' => static function( $container ) : Onboarding_REST_Controller {
return new Onboarding_REST_Controller( $container );
},
);

View file

@ -51,7 +51,7 @@ class OnboardingAssets {
LoginSellerEndpoint $login_seller_endpoint
) {
$this->module_url = $module_url;
$this->module_url = untrailingslashit( $module_url );
$this->state = $state;
$this->login_seller_endpoint = $login_seller_endpoint;
}
@ -78,9 +78,6 @@ class OnboardingAssets {
1,
true
);
if ( ! $this->should_render_onboarding_script() ) {
return false;
}
$url = $this->module_url . '/assets/js/onboarding.js';
wp_register_script(
@ -93,15 +90,25 @@ class OnboardingAssets {
wp_localize_script(
'ppcp-onboarding',
'PayPalCommerceGatewayOnboarding',
array(
'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ),
)
$this->get_script_data()
);
return true;
}
/**
* Returns the data associated to the onboarding script.
*
* @return array
*/
public function get_script_data() {
return array(
'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ),
'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js',
);
}
/**
* Enqueues the necessary scripts.
*

View file

@ -52,6 +52,23 @@ class OnboardingRenderer {
$this->sandbox_partner_referrals = $sandbox_partner_referrals;
}
/**
* Returns the action URL for the onboarding button/link.
*
* @param boolean $is_production Whether the production or sandbox button should be rendered.
* @return string URL.
*/
public function get_signup_link( bool $is_production ) {
$args = array(
'displayMode' => 'minibrowser',
);
$url = $is_production ? $this->production_partner_referrals->signup_link() : $this->sandbox_partner_referrals->signup_link();
$url = add_query_arg( $args, $url );
return $url;
}
/**
* Renders the "Connect to PayPal" button.
*
@ -59,27 +76,12 @@ class OnboardingRenderer {
*/
public function render( bool $is_production ) {
try {
$args = array(
'displayMode' => 'minibrowser',
$this->render_button(
$this->get_signup_link( $is_production ),
$is_production ? 'connect-to-production' : 'connect-to-sandbox',
$is_production ? __( 'Connect to PayPal', 'woocommerce-paypal-payments' ) : __( 'Connect to PayPal Sandbox', 'woocommerce-paypal-payments' ),
$is_production ? 'production' : 'sandbox'
);
$url = $is_production ? $this->production_partner_referrals->signup_link() : $this->sandbox_partner_referrals->signup_link();
$url = add_query_arg( $args, $url );
$id = $is_production ? 'connect-to-production' : 'connect-to-sandbox';
$label = $is_production ? __( 'Connect to PayPal', 'woocommerce-paypal-payments' ) : __( 'Connect to PayPal Sandbox', 'woocommerce-paypal-payments' );
$this->render_button(
$url,
$id,
$label
);
$script_url = 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js'; ?>
<script>document.querySelectorAll('[data-paypal-onboard-complete=onboardingCallback]').forEach( (element) => { element.addEventListener('click', (e) => {if ('undefined' === typeof PAYPAL ) e.preventDefault(); }) });</script>
<script
id="paypal-js"
src="<?php echo esc_url( $script_url ); ?>"
></script>
<?php
} catch ( RuntimeException $exception ) {
esc_html_e(
'We could not properly connect to PayPal. Please reload the page to continue',
@ -94,14 +96,16 @@ class OnboardingRenderer {
* @param string $url The url of the button.
* @param string $id The ID of the button.
* @param string $label The button text.
* @param string $env The environment ('production' or 'sandbox').
*/
private function render_button( string $url, string $id, string $label ) {
private function render_button( string $url, string $id, string $label, string $env ) {
?>
<a
target="_blank"
class="button-primary"
id="<?php echo esc_attr( $id ); ?>"
data-paypal-onboard-complete="onboardingCallback"
data-paypal-onboard-complete="ppcp_onboarding_<?php echo esc_attr( $env ); ?>Callback"
data-paypal-onboard-button="true"
href="<?php echo esc_url( $url ); ?>"
data-paypal-button="true"
>

View file

@ -0,0 +1,321 @@
<?php
/**
* Onboarding REST controller.
*
* @package WooCommerce\PayPalCommerce\Onboarding
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding;
use Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Exposes and handles REST routes related to onboarding.
*/
class Onboarding_REST_Controller {
/**
* REST namespace.
*
* @var string
*/
protected $rest_namespace = 'wc-paypal/v1';
/**
* REST base.
*
* @var string
*/
protected $rest_base = 'onboarding';
/**
* Module container with access to plugin services.
*
* @var ContainerInterface
*/
private $container = null;
/**
* Used to temporarily store URL arguments to add to the return URL associated to a signup link.
*
* @var array
*/
private $return_url_args = array();
/**
* OnboardingRESTController constructor.
*
* @param ContainerInterface $container Module container with access to plugin services.
*/
public function __construct( ContainerInterface $container ) {
$this->container = $container;
}
/**
* Registers REST routes under 'wc-paypal/v1/onboarding'.
* Specifically:
* - `/onboarding/get-params`, which returns information useful to display an onboarding button.
* - `/onboarding/get-status`, which returns information about the current environment and its onboarding state.
* - `/onboarding/set-credentials`, which allows setting merchant/API credentials.
*/
public function register_routes() {
register_rest_route(
$this->rest_namespace,
'/' . $this->rest_base . '/get-params',
array(
'methods' => 'POST',
'callback' => array( $this, 'get_params' ),
'permission_callback' => array( $this, 'check_permission' ),
)
);
register_rest_route(
$this->rest_namespace,
'/' . $this->rest_base . '/get-status',
array(
'methods' => 'GET',
'callback' => array( $this, 'get_status' ),
'permission_callback' => array( $this, 'check_permission' ),
)
);
register_rest_route(
$this->rest_namespace,
'/' . $this->rest_base . '/set-credentials',
array(
'methods' => 'POST',
'callback' => array( $this, 'set_credentials' ),
'permission_callback' => array( $this, 'check_permission' ),
)
);
}
/**
* Validate the requester's permissions.
*
* @param WP_REST_Request $request The request.
* @return bool
*/
public function check_permission( $request ) {
return current_user_can( 'install_plugins' );
}
/**
* Callback for the `/onboarding/get-params` endpoint.
*
* @param WP_REST_Request $request The request.
* @return array
*/
public function get_params( $request ) {
$params = $request->get_json_params();
$environment = ( isset( $params['environment'] ) && in_array( $params['environment'], array( 'production', 'sandbox' ), true ) ) ? $params['environment'] : 'sandbox';
return array(
'scriptURL' => trailingslashit( $this->container->get( 'onboarding.url' ) ) . 'assets/js/onboarding.js',
'scriptData' => $this->container->get( 'onboarding.assets' )->get_script_data(),
'environment' => $environment,
'onboardCompleteCallback' => 'ppcp_onboarding_' . $environment . 'Callback',
'signupLink' => $this->generate_signup_link( $environment, ( ! empty( $params['returnUrlArgs'] ) ? $params['returnUrlArgs'] : array() ) ),
);
}
/**
* Callback for the `/onboarding/get-status` endpoint.
*
* @param WP_REST_Request $request The request.
* @return array
*/
public function get_status( $request ) {
$environment = $this->container->get( 'onboarding.environment' );
$state = $this->container->get( 'onboarding.state' );
return array(
'environment' => $environment->current_environment(),
'onboarded' => ( $state->current_state() >= State::STATE_ONBOARDED ),
'state' => $this->get_onboarding_state_name( $state->current_state() ),
'sandbox' => array(
'state' => $this->get_onboarding_state_name( $state->sandbox_state() ),
'onboarded' => ( $state->sandbox_state() >= State::STATE_ONBOARDED ),
),
'production' => array(
'state' => $this->get_onboarding_state_name( $state->production_state() ),
'onboarded' => ( $state->production_state() >= State::STATE_ONBOARDED ),
),
);
}
/**
* Callback for the `/onboarding/set-credentials` endpoint.
*
* @param WP_REST_Request $request The request.
* @return WP_Error|array
*/
public function set_credentials( $request ) {
static $credential_keys = array(
'merchant_id',
'merchant_email',
'client_id',
'client_secret',
);
// Sanitize params.
$params = array_filter( array_map( 'trim', $request->get_json_params() ) );
// Validate 'environment'.
if ( empty( $params['environment'] ) || ! in_array( $params['environment'], array( 'sandbox', 'production' ), true ) ) {
return new \WP_Error(
'woocommerce_paypal_payments_invalid_environment',
sprintf(
/* translators: placeholder is an arbitrary string. */
__( 'Environment "%s" is invalid. Use "sandbox" or "production".', 'woocommerce-paypal-payments' ),
isset( $params['environment'] ) ? $params['environment'] : ''
),
array( 'status' => 400 )
);
}
// Validate the other fields.
$missing_keys = array_values( array_diff( $credential_keys, array_keys( $params ) ) );
if ( $missing_keys ) {
return new \WP_Error(
'woocommerce_paypal_payments_credentials_incomplete',
sprintf(
/* translators: placeholder is a comma-separated list of fields. */
__( 'Credentials are incomplete. Missing fields: %s.', 'woocommerce-paypal-payments' ),
implode( ', ', $missing_keys )
),
array(
'missing_fields' => $missing_keys,
'status' => 400,
)
);
}
$settings = $this->container->get( 'wcgateway.settings' );
$skip_persist = true;
// Enable gateway.
if ( ! $settings->has( 'enabled' ) || ! $settings->get( 'enabled' ) ) {
$settings->set( 'enabled', true );
$skip_persist = false;
}
foreach ( WC()->payment_gateways->payment_gateways() as $gateway ) {
if ( PayPalGateway::ID === $gateway->id ) {
$gateway->update_option( 'enabled', 'yes' );
break;
}
}
// Update settings.
$sandbox_on = ( 'sandbox' === $params['environment'] );
if ( ! $settings->has( 'sandbox_on' ) || ( (bool) $settings->get( 'sandbox_on' ) !== $sandbox_on ) ) {
$settings->set( 'sandbox_on', $sandbox_on );
$skip_persist = false;
}
foreach ( $credential_keys as $key ) {
$value = $params[ $key ];
$env_key = $key . '_' . $params['environment'];
if ( ! $settings->has( $key ) || ! $settings->has( $env_key ) || $settings->get( $key ) !== $value || $settings->get( $env_key ) !== $value ) {
$settings->set( $key, $value );
$settings->set( $env_key, $value );
$skip_persist = false;
}
}
if ( $skip_persist ) {
return array();
}
$settings->set( 'products_dcc_enabled', null );
if ( ! $settings->persist() ) {
return new \WP_Error(
'woocommerce_paypal_payments_credentials_not_saved',
__( 'An error occurred while saving the credentials.', 'woocommerce-paypal-payments' ),
array(
'status' => 500,
)
);
}
$webhook_registrar = $this->container->get( 'webhook.registrar' );
$webhook_registrar->unregister();
$webhook_registrar->register();
return array();
}
/**
* Appends URL parameters stored in this class to a given URL.
*
* @hooked woocommerce_paypal_payments_partner_config_override_return_url - 10
* @param string $url URL.
* @return string The URL with the stored URL parameters added to it.
*/
public function add_args_to_return_url( $url ) {
return add_query_arg( $this->return_url_args, $url );
}
/**
* Translates an onboarding state to a string.
*
* @param int $state An onboarding state to translate as returned by {@link State} methods.
* @return string A string representing the state: "start", "progressive" or "onboarded".
* @see State::current_state(), State::sandbox_state(), State::production_state().
*/
public function get_onboarding_state_name( $state ) {
$name = 'unknown';
switch ( absint( $state ) ) {
case State::STATE_START:
$name = 'start';
break;
case State::STATE_PROGRESSIVE:
$name = 'progressive';
break;
case State::STATE_ONBOARDED:
$name = 'onboarded';
break;
default:
break;
}
return $name;
}
/**
* Generates a signup link for onboarding for a given environment and optionally adding certain URL arguments
* to the URL users are redirected after completing the onboarding flow.
*
* @param string $environment The environment to use. Either 'sandbox' or 'production'. Defaults to 'sandbox'.
* @param array $url_args An array of URL arguments to add to the return URL via {@link add_query_arg()}.
* @return string
*/
private function generate_signup_link( $environment = 'sandbox', $url_args = array() ) {
$this->return_url_args = ( ! empty( $url_args ) && is_array( $url_args ) ) ? $url_args : array();
if ( $this->return_url_args ) {
add_filter( 'woocommerce_paypal_payments_partner_config_override_return_url', array( $this, 'add_args_to_return_url' ) );
}
$link = $this->container->get( 'onboarding.render' )->get_signup_link( 'production' === $environment );
if ( $this->return_url_args ) {
remove_filter( 'woocommerce_paypal_payments_partner_config_override_return_url', array( $this, 'add_args_to_return_url' ) );
$this->return_url_args = array();
}
return $link;
}
}

View file

@ -41,7 +41,6 @@ class OnboardingModule implements ModuleInterface {
* @param ContainerInterface|null $container The container.
*/
public function run( ContainerInterface $container = null ) {
$asset_loader = $container->get( 'onboarding.assets' );
/**
* The OnboardingAssets.
@ -100,6 +99,10 @@ class OnboardingModule implements ModuleInterface {
$endpoint->handle_request();
}
);
// Initialize REST routes at the appropriate time.
$rest_controller = $container->get( 'onboarding.rest' );
add_action( 'rest_api_init', array( $rest_controller, 'register_routes' ) );
}
/**

View file

@ -541,7 +541,7 @@ return array(
'default' => false,
'desc_tip' => true,
'description' => __(
'If you enable this setting, PayPal will be instructed not to allow the buyer to use funding sources that take additional time to complete (for example, eChecks). Instead, the buyer will be required to use an instant funding source, such as an instant transfer, a credit/debit card, or PayPal Credit.',
'If you enable this setting, PayPal will be instructed not to allow the buyer to use funding sources that take additional time to complete (for example, eChecks). Instead, the buyer will be required to use an instant funding source, such as an instant transfer, a credit/debit card, or Pay Later.',
'woocommerce-paypal-payments'
),
'label' => __( 'Require Instant Payment', 'woocommerce-paypal-payments' ),
@ -603,7 +603,7 @@ return array(
),
'options' => array(
'card' => _x( 'Credit or debit cards', 'Name of payment method', 'woocommerce-paypal-payments' ),
'credit' => _x( 'PayPal Credit', 'Name of payment method', 'woocommerce-paypal-payments' ),
'credit' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
'sepa' => _x( 'SEPA-Lastschrift', 'Name of payment method', 'woocommerce-paypal-payments' ),
'bancontact' => _x( 'Bancontact', 'Name of payment method', 'woocommerce-paypal-payments' ),
'eps' => _x( 'eps', 'Name of payment method', 'woocommerce-paypal-payments' ),
@ -796,7 +796,7 @@ return array(
'gateway' => 'paypal',
),
'message_heading' => array(
'heading' => __( 'Credit Messaging on Checkout', 'woocommerce-paypal-payments' ),
'heading' => __( 'Pay Later on Checkout', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
State::STATE_PROGRESSIVE,
@ -804,7 +804,7 @@ return array(
),
'requirements' => array( 'messages' ),
'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>PayPal Credit messages</a> on checkout to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>Pay Later messages</a> on checkout to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ),
),
'message_enabled' => array(
@ -820,7 +820,7 @@ return array(
'gateway' => 'paypal',
),
'message_layout' => array(
'title' => __( 'Credit Messaging layout', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging layout', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -842,7 +842,7 @@ return array(
'gateway' => 'paypal',
),
'message_logo' => array(
'title' => __( 'Credit Messaging logo', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -866,7 +866,7 @@ return array(
'gateway' => 'paypal',
),
'message_position' => array(
'title' => __( 'Credit Messaging logo position', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo position', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -889,7 +889,7 @@ return array(
'gateway' => 'paypal',
),
'message_color' => array(
'title' => __( 'Credit Messaging text color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging text color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -913,7 +913,7 @@ return array(
'gateway' => 'paypal',
),
'message_flex_color' => array(
'title' => __( 'Credit Messaging color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -940,7 +940,7 @@ return array(
'gateway' => 'paypal',
),
'message_flex_ratio' => array(
'title' => __( 'Credit Messaging ratio', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging ratio', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1099,7 +1099,7 @@ return array(
),
'message_product_heading' => array(
'heading' => __( 'Credit Messaging on Single Product Page', 'woocommerce-paypal-payments' ),
'heading' => __( 'Pay Later on Single Product Page', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
State::STATE_PROGRESSIVE,
@ -1107,7 +1107,7 @@ return array(
),
'requirements' => array( 'messages' ),
'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>PayPal Credit messages</a> on product pages to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>Pay Later messages</a> on product pages to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ),
),
'message_product_enabled' => array(
@ -1123,7 +1123,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_layout' => array(
'title' => __( 'Credit Messaging layout', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging layout', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1145,7 +1145,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_logo' => array(
'title' => __( 'Credit Messaging logo', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1169,7 +1169,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_position' => array(
'title' => __( 'Credit Messaging logo position', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo position', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1192,7 +1192,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_color' => array(
'title' => __( 'Credit Messaging text color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging text color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1216,7 +1216,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_flex_color' => array(
'title' => __( 'Credit Messaging color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1243,7 +1243,7 @@ return array(
'gateway' => 'paypal',
),
'message_product_flex_ratio' => array(
'title' => __( 'Credit Messaging ratio', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging ratio', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1402,7 +1402,7 @@ return array(
),
'message_cart_heading' => array(
'heading' => __( 'Credit Messaging on Cart', 'woocommerce-paypal-payments' ),
'heading' => __( 'Pay Later on Cart', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
State::STATE_PROGRESSIVE,
@ -1410,7 +1410,7 @@ return array(
),
'requirements' => array( 'messages' ),
'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>PayPal Credit messages</a> on your cart page to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'description' => str_replace( '<a>', '<a href="https://www.paypal.com/us/business/buy-now-pay-later">', __( 'Customize the appearance of <a>Pay Later messages</a> on your cart page to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ),
),
'message_cart_enabled' => array(
@ -1426,7 +1426,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_layout' => array(
'title' => __( 'Credit Messaging layout', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging layout', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1448,7 +1448,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_logo' => array(
'title' => __( 'Credit Messaging logo', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1472,7 +1472,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_position' => array(
'title' => __( 'Credit Messaging logo position', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging logo position', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1495,7 +1495,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_color' => array(
'title' => __( 'Credit Messaging text color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging text color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1519,7 +1519,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_flex_color' => array(
'title' => __( 'Credit Messaging color', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1546,7 +1546,7 @@ return array(
'gateway' => 'paypal',
),
'message_cart_flex_ratio' => array(
'title' => __( 'Credit Messaging ratio', 'woocommerce-paypal-payments' ),
'title' => __( 'Pay Later Messaging ratio', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@ -1785,6 +1785,29 @@ return array(
unset( $fields['disable_funding']['options']['card'] );
}
/**
* Set Pay in 3 heading and description for UK.
*/
if ( 'GB' === $country ) {
$fields['message_heading']['heading'] = __( 'Pay Later Messaging on Checkout', 'woocommerce-paypal-payments' );
$fields['message_heading']['description'] = __( 'Display pay later messaging on your site for offers like Pay in 3, which lets customers pay with 3 interest-free monthly payments. Well show messages on your site to promote this feature for you. You may not promote pay later offers with any other content, marketing, or materials.', 'woocommerce-paypal-payments' );
$fields['message_product_heading']['heading'] = __( 'Pay Later Messaging on Single Product Page', 'woocommerce-paypal-payments' );
$fields['message_product_heading']['description'] = __( 'Display pay later messaging on your site for offers like Pay in 3, which lets customers pay with 3 interest-free monthly payments. Well show messages on your site to promote this feature for you. You may not promote pay later offers with any other content, marketing, or materials.', 'woocommerce-paypal-payments' );
$fields['message_cart_heading']['heading'] = __( 'Pay Later Messaging on Cart', 'woocommerce-paypal-payments' );
$fields['message_cart_heading']['description'] = __( 'Display pay later messaging on your site for offers like Pay in 3, which lets customers pay with 3 interest-free monthly payments. Well show messages on your site to promote this feature for you. You may not promote pay later offers with any other content, marketing, or materials.', 'woocommerce-paypal-payments' );
}
/**
* Set Pay Later link for DE
*/
if ( 'DE' === $country ) {
$fields['message_heading']['description'] = str_replace( '<a>', '<a href="https://www.paypal.com/de/webapps/mpp/installments">', __( 'Customize the appearance of <a>Pay Later messages</a> on checkout to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) );
$fields['message_product_heading']['description'] = str_replace( '<a>', '<a href="https://www.paypal.com/de/webapps/mpp/installments">', __( 'Customize the appearance of <a>Pay Later messages</a> on checkout to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) );
$fields['message_cart_heading']['description'] = str_replace( '<a>', '<a href="https://www.paypal.com/de/webapps/mpp/installments">', __( 'Customize the appearance of <a>Pay Later messages</a> on checkout to promote special financing offers, which help increase sales.', 'woocommerce-paypal-payments' ) );
}
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
/**
* Depending on your store location, some credit cards can't be used.

View file

@ -290,7 +290,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
return __(
'Accept PayPal, PayPal Credit and alternative payment types.',
'Accept PayPal, Pay Later and alternative payment types.',
'woocommerce-paypal-payments'
);
}

View file

@ -69,7 +69,7 @@ class Settings implements ContainerInterface {
*/
public function persist() {
update_option( self::KEY, $this->settings );
return update_option( self::KEY, $this->settings );
}

View file

@ -117,10 +117,14 @@ class SettingsListener {
$this->settings->set( 'merchant_email_production', $merchant_email );
}
$this->settings->persist();
$redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' );
do_action( 'woocommerce_paypal_payments_onboarding_before_redirect' );
$redirect_url = apply_filters( 'woocommerce_paypal_payments_onboarding_redirect_url', admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ) );
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
$redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-onboarding-error=1' );
$redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
}
wp_safe_redirect( $redirect_url, 302 );
exit;
}

View file

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

5
patchwork.json Normal file
View file

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

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell,
Requires at least: 5.3
Tested up to: 5.6
Requires PHP: 7.0
Stable tag: 1.0.4
Stable tag: 1.1.0
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -58,6 +58,11 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog ==
= 1.1.0 =
* Add - Buy Now Pay Later for UK. #104
* Add - DE now has 12 month installments. #106
* Fix - Check phone for empty string. #102
= 1.0.4 =
* Fix - Check if WooCommerce is active before initialize. #99
* Fix - Payment buttons only visible on order-pay site when Mini Cart is enabled; payment fails. #96

View file

@ -6,10 +6,12 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use Hamcrest\Matchers;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
@ -238,6 +240,13 @@ class OrderEndpointTest extends TestCase
);
expect('is_wp_error')->with($rawResponse)->andReturn(false);
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(201);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$payment = Mockery::mock(Payments::class);
$capture = Mockery::mock(Capture::class);
$expectedOrder->shouldReceive('purchase_units')->once()->andReturn(['0'=>$purchaseUnit]);
$purchaseUnit->shouldReceive('payments')->once()->andReturn($payment);
$payment->shouldReceive('captures')->once()->andReturn(['0'=>$capture]);
$capture->shouldReceive('status')->once()->andReturn('');
$result = $testee->capture($orderToCapture);
$this->assertEquals($expectedOrder, $result);

View file

@ -16,6 +16,9 @@ class PayerTest extends TestCase
$address
->expects('to_array')
->andReturn(['address']);
$address
->expects('country_code')
->andReturn('UK');
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('to_array')
@ -65,6 +68,9 @@ class PayerTest extends TestCase
$address
->expects('to_array')
->andReturn(['address']);
$address
->expects('country_code')
->andReturn('UK');
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('to_array')
@ -102,6 +108,9 @@ class PayerTest extends TestCase
$address
->expects('to_array')
->andReturn(['address']);
$address
->expects('country_code')
->andReturn('UK');
$phone = null;
$taxInfo = Mockery::mock(PayerTaxInfo::class);
$taxInfo
@ -136,6 +145,9 @@ class PayerTest extends TestCase
$address
->expects('to_array')
->andReturn(['address']);
$address
->expects('country_code')
->andReturn('UK');
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('to_array')
@ -170,6 +182,9 @@ class PayerTest extends TestCase
$address
->expects('to_array')
->andReturn(['address']);
$address
->expects('country_code')
->andReturn('UK');
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('to_array')

View file

@ -48,7 +48,9 @@ class ItemFactoryTest extends TestCase
expect('wc_get_price_excluding_tax')
->with($product)
->andReturn(1);
expect('wp_strip_all_tags')
->with('description')
->andReturn('description');
$result = $testee->from_wc_cart($cart);
$this->assertCount(1, $result);
@ -102,6 +104,9 @@ class ItemFactoryTest extends TestCase
expect('wc_get_price_excluding_tax')
->with($product)
->andReturn(1);
expect('wp_strip_all_tags')
->with('description')
->andReturn('description');
$result = $testee->from_wc_cart($cart);
@ -126,6 +131,9 @@ class ItemFactoryTest extends TestCase
$product
->expects('is_virtual')
->andReturn(false);
expect('wp_strip_all_tags')
->with('description')
->andReturn('description');
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
@ -183,6 +191,9 @@ class ItemFactoryTest extends TestCase
$product
->expects('is_virtual')
->andReturn(true);
expect('wp_strip_all_tags')
->with('description')
->andReturn('description');
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
@ -235,6 +246,9 @@ class ItemFactoryTest extends TestCase
$product
->expects('is_virtual')
->andReturn(true);
expect('wp_strip_all_tags')
->with($description)
->andReturn(mb_substr( $description, 0, 127 ));
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item

View file

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use ReflectionClass;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use function Brain\Monkey\Functions\expect;
class CreateOrderEndpointTest extends TestCase
{
use MockeryPHPUnitIntegration;
/**
* @dataProvider dataForTestPhoneNumber
* @test
*
* @param $data
* @param $expectedResult
*/
public function payerVerifiesPhoneNumber($data, $expectedResult)
{
list($payer_factory, $testee) = $this->mockTestee();
$method = $this->testPrivateMethod(CreateOrderEndpoint::class, 'payer');
$dataString = wp_json_encode($expectedResult['payer']);
$dataObj = json_decode(wp_json_encode($expectedResult['payer']));
expect('wp_json_encode')->once()->with($expectedResult['payer'])
->andReturn($dataString);
expect('json_decode')->once()->with($dataString)->andReturn($dataObj);
$payer_factory->expects('from_paypal_response')->with($dataObj);
$method->invokeArgs($testee, array($data));
}
public function dataForTestPhoneNumber() : array {
return [
'emptyStringPhone' => [
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>''
]
]
]
],
[
'context' => 'none',
'payer' => [
'name' => ['given_name' => 'testName']
]
]
],
'tooLongStringPhone' => [
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>'43241341234123412341234123123412341'
]
]
]
],
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>'43241341234123'
]
]
]
]
],
'removeNonISOStringPhone' => [
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>'432a34as73737373'
]
]
]
],
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>'4323473737373'
]
]
]
]
],
'notNumbersStringPhone' => [
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName'],
'phone'=>[
'phone_number'=>[
'national_number'=>'this is_notaPhone'
]
]
]
],
[
'context' => 'none',
'payer'=>[
'name'=>['given_name'=>'testName']
]
]
]
];
}
/**
* @return array
*/
protected function mockTestee()
{
$request_data = Mockery::mock(RequestData::class);
$cart_repository = Mockery::mock(CartRepository::class);
$purchase_unit_factory = Mockery::mock(PurchaseUnitFactory::class);
$order_endpoint = Mockery::mock(OrderEndpoint::class);
$payer_factory = Mockery::mock(PayerFactory::class);
$session_handler = Mockery::mock(SessionHandler::class);
$settings = Mockery::mock(Settings::class);
$early_order_handler = Mockery::mock(EarlyOrderHandler::class);
$testee = new CreateOrderEndpoint(
$request_data,
$cart_repository,
$purchase_unit_factory,
$order_endpoint,
$payer_factory,
$session_handler,
$settings,
$early_order_handler
);
return array($payer_factory, $testee);
}
/**
* @param $class
*
* @param $method
*
* @return \ReflectionMethod
* @throws \ReflectionException
*/
protected function testPrivateMethod($class, $method)
{
$reflector = new ReflectionClass($class);
$method = $reflector->getMethod($method);
$method->setAccessible(true);
return $method;
}
}

View file

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, PayPal Credit, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 1.0.4
* Version: 1.1.0
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* License: GPL-2.0