Merge branch 'trunk' into pcp-254-improve-status-report

This commit is contained in:
Alex P 2021-11-23 16:51:47 +02:00
commit 27d71e14c3
21 changed files with 225 additions and 220 deletions

View file

@ -1,18 +1,21 @@
*** Changelog *** *** Changelog ***
= 1.6.2 - TBD = = 1.6.2 - 2021-11-22 =
* Fix - Order of WooCommerce checkout actions causing incompatibility with AvaTax address validation #335 * Fix - Order of WooCommerce checkout actions causing incompatibility with AvaTax address validation #335
* Fix - Can't checkout to certain countries with optional postcode #330 * Fix - Can't checkout to certain countries with optional postcode #330
* FIx - Prevent subscription from being purchased when saving payment fails #308 * Fix - Prevent subscription from being purchased when saving payment fails #308
* FIx - Guest users must checkout twice for subscriptions, no smart buttons loaded #342 * Fix - Guest users must checkout twice for subscriptions, no smart buttons loaded #342
* FIx - Failed PayPal API request causing strange error #347 * Fix - Failed PayPal API request causing strange error #347
* FIx - PayPal payments page empty after switching packages #350 * Fix - PayPal payments page empty after switching packages #350
* FIx - Could Not Validate Nonce Error #239 * Fix - Could Not Validate Nonce Error #239
* FIx - Refund via PayPal dashboard does not set the WooCommerce order to "Refunded" #241 * Fix - Refund via PayPal dashboard does not set the WooCommerce order to "Refunded" #241
* FIx - Uncaught TypeError: round() #344 * Fix - Uncaught TypeError: round() #344
* FIx - Broken multi-level (nested) associative array values after getting submitted from checkout page #307 * Fix - Broken multi-level (nested) associative array values after getting submitted from checkout page #307
* FIx - Transaction id missing in some cases #328 * Fix - Transaction id missing in some cases #328
* FIx - Payment not possible in pay for order form because of terms checkbox missing #294 * Fix - Payment not possible in pay for order form because of terms checkbox missing #294
* Fix - "Save your Credit Card" shouldn't be optional when paying for a subscription #368
* Fix - When paying for a subscription and vaulting fails, cart is cleared #367
* Fix - Fatal error when activating PayPal Checkout plugin #363
= 1.6.1 - 2021-10-12 = = 1.6.1 - 2021-10-12 =
* Fix - Handle authorization capture failures #312 * Fix - Handle authorization capture failures #312

View file

@ -131,14 +131,15 @@ return array(
); );
}, },
'api.endpoint.identity-token' => static function ( ContainerInterface $container ) : IdentityToken { 'api.endpoint.identity-token' => static function ( ContainerInterface $container ) : IdentityToken {
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
$prefix = $container->get( 'api.prefix' ); $prefix = $container->get( 'api.prefix' );
$settings = $container->get( 'wcgateway.settings' );
return new IdentityToken( return new IdentityToken(
$container->get( 'api.host' ), $container->get( 'api.host' ),
$container->get( 'api.bearer' ), $container->get( 'api.bearer' ),
$logger, $logger,
$prefix $prefix,
$settings
); );
}, },
'api.endpoint.payments' => static function ( ContainerInterface $container ): PaymentsEndpoint { 'api.endpoint.payments' => static function ( ContainerInterface $container ): PaymentsEndpoint {

View file

@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
* Class IdentityToken * Class IdentityToken
@ -50,6 +51,13 @@ class IdentityToken {
*/ */
private $prefix; private $prefix;
/**
* The settings
*
* @var Settings
*/
private $settings;
/** /**
* IdentityToken constructor. * IdentityToken constructor.
* *
@ -57,12 +65,14 @@ class IdentityToken {
* @param Bearer $bearer The bearer. * @param Bearer $bearer The bearer.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param string $prefix The prefix. * @param string $prefix The prefix.
* @param Settings $settings The settings.
*/ */
public function __construct( string $host, Bearer $bearer, LoggerInterface $logger, string $prefix ) { public function __construct( string $host, Bearer $bearer, LoggerInterface $logger, string $prefix, Settings $settings ) {
$this->host = $host; $this->host = $host;
$this->bearer = $bearer; $this->bearer = $bearer;
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
$this->settings = $settings;
} }
/** /**
@ -84,7 +94,11 @@ class IdentityToken {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
), ),
); );
if ( $customer_id && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION ) { if (
$customer_id
&& ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) )
&& defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION
) {
$args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id ) ); $args['body'] = wp_json_encode( array( 'customer_id' => $this->prefix . $customer_id ) );
} }

View file

@ -64,7 +64,7 @@ class AmountFactory {
); );
$taxes = new Money( $taxes = new Money(
(float) $cart->get_cart_contents_tax() + (float) $cart->get_discount_tax(), $cart->get_subtotal_tax(),
$currency $currency
); );

View file

@ -119,10 +119,11 @@ class PurchaseUnitFactory {
$reference_id = 'default'; $reference_id = 'default';
$description = ''; $description = '';
$payee = $this->payee_repository->payee(); $payee = $this->payee_repository->payee();
$wc_order_id = $order->get_order_number(); $custom_id = (string) $order->get_id();
$custom_id = $this->prefix . $wc_order_id; $invoice_id = $this->prefix . $order->get_order_number();
$invoice_id = $this->prefix . $wc_order_id; $retry = $order->get_meta( 'ppcp-retry' ) ? '-' . $order->get_meta( 'ppcp-retry' ) : '';
$soft_descriptor = ''; $soft_descriptor = '';
$purchase_unit = new PurchaseUnit( $purchase_unit = new PurchaseUnit(
$amount, $amount,
$items, $items,

View file

@ -11,3 +11,7 @@
.ppcp-credit-card-gateway-form-field-disabled { .ppcp-credit-card-gateway-form-field-disabled {
opacity: .5 !important; opacity: .5 !important;
} }
.ppcp-dcc-order-button {
float: right;
}

View file

@ -1,5 +1,6 @@
import ErrorHandler from '../ErrorHandler'; import ErrorHandler from '../ErrorHandler';
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
import { setVisible } from '../Helper/Hiding';
class CheckoutBootstap { class CheckoutBootstap {
constructor(gateway, renderer, messages, spinner) { constructor(gateway, renderer, messages, spinner) {
@ -7,31 +8,38 @@ class CheckoutBootstap {
this.renderer = renderer; this.renderer = renderer;
this.messages = messages; this.messages = messages;
this.spinner = spinner; this.spinner = spinner;
this.standardOrderButtonSelector = '#place_order';
this.buttonChangeObserver = new MutationObserver((el) => {
this.updateUi();
});
} }
init() { init() {
this.render(); this.render();
// Unselect saved card.
// WC saves form values, so with our current UI it would be a bit weird
// if the user paid with saved, then after some time tries to pay again,
// but wants to enter a new card, and to do that they have to choose “Select payment” in the list.
jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val());
jQuery(document.body).on('updated_checkout', () => { jQuery(document.body).on('updated_checkout', () => {
this.render() this.render()
}); });
jQuery(document.body). jQuery(document.body).on('updated_checkout payment_method_selected', () => {
on('updated_checkout payment_method_selected', () => { this.updateUi();
this.switchBetweenPayPalandOrderButton() });
this.displayPlaceOrderButtonForSavedCreditCards()
})
jQuery(document).on('hosted_fields_loaded', () => { jQuery(document).on('hosted_fields_loaded', () => {
jQuery('#saved-credit-card').on('change', () => { jQuery('#saved-credit-card').on('change', () => {
this.displayPlaceOrderButtonForSavedCreditCards() this.updateUi();
}) })
}); });
this.switchBetweenPayPalandOrderButton() this.updateUi();
this.displayPlaceOrderButtonForSavedCreditCards()
} }
shouldRender() { shouldRender() {
@ -60,55 +68,35 @@ class CheckoutBootstap {
this.gateway.hosted_fields.wrapper, this.gateway.hosted_fields.wrapper,
actionHandler.configuration(), actionHandler.configuration(),
); );
this.buttonChangeObserver.observe(
document.querySelector(this.standardOrderButtonSelector),
{attributes: true}
);
} }
switchBetweenPayPalandOrderButton() { updateUi() {
jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); const currentPaymentMethod = this.currentPaymentMethod();
const isPaypal = currentPaymentMethod === 'ppcp-gateway';
const isCard = currentPaymentMethod === 'ppcp-credit-card-gateway';
const isSavedCard = isCard && this.isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard;
const currentPaymentMethod = jQuery( setVisible(this.standardOrderButtonSelector, isNotOurGateway || isSavedCard, true);
'input[name="payment_method"]:checked').val(); setVisible(this.gateway.button.wrapper, isPaypal);
setVisible(this.gateway.messages.wrapper, isPaypal);
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard);
if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') { if (isPaypal) {
this.renderer.hideButtons(this.gateway.button.wrapper); this.messages.render();
this.renderer.hideButtons(this.gateway.messages.wrapper);
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
jQuery('#place_order').show();
}
else {
jQuery('#place_order').hide();
if (currentPaymentMethod === 'ppcp-gateway') {
this.renderer.showButtons(this.gateway.button.wrapper);
this.renderer.showButtons(this.gateway.messages.wrapper);
this.messages.render()
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper)
}
if (currentPaymentMethod === 'ppcp-credit-card-gateway') {
this.renderer.hideButtons(this.gateway.button.wrapper)
this.renderer.hideButtons(this.gateway.messages.wrapper)
this.renderer.showButtons(this.gateway.hosted_fields.wrapper)
}
}
} }
displayPlaceOrderButtonForSavedCreditCards() { if (isCard) {
const currentPaymentMethod = jQuery( if (isSavedCard) {
'input[name="payment_method"]:checked').val(); this.disableCreditCardFields();
if (currentPaymentMethod !== 'ppcp-credit-card-gateway') {
return;
}
if (jQuery('#saved-credit-card').length && jQuery('#saved-credit-card').val() !== '') {
this.renderer.hideButtons(this.gateway.button.wrapper)
this.renderer.hideButtons(this.gateway.messages.wrapper)
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper)
jQuery('#place_order').show()
this.disableCreditCardFields()
} else { } else {
jQuery('#place_order').hide() this.enableCreditCardFields();
this.renderer.hideButtons(this.gateway.button.wrapper) }
this.renderer.hideButtons(this.gateway.messages.wrapper)
this.renderer.showButtons(this.gateway.hosted_fields.wrapper)
this.enableCreditCardFields()
} }
} }
@ -137,6 +125,15 @@ class CheckoutBootstap {
jQuery('#ppcp-credit-card-vault').attr("disabled", false) jQuery('#ppcp-credit-card-vault').attr("disabled", false)
this.renderer.enableCreditCardFields() this.renderer.enableCreditCardFields()
} }
currentPaymentMethod() {
return jQuery('input[name="payment_method"]:checked').val();
}
isSavedCardSelected() {
const savedCardList = jQuery('#saved-credit-card');
return savedCardList.length && savedCardList.val() !== '';
}
} }
export default CheckoutBootstap export default CheckoutBootstap

View file

@ -1,86 +1,17 @@
import ErrorHandler from '../ErrorHandler'; import CheckoutBootstap from './CheckoutBootstap'
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
class PayNowBootstrap { class PayNowBootstrap extends CheckoutBootstap {
constructor(gateway, renderer, messages, spinner) { constructor(gateway, renderer, messages, spinner) {
this.gateway = gateway; super(gateway, renderer, messages, spinner)
this.renderer = renderer;
this.messages = messages;
this.spinner = spinner;
} }
init() { updateUi() {
this.render();
jQuery(document.body).on('updated_checkout', () => {
this.render();
});
jQuery(document.body).
on('updated_checkout payment_method_selected', () => {
this.switchBetweenPayPalandOrderButton();
});
this.switchBetweenPayPalandOrderButton();
}
shouldRender() {
if (document.querySelector(this.gateway.button.cancel_wrapper)) {
return false;
}
return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null;
}
render() {
if (!this.shouldRender()) {
return;
}
if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) {
document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', '');
}
const actionHandler = new CheckoutActionHandler(
PayPalCommerceGateway,
new ErrorHandler(this.gateway.labels.error.generic),
this.spinner
);
this.renderer.render(
this.gateway.button.wrapper,
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(),
);
}
switchBetweenPayPalandOrderButton() {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('change_payment_method')) { if (urlParams.has('change_payment_method')) {
return return
} }
const currentPaymentMethod = jQuery( super.updateUi();
'input[name="payment_method"]:checked').val();
if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') {
this.renderer.hideButtons(this.gateway.button.wrapper);
this.renderer.hideButtons(this.gateway.messages.wrapper);
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
jQuery('#place_order').show();
}
else {
jQuery('#place_order').hide();
if (currentPaymentMethod === 'ppcp-gateway') {
this.renderer.showButtons(this.gateway.button.wrapper);
this.renderer.showButtons(this.gateway.messages.wrapper);
this.messages.render();
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
}
if (currentPaymentMethod === 'ppcp-credit-card-gateway') {
this.renderer.hideButtons(this.gateway.button.wrapper);
this.renderer.hideButtons(this.gateway.messages.wrapper);
this.renderer.showButtons(this.gateway.hosted_fields.wrapper);
}
}
} }
} }

View file

@ -0,0 +1,44 @@
const getElement = (selectorOrElement) => {
if (typeof selectorOrElement === 'string') {
return document.querySelector(selectorOrElement);
}
return selectorOrElement;
}
export const isVisible = (element) => {
return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
export const setVisible = (selectorOrElement, show, important = false) => {
const element = getElement(selectorOrElement);
if (!element) {
return;
}
const currentValue = element.style.getPropertyValue('display');
if (!show) {
if (currentValue === 'none') {
return;
}
element.style.setProperty('display', 'none', important ? 'important' : '');
} else {
if (currentValue === 'none') {
element.style.removeProperty('display');
}
// still not visible (if something else added display: none in CSS)
if (!isVisible(element)) {
element.style.setProperty('display', 'block');
}
}
};
export const hide = (selectorOrElement, important = false) => {
setVisible(selectorOrElement, false, important);
};
export const show = (selectorOrElement) => {
setVisible(selectorOrElement, true);
};

View file

@ -199,14 +199,18 @@ class SmartButton implements SmartButtonInterface {
11 11
); );
$subscription_helper = $this->subscription_helper;
add_filter( add_filter(
'woocommerce_credit_card_form_fields', 'woocommerce_credit_card_form_fields',
function ( $default_fields, $id ) { function ( array $default_fields, $id ) use ( $subscription_helper ) : array {
if ( is_user_logged_in() && $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) && CreditCardGateway::ID === $id ) { if ( is_user_logged_in() && $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) && CreditCardGateway::ID === $id ) {
if ( ! $subscription_helper->cart_contains_subscription() ) {
$default_fields['card-vault'] = sprintf( $default_fields['card-vault'] = sprintf(
'<p class="form-row form-row-wide"><label for="vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>', '<p class="form-row form-row-wide"><label for="vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>',
esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' ) esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' )
); );
}
$tokens = $this->payment_token_repository->all_for_user_id( get_current_user_id() ); $tokens = $this->payment_token_repository->all_for_user_id( get_current_user_id() );
if ( $tokens && $this->payment_token_repository->tokens_contains_card( $tokens ) ) { if ( $tokens && $this->payment_token_repository->tokens_contains_card( $tokens ) ) {
@ -560,7 +564,7 @@ class SmartButton implements SmartButtonInterface {
printf( printf(
'<div id="%1$s" style="display:none;"> '<div id="%1$s" style="display:none;">
<button class="button alt">%2$s</button> <button class="button alt ppcp-dcc-order-button">%2$s</button>
</div><div id="payments-sdk__contingency-lightbox"></div><style id="ppcp-hide-dcc">.payment_method_ppcp-credit-card-gateway {display:none;}</style>', </div><div id="payments-sdk__contingency-lightbox"></div><style id="ppcp-hide-dcc">.payment_method_ppcp-credit-card-gateway {display:none;}</style>',
esc_attr( $id ), esc_attr( $id ),
esc_html( $label ) esc_html( $label )

View file

@ -47,7 +47,7 @@ class SubscriptionHelper {
if ( ! isset( $item['data'] ) || ! is_a( $item['data'], \WC_Product::class ) ) { if ( ! isset( $item['data'] ) || ! is_a( $item['data'], \WC_Product::class ) ) {
continue; continue;
} }
if ( $item['data']->is_type( 'subscription' ) ) { if ( $item['data']->is_type( 'subscription' ) || $item['data']->is_type( 'subscription_variation' ) ) {
return true; return true;
} }
} }

View file

@ -38,6 +38,11 @@ class VaultingModule implements ModuleInterface {
*/ */
public function run( ContainerInterface $container ): void { public function run( ContainerInterface $container ): void {
$settings = $container->get( 'wcgateway.settings' );
if ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) {
return;
}
add_filter( add_filter(
'woocommerce_account_menu_items', 'woocommerce_account_menu_items',
function( $menu_links ) { function( $menu_links ) {

View file

@ -58,8 +58,8 @@ trait ProcessPaymentTrait {
* If customer has chosen a saved credit card payment. * If customer has chosen a saved credit card payment.
*/ */
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); $change_payment = filter_input( INPUT_GET, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( $saved_credit_card && ! isset( $pay_for_order ) ) { if ( $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id(); $user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id ); $customer = new \WC_Customer( $user_id );
@ -253,13 +253,19 @@ trait ProcessPaymentTrait {
} }
} }
// Adds retry counter meta to avoid duplicate invoice id error on consequent tries.
$wc_order->update_meta_data( 'ppcp-retry', (int) $wc_order->get_meta( 'ppcp-retry' ) + 1 );
$wc_order->save_meta_data();
$this->session_handler->destroy_session_data(); $this->session_handler->destroy_session_data();
wc_add_notice( $error_message, 'error' ); wc_add_notice( $error_message, 'error' );
return $failure_data; return $failure_data;
} }
WC()->cart->empty_cart();
$this->session_handler->destroy_session_data(); $this->session_handler->destroy_session_data();
return array( return array(
'result' => 'success', 'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ), 'redirect' => $this->get_return_url( $wc_order ),

View file

@ -189,8 +189,6 @@ class OrderProcessor {
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' ); $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' );
$wc_order->update_status( 'processing' ); $wc_order->update_status( 'processing' );
} }
WC()->cart->empty_cart();
$this->session_handler->destroy_session_data();
$this->last_error = ''; $this->last_error = '';
return true; return true;
} }

View file

@ -31,7 +31,10 @@ trait PrefixTrait {
*/ */
private function sanitize_custom_id( string $custom_id ): int { private function sanitize_custom_id( string $custom_id ): int {
$id = str_replace( $this->prefix, '', $custom_id ); $id = $custom_id;
if ( strlen( $this->prefix ) > 0 && 0 === strpos( $id, $this->prefix ) ) {
$id = substr( $id, strlen( $this->prefix ) );
}
return (int) $id; return (int) $id;
} }
} }

View file

@ -84,16 +84,19 @@ Follow the steps below to connect the plugin to your PayPal account:
= 1.6.2 = = 1.6.2 =
* Fix - Order of WooCommerce checkout actions causing incompatibility with AvaTax address validation #335 * Fix - Order of WooCommerce checkout actions causing incompatibility with AvaTax address validation #335
* Fix - Can't checkout to certain countries with optional postcode #330 * Fix - Can't checkout to certain countries with optional postcode #330
* FIx - Prevent subscription from being purchased when saving payment fails #308 * Fix - Prevent subscription from being purchased when saving payment fails #308
* FIx - Guest users must checkout twice for subscriptions, no smart buttons loaded #342 * Fix - Guest users must checkout twice for subscriptions, no smart buttons loaded #342
* FIx - Failed PayPal API request causing strange error #347 * Fix - Failed PayPal API request causing strange error #347
* FIx - PayPal payments page empty after switching packages #350 * Fix - PayPal payments page empty after switching packages #350
* FIx - Could Not Validate Nonce Error #239 * Fix - Could Not Validate Nonce Error #239
* FIx - Refund via PayPal dashboard does not set the WooCommerce order to "Refunded" #241 * Fix - Refund via PayPal dashboard does not set the WooCommerce order to "Refunded" #241
* FIx - Uncaught TypeError: round() #344 * Fix - Uncaught TypeError: round() #344
* FIx - Broken multi-level (nested) associative array values after getting submitted from checkout page #307 * Fix - Broken multi-level (nested) associative array values after getting submitted from checkout page #307
* FIx - Transaction id missing in some cases #328 * Fix - Transaction id missing in some cases #328
* FIx - Payment not possible in pay for order form because of terms checkbox missing #294 * Fix - Payment not possible in pay for order form because of terms checkbox missing #294
* Fix - "Save your Credit Card" shouldn't be optional when paying for a subscription #368
* Fix - When paying for a subscription and vaulting fails, cart is cleared #367
* Fix - Fatal error when activating PayPal Checkout plugin #363
= 1.6.1 = = 1.6.1 =
* Fix - Handle authorization capture failures #312 * Fix - Handle authorization capture failures #312

View file

@ -11,6 +11,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\TestCase; use WooCommerce\PayPalCommerce\ApiClient\TestCase;
use Mockery; use Mockery;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use function Brain\Monkey\Functions\expect; use function Brain\Monkey\Functions\expect;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -20,6 +21,7 @@ class IdentityTokenTest extends TestCase
private $bearer; private $bearer;
private $logger; private $logger;
private $prefix; private $prefix;
private $settings;
private $sut; private $sut;
public function setUp(): void public function setUp(): void
@ -30,7 +32,9 @@ class IdentityTokenTest extends TestCase
$this->bearer = Mockery::mock(Bearer::class); $this->bearer = Mockery::mock(Bearer::class);
$this->logger = Mockery::mock(LoggerInterface::class); $this->logger = Mockery::mock(LoggerInterface::class);
$this->prefix = 'prefix'; $this->prefix = 'prefix';
$this->sut = new IdentityToken($this->host, $this->bearer, $this->logger, $this->prefix); $this->settings = Mockery::mock(Settings::class);
$this->sut = new IdentityToken($this->host, $this->bearer, $this->logger, $this->prefix, $this->settings);
} }
public function testGenerateForCustomerReturnsToken() public function testGenerateForCustomerReturnsToken()
@ -46,6 +50,8 @@ class IdentityTokenTest extends TestCase
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class); $headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll'); $headers->shouldReceive('getAll');
$this->logger->shouldReceive('debug'); $this->logger->shouldReceive('debug');
$this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true);
$rawResponse = [ $rawResponse = [
'body' => '{"client_token":"abc123", "expires_in":3600}', 'body' => '{"client_token":"abc123", "expires_in":3600}',
@ -96,6 +102,8 @@ class IdentityTokenTest extends TestCase
when('wc_print_r')->returnArg(); when('wc_print_r')->returnArg();
$this->logger->shouldReceive('log'); $this->logger->shouldReceive('log');
$this->logger->shouldReceive('debug'); $this->logger->shouldReceive('debug');
$this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true);
$this->expectException(RuntimeException::class); $this->expectException(RuntimeException::class);
$this->sut->generate_for_customer(1); $this->sut->generate_for_customer(1);
@ -120,6 +128,8 @@ class IdentityTokenTest extends TestCase
when('wc_print_r')->returnArg(); when('wc_print_r')->returnArg();
$this->logger->shouldReceive('log'); $this->logger->shouldReceive('log');
$this->logger->shouldReceive('debug'); $this->logger->shouldReceive('debug');
$this->settings->shouldReceive('has')->andReturn(true);
$this->settings->shouldReceive('get')->andReturn(true);
$this->expectException(PayPalApiException::class); $this->expectException(PayPalApiException::class);
$this->sut->generate_for_customer(1); $this->sut->generate_for_customer(1);

View file

@ -43,6 +43,9 @@ class AmountFactoryTest extends TestCase
$cart $cart
->shouldReceive('get_discount_tax') ->shouldReceive('get_discount_tax')
->andReturn(7); ->andReturn(7);
$cart
->shouldReceive('get_subtotal_tax')
->andReturn(8);
expect('get_woocommerce_currency')->andReturn($expectedCurrency); expect('get_woocommerce_currency')->andReturn($expectedCurrency);
@ -61,7 +64,7 @@ class AmountFactoryTest extends TestCase
$this->assertEquals($expectedCurrency, $result->breakdown()->shipping()->currency_code()); $this->assertEquals($expectedCurrency, $result->breakdown()->shipping()->currency_code());
$this->assertEquals((float) 5, $result->breakdown()->item_total()->value()); $this->assertEquals((float) 5, $result->breakdown()->item_total()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->item_total()->currency_code()); $this->assertEquals($expectedCurrency, $result->breakdown()->item_total()->currency_code());
$this->assertEquals((float) 13, $result->breakdown()->tax_total()->value()); $this->assertEquals((float) 8, $result->breakdown()->tax_total()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->tax_total()->currency_code()); $this->assertEquals($expectedCurrency, $result->breakdown()->tax_total()->currency_code());
} }
@ -95,6 +98,9 @@ class AmountFactoryTest extends TestCase
$cart $cart
->shouldReceive('get_discount_tax') ->shouldReceive('get_discount_tax')
->andReturn(0); ->andReturn(0);
$cart
->shouldReceive('get_subtotal_tax')
->andReturn(11);
expect('get_woocommerce_currency')->andReturn($expectedCurrency); expect('get_woocommerce_currency')->andReturn($expectedCurrency);

View file

@ -18,13 +18,15 @@ use function Brain\Monkey\Functions\expect;
class PurchaseUnitFactoryTest extends TestCase class PurchaseUnitFactoryTest extends TestCase
{ {
private $wcOrderId = 1;
private $wcOrderNumber = '100000';
public function testWcOrderDefault() public function testWcOrderDefault()
{ {
$wcOrderId = 1;
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder $wcOrder->expects('get_order_number')->andReturn($this->wcOrderNumber);
->expects('get_order_number')->andReturn($wcOrderId); $wcOrder->expects('get_id')->andReturn($this->wcOrderId);
$wcOrder->expects('get_meta')->andReturn('');
$amount = Mockery::mock(Amount::class); $amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class); $amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory $amountFactory
@ -76,9 +78,9 @@ class PurchaseUnitFactoryTest extends TestCase
$this->assertEquals($payee, $unit->payee()); $this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description()); $this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->reference_id()); $this->assertEquals('default', $unit->reference_id());
$this->assertEquals('WC-' . $wcOrderId, $unit->custom_id()); $this->assertEquals($this->wcOrderId, $unit->custom_id());
$this->assertEquals('', $unit->soft_descriptor()); $this->assertEquals('', $unit->soft_descriptor());
$this->assertEquals('WC-' . $wcOrderId, $unit->invoice_id()); $this->assertEquals('WC-' . $this->wcOrderNumber, $unit->invoice_id());
$this->assertEquals([$item], $unit->items()); $this->assertEquals([$item], $unit->items());
$this->assertEquals($amount, $unit->amount()); $this->assertEquals($amount, $unit->amount());
$this->assertEquals($shipping, $unit->shipping()); $this->assertEquals($shipping, $unit->shipping());
@ -87,8 +89,9 @@ class PurchaseUnitFactoryTest extends TestCase
public function testWcOrderShippingGetsDroppedWhenNoPostalCode() public function testWcOrderShippingGetsDroppedWhenNoPostalCode()
{ {
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder $wcOrder->expects('get_order_number')->andReturn($this->wcOrderNumber);
->expects('get_order_number')->andReturn(1); $wcOrder->expects('get_id')->andReturn($this->wcOrderId);
$wcOrder->expects('get_meta')->andReturn('');
$amount = Mockery::mock(Amount::class); $amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class); $amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory $amountFactory
@ -142,8 +145,9 @@ class PurchaseUnitFactoryTest extends TestCase
public function testWcOrderShippingGetsDroppedWhenNoCountryCode() public function testWcOrderShippingGetsDroppedWhenNoCountryCode()
{ {
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder $wcOrder->expects('get_order_number')->andReturn($this->wcOrderNumber);
->expects('get_order_number')->andReturn(1); $wcOrder->expects('get_id')->andReturn($this->wcOrderId);
$wcOrder->expects('get_meta')->andReturn('');
$amount = Mockery::mock(Amount::class); $amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class); $amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory $amountFactory

View file

@ -42,7 +42,7 @@ class WcGatewayTest extends TestCase
$orderId = 1; $orderId = 1;
$wcOrder = Mockery::mock(\WC_Order::class); $wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->shouldReceive('get_customer_id')->andReturn(1); $wcOrder->shouldReceive('get_customer_id')->andReturn(1);
$wcOrder->shouldReceive('get_meta')->andReturn('');
$settingsRenderer = Mockery::mock(SettingsRenderer::class); $settingsRenderer = Mockery::mock(SettingsRenderer::class);
$orderProcessor = Mockery::mock(OrderProcessor::class); $orderProcessor = Mockery::mock(OrderProcessor::class);
$orderProcessor $orderProcessor
@ -106,6 +106,12 @@ class WcGatewayTest extends TestCase
when('wc_get_checkout_url') when('wc_get_checkout_url')
->justReturn('test'); ->justReturn('test');
$woocommerce = Mockery::mock(\WooCommerce::class);
$cart = Mockery::mock(\WC_Cart::class);
when('WC')->justReturn($woocommerce);
$woocommerce->cart = $cart;
$cart->shouldReceive('empty_cart');
$result = $testee->process_payment($orderId); $result = $testee->process_payment($orderId);
$this->assertIsArray($result); $this->assertIsArray($result);

View file

@ -88,8 +88,6 @@ class OrderProcessorTest extends TestCase
$sessionHandler $sessionHandler
->expects('order') ->expects('order')
->andReturn($currentOrder); ->andReturn($currentOrder);
$sessionHandler
->expects('destroy_session_data');
$orderEndpoint = Mockery::mock(OrderEndpoint::class); $orderEndpoint = Mockery::mock(OrderEndpoint::class);
$orderEndpoint $orderEndpoint
@ -129,15 +127,6 @@ class OrderProcessorTest extends TestCase
$this->environment $this->environment
); );
$cart = Mockery::mock(\WC_Cart::class);
$cart
->expects('empty_cart');
$woocommerce = Mockery::mock(\WooCommerce::class);
when('WC')
->justReturn($woocommerce);
$woocommerce->cart = $cart;
$wcOrder $wcOrder
->expects('update_meta_data') ->expects('update_meta_data')
->with( ->with(
@ -211,8 +200,6 @@ class OrderProcessorTest extends TestCase
$sessionHandler $sessionHandler
->expects('order') ->expects('order')
->andReturn($currentOrder); ->andReturn($currentOrder);
$sessionHandler
->expects('destroy_session_data');
$orderEndpoint = Mockery::mock(OrderEndpoint::class); $orderEndpoint = Mockery::mock(OrderEndpoint::class);
$orderEndpoint $orderEndpoint
->expects('patch_order_with') ->expects('patch_order_with')
@ -234,21 +221,8 @@ class OrderProcessorTest extends TestCase
->shouldReceive('has') ->shouldReceive('has')
->andReturnFalse(); ->andReturnFalse();
$cart = Mockery::mock(\WC_Cart::class);
$cart
->shouldReceive('empty_cart');
$woocommerce = Mockery::Mock(\Woocommerce::class);
$woocommerce
->shouldReceive('__get')
->with('cart')
->set('cart', $cart);
when('WC')
->justReturn($woocommerce);
$logger = Mockery::mock(LoggerInterface::class); $logger = Mockery::mock(LoggerInterface::class);
$testee = new OrderProcessor( $testee = new OrderProcessor(
$sessionHandler, $sessionHandler,
$orderEndpoint, $orderEndpoint,
@ -260,15 +234,6 @@ class OrderProcessorTest extends TestCase
$this->environment $this->environment
); );
$cart = Mockery::mock(\WC_Cart::class);
$cart
->expects('empty_cart');
$woocommerce = Mockery::mock(\WooCommerce::class);
$woocommerce->cart = $cart;
when('WC')
->justReturn($woocommerce);
$wcOrder $wcOrder
->expects('update_meta_data') ->expects('update_meta_data')
->with( ->with(