Fix merge conflicts with trunk

This commit is contained in:
dinamiko 2021-03-31 11:22:01 +02:00
commit 34b624905d
31 changed files with 1008 additions and 165 deletions

View file

@ -1,6 +1,6 @@
# WooCommerce PayPal Payments
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.
PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
## Requirements

View file

@ -1,5 +1,12 @@
*** Changelog ***
= 1.2.1 - 2021-03-08 =
* Fix - Address compatibility issue with Jetpack.
= 1.2.0 - 2021-03-08 =
* Add - Rework onboarding code and add REST controller for integration with the OBW. #121
* Fix - Remove spinner on click, on cancel and on error. #124
= 1.1.0 - 2021-02-01 =
* Add - Buy Now Pay Later for UK. #104
* Add - DE now has 12 month installments. #106

View file

@ -5,7 +5,7 @@
"license": "GPL-2.0",
"require": {
"dhii/module-interface": "0.1",
"psr/container": "^1.0",
"psr/container": "1.0.0",
"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

@ -19,9 +19,6 @@ class CheckoutActionHandler {
const errorHandler = this.errorHandler;
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
spinner.setTarget(formSelector);
spinner.block();
const formValues = jQuery(formSelector).serialize();
return fetch(this.config.ajax.create_order.endpoint, {
@ -39,7 +36,18 @@ class CheckoutActionHandler {
}).then(function (data) {
if (!data.success) {
spinner.unblock();
errorHandler.message(data.data.message, true);
//handle both messages sent from Woocommerce (data.messages) and this plugin (data.data.message)
if (typeof(data.messages) !== 'undefined' )
{
const domParser = new DOMParser();
errorHandler.appendPreparedErrorMessageElement(
domParser.parseFromString(data.messages, 'text/html')
.querySelector('ul')
);
} else {
errorHandler.message(data.data.message, true);
}
return;
}
const input = document.createElement('input');
@ -53,8 +61,12 @@ class CheckoutActionHandler {
return {
createOrder,
onApprove:onApprove(this, this.errorHandler, this.spinner),
onError: (error) => {
onCancel: () => {
spinner.unblock();
},
onError: () => {
this.errorHandler.genericError();
spinner.unblock();
}
}
}

View file

@ -4,6 +4,7 @@ class ErrorHandler {
{
this.genericErrorText = genericErrorText;
this.wrapper = document.querySelector('.woocommerce-notices-wrapper');
this.messagesList = document.querySelector('ul.woocommerce-error');
}
genericError() {
@ -14,18 +15,55 @@ class ErrorHandler {
this.message(this.genericErrorText)
}
appendPreparedErrorMessageElement(errorMessageElement)
{
if(this.messagesList === null) {
this.prepareMessagesList();
}
this.messagesList.replaceWith(errorMessageElement);
}
message(text, persist = false)
{
this.wrapper.classList.add('woocommerce-error');
if(! typeof String || text.length === 0){
throw new Error('A new message text must be a non-empty string.');
}
if(this.messagesList === null){
this.prepareMessagesList();
}
if (persist) {
this.wrapper.classList.add('ppcp-persist');
} else {
this.wrapper.classList.remove('ppcp-persist');
}
this.wrapper.innerHTML = this.sanitize(text);
let messageNode = this.prepareMessagesListItem(text);
this.messagesList.appendChild(messageNode);
jQuery.scroll_to_notices(jQuery('.woocommerce-notices-wrapper'))
}
prepareMessagesList()
{
if(this.messagesList === null){
this.messagesList = document.createElement('ul');
this.messagesList.setAttribute('class', 'woocommerce-error');
this.messagesList.setAttribute('role', 'alert');
this.wrapper.appendChild(this.messagesList);
}
}
prepareMessagesListItem(message)
{
const li = document.createElement('li');
li.innerHTML = message;
return li;
}
sanitize(text)
{
const textarea = document.createElement('textarea');

View file

@ -113,6 +113,9 @@ class CreditCardRenderer {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove(payload);
}).catch(() => {
this.errorHandler.genericError();
this.spinner.unblock();
});
} else {
this.spinner.unblock();

View file

@ -60,7 +60,6 @@ return array(
return new DisabledSmartButton();
}
$payee_repository = $container->get( 'api.repository.payee' );
$identity_token = $container->get( 'api.endpoint.identity-token' );
$payer_factory = $container->get( 'api.factory.payer' );
$request_data = $container->get( 'button.request-data' );
@ -75,7 +74,6 @@ return array(
$container->get( 'session.handler' ),
$settings,
$payee_repository,
$identity_token,
$payer_factory,
$client_id,
$request_data,

View file

@ -60,13 +60,6 @@ class SmartButton implements SmartButtonInterface {
*/
private $payee_repository;
/**
* The Identity Token.
*
* @var IdentityToken
*/
private $identity_token;
/**
* The Payer Factory.
*
@ -145,7 +138,6 @@ class SmartButton implements SmartButtonInterface {
SessionHandler $session_handler,
Settings $settings,
PayeeRepository $payee_repository,
IdentityToken $identity_token,
PayerFactory $payer_factory,
string $client_id,
RequestData $request_data,
@ -160,7 +152,6 @@ class SmartButton implements SmartButtonInterface {
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->payee_repository = $payee_repository;
$this->identity_token = $identity_token;
$this->payer_factory = $payer_factory;
$this->client_id = $client_id;
$this->request_data = $request_data;
@ -373,7 +364,7 @@ class SmartButton implements SmartButtonInterface {
);
}
add_action( 'woocommerce_review_order_after_submit', array( $this, 'button_renderer' ), 10 );
add_action( 'woocommerce_review_order_after_payment', array( $this, 'button_renderer' ), 10 );
add_action( 'woocommerce_pay_order_after_submit', array( $this, 'button_renderer' ), 10 );
return true;
@ -613,7 +604,6 @@ class SmartButton implements SmartButtonInterface {
return is_user_logged_in();
}
/**
* Whether we need to initialize the script to enable tokenization for subscriptions or not.
*
@ -775,22 +765,15 @@ class SmartButton implements SmartButtonInterface {
if ( $payee->merchant_id() ) {
$params['merchant-id'] = $payee->merchant_id();
}
$disable_funding = $this->settings->has( 'disable_funding' ) ?
$disable_funding = $this->settings->has( 'disable_funding' ) ?
$this->settings->get( 'disable_funding' ) : array();
$disable_funding[] = 'venmo';
if ( ! is_checkout() ) {
$disable_funding[] = 'card';
}
/**
* Disable card for UK.
*/
$region = wc_get_base_location();
$country = $region['country'];
if ( 'GB' === $country ) {
$disable_funding[] = 'credit';
if ( count( $disable_funding ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
}
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
$smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' );
return $smart_button_url;

View file

@ -163,7 +163,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->set_bn_code( $data );
$needs_shipping = WC()->cart && WC()->cart->needs_shipping();
$shipping_address_is_fix = $needs_shipping && 'checkout' === $data['context'] ? true : false;
$shipping_address_is_fix = $needs_shipping && 'checkout' === $data['context'];
$order = $this->api_endpoint->create(
$purchase_units,
$this->payer( $data, $wc_order ),
@ -173,7 +173,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$shipping_address_is_fix
);
if ( 'checkout' === $data['context'] ) {
$this->validate_checkout_form( $data['form'], $order );
$this->process_checkout_form( $data['form'], $order );
}
if ( 'pay-now' === $data['context'] && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
$this->validate_paynow_form( $data['form'] );
@ -263,7 +263,7 @@ class CreateOrderEndpoint implements EndpointInterface {
*
* @throws \Exception On Error.
*/
private function validate_checkout_form( string $form_values, Order $order ) {
private function process_checkout_form( string $form_values, Order $order ) {
$this->order = $order;
$form_values = explode( '&', $form_values );

View file

@ -24,6 +24,7 @@ class MessagesApply {
'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

@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
@ -22,6 +23,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use Woocommerce\PayPalCommerce\WcGateway\Helper\DccProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
@ -32,7 +34,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WpOop\TransientCache\CachePoolFactory;
return array(
'wcgateway.paypal-gateway' => static function ( $container ): PayPalGateway {
@ -44,6 +45,7 @@ return array(
$session_handler = $container->get( 'session.handler' );
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
$state = $container->get( 'onboarding.state' );
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
return new PayPalGateway(
$settings_renderer,
@ -53,7 +55,8 @@ return array(
$settings,
$session_handler,
$refund_processor,
$state
$state,
$transaction_url_provider
);
},
'wcgateway.credit-card-gateway' => static function ( $container ): CreditCardGateway {
@ -66,6 +69,7 @@ return array(
$session_handler = $container->get( 'session.handler' );
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
$state = $container->get( 'onboarding.state' );
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
$payer_factory = $container->get( 'api.factory.payer' );
@ -80,6 +84,7 @@ return array(
$session_handler,
$refund_processor,
$state,
$transaction_url_provider,
$payment_token_repository,
$purchase_unit_factory,
$payer_factory,
@ -139,7 +144,10 @@ return array(
$order_factory = $container->get( 'api.factory.order' );
$threed_secure = $container->get( 'button.helper.three-d-secure' );
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
$settings = $container->get( 'wcgateway.settings' );
$settings = $container->get( 'wcgateway.settings' );
$environment = $container->get( 'onboarding.environment' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new OrderProcessor(
$session_handler,
$cart_repository,
@ -148,7 +156,9 @@ return array(
$order_factory,
$threed_secure,
$authorized_payments_processor,
$settings
$settings,
$logger,
$environment->current_environment_is( Environment::SANDBOX )
);
},
'wcgateway.processor.refunds' => static function ( $container ): RefundProcessor {
@ -648,6 +658,7 @@ return array(
'mybank' => _x( 'MyBank', 'Name of payment method', 'woocommerce-paypal-payments' ),
'p24' => _x( 'Przelewy24', 'Name of payment method', 'woocommerce-paypal-payments' ),
'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', 'Name of payment method', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_PROGRESSIVE,
@ -1847,6 +1858,28 @@ return array(
$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' );
}
if ( 'FR' === $country ) {
// todo: replace this with the text in English and use this text for French translation when it will be created.
$french_pay_later_description = 'Affichez le Paiement en 4X PayPal sur votre site.' .
'Le Paiement en 4X PayPal permet aux consommateurs français de payer en 4 versements égaux.' .
'Vous pouvez promouvoir le Paiement en 4X PayPal uniquement si vous êtes un commerçant basé en France, ' .
'avec un site internet en français et uneintégration PayPal standard. ' .
'Les marchands ayantloutil Vaulting(coffre-fort numérique) ou une intégration de paiements récurrents/abonnement, ' .
'ainsi que ceux présentant certaines activités (vente de biens numériques / de biens non physiques) ' .
'ne sont pas éligibles pour promouvoir le Paiement en 4X PayPal.' .
'Nous afficherons des messages sur votre site pour promouvoir le Paiement en 4X PayPal. ' .
'Vous ne pouvez pas promouvoir le Paiement en 4X PayPal avec un autre contenu, quel quil soit.';
$fields['message_heading']['heading'] = __( 'Pay Later Messaging on Checkout', 'woocommerce-paypal-payments' );
$fields['message_heading']['description'] = $french_pay_later_description;
$fields['message_product_heading']['heading'] = __( 'Pay Later Messaging on Single Product Page', 'woocommerce-paypal-payments' );
$fields['message_product_heading']['description'] = $french_pay_later_description;
$fields['message_cart_heading']['heading'] = __( 'Pay Later Messaging on Cart', 'woocommerce-paypal-payments' );
$fields['message_cart_heading']['description'] = $french_pay_later_description;
}
/**
* Set Pay Later link for DE
*/
@ -1908,6 +1941,22 @@ return array(
$prefix
);
},
'wcgateway.transaction-url-sandbox' => static function ( $container ): string {
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
},
'wcgateway.transaction-url-live' => static function ( $container ): string {
return 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
},
'wcgateway.transaction-url-provider' => static function ( $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 ( $container ) : DccProductStatus {
$settings = $container->get( 'wcgateway.settings' );

View file

@ -31,6 +31,13 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
const ID = 'ppcp-credit-card-gateway';
/**
* Service to get transaction url for an order.
*
* @var TransactionUrlProvider
*/
protected $transaction_url_provider;
/**
* The URL to the module.
*
@ -89,6 +96,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param PayerFactory $payer_factory The payer factory.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@ -99,7 +107,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
string $module_url,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
State $state,
State $state,
TransactionUrlProvider $transaction_url_provider,
PaymentTokenRepository $payment_token_repository,
PurchaseUnitFactory $purchase_unit_factory,
PayerFactory $payer_factory,
@ -168,6 +177,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
$this->purchase_unit_factory = $purchase_unit_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->transaction_url_provider = $transaction_url_provider;
}
/**
@ -292,4 +302,17 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
}
return $this->refund_processor->process( $order, (float) $amount, (string) $reason );
}
/**
* Set the class property then call parent function.
*
* @param \WC_Order $order WC Order to get transaction url for.
*
* @inheritDoc
*/
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 );
}
}

View file

@ -9,7 +9,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
@ -27,10 +26,11 @@ class PayPalGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait;
const ID = 'ppcp-gateway';
const CAPTURED_META_KEY = '_ppcp_paypal_captured';
const INTENT_META_KEY = '_ppcp_paypal_intent';
const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
const ID = 'ppcp-gateway';
const CAPTURED_META_KEY = '_ppcp_paypal_captured';
const INTENT_META_KEY = '_ppcp_paypal_intent';
const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
/**
* The Settings Renderer.
@ -74,6 +74,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $session_handler;
/**
* Service able to provide transaction url for an order.
*
* @var TransactionUrlProvider
*/
protected $transaction_url_provider;
/**
* The Refund Processor.
*
@ -92,6 +99,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @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.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@ -101,17 +109,19 @@ class PayPalGateway extends \WC_Payment_Gateway {
ContainerInterface $config,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
State $state
State $state,
TransactionUrlProvider $transaction_url_provider
) {
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments = $authorized_payments_processor;
$this->notice = $notice;
$this->settings_renderer = $settings_renderer;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments = $authorized_payments_processor;
$this->notice = $notice;
$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;
if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' );
@ -339,4 +349,17 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
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 );
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Service able to provide transaction url base (URL with the placeholder instead of an actual transaction id)
* based on the given WC Order.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
/**
* Class TransactionUrlProvider
*/
class TransactionUrlProvider {
/**
* Transaction URL base used for sandbox payments.
*
* @var string
*/
protected $transaction_url_base_sandbox;
/**
* Transaction URL base used for live payments.
*
* @var string
*/
protected $transaction_url_base_live;
/**
* TransactionUrlProvider constructor.
*
* @param string $transaction_url_base_sandbox URL for sandbox orders.
* @param string $transaction_url_base_live URL for live orders.
*/
public function __construct(
string $transaction_url_base_sandbox,
string $transaction_url_base_live
) {
$this->transaction_url_base_sandbox = $transaction_url_base_sandbox;
$this->transaction_url_base_live = $transaction_url_base_live;
}
/**
* Return transaction url base
*
* @param \WC_Order $order WC order to get payment type from.
*
* @return string
*/
public function get_transaction_url_base( \WC_Order $order ): string {
$order_payment_mode = $order->get_meta( PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, true );
return 'sandbox' === $order_payment_mode ? $this->transaction_url_base_sandbox : $this->transaction_url_base_live;
}
}

View file

@ -1,18 +0,0 @@
<?php
/**
* The WcGateway interface.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
/**
* Interface WcGatewayInterface
*/
interface WcGatewayInterface {
}

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
@ -25,6 +26,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/
class OrderProcessor {
/**
* Whether current payment mode is sandbox.
*
* @var bool
*/
protected $sandbox_mode;
/**
* The Session Handler.
*
@ -88,6 +96,13 @@ class OrderProcessor {
*/
private $last_error = '';
/**
* A logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* OrderProcessor constructor.
*
@ -99,6 +114,8 @@ class OrderProcessor {
* @param ThreeDSecure $three_d_secure The ThreeDSecure Helper.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
* @param Settings $settings The Settings.
* @param LoggerInterface $logger A logger service.
* @param bool $sandbox_mode Whether sandbox mode enabled.
*/
public function __construct(
SessionHandler $session_handler,
@ -108,7 +125,9 @@ class OrderProcessor {
OrderFactory $order_factory,
ThreeDSecure $three_d_secure,
AuthorizedPaymentsProcessor $authorized_payments_processor,
Settings $settings
Settings $settings,
LoggerInterface $logger,
bool $sandbox_mode
) {
$this->session_handler = $session_handler;
@ -119,6 +138,8 @@ class OrderProcessor {
$this->threed_secure = $three_d_secure;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings = $settings;
$this->sandbox_mode = $sandbox_mode;
$this->logger = $logger;
}
/**
@ -136,9 +157,13 @@ class OrderProcessor {
}
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$wc_order->update_meta_data(
PayPalGateway::ORDER_PAYMENT_MODE_META_KEY,
$this->sandbox_mode ? 'sandbox' : 'live'
);
$error_message = null;
if ( ! $order || ! $this->order_is_approved( $order ) ) {
if ( ! $this->order_is_approved( $order ) ) {
$error_message = __(
'The payment has not been approved yet.',
'woocommerce-paypal-payments'
@ -163,6 +188,12 @@ class OrderProcessor {
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( '' !== $transaction_id ) {
$this->set_order_transaction_id( $transaction_id, $wc_order );
}
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
@ -187,6 +218,55 @@ class OrderProcessor {
return true;
}
/**
* Set transaction id to WC order meta data.
*
* @param string $transaction_id Transaction id to set.
* @param \WC_Order $wc_order Order to set transaction ID to.
*/
public function set_order_transaction_id( string $transaction_id, \WC_Order $wc_order ) {
try {
$wc_order->set_transaction_id( $transaction_id );
} catch ( \WC_Data_Exception $exception ) {
$this->logger->log(
'warning',
sprintf(
'Failed to set transaction ID. Exception caught when tried: %1$s',
$exception->getMessage()
)
);
}
}
/**
* Retrieve transaction id from PayPal order.
*
* @param Order $order Order to get transaction id from.
*
* @return string
*/
private function get_paypal_order_transaction_id( Order $order ): string {
$purchase_units = $order->purchase_units();
if ( ! isset( $purchase_units[0] ) ) {
return '';
}
$payments = $purchase_units[0]->payments();
if ( null === $payments ) {
return '';
}
$captures = $payments->captures();
if ( isset( $captures[0] ) ) {
return $captures[0]->id();
}
return '';
}
/**
* Returns if an order should be captured immediately.
*

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.1.0",
"version": "1.2.1",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",

View file

@ -4,11 +4,11 @@ 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.1.0
Stable tag: 1.2.1
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
PayPal's latest payments processing solution. Accept PayPal, PayPal Credit, credit/debit cards, alternative digital wallets and bank accounts.
PayPal's latest payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets and bank accounts.
== Description ==
@ -58,6 +58,13 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog ==
= 1.2.1 =
* Fix - Address compatibility issue with Jetpack.
= 1.2.0 =
* Add - Rework onboarding code and add REST controller for integration with the OBW. #121
* Fix - Remove spinner on click, on cancel and on error. #124
= 1.1.0 =
* Add - Buy Now Pay Later for UK. #104
* Add - DE now has 12 month installments. #106

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use WooCommerce\PayPalCommerce\TestCase;
class TransactionUrlProviderTest extends TestCase
{
/**
* @dataProvider getTransactionUrlDataProvider
*/
public function testGetTransactionUrlBase(
string $sandboxUrl,
string $liveUrl,
string $orderPaymentMode,
$expectedResult
) {
$testee = new TransactionUrlProvider($sandboxUrl, $liveUrl);
$wcOrder = \Mockery::mock(\WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, true)
->andReturn($orderPaymentMode);
$this->assertSame($expectedResult, $testee->get_transaction_url_base($wcOrder));
}
function getTransactionUrlDataProvider(): array
{
$sandboxUrl = 'sandbox.example.com';
$liveUrl = 'example.com';
return [
[
$sandboxUrl, $liveUrl, 'sandbox', $sandboxUrl
],
[
$sandboxUrl, $liveUrl, 'live', $liveUrl
],
[
$sandboxUrl, $liveUrl, '', $liveUrl
]
];
}
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\TestCase;
@ -45,6 +44,7 @@ class WcGatewayTest extends TestCase
$settings
->shouldReceive('has')->andReturnFalse();
$refundProcessor = Mockery::mock(RefundProcessor::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state = Mockery::mock(State::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
@ -56,7 +56,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
expect('wc_get_order')
@ -86,6 +87,7 @@ class WcGatewayTest extends TestCase
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$state = Mockery::mock(State::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$testee = new PayPalGateway(
@ -96,7 +98,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
expect('wc_get_order')
@ -132,6 +135,7 @@ class WcGatewayTest extends TestCase
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$state = Mockery::mock(State::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$testee = new PayPalGateway(
@ -142,7 +146,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
expect('wc_get_order')
@ -193,6 +198,7 @@ class WcGatewayTest extends TestCase
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$state = Mockery::mock(State::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$testee = new PayPalGateway(
@ -203,7 +209,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
$this->assertTrue($testee->capture_authorized_payment($wcOrder));
@ -246,6 +253,7 @@ class WcGatewayTest extends TestCase
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$state = Mockery::mock(State::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$testee = new PayPalGateway(
@ -256,7 +264,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
$this->assertTrue($testee->capture_authorized_payment($wcOrder));
@ -292,6 +301,7 @@ class WcGatewayTest extends TestCase
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$state = Mockery::mock(State::class);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$state
->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
$testee = new PayPalGateway(
@ -302,7 +312,8 @@ class WcGatewayTest extends TestCase
$settings,
$sessionHandler,
$refundProcessor,
$state
$state,
$transactionUrlProvider
);
$this->assertFalse($testee->capture_authorized_payment($wcOrder));
@ -325,4 +336,4 @@ class WcGatewayTest extends TestCase
],
];
}
}
}

View file

@ -4,10 +4,14 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
@ -15,14 +19,32 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\WooCommerce\Logging\WooCommerceLoggingModule;
use Mockery;
class OrderProcessorTest extends TestCase
{
public function testAuthorize() {
$transactionId = 'ABC123';
$capture = Mockery::mock(Capture::class);
$capture->expects('id')
->andReturn($transactionId);
$payments = Mockery::mock(Payments::class);
$payments->expects('captures')
->andReturn([$capture]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->expects('payments')
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->expects('set_transaction_id')
->with($transactionId);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
->expects('is')
@ -32,8 +54,10 @@ class OrderProcessorTest extends TestCase
->expects('is')
->with(OrderStatus::COMPLETED)
->andReturn(true);
$orderId = 'abc';
$orderIntent = 'AUTHORIZE';
$currentOrder = Mockery::mock(Order::class);
$currentOrder
->expects('id')
@ -44,13 +68,18 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('status')
->andReturn($orderStatus);
$currentOrder->expects('purchase_units')
->andReturn([$purchaseUnit]);
$sessionHandler = Mockery::mock(SessionHandler::class);
$sessionHandler
->expects('order')
->andReturn($currentOrder);
$sessionHandler
->expects('destroy_session_data');
$cartRepository = Mockery::mock(CartRepository::class);
$orderEndpoint = Mockery::mock(OrderEndpoint::class);
$orderEndpoint
->expects('patch_order_with')
@ -60,19 +89,26 @@ class OrderProcessorTest extends TestCase
->expects('authorize')
->with($currentOrder)
->andReturn($currentOrder);
$paymentsEndpoint = Mockery::mock(PaymentsEndpoint::class);
$orderFactory = Mockery::mock(OrderFactory::class);
$orderFactory
->expects('from_wc_order')
->with($wcOrder, $currentOrder)
->andReturn($currentOrder);
$threeDSecure = Mockery::mock(ThreeDSecure::class);
$authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$settings = Mockery::mock(Settings::class);
$settings
->shouldReceive('has')
->andReturnFalse();
$logger = Mockery::mock(LoggerInterface::class);
$testee = new OrderProcessor(
$sessionHandler,
$cartRepository,
@ -81,7 +117,9 @@ class OrderProcessorTest extends TestCase
$orderFactory,
$threeDSecure,
$authorizedPaymentProcessor,
$settings
$settings,
$logger,
false
);
$cart = Mockery::mock(\WC_Cart::class);
@ -115,6 +153,20 @@ class OrderProcessorTest extends TestCase
}
public function testCapture() {
$transactionId = 'ABC123';
$capture = Mockery::mock(Capture::class);
$capture->expects('id')
->andReturn($transactionId);
$payments = Mockery::mock(Payments::class);
$payments->expects('captures')
->andReturn([$capture]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->expects('payments')
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
@ -137,6 +189,9 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('status')
->andReturn($orderStatus);
$currentOrder
->expects('purchase_units')
->andReturn([$purchaseUnit]);
$sessionHandler = Mockery::mock(SessionHandler::class);
$sessionHandler
->expects('order')
@ -166,7 +221,10 @@ class OrderProcessorTest extends TestCase
->shouldReceive('has')
->andReturnFalse();
$testee = new OrderProcessor(
$logger = Mockery::mock(LoggerInterface::class);
$testee = new OrderProcessor(
$sessionHandler,
$cartRepository,
$orderEndpoint,
@ -174,7 +232,9 @@ class OrderProcessorTest extends TestCase
$orderFactory,
$threeDSecure,
$authorizedPaymentProcessor,
$settings
$settings,
$logger,
false
);
$cart = Mockery::mock(\WC_Cart::class);
@ -198,6 +258,10 @@ class OrderProcessorTest extends TestCase
$wcOrder
->expects('update_status')
->with('on-hold', 'Awaiting payment.');
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->expects('set_transaction_id')
->with($transactionId);
$wcOrder
->expects('update_status')
->with('processing', 'Payment received.');
@ -205,7 +269,26 @@ class OrderProcessorTest extends TestCase
}
public function testError() {
$transactionId = 'ABC123';
$capture = Mockery::mock(Capture::class);
$capture->shouldReceive('id')
->andReturn($transactionId);
$payments = Mockery::mock(Payments::class);
$payments->shouldReceive('captures')
->andReturn([$capture]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->shouldReceive('payments')
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->shouldReceive('set_transaction_id')
->with($transactionId);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
->expects('is')
@ -226,6 +309,9 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('payment_source')
->andReturnNull();
$currentOrder
->shouldReceive('purchase_units')
->andReturn([$purchaseUnit]);
$sessionHandler = Mockery::mock(SessionHandler::class);
$sessionHandler
->expects('order')
@ -238,7 +324,9 @@ class OrderProcessorTest extends TestCase
$authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$settings = Mockery::mock(Settings::class);
$testee = new OrderProcessor(
$logger = Mockery::mock(LoggerInterface::class);
$testee = new OrderProcessor(
$sessionHandler,
$cartRepository,
$orderEndpoint,
@ -246,7 +334,9 @@ class OrderProcessorTest extends TestCase
$orderFactory,
$threeDSecure,
$authorizedPaymentProcessor,
$settings
$settings,
$logger,
false
);
$cart = Mockery::mock(\WC_Cart::class);
@ -270,4 +360,4 @@ class OrderProcessorTest extends TestCase
}
}
}

View file

@ -2,8 +2,8 @@
/**
* 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.1.0
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 1.2.1
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* License: GPL-2.0