diff --git a/.github/workflows/e2e-pull-request.yml b/.github/workflows/e2e-pull-request.yml index ab0cd702a86..020ce16c0bd 100644 --- a/.github/workflows/e2e-pull-request.yml +++ b/.github/workflows/e2e-pull-request.yml @@ -47,13 +47,8 @@ jobs: strategy: fail-fast: false matrix: - test_groups: [ 'wcpay', 'subscriptions', 'upe', 'upeSplit' ] # [TODO] Unskip blocks tests after investigating constant failures. + test_groups: [ 'wcpay', 'subscriptions' ] # [TODO] Unskip blocks tests after investigating constant failures. test_branches: [ 'merchant', 'shopper' ] - exclude: - - test_groups: 'upe' - test_branches: 'merchant' - - test_groups: 'upeSplit' - test_branches: 'merchant' name: WC - latest | ${{ matrix.test_groups }} - ${{ matrix.test_branches }} diff --git a/.github/workflows/e2e-tests-atomic.yml b/.github/workflows/e2e-tests-atomic.yml index bf31de432f0..55cbe67e04a 100644 --- a/.github/workflows/e2e-tests-atomic.yml +++ b/.github/workflows/e2e-tests-atomic.yml @@ -26,16 +26,12 @@ jobs: fail-fast: false max-parallel: 1 matrix: - test_groups: [ 'wcpay', 'subscriptions', 'upe', 'upeSplit' ] + test_groups: [ 'wcpay', 'subscriptions' ] test_branches: [ 'merchant', 'shopper' ] exclude: - test_groups: 'subscriptions' - test_groups: 'wcpay' test_branches: 'merchant' - - test_groups: 'upe' - test_branches: 'merchant' - - test_groups: 'upeSplit' - test_branches: 'merchant' env: E2E_GROUP: ${{ matrix.test_groups }} diff --git a/README.md b/README.md index 72508e1b51a..f43b580b083 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ We currently support the following variables: ## Test account setup -For setting up a test account follow [these instructions](https://woo.com/document/woopayments/testing-and-troubleshooting/dev-mode/). +For setting up a test account follow [these instructions](https://woo.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/). You will need a externally accessible URL to set up the plugin. You can use ngrok for this. diff --git a/assets/images/illustrations/po-eligibility.svg b/assets/images/illustrations/po-eligibility.svg deleted file mode 100644 index 27f0885b30a..00000000000 --- a/assets/images/illustrations/po-eligibility.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 68da1290131..598679d0f3c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,54 @@ *** WooPayments Changelog *** += 7.1.0 - 2024-01-25 = +* Add - Add active plugins array to compatibility data. +* Add - Add post_types and their counts as an array to compatibility data. +* Add - Add the active theme name of the blog to the compatibility service +* Add - Expose the refund transaction ID in WooCommerce Order Refund API +* Add - Select the proper payment element when using saved Stripe Link tokens or choosing to use Stripe Link for new email. +* Add - Track filtering interactions on the Transactions page. +* Fix - Allow subscription purchase via Payment Request when no shipping methods are present. +* Fix - Allow zero-amount refunds for backwards compatibility with basic payment gateway and to allow re-stock of refunded orders. +* Fix - Checking if wcpayPaymentRequestPayForOrderParams before using it in Pay for Order page +* Fix - Checkout error when page URL is too long +* Fix - Comment: Fix QIT security tests errors. +* Fix - Fix incorrect test mode notice when left KYC early after going live from builder mode +* Fix - Fix network error that occurs when viewing an test mode order with test mode disabled, and vice versa. +* Fix - fix pay-for-order quirks and 3DS behavior +* Fix - Fix Safe Mode message reversed host +* Fix - Fix Stripe Link autofill on checkout. +* Fix - Fix Stripe Link button alignment in the Checkout Block +* Fix - Hide the transaction details refund menu for ineligble disputed transactions +* Fix - Improve clarity & readability of disputed order notice (not all text bold). +* Fix - Prevent possible fatal when using get_edit_post_link filter. +* Fix - Re-render WooPay button when cart updates, when checkout updates. +* Fix - Reinstate first deposit waiting period notice in payments overview (fix bug) +* Fix - Remove unnecessary import statement which leads to a warning when first loaded +* Fix - Resolved an error that would occur with WC 8.5.0 when editing a subscription customer from the admin dashboard. +* Fix - Resolved an issue that caused ordering the Admin Subscriptions List Table to not work when HPOS is enabled. +* Fix - Restock order items when performing full refund from transaction details page +* Fix - Reverting to manual styling over native WordPress components to fix CSS defects on Analytics page +* Fix - Send metadata in error message +* Fix - Show the correct number of days in the new account waiting period notice. +* Fix - Update WooPay tablet breakpoint. +* Fix - Verify that order exists before offering "Partial refund" option on transaction details page. +* Update - Changed the edit subscription product "Expire after" (Subscription length) so it more clearly describes when a subscription will automatically stop renewing. +* Update - Pass currency parameter and not transaction_ids parameter when creating instant deposit. +* Update - Store balance transaction ID in order metadata. +* Update - Updated BNPL sorting in settings for consistency with onboarding. +* Update - Update references to dev mode to use sandbox mode terminology. +* Update - Updates to the styling of the onboarding mode selection page. +* Update - Update style of notices within the deposits section of the settings screen. +* Dev - Added enum class for country codes +* Dev - Add new Tracks events to WooPay Save My Info checkbox +* Dev - Allow test pipelines to pass by slightly adjusting HTML selectors +* Dev - Merge UPE tests into the single and main gateway test file for unit and E2E tests. +* Dev - Place order button Tracks +* Dev - Track payment-request-button loads +* Dev - Update jetpack dependencies for syncing. +* Dev - Updates to account status logic to streamline it. +* Dev - Update subscriptions-core to 6.7.1. + = 7.0.0 - 2024-01-03 = * Add - Add Account Management tools with reset account functionality for partially onboarded accounts. * Add - Adding Compatibility Service to assist with flagging possible compatibility issues in the future. diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index 9b05ea40a26..1c42111e6e0 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -85,6 +85,7 @@ export default class WCPayAPI { if ( ! this.stripe ) { let betas = [ 'card_country_event_beta_1' ]; if ( isStripeLinkEnabled ) { + // https://stripe.com/docs/payments/link/autofill-modal betas = betas.concat( [ 'link_autofill_modal_beta_1' ] ); } diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index deed017c0d9..0ce235a0086 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -34,6 +34,7 @@ import { } from '../constants.js'; import { getDeferredIntentCreationUPEFields } from './payment-elements'; import { handleWooPayEmailInput } from '../woopay/email-input-iframe'; +import wcpayTracks from 'tracks'; import wooPayExpressCheckoutPaymentMethod from '../woopay/express-button/woopay-express-checkout-payment-method'; import { isPreviewing } from '../preview'; @@ -113,6 +114,24 @@ Object.entries( enabledPaymentMethodsConfig ) } ); } ); +const addCheckoutTracking = () => { + const placeOrderButton = document.getElementsByClassName( + 'wc-block-components-checkout-place-order-button' + ); + if ( placeOrderButton.length ) { + placeOrderButton[ 0 ].addEventListener( 'click', () => { + const blocksCheckbox = document.getElementById( + 'radio-control-wc-payment-method-options-woocommerce_payments' + ); + if ( ! blocksCheckbox?.checked ) { + return; + } + + wcpayTracks.recordUserEvent( wcpayTracks.events.PLACE_ORDER_CLICK ); + } ); + } +}; + // Call handleWooPayEmailInput if woopay is enabled and this is the checkout page. if ( getUPEConfig( 'isWooPayEnabled' ) ) { if ( @@ -131,4 +150,5 @@ if ( getUPEConfig( 'isWooPayEnabled' ) ) { registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) ); window.addEventListener( 'load', () => { enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); + addCheckoutTracking(); } ); diff --git a/client/checkout/blocks/payment-processor.js b/client/checkout/blocks/payment-processor.js index aff4137356f..a88c74fa7cd 100644 --- a/client/checkout/blocks/payment-processor.js +++ b/client/checkout/blocks/payment-processor.js @@ -27,10 +27,6 @@ import { import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; import { getUPEConfig } from 'wcpay/utils/checkout'; import { validateElements } from 'wcpay/checkout/classic/payment-processing'; -import { - BLOCKS_SHIPPING_ADDRESS_FIELDS, - BLOCKS_BILLING_ADDRESS_FIELDS, -} from '../constants'; const getBillingDetails = ( billingData ) => { return { @@ -74,8 +70,11 @@ const PaymentProcessor = ( { const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); const isTestMode = getUPEConfig( 'testMode' ); const gatewayConfig = getPaymentMethods()[ upeMethods[ paymentMethodId ] ]; - const customerData = useCustomerData(); - const billingData = customerData.billingAddress; + const { + billingAddress: billingData, + setShippingAddress, + setBillingAddress, + } = useCustomerData(); useEffect( () => { if ( isLinkEnabled( paymentMethodsConfig ) ) { @@ -83,57 +82,52 @@ const PaymentProcessor = ( { api: api, elements: elements, emailId: 'email', - fill_field_method: ( address, nodeId, key ) => { - const setAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.setShippingAddress - : customerData.setBillingData || - customerData.setBillingAddress; - const customerAddress = - BLOCKS_SHIPPING_ADDRESS_FIELDS[ key ] === nodeId - ? customerData.shippingAddress - : customerData.billingData || - customerData.billingAddress; - - if ( key === 'line1' ) { - customerAddress.address_1 = address.address[ key ]; - } else if ( key === 'line2' ) { - customerAddress.address_2 = address.address[ key ]; - } else if ( key === 'postal_code' ) { - customerAddress.postcode = address.address[ key ]; - } else { - customerAddress[ key ] = address.address[ key ]; + onAutofill: ( billingAddress, shippingAddress ) => { + // in some cases (e.g.: customer doesn't select the payment method in the Link modal), the billing address is empty. + if ( billingAddress ) { + // setting the country first, in case the "state"/"county"/"province" + // select changes from a select to a text field (or vice-versa). + setBillingAddress( { + country: billingAddress.country, + } ); + // after the country, we can safely set the other fields + setBillingAddress( { + ...billingAddress, + } ); } - setAddress( customerAddress ); - - if ( customerData.billingData ) { - customerData.billingData.email = getBlocksEmailValue(); - customerData.setBillingData( customerData.billingData ); - } else { - customerData.billingAddress.email = getBlocksEmailValue(); - customerData.setBillingAddress( - customerData.billingAddress - ); + // in some cases (e.g.: customer doesn't select the shipping address method in the Link modal), + // the shipping address is empty. + if ( shippingAddress ) { + // setting the country first, in case the "state"/"county"/"province" + // select changes from a select to a text field (or vice-versa). + setShippingAddress( { + country: shippingAddress.country, + } ); + // after the country, we can safely set the other fields + setShippingAddress( { + ...shippingAddress, + } ); } + + // after all the above, we can now set the email field by getting its value from the DOM. + setBillingAddress( { + email: getBlocksEmailValue(), + } ); + setShippingAddress( { + email: getBlocksEmailValue(), + } ); }, - show_button: blocksShowLinkButtonHandler, - shipping_fields: BLOCKS_SHIPPING_ADDRESS_FIELDS, - billing_fields: BLOCKS_BILLING_ADDRESS_FIELDS, - complete_shipping: () => { - return ( - document.getElementById( 'shipping-address_1' ) !== null - ); - }, - complete_billing: () => { - return ( - document.getElementById( 'billing-address_1' ) !== null - ); - }, + onButtonShow: blocksShowLinkButtonHandler, } ); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ elements ] ); + }, [ + api, + elements, + paymentMethodsConfig, + setBillingAddress, + setShippingAddress, + ] ); useEffect( () => diff --git a/client/checkout/blocks/saved-token-handler.js b/client/checkout/blocks/saved-token-handler.js index 0f8d6e42d46..86becd43f88 100644 --- a/client/checkout/blocks/saved-token-handler.js +++ b/client/checkout/blocks/saved-token-handler.js @@ -4,6 +4,7 @@ import { useEffect } from 'react'; import { usePaymentCompleteHandler } from './hooks'; import { useSelect } from '@wordpress/data'; +import { removeLinkButton } from '../stripe-link'; export const SavedTokenHandler = ( { api, @@ -46,5 +47,9 @@ export const SavedTokenHandler = ( { false // No need to save a payment that has already been saved. ); + // Once saved token component is loaded, Stripe Link button should be removed, + // because payment elements are not used then and there's no element to attach the button to. + removeLinkButton(); + return <>; }; diff --git a/client/checkout/blocks/style.scss b/client/checkout/blocks/style.scss index 22f8aaa7dd2..3b93a1ccdbd 100644 --- a/client/checkout/blocks/style.scss +++ b/client/checkout/blocks/style.scss @@ -22,6 +22,11 @@ padding-bottom: 1.5em; } +.wc-block-components-text-input button.wcpay-stripelink-modal-trigger { + top: 50%; + transform: translateY( -50% ); +} + button.wcpay-stripelink-modal-trigger { display: none; position: absolute; diff --git a/client/checkout/classic/3ds-flow-handling.js b/client/checkout/classic/3ds-flow-handling.js index 28171ec7207..305db950ca4 100644 --- a/client/checkout/classic/3ds-flow-handling.js +++ b/client/checkout/classic/3ds-flow-handling.js @@ -22,37 +22,27 @@ const cleanupURL = () => { }; export const showAuthenticationModalIfRequired = ( api ) => { - const url = window.location.href; const paymentMethodId = document.querySelector( '#wcpay-payment-method' ) ?.value; const confirmation = api.confirmIntent( - url, + window.location.href, shouldSavePaymentPaymentMethod() ? paymentMethodId : null ); // Boolean `true` means that there is nothing to confirm. if ( confirmation === true ) { - return; + return Promise.resolve(); } const { request } = confirmation; cleanupURL(); - request + return request .then( ( redirectUrl ) => { window.location = redirectUrl; } ) .catch( ( error ) => { - document - .querySelector( 'form.checkout' ) - .classList.remove( 'processing' ); - - const elements = document.getElementsByClassName( 'blockUI' ); - Array.from( elements ).forEach( ( element ) => { - element.parentNode.removeChild( element ); - } ); - let errorMessage = error.message; // If this is a generic error, we probably don't want to display the error message to the user, diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index 00a2581b8a9..b6daa42875a 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -19,6 +19,8 @@ import { renderTerms, createAndConfirmSetupIntent, maybeEnableStripeLink, + blockUI, + unblockUI, } from './payment-processing'; import enqueueFraudScripts from 'fraud-scripts'; import { showAuthenticationModalIfRequired } from './3ds-flow-handling'; @@ -26,6 +28,7 @@ import WCPayAPI from 'wcpay/checkout/api'; import apiRequest from '../utils/request'; import { handleWooPayEmailInput } from 'wcpay/checkout/woopay/email-input-iframe'; import { isPreviewing } from 'wcpay/checkout/preview'; +import wcpayTracks from 'tracks'; jQuery( function ( $ ) { enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); @@ -36,6 +39,15 @@ jQuery( function ( $ ) { return; } + const $checkoutForm = $( 'form.checkout' ); + const $addPaymentMethodForm = $( 'form#add_payment_method' ); + const $payForOrderForm = $( 'form#order_review' ); + + // creating a new jQuery object containing all the forms that need to be updated on submit, failure, or other events. + const $forms = jQuery( $checkoutForm ) + .add( $addPaymentMethodForm ) + .add( $payForOrderForm ); + const api = new WCPayAPI( { publishableKey: publishableKey, @@ -48,19 +60,38 @@ jQuery( function ( $ ) { }, apiRequest ); - showAuthenticationModalIfRequired( api ); + + blockUI( $forms ); + showAuthenticationModalIfRequired( api ).finally( () => { + unblockUI( $forms ); + } ); $( document.body ).on( 'updated_checkout', () => { maybeMountStripePaymentElement(); } ); - $( 'form.checkout' ).on( generateCheckoutEventNames(), function () { + $checkoutForm.on( generateCheckoutEventNames(), function () { return processPaymentIfNotUsingSavedMethod( $( this ) ); } ); + $checkoutForm.on( 'click', '#place_order', function () { + const isWCPay = document.getElementById( + 'payment_method_woocommerce_payments' + )?.checked; + + if ( ! isWCPay ) { + return; + } + + wcpayTracks.recordUserEvent( wcpayTracks.events.PLACE_ORDER_CLICK ); + } ); + window.addEventListener( 'hashchange', () => { if ( window.location.hash.startsWith( '#wcpay-confirm-' ) ) { - showAuthenticationModalIfRequired( api ); + blockUI( $forms ); + showAuthenticationModalIfRequired( api, $forms ).finally( () => { + unblockUI( $forms ); + } ); } } ); @@ -73,35 +104,33 @@ jQuery( function ( $ ) { } } ); - if ( - $( 'form#add_payment_method' ).length || - $( 'form#order_review' ).length - ) { + if ( $addPaymentMethodForm.length || $payForOrderForm.length ) { maybeMountStripePaymentElement(); } - $( 'form#add_payment_method' ).on( 'submit', function () { + $addPaymentMethodForm.on( 'submit', function () { if ( - $( - "#add_payment_method input:checked[name='payment_method']" - ).val() !== 'woocommerce_payments' + $addPaymentMethodForm + .find( "input:checked[name='payment_method']" ) + .val() !== 'woocommerce_payments' ) { return; } + // WC core calls block() when add_payment_method form is submitted, so we need to enable the ignore flag here to avoid // the overlay blink when the form is blocked twice. $.blockUI.defaults.ignoreIfBlocked = true; return processPayment( api, - $( 'form#add_payment_method' ), + $addPaymentMethodForm, getSelectedUPEGatewayPaymentMethod(), createAndConfirmSetupIntent ); } ); - $( 'form#order_review' ).on( 'submit', function () { - return processPaymentIfNotUsingSavedMethod( $( 'form#order_review' ) ); + $payForOrderForm.on( 'submit', function () { + return processPaymentIfNotUsingSavedMethod( $payForOrderForm ); } ); if ( @@ -135,6 +164,8 @@ jQuery( function ( $ ) { function restrictPaymentMethodToLocation( upeElement ) { if ( isPaymentMethodRestrictedToLocation( upeElement ) ) { togglePaymentMethodForCountry( upeElement ); + + // this event only applies to the checkout form, but not "place order" or "add payment method" pages. $( '#billing_country' ).on( 'change', function () { togglePaymentMethodForCountry( upeElement ); } ); diff --git a/client/checkout/classic/payment-processing.js b/client/checkout/classic/payment-processing.js index 2b1542792ff..e551d49f80e 100644 --- a/client/checkout/classic/payment-processing.js +++ b/client/checkout/classic/payment-processing.js @@ -16,7 +16,9 @@ import { getUpeSettings, isLinkEnabled, } from 'wcpay/checkout/utils/upe'; -import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; +import enableStripeLinkPaymentMethod, { + switchToNewPaymentTokenElement, +} from 'wcpay/checkout/stripe-link'; import { SHORTCODE_SHIPPING_ADDRESS_FIELDS, SHORTCODE_BILLING_ADDRESS_FIELDS, @@ -51,12 +53,12 @@ function initializeAppearance( api ) { } /** - * Block UI to indicate processing and avoid duplicate submission. + * Block the UI to indicate processing and avoid duplicate submission. * - * @param {Object} jQueryForm The jQuery object for the jQueryForm. + * @param {Object} $form The jQuery object for the form. */ -function blockUI( jQueryForm ) { - jQueryForm.addClass( 'processing' ).block( { +export function blockUI( $form ) { + $form.addClass( 'processing' ).block( { message: null, overlayCSS: { background: '#fff', @@ -65,6 +67,15 @@ function blockUI( jQueryForm ) { } ); } +/** + * Unblocks the UI to allow payment processing. + * + * @param {Object} $form The jQuery object for the form. + */ +export function unblockUI( $form ) { + $form.removeClass( 'processing' ).unblock(); +} + /** * Validates the Stripe elements by submitting them and handling any errors that occur during submission. * If an error occurs, the function removes loading effect from the provided jQuery form and thus unblocks it, @@ -106,32 +117,56 @@ function createStripePaymentMethod( jQueryForm, paymentMethodType ) { + /* global wcpayCustomerData */ let params = {}; + if ( window.wcpayCustomerData ) { + params = { + billing_details: { + name: wcpayCustomerData.name || undefined, + email: wcpayCustomerData.email, + address: { + country: wcpayCustomerData.billing_country, + }, + }, + }; + } + if ( jQueryForm.attr( 'name' ) === 'checkout' ) { params = { billing_details: { - name: document.querySelector( '#billing_first_name' ) - ? ( - document.querySelector( '#billing_first_name' ) - ?.value + - ' ' + - document.querySelector( '#billing_last_name' ) - ?.value - ).trim() - : undefined, + ...params.billing_details, + name: + `${ + document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name }` + )?.value || '' + } ${ + document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name }` + )?.value || '' + }`.trim() || undefined, email: document.querySelector( '#billing_email' )?.value, phone: document.querySelector( '#billing_phone' )?.value, address: { - city: document.querySelector( '#billing_city' )?.value, - country: document.querySelector( '#billing_country' ) - ?.value, - line1: document.querySelector( '#billing_address_1' ) - ?.value, - line2: document.querySelector( '#billing_address_2' ) - ?.value, - postal_code: document.querySelector( '#billing_postcode' ) - ?.value, - state: document.querySelector( '#billing_state' )?.value, + ...params.billing_details?.address, + city: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.city }` + )?.value, + country: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }` + )?.value, + line1: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.address_1 }` + )?.value, + line2: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.address_2 }` + )?.value, + postal_code: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.postcode }` + )?.value, + state: document.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.state }` + )?.value, }, }, }; @@ -205,6 +240,28 @@ function appendSetupIntentToForm( form, confirmedIntent ) { form.append( input ); } +const ensureSameAsBillingIsUnchecked = () => { + const sameAsBillingCheckbox = document.getElementById( + 'ship-to-different-address-checkbox' + ); + + if ( ! sameAsBillingCheckbox ) { + return; + } + + if ( sameAsBillingCheckbox.checked === true ) { + return; + } + + sameAsBillingCheckbox.checked = true; + + if ( window.jQuery ) { + const $sameAsBillingCheckbox = window.jQuery( sameAsBillingCheckbox ); + + $sameAsBillingCheckbox.prop( 'checked', true ).change(); + } +}; + /** * If Link is enabled, add event listeners and handlers. * @@ -216,21 +273,85 @@ export function maybeEnableStripeLink( api ) { api: api, elements: gatewayUPEComponents.card.elements, emailId: 'billing_email', - complete_billing: () => { - return true; + onAutofill: ( billingAddress, shippingAddress ) => { + const fillAddress = ( addressValues, fieldsMap ) => { + // in some cases, the address might be empty. + if ( ! addressValues ) return; + + // setting the country first, in case the "state"/"county"/"province" + // select changes from a select to a text field (or vice-versa). + const countryElement = document.getElementById( + fieldsMap.country + ); + if ( countryElement ) { + countryElement.value = addressValues.country; + // manually dispatching the "change" event, since the element might not be a `select2` component. + countryElement.dispatchEvent( new Event( 'change' ) ); + } + + Object.entries( addressValues ).forEach( + ( [ piece, value ] ) => { + const element = document.getElementById( + fieldsMap[ piece ] + ); + if ( element ) { + element.value = value; + } + } + ); + }; + + // this is needed on shortcode checkout, but not on blocks checkout. + ensureSameAsBillingIsUnchecked(); + + fillAddress( billingAddress, SHORTCODE_BILLING_ADDRESS_FIELDS ); + fillAddress( + shippingAddress, + SHORTCODE_SHIPPING_ADDRESS_FIELDS + ); + + // manually dispatching the "change" event, since the element might be a `select2` component. + document + .querySelectorAll( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }, #${ SHORTCODE_BILLING_ADDRESS_FIELDS.state }, ` + + `#${ SHORTCODE_SHIPPING_ADDRESS_FIELDS.country }, #${ SHORTCODE_SHIPPING_ADDRESS_FIELDS.state }` + ) + .forEach( ( element ) => { + if ( ! window.jQuery ) return; + + const $element = window.jQuery( element ); + if ( $element.data( 'select2' ) ) { + $element.trigger( 'change' ); + } + } ); }, - complete_shipping: () => { - return ( - document.getElementById( - 'ship-to-different-address-checkbox' - ) && - document.getElementById( - 'ship-to-different-address-checkbox' - ).checked + onButtonShow: ( linkAutofill ) => { + // Display StripeLink button if email field is prefilled. + const billingEmailInput = document.getElementById( + 'billing_email' + ); + if ( billingEmailInput.value !== '' ) { + const linkButtonTop = + billingEmailInput.offsetTop + + ( billingEmailInput.offsetHeight - 40 ) / 2; + const stripeLinkButton = document.querySelector( + '.wcpay-stripelink-modal-trigger' + ); + stripeLinkButton.style.display = 'block'; + stripeLinkButton.style.top = `${ linkButtonTop }px`; + } + + // Handle StripeLink button click. + const stripeLinkButton = document.querySelector( + '.wcpay-stripelink-modal-trigger' ); + stripeLinkButton.addEventListener( 'click', ( event ) => { + event.preventDefault(); + // Trigger modal. + linkAutofill.launch( { email: billingEmailInput.value } ); + switchToNewPaymentTokenElement(); + } ); }, - shipping_fields: SHORTCODE_SHIPPING_ADDRESS_FIELDS, - billing_fields: SHORTCODE_BILLING_ADDRESS_FIELDS, } ); } } @@ -325,16 +446,16 @@ export function renderTerms( event ) { let hasCheckoutCompleted; export const processPayment = ( api, - jQueryForm, + $form, paymentMethodType, - additionalActionsHandler = () => {} + additionalActionsHandler = () => Promise.resolve() ) => { if ( hasCheckoutCompleted ) { hasCheckoutCompleted = false; return; } - blockUI( jQueryForm ); + blockUI( $form ); const elements = gatewayUPEComponents[ paymentMethodType ].elements; @@ -344,24 +465,24 @@ export const processPayment = ( const paymentMethodObject = await createStripePaymentMethod( api, elements, - jQueryForm, + $form, paymentMethodType ); - appendFingerprintInputToForm( jQueryForm, fingerprint ); + appendFingerprintInputToForm( $form, fingerprint ); appendPaymentMethodIdToForm( - jQueryForm, + $form, paymentMethodObject.paymentMethod.id ); await additionalActionsHandler( paymentMethodObject.paymentMethod, - jQueryForm, + $form, api ); hasCheckoutCompleted = true; - submitForm( jQueryForm ); + submitForm( $form ); } catch ( err ) { hasCheckoutCompleted = false; - jQueryForm.removeClass( 'processing' ).unblock(); + unblockUI( $form ); showErrorCheckout( err.message ); } } )(); diff --git a/client/checkout/constants.js b/client/checkout/constants.js index 4f48eae96fd..e2e4c6dfe93 100644 --- a/client/checkout/constants.js +++ b/client/checkout/constants.js @@ -35,42 +35,22 @@ export function getPaymentMethodsConstants() { ]; } -export const BLOCKS_SHIPPING_ADDRESS_FIELDS = { - line1: 'shipping-address_1', - line2: 'shipping-address_2', - city: 'shipping-city', - state: 'components-form-token-input-1', - postal_code: 'shipping-postcode', - country: 'components-form-token-input-0', - first_name: 'shipping-first_name', - last_name: 'shipping-last_name', -}; -export const BLOCKS_BILLING_ADDRESS_FIELDS = { - line1: 'billing-address_1', - line2: 'billing-address_2', - city: 'billing-city', - state: 'components-form-token-input-3', - postal_code: 'billing-postcode', - country: 'components-form-token-input-2', - first_name: 'billing-first_name', - last_name: 'billing-last_name', -}; export const SHORTCODE_SHIPPING_ADDRESS_FIELDS = { - line1: 'shipping_address_1', - line2: 'shipping_address_2', + address_1: 'shipping_address_1', + address_2: 'shipping_address_2', city: 'shipping_city', state: 'shipping_state', - postal_code: 'shipping_postcode', + postcode: 'shipping_postcode', country: 'shipping_country', first_name: 'shipping_first_name', last_name: 'shipping_last_name', }; export const SHORTCODE_BILLING_ADDRESS_FIELDS = { - line1: 'billing_address_1', - line2: 'billing_address_2', + address_1: 'billing_address_1', + address_2: 'billing_address_2', city: 'billing_city', state: 'billing_state', - postal_code: 'billing_postcode', + postcode: 'billing_postcode', country: 'billing_country', first_name: 'billing_first_name', last_name: 'billing_last_name', diff --git a/client/checkout/stripe-link/index.js b/client/checkout/stripe-link/index.js index 08ba006f814..9a9c63acf4a 100644 --- a/client/checkout/stripe-link/index.js +++ b/client/checkout/stripe-link/index.js @@ -1,133 +1,70 @@ -const showLinkButton = ( linkAutofill ) => { - // Display StripeLink button if email field is prefilled. - const billingEmailInput = document.getElementById( 'billing_email' ); - if ( billingEmailInput.value !== '' ) { - const linkButtonTop = - billingEmailInput.offsetTop + - ( billingEmailInput.offsetHeight - 40 ) / 2; - const stripeLinkButton = document.querySelector( - '.wcpay-stripelink-modal-trigger' - ); - stripeLinkButton.style.display = 'block'; - stripeLinkButton.style.top = `${ linkButtonTop }px`; +/** + * Internal dependencies + */ +import { dispatchChangeEventFor } from '../utils/upe'; + +export const switchToNewPaymentTokenElement = () => { + const newPaymentTokenElement = document.getElementById( + 'wc-woocommerce_payments-payment-token-new' + ); + if ( newPaymentTokenElement && ! newPaymentTokenElement.checked ) { + newPaymentTokenElement.checked = true; + dispatchChangeEventFor( newPaymentTokenElement ); } +}; - // Handle StripeLink button click. +export const removeLinkButton = () => { const stripeLinkButton = document.querySelector( '.wcpay-stripelink-modal-trigger' ); - stripeLinkButton.addEventListener( 'click', ( event ) => { - event.preventDefault(); - // Trigger modal. - linkAutofill.launch( { email: billingEmailInput.value } ); - } ); -}; - -export const autofill = ( event, options ) => { - const { billingAddress, shippingAddress } = event.value; - const fillWith = options.fill_field_method - ? options.fill_field_method - : ( address, nodeId, key ) => { - if ( document.getElementById( nodeId ) !== null ) { - document.getElementById( nodeId ).value = - address.address[ key ]; - } - }; - - if ( options.complete_shipping() ) { - const shippingNames = shippingAddress.name.split( / (.*)/s, 2 ); - shippingAddress.address.last_name = shippingNames[ 1 ]; - shippingAddress.address.first_name = shippingNames[ 0 ]; - - fillWith( shippingAddress, options.shipping_fields.line1, 'line1' ); - fillWith( shippingAddress, options.shipping_fields.line2, 'line2' ); - fillWith( shippingAddress, options.shipping_fields.city, 'city' ); - fillWith( shippingAddress, options.shipping_fields.country, 'country' ); - fillWith( - shippingAddress, - options.shipping_fields.first_name, - 'first_name' - ); - fillWith( - shippingAddress, - options.shipping_fields.last_name, - 'last_name' - ); - const billingCountryStateSelects = document.querySelectorAll( - '#billing_country, #billing_state, #shipping_country, #shipping_state' - ); - billingCountryStateSelects.forEach( ( select ) => - select.dispatchEvent( new Event( 'change' ) ) - ); - fillWith( shippingAddress, options.shipping_fields.state, 'state' ); - fillWith( - shippingAddress, - options.shipping_fields.postal_code, - 'postal_code' - ); + if ( stripeLinkButton ) { + stripeLinkButton.remove(); } +}; - if ( options.complete_billing() ) { - const billingNames = billingAddress.name.split( / (.*)/s, 2 ); - billingAddress.address.last_name = billingNames[ 1 ]; - billingAddress.address.first_name = billingNames[ 0 ]; - - fillWith( billingAddress, options.billing_fields.line1, 'line1' ); - fillWith( billingAddress, options.billing_fields.line2, 'line2' ); - fillWith( billingAddress, options.billing_fields.city, 'city' ); - fillWith( billingAddress, options.billing_fields.country, 'country' ); - fillWith( - billingAddress, - options.billing_fields.first_name, - 'first_name' - ); - fillWith( - billingAddress, - options.billing_fields.last_name, - 'last_name' - ); +const transformStripeLinkAddress = ( address ) => { + // when clicking "use another address" or "use another payment method", the returned value for shipping/billing might be `null`. + if ( ! address ) return null; - const billingCountryStateSelects = document.querySelectorAll( - '#billing_country, #billing_state, #shipping_country, #shipping_state' - ); - billingCountryStateSelects.forEach( ( select ) => - select.dispatchEvent( new Event( 'change' ) ) - ); - fillWith( billingAddress, options.billing_fields.state, 'state' ); - fillWith( - billingAddress, - options.billing_fields.postal_code, - 'postal_code' - ); - } - const billingCountryStateSelects = document.querySelectorAll( - '#billing_country, #billing_state, #shipping_country, #shipping_state' - ); - billingCountryStateSelects.forEach( ( select ) => - select.dispatchEvent( new Event( 'change' ) ) - ); + const [ firstName, lastName ] = address.name.split( / (.*)/s, 2 ); + return { + first_name: firstName || '', + last_name: lastName || '', + address_1: address.address.line1 || '', + address_2: address.address.line2 || '', + city: address.address.city || '', + country: address.address.country || '', + postcode: address.address.postal_code || '', + state: address.address.state || '', + // missing fields from Stripe autofill: phone, company + }; }; const enableStripeLinkPaymentMethod = ( options ) => { - if ( ! document.getElementById( options.emailId ) ) { + const emailField = document.getElementById( options.emailId ); + + if ( ! emailField ) { return; } - const api = options.api; - const linkAutofill = api.getStripe().linkAutofillModal( options.elements ); - document - .getElementById( options.emailId ) - .addEventListener( 'keyup', ( event ) => { - linkAutofill.launch( { email: event.target.value } ); - } ); + // https://stripe.com/docs/payments/link/autofill-modal + const linkAutofill = options.api + .getStripe() + .linkAutofillModal( options.elements ); - const showButton = options.show_button - ? options.show_button - : showLinkButton; - showButton( linkAutofill ); + emailField.addEventListener( 'keyup', ( event ) => { + linkAutofill.launch( { email: event.target.value } ); + } ); + + options.onButtonShow( linkAutofill ); linkAutofill.on( 'autofill', ( event ) => { - autofill( event, options ); + const { billingAddress, shippingAddress } = event.value; + options.onAutofill( + transformStripeLinkAddress( billingAddress ), + transformStripeLinkAddress( shippingAddress ) + ); + switchToNewPaymentTokenElement(); } ); }; diff --git a/client/checkout/stripe-link/test/index.test.js b/client/checkout/stripe-link/test/index.test.js index 4440a4df7c2..28f2cc10669 100644 --- a/client/checkout/stripe-link/test/index.test.js +++ b/client/checkout/stripe-link/test/index.test.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import enableStripeLinkPaymentMethod, { autofill } from '..'; +import enableStripeLinkPaymentMethod from '..'; import WCPayAPI from 'wcpay/checkout/api'; jest.mock( 'wcpay/checkout/api', () => { @@ -45,6 +45,8 @@ describe( 'Stripe Link elements behavior', () => { enableStripeLinkPaymentMethod( { api: WCPayAPI(), emailId: 'billing_email', + onAutofill: () => null, + onButtonShow: () => null, } ); expect( WCPayAPI().getStripe().linkAutofillModal ).toHaveBeenCalled(); } ); @@ -60,6 +62,8 @@ describe( 'Stripe Link elements behavior', () => { enableStripeLinkPaymentMethod( { api: WCPayAPI(), emailId: 'billing_email', + onAutofill: () => null, + onButtonShow: () => null, } ); billingEmailInput.dispatchEvent( new Event( 'keyup' ) ); @@ -72,74 +76,17 @@ describe( 'Stripe Link elements behavior', () => { ).toHaveBeenCalledWith( { email: billingEmail } ); } ); - test( 'Stripe Link button should be visible and clickable', () => { + test( 'Stripe Link button should call onButtonShow configuration value', () => { createStripeLinkElements(); - const stripeLinkButton = document.getElementsByClassName( - 'wcpay-stripelink-modal-trigger' - )[ 0 ]; - const addEventListenerSpy = jest.spyOn( - stripeLinkButton, - 'addEventListener' - ); - + const handleButtonShow = jest.fn(); enableStripeLinkPaymentMethod( { api: WCPayAPI(), emailId: 'billing_email', + onAutofill: () => null, + onButtonShow: handleButtonShow, } ); - expect( stripeLinkButton.style.display ).toBe( 'block' ); - expect( stripeLinkButton.style.top ).not.toBe( '' ); - - stripeLinkButton.click(); - expect( addEventListenerSpy ).toHaveBeenCalledWith( - 'click', - expect.any( Function ) - ); - expect( - WCPayAPI().getStripe().linkAutofillModal().launch - ).toHaveBeenCalledWith( { email: billingEmail } ); - } ); - - test( 'Custom fill function should be called', () => { - const customFillFunction = jest.fn(); - autofill( - { - value: { - billingAddress: '123 Main St', - shippingAddress: { - name: 'First Last', - address: { - line1: '123 Main St', - line2: 'shipping', - state: 'AK', - country: 'US', - city: 'San Francisco', - postal_code: '94110', - }, - }, - }, - }, - { - fill_field_method: customFillFunction, - complete_shipping: () => { - return true; - }, - complete_billing: () => { - return false; - }, - shipping_fields: { - line1: 'shipping_address_1', - line2: 'shipping_address_2', - city: 'shipping_city', - state: 'shipping_state', - postal_code: 'shipping_postcode', - country: 'shipping_country', - first_name: 'shipping_first_name', - last_name: 'shipping_last_name', - }, - } - ); - expect( customFillFunction ).toHaveBeenCalled(); + expect( handleButtonShow ).toHaveBeenCalled(); } ); function createStripeLinkElements() { @@ -162,330 +109,3 @@ describe( 'Stripe Link elements behavior', () => { document.body.appendChild( stripeLinkButton ); } } ); - -describe( 'Autofilling billing fields', () => { - beforeEach( () => { - createEmptyBillingFields(); - } ); - - afterEach( () => { - resetBillingFields(); - } ); - - test( 'Should fill the billing fields when complete_billing is true', () => { - autofill( - { - value: { - billingAddress: { - name: 'First Last', - address: { - line1: '123 Main St', - line2: 'shipping', - state: 'AK', - country: 'US', - city: 'San Francisco', - postal_code: '94110', - }, - }, - }, - }, - { - complete_shipping: () => { - return false; - }, - complete_billing: () => { - return true; - }, - billing_fields: { - line1: 'billing_address_1', - line2: 'billing_address_2', - city: 'billing_city', - state: 'billing_state', - postal_code: 'billing_postcode', - country: 'billing_country', - first_name: 'billing_first_name', - last_name: 'billing_last_name', - }, - } - ); - - expect( document.getElementById( 'billing_first_name' ).value ).toBe( - 'First' - ); - expect( document.getElementById( 'billing_last_name' ).value ).toBe( - 'Last' - ); - expect( document.getElementById( 'billing_address_1' ).value ).toBe( - '123 Main St' - ); - expect( document.getElementById( 'billing_address_2' ).value ).toBe( - 'shipping' - ); - expect( document.getElementById( 'billing_city' ).value ).toBe( - 'San Francisco' - ); - expect( document.getElementById( 'billing_state' ).value ).toBe( 'AK' ); - expect( document.getElementById( 'billing_postcode' ).value ).toBe( - '94110' - ); - expect( document.getElementById( 'billing_country' ).value ).toBe( - 'US' - ); - } ); - - test( 'Should not fill the billing fields when complete_billing is false', () => { - autofill( - { - value: {}, - }, - { - complete_shipping: () => { - return false; - }, - complete_billing: () => { - return false; - }, - } - ); - - expect( document.getElementById( 'billing_first_name' ).value ).toBe( - '' - ); - expect( document.getElementById( 'billing_last_name' ).value ).toBe( - '' - ); - expect( document.getElementById( 'billing_address_1' ).value ).toBe( - '' - ); - expect( document.getElementById( 'billing_address_2' ).value ).toBe( - '' - ); - expect( document.getElementById( 'billing_city' ).value ).toBe( '' ); - expect( document.getElementById( 'billing_state' ).value ).toBe( '' ); - expect( document.getElementById( 'billing_postcode' ).value ).toBe( - '' - ); - expect( document.getElementById( 'billing_country' ).value ).toBe( '' ); - } ); - - function createEmptyBillingFields() { - const firstName = document.createElement( 'input' ); - firstName.setAttribute( 'id', 'billing_first_name' ); - - const lastName = document.createElement( 'input' ); - lastName.setAttribute( 'id', 'billing_last_name' ); - - const address1 = document.createElement( 'input' ); - address1.setAttribute( 'id', 'billing_address_1' ); - - const address2 = document.createElement( 'input' ); - address2.setAttribute( 'id', 'billing_address_2' ); - - const city = document.createElement( 'input' ); - city.setAttribute( 'id', 'billing_city' ); - - const state = document.createElement( 'input' ); - state.setAttribute( 'id', 'billing_state' ); - - const postcode = document.createElement( 'input' ); - postcode.setAttribute( 'id', 'billing_postcode' ); - - const country = document.createElement( 'input' ); - country.setAttribute( 'id', 'billing_country' ); - - document.body.appendChild( firstName ); - document.body.appendChild( lastName ); - document.body.appendChild( address1 ); - document.body.appendChild( address2 ); - document.body.appendChild( city ); - document.body.appendChild( state ); - document.body.appendChild( postcode ); - document.body.appendChild( country ); - } - - function resetBillingFields() { - const firstName = document.querySelector( '#billing_first_name' ); - const lastName = document.querySelector( '#billing_last_name' ); - const address1 = document.querySelector( '#billing_address_1' ); - const address2 = document.querySelector( '#billing_address_2' ); - const city = document.querySelector( '#billing_city' ); - const state = document.querySelector( '#billing_state' ); - const postcode = document.querySelector( '#billing_postcode' ); - const country = document.querySelector( '#billing_country' ); - - firstName.value = ''; - lastName.value = ''; - address1.value = ''; - address2.value = ''; - city.value = ''; - state.value = ''; - postcode.value = ''; - country.value = ''; - } -} ); - -describe( 'Autofilling shipping fields', () => { - beforeEach( () => { - createEmptyShippingFields(); - } ); - - afterEach( () => { - resetShippingFields(); - } ); - - test( 'Should fill the shipping fields when complete_shipping is true', () => { - autofill( - { - value: { - billingAddress: '123 Main St', - shippingAddress: { - name: 'First Last', - address: { - line1: '123 Main St', - line2: 'shipping', - state: 'AK', - country: 'US', - city: 'San Francisco', - postal_code: '94110', - }, - }, - }, - }, - { - complete_shipping: () => { - return true; - }, - complete_billing: () => { - return false; - }, - shipping_fields: { - line1: 'shipping_address_1', - line2: 'shipping_address_2', - city: 'shipping_city', - state: 'shipping_state', - postal_code: 'shipping_postcode', - country: 'shipping_country', - first_name: 'shipping_first_name', - last_name: 'shipping_last_name', - }, - } - ); - - expect( document.getElementById( 'shipping_first_name' ).value ).toBe( - 'First' - ); - expect( document.getElementById( 'shipping_last_name' ).value ).toBe( - 'Last' - ); - expect( document.getElementById( 'shipping_address_1' ).value ).toBe( - '123 Main St' - ); - expect( document.getElementById( 'shipping_address_2' ).value ).toBe( - 'shipping' - ); - expect( document.getElementById( 'shipping_city' ).value ).toBe( - 'San Francisco' - ); - expect( document.getElementById( 'shipping_state' ).value ).toBe( - 'AK' - ); - expect( document.getElementById( 'shipping_postcode' ).value ).toBe( - '94110' - ); - expect( document.getElementById( 'shipping_country' ).value ).toBe( - 'US' - ); - } ); - - test( 'Should not fill the shipping fields when complete_shipping is false', () => { - autofill( - { - value: {}, - }, - { - complete_shipping: () => { - return false; - }, - complete_billing: () => { - return false; - }, - } - ); - - expect( document.getElementById( 'shipping_first_name' ).value ).toBe( - '' - ); - expect( document.getElementById( 'shipping_last_name' ).value ).toBe( - '' - ); - expect( document.getElementById( 'shipping_address_1' ).value ).toBe( - '' - ); - expect( document.getElementById( 'shipping_address_2' ).value ).toBe( - '' - ); - expect( document.getElementById( 'shipping_city' ).value ).toBe( '' ); - expect( document.getElementById( 'shipping_state' ).value ).toBe( '' ); - expect( document.getElementById( 'shipping_postcode' ).value ).toBe( - '' - ); - expect( document.getElementById( 'shipping_country' ).value ).toBe( - '' - ); - } ); - - function createEmptyShippingFields() { - const firstName = document.createElement( 'input' ); - firstName.setAttribute( 'id', 'shipping_first_name' ); - - const lastName = document.createElement( 'input' ); - lastName.setAttribute( 'id', 'shipping_last_name' ); - - const address1 = document.createElement( 'input' ); - address1.setAttribute( 'id', 'shipping_address_1' ); - - const address2 = document.createElement( 'input' ); - address2.setAttribute( 'id', 'shipping_address_2' ); - - const city = document.createElement( 'input' ); - city.setAttribute( 'id', 'shipping_city' ); - - const state = document.createElement( 'input' ); - state.setAttribute( 'id', 'shipping_state' ); - - const postcode = document.createElement( 'input' ); - postcode.setAttribute( 'id', 'shipping_postcode' ); - - const country = document.createElement( 'input' ); - country.setAttribute( 'id', 'shipping_country' ); - - document.body.appendChild( firstName ); - document.body.appendChild( lastName ); - document.body.appendChild( address1 ); - document.body.appendChild( address2 ); - document.body.appendChild( city ); - document.body.appendChild( state ); - document.body.appendChild( postcode ); - document.body.appendChild( country ); - } - - function resetShippingFields() { - const firstName = document.querySelector( '#shipping_first_name' ); - const lastName = document.querySelector( '#shipping_last_name' ); - const address1 = document.querySelector( '#shipping_address_1' ); - const address2 = document.querySelector( '#shipping_address_2' ); - const city = document.querySelector( '#shipping_city' ); - const state = document.querySelector( '#shipping_state' ); - const postcode = document.querySelector( '#shipping_postcode' ); - const country = document.querySelector( '#shipping_country' ); - - firstName.value = ''; - lastName.value = ''; - address1.value = ''; - address2.value = ''; - city.value = ''; - state.value = ''; - postcode.value = ''; - country.value = ''; - } -} ); diff --git a/client/checkout/utils/fingerprint.js b/client/checkout/utils/fingerprint.js index d4b5f1f4087..d11198475fb 100644 --- a/client/checkout/utils/fingerprint.js +++ b/client/checkout/utils/fingerprint.js @@ -27,14 +27,14 @@ export const getFingerprint = async () => { /** * Appends a hidden input with the user fingerprint to the checkout form. * - * @param {Object} form The jQuery Checkout form object. + * @param {Object} $form The jQuery Checkout form object. * @param {string} fingerprint User fingerprint. */ -export const appendFingerprintInputToForm = ( form, fingerprint = '' ) => { +export const appendFingerprintInputToForm = ( $form, fingerprint = '' ) => { // Remove any existing wcpay-fingerprint input. - form.find( 'input[name="wcpay-fingerprint"]' ).remove(); + $form.find( 'input[name="wcpay-fingerprint"]' ).remove(); // Append an input with the correct fingerprint to the form. const fingerprintInput = ``; - form.append( fingerprintInput ); + $form.append( fingerprintInput ); }; diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index c785baa427b..baf8702a83a 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -11,6 +11,7 @@ import { blocksShowLinkButtonHandler, getSelectedUPEGatewayPaymentMethod, isUsingSavedPaymentMethod, + dispatchChangeEventFor, } from '../upe'; import { getPaymentMethodsConstants } from '../../constants'; import { getUPEConfig } from 'wcpay/utils/checkout'; @@ -533,3 +534,37 @@ describe( 'isUsingSavedPaymentMethod', () => { expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( false ); } ); } ); + +describe( 'dispatching change event for element', () => { + it( 'should dispatch a change event with bubbling', () => { + const mockElement = document.createElement( 'input' ); + jest.spyOn( mockElement, 'dispatchEvent' ); + + dispatchChangeEventFor( mockElement ); + + expect( mockElement.dispatchEvent ).toHaveBeenCalledWith( + expect.objectContaining( { + type: 'change', + bubbles: true, + } ) + ); + } ); + + it( 'should throw an error when called with an invalid element', () => { + expect( () => { + dispatchChangeEventFor( null ); + } ).toThrow(); + + expect( () => { + dispatchChangeEventFor( undefined ); + } ).toThrow(); + + expect( () => { + dispatchChangeEventFor( {} ); + } ).toThrow(); + + expect( () => { + dispatchChangeEventFor( 'not-an-element' ); + } ).toThrow(); + } ); +} ); diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index f7b49138690..87ba0308330 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -166,8 +166,8 @@ export const generateCheckoutEventNames = () => { .join( ' ' ); }; -export const appendPaymentMethodIdToForm = ( form, paymentMethodId ) => { - form.append( +export const appendPaymentMethodIdToForm = ( $form, paymentMethodId ) => { + $form.append( `` ); }; @@ -192,6 +192,11 @@ export function isUsingSavedPaymentMethod( paymentMethodType ) { ); } +export function dispatchChangeEventFor( element ) { + const event = new Event( 'change', { bubbles: true } ); + element.dispatchEvent( event ); +} + /** * * Custom React hook that provides customer data and related functions for managing customer information. @@ -200,13 +205,9 @@ export function isUsingSavedPaymentMethod( paymentMethodType ) { * @return {Object} An object containing customer data and functions for managing customer information. */ export const useCustomerData = () => { - const { customerData, isInitialized } = useSelect( ( select ) => { - const store = select( WC_STORE_CART ); - return { - customerData: store.getCustomerData(), - isInitialized: store.hasFinishedResolution( 'getCartData' ), - }; - } ); + const customerData = useSelect( ( select ) => + select( WC_STORE_CART ).getCustomerData() + ); const { setShippingAddress, setBillingData, @@ -214,14 +215,10 @@ export const useCustomerData = () => { } = useDispatch( WC_STORE_CART ); return { - isInitialized, - billingData: customerData.billingData, // Backward compatibility billingData/billingAddress - billingAddress: customerData.billingAddress, - shippingAddress: customerData.shippingAddress, - setBillingData, + billingAddress: customerData.billingAddress || customerData.billingData, // Backward compatibility setBillingData/setBillingAddress - setBillingAddress, + setBillingAddress: setBillingAddress || setBillingData, setShippingAddress, }; }; @@ -415,7 +412,13 @@ export const togglePaymentMethodForCountry = ( upeElement ) => { const supportedCountries = paymentMethodsConfig[ paymentMethodType ].countries; - const billingCountry = document.getElementById( 'billing_country' ).value; + /* global wcpayCustomerData */ + // in the case of "pay for order", there is no "billing country" input, so we need to rely on backend data. + const billingCountry = + document.getElementById( 'billing_country' )?.value || + wcpayCustomerData?.billing_country || + ''; + const upeContainer = document.querySelector( '.payment_method_woocommerce_payments_' + paymentMethodType ); diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index f57c3b4e90f..0c285572d95 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -116,7 +116,7 @@ export const handleWooPayEmailInput = async ( } // If the window width is less than the breakpoint, reset the styles and return. - if ( fullScreenModalBreakpoint >= window.innerWidth ) { + if ( fullScreenModalBreakpoint > window.innerWidth ) { iframe.style.left = '0'; iframe.style.right = ''; return; @@ -263,7 +263,10 @@ export const handleWooPayEmailInput = async ( ); urlParams.append( 'wcpayVersion', getConfig( 'wcpayVersionNumber' ) ); urlParams.append( 'is_blocks', isBlocksCheckout ? 'true' : 'false' ); - urlParams.append( 'source_url', window.location.href ); + urlParams.append( + 'source_url', + wcSettings?.storePages?.checkout?.permalink + ); urlParams.append( 'viewport', `${ viewportWidth }x${ viewportHeight }` @@ -401,8 +404,7 @@ export const handleWooPayEmailInput = async ( } else if ( data.code !== 'rest_invalid_param' ) { wcpayTracks.recordUserEvent( wcpayTracks.events.WOOPAY_OFFERED, - [], - true + [] ); } } ) diff --git a/client/checkout/woopay/express-button/express-checkout-iframe.js b/client/checkout/woopay/express-button/express-checkout-iframe.js index 97b9b647648..d8b5e693a7e 100644 --- a/client/checkout/woopay/express-button/express-checkout-iframe.js +++ b/client/checkout/woopay/express-button/express-checkout-iframe.js @@ -75,7 +75,7 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { } // If the window width is less than the breakpoint, set iframe to full window - if ( fullScreenModalBreakpoint >= window.innerWidth ) { + if ( fullScreenModalBreakpoint > window.innerWidth ) { iframe.style.left = '0'; iframe.style.right = ''; iframe.style.top = '0'; diff --git a/client/checkout/woopay/express-button/index.js b/client/checkout/woopay/express-button/index.js index 6058d71a8f4..fed30d6e96f 100644 --- a/client/checkout/woopay/express-button/index.js +++ b/client/checkout/woopay/express-button/index.js @@ -1,3 +1,4 @@ +/* global jQuery */ /** * External dependencies */ @@ -57,17 +58,17 @@ const renderWooPayExpressCheckoutButtonWithCallbacks = () => { renderWooPayExpressCheckoutButton( listenForCartChanges ); }; -document.addEventListener( 'DOMContentLoaded', () => { +jQuery( ( $ ) => { listenForCartChanges = { start: () => { - document.body.addEventListener( - 'updated_cart_totals', + $( document.body ).on( + 'updated_cart_totals updated_checkout', renderWooPayExpressCheckoutButtonWithCallbacks ); }, stop: () => { - document.body.removeEventListener( - 'updated_cart_totals', + $( document.body ).off( + 'updated_cart_totals updated_checkout', renderWooPayExpressCheckoutButtonWithCallbacks ); }, diff --git a/client/components/account-balances/test/index.test.tsx b/client/components/account-balances/test/index.test.tsx index 3a09b9b2004..a491b244747 100644 --- a/client/components/account-balances/test/index.test.tsx +++ b/client/components/account-balances/test/index.test.tsx @@ -145,7 +145,6 @@ const createMockOverview = ( fee: 0, net: 0, fee_percentage: 0, - transaction_ids: [], }, }; }; diff --git a/client/components/account-status/account-tools/test/index.test.tsx b/client/components/account-status/account-tools/test/index.test.tsx index 7c5827b115a..295970da639 100644 --- a/client/components/account-status/account-tools/test/index.test.tsx +++ b/client/components/account-status/account-tools/test/index.test.tsx @@ -31,7 +31,7 @@ describe( 'AccountTools', () => { expect( container ).toMatchSnapshot(); } ); - it( 'should not render in dev mode', () => { + it( 'should not render in sandbox mode', () => { global.wcpaySettings = { devMode: true, }; diff --git a/client/components/confetti-animation/index.tsx b/client/components/confetti-animation/index.tsx new file mode 100644 index 00000000000..32c2d971112 --- /dev/null +++ b/client/components/confetti-animation/index.tsx @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import React, { useEffect } from 'react'; +import confetti from 'canvas-confetti'; + +const defaultColors = [ '#889BF2', '#C3CDF9', '#6079ED' ]; + +const fireConfetti = ( colors: string[] ) => { + const defaults = { + origin: { y: 0.3 }, + spread: 360, + zIndex: 1000000, + colors, + }; + + const rectangle = confetti.shapeFromPath( { + path: 'M0,0 L2,0 L2,1 L0,1 Z', + } ); + + confetti( { + ...defaults, + particleCount: 20, + shapes: [ rectangle ], + scalar: 4, + startVelocity: 60, + } ); + confetti( { + ...defaults, + particleCount: 20, + shapes: [ rectangle ], + scalar: 2, + startVelocity: 40, + } ); + confetti( { + ...defaults, + particleCount: 40, + shapes: [ 'circle' ], + startVelocity: 20, + } ); +}; + +interface Props { + trigger?: boolean; + delay?: number; + colors?: string[]; +} + +const ConfettiAnimation: React.FC< Props > = ( { + trigger = true, + delay = 250, + colors = defaultColors, +} ) => { + useEffect( () => { + if ( trigger ) { + setTimeout( () => fireConfetti( colors ), delay ); + } + }, [ trigger, delay, colors ] ); + + return null; +}; + +export default ConfettiAnimation; diff --git a/client/components/deposits-overview/deposit-notices.tsx b/client/components/deposits-overview/deposit-notices.tsx index 86f0de918a0..abf821275a5 100644 --- a/client/components/deposits-overview/deposit-notices.tsx +++ b/client/components/deposits-overview/deposit-notices.tsx @@ -83,7 +83,7 @@ export const NewAccountWaitingPeriodNotice: React.FC = () => ( > { interpolateComponents( { mixedString: __( - 'Your first deposit is held for seven business days. {{whyLink}}Why?{{/whyLink}}', + 'Your first deposit is held for 7-14 days. {{whyLink}}Why?{{/whyLink}}', 'woocommerce-payments' ), components: { diff --git a/client/components/deposits-overview/index.tsx b/client/components/deposits-overview/index.tsx index 8ab797f4977..e954f7a7ded 100644 --- a/client/components/deposits-overview/index.tsx +++ b/client/components/deposits-overview/index.tsx @@ -57,9 +57,10 @@ const DepositsOverview: React.FC = () => { availableFunds === 0 && pendingFunds > 0; const hasCompletedWaitingPeriod = wcpaySettings.accountStatus.deposits?.completed_waiting_period; + const canChangeDepositSchedule = + ! account?.deposits_blocked && hasCompletedWaitingPeriod; // Only show the deposit history section if the page is finished loading and there are deposits. */ } - const showRecentDeposits = - ! isLoading && deposits?.length > 0 && !! account; + const hasRecentDeposits = ! isLoading && deposits?.length > 0 && !! account; // Show a loading state if the page is still loading. if ( isLoading ) { @@ -88,8 +89,13 @@ const DepositsOverview: React.FC = () => { ); } - // This card isn't shown if there are no deposits, so we can bail early. - if ( ! isLoading && deposits.length === 0 ) { + if ( + ! hasCompletedWaitingPeriod && + availableFunds === 0 && + pendingFunds === 0 + ) { + // If still in new account waiting period and account has no transactions, + // don't render deposits card (nothing to show). return null; } @@ -133,7 +139,7 @@ const DepositsOverview: React.FC = () => { ) } - { showRecentDeposits && ( + { hasRecentDeposits && ( <> @@ -144,50 +150,54 @@ const DepositsOverview: React.FC = () => { ) } - - ) } - - { ! account?.deposits_blocked && ( - - ) } - + { canChangeDepositSchedule && ( + + ) } + + ) } ); }; diff --git a/client/components/deposits-overview/style.scss b/client/components/deposits-overview/style.scss index bfecab9f98c..e3a7e4d16bf 100644 --- a/client/components/deposits-overview/style.scss +++ b/client/components/deposits-overview/style.scss @@ -30,8 +30,12 @@ // so table CardDivider is full width. .components-card__body.wcpay-deposits-overview__table__container, .components-card__body.wcpay-deposits-overview__notices__container { - padding-bottom: 0; padding-top: 0; + + // If the CardFooter is rendered, remove the bottom padding + &:not( :last-child ) { + padding-bottom: 0; + } } // Override display:block on the first FlexItem with the date and icon diff --git a/client/components/deposits-overview/test/index.tsx b/client/components/deposits-overview/test/index.tsx index e2fa1e98992..493f01223c5 100644 --- a/client/components/deposits-overview/test/index.tsx +++ b/client/components/deposits-overview/test/index.tsx @@ -136,7 +136,6 @@ const createMockOverview = ( fee: 0, net: 0, fee_percentage: 0, - transaction_ids: [], }, }; }; @@ -269,7 +268,8 @@ describe( 'Deposits Overview information', () => { expect( container ).toMatchSnapshot(); } ); - test( `Component doesn't render for new account`, () => { + test( `Component doesn't render for new accounts with no pending funds`, () => { + global.wcpaySettings.accountStatus.deposits.completed_waiting_period = false; mockOverviews( [ createMockNewAccountOverview( 'eur' ) ] ); mockDepositOverviews( [ createMockNewAccountOverview( 'eur' ) ] ); mockUseDeposits.mockReturnValue( { @@ -285,7 +285,8 @@ describe( 'Deposits Overview information', () => { expect( container ).toBeEmptyDOMElement(); } ); - test( `Component doesn't render for new accounts with pending funds but no available funds`, () => { + test( `Component renders for new accounts with pending funds but no available funds`, () => { + global.wcpaySettings.accountStatus.deposits.completed_waiting_period = false; mockOverviews( [ createMockNewAccountOverview( 'eur', 5000, 0 ) ] ); mockDepositOverviews( [ createMockNewAccountOverview( 'eur', 5000, 0 ), @@ -299,8 +300,12 @@ describe( 'Deposits Overview information', () => { selectedCurrency: 'eur', setSelectedCurrency: mockSetSelectedCurrency, } ); - const { container } = render( ); - expect( container ).toBeEmptyDOMElement(); + const { getByText, queryByText } = render( ); + getByText( /Your first deposit is held for/, { + ignore: '.a11y-speak-region', + } ); + expect( queryByText( 'Change deposit schedule' ) ).toBeFalsy(); + expect( queryByText( 'View full deposits history' ) ).toBeFalsy(); } ); test( 'Confirm notice renders if deposits blocked', () => { @@ -403,7 +408,7 @@ describe( 'Deposits Overview information', () => { ).toBeFalsy(); } ); - test( 'Confirm new account waiting period notice does not show', () => { + test( 'Confirm new account waiting period notice does not show if outside waiting period', () => { global.wcpaySettings.accountStatus.deposits.completed_waiting_period = true; const accountOverview = createMockNewAccountOverview( 'eur', @@ -418,12 +423,10 @@ describe( 'Deposits Overview information', () => { } ); const { queryByText } = render( ); - expect( - queryByText( 'Your first deposit is held for seven business days' ) - ).toBeFalsy(); + expect( queryByText( /Your first deposit is held for/ ) ).toBeFalsy(); } ); - test( 'Confirm new account waiting period notice shows', () => { + test( 'Confirm new account waiting period notice shows if within waiting period', () => { global.wcpaySettings.accountStatus.deposits.completed_waiting_period = false; const accountOverview = createMockNewAccountOverview( 'eur', @@ -438,7 +441,7 @@ describe( 'Deposits Overview information', () => { } ); const { getByText, getByRole } = render( ); - getByText( /Your first deposit is held for seven business days/, { + getByText( /Your first deposit is held for/, { ignore: '.a11y-speak-region', } ); expect( getByRole( 'link', { name: /Why\?/ } ) ).toHaveAttribute( diff --git a/client/components/disputed-order-notice/index.js b/client/components/disputed-order-notice/index.js index 13ea7af71e5..eb9d6beb4e4 100644 --- a/client/components/disputed-order-notice/index.js +++ b/client/components/disputed-order-notice/index.js @@ -14,6 +14,7 @@ import { getDetailsURL } from 'wcpay/components/details-link'; import { isAwaitingResponse, isInquiry, + isRefundable, isUnderReview, } from 'wcpay/disputes/utils'; import { useCharge } from 'wcpay/data'; @@ -30,10 +31,7 @@ const DisputedOrderNoticeHandler = ( { chargeId, onDisableOrderRefund } ) => { if ( ! charge?.dispute ) { return; } - // Refunds are only allowed if the dispute is an inquiry or if it's won. - const isRefundable = - isInquiry( dispute ) || [ 'won' ].includes( dispute.status ); - if ( ! isRefundable ) { + if ( ! isRefundable( dispute.status ) ) { onDisableOrderRefund( dispute.status ); } }, [ charge, onDisableOrderRefund ] ); @@ -42,8 +40,6 @@ const DisputedOrderNoticeHandler = ( { chargeId, onDisableOrderRefund } ) => { if ( ! charge?.dispute ) { return null; } - const isRefundable = - isInquiry( dispute ) || [ 'won' ].includes( dispute.status ); // Special case the dispute "under review" notice which is much simpler. // (And return early.) @@ -66,7 +62,7 @@ const DisputedOrderNoticeHandler = ( { chargeId, onDisableOrderRefund } ) => { // This may be dead code. Leaving in for now as this is consistent with // the logic before this PR. // https://github.com/Automattic/woocommerce-payments/pull/7557 - if ( dispute.status === 'lost' && ! isRefundable ) { + if ( dispute.status === 'lost' ) { return ( { ); }; +const UrgentDisputeNoticeBody = ( { + isPreDisputeInquiry, + disputeReason, + formattedAmount, + dueBy, + countdownDays, +} ) => { + const formatString = isPreDisputeInquiry + ? __( + // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. + "Please resolve the inquiry on this order of %1$s labeled '%2$s' by %3$s.", + 'woocommerce-payments' + ) + : __( + // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. + "Please resolve the dispute on this order of %1$s labeled '%2$s' by %3$s.", + 'woocommerce-payments' + ); + + const message = sprintf( + formatString, + formattedAmount, + reasons[ disputeReason ].display, + dateI18n( 'M j, Y', dueBy.local().toISOString() ) + ); + + let suffix = sprintf( + // Translators: %s is the number of days left to respond to the dispute. + _n( + '(%s day left)', + '(%s days left)', + countdownDays, + 'woocommerce-payments' + ), + countdownDays + ); + if ( countdownDays < 1 ) { + suffix = __( '(Last day today)', 'woocommerce-payments' ); + } + + return ( + <> + { message } { suffix } + + ); +}; + +const RegularDisputeNoticeBody = ( { + isPreDisputeInquiry, + disputeReason, + formattedAmount, + dueBy, +} ) => { + const formatString = isPreDisputeInquiry + ? __( + // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason. + "Please resolve the inquiry on this order of %1$s with reason '%2$s'.", + 'woocommerce-payments' + ) + : __( + // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason. + "This order has a payment dispute for %1$s for the reason '%2$s'. ", + 'woocommerce-payments' + ); + + const boldMessage = sprintf( + formatString, + formattedAmount, + reasons[ disputeReason ].display + ); + + const suffix = sprintf( + // Translators: %1$s is the dispute due date. + __( 'Please respond before %1$s.', 'woocommerce-payments' ), + dateI18n( 'M j, Y', dueBy.local().toISOString() ) + ); + + return ( + <> + { boldMessage } { suffix } + + ); +}; + const DisputeNeedsResponseNotice = ( { disputeReason, formattedAmount, @@ -128,67 +208,29 @@ const DisputeNeedsResponseNotice = ( { } ); }, [ isPreDisputeInquiry, disputeReason, countdownDays ] ); - const titleStrings = { - // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. - dispute_default: __( - // eslint-disable-next-line max-len - 'This order has been disputed in the amount of %1$s. The customer provided the following reason: %2$s. Please respond to this dispute before %3$s.', - 'woocommerce-payments' - ), - // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. - inquiry_default: __( - // eslint-disable-next-line max-len - 'The card network involved in this order has opened an inquiry into the transaction with the following reason: %2$s. Please respond to this inquiry before %3$s, just like you would for a formal dispute.', - 'woocommerce-payments' - ), - // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. - dispute_urgent: __( - 'Please resolve the dispute on this order for %1$s labeled "%2$s" by %3$s.', - 'woocommerce-payments' - ), - // Translators: %1$s is the formatted dispute amount, %2$s is the dispute reason, %3$s is the due date. - inquiry_urgent: __( - 'Please resolve the inquiry on this order for %1$s labeled "%2$s" by %3$s.', - 'woocommerce-payments' - ), - }; - - let buttonLabel = __( 'Respond now', 'woocommerce-payments' ); - let suffix = ''; - - let titleText = isPreDisputeInquiry - ? titleStrings.inquiry_default - : titleStrings.dispute_default; - - // If the dispute is due within 7 days, adjust wording and highlight urgency. - if ( countdownDays < 7 ) { - titleText = isPreDisputeInquiry - ? titleStrings.inquiry_urgent - : titleStrings.dispute_urgent; - - suffix = sprintf( - // Translators: %s is the number of days left to respond to the dispute. - _n( - '(%s day left)', - '(%s days left)', - countdownDays, - 'woocommerce-payments' - ), - countdownDays - ); - } + const isUrgent = countdownDays < 7; - const title = sprintf( - titleText, - formattedAmount, - reasons[ disputeReason ].display, - dateI18n( 'M j, Y', dueBy.local().toISOString() ) - ); + const buttonLabel = + countdownDays < 1 + ? __( 'Respond today', 'woocommerce-payments' ) + : __( 'Respond now', 'woocommerce-payments' ); - if ( countdownDays < 1 ) { - buttonLabel = __( 'Respond today', 'woocommerce-payments' ); - suffix = __( '(Last day today)', 'woocommerce-payments' ); - } + const noticeBody = isUrgent ? ( + + ) : ( + + ); return ( - { { `${ title } ${ suffix }` } } + { noticeBody } ); }; diff --git a/client/components/radio-card/index.tsx b/client/components/radio-card/index.tsx deleted file mode 100644 index f8a2f9f74de..00000000000 --- a/client/components/radio-card/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import classNames from 'classnames'; - -/** - * Internal dependencies - */ -import './style.scss'; - -interface Props { - name: string; - selected: string; - options: { - label: string; - value: string; - icon: React.ReactNode; - content: React.ReactNode; - }[]; - onChange: ( value: string ) => void; - className?: string; -} -const RadioCard: React.FC< Props > = ( { - name, - selected, - options, - onChange, - className, -} ) => { - return ( - <> - { options.map( ( { label, icon, value, content } ) => { - const id = `radio-card-${ name }-${ value }`; - const checked = value === selected; - const handleChange = () => onChange( value ); - - return ( -
{ - if ( event.key === 'Enter' ) handleChange(); - } } - className={ classNames( - 'wcpay-component-radio-card', - { checked }, - className - ) } - > -
- - - { icon } -
- { checked && content } -
- ); - } ) } - - ); -}; - -export default RadioCard; diff --git a/client/components/radio-card/style.scss b/client/components/radio-card/style.scss deleted file mode 100644 index f2e0f75766c..00000000000 --- a/client/components/radio-card/style.scss +++ /dev/null @@ -1,57 +0,0 @@ -.wcpay-component-radio-card { - cursor: pointer; - display: block; - padding: $gap-large; - background-color: #fff; - border-radius: 3px; - border: 1px solid $light-gray-500; - - &:not( :last-of-type ) { - margin-bottom: $gap-large; - } - - &:hover, - &.checked, - &:focus-visible { - box-shadow: 0 0 0 1.5px var( --wp-admin-theme-color ); - } - - &__label { - display: grid; - grid-template-columns: 24px 1fr 25px; - column-gap: $gap-small; - align-items: center; - font-weight: bold; - - &:not( :last-child ) { - margin-bottom: $gap; - } - - svg { - fill: #bbb; - } - - input[type='radio'] { - margin: 0; - justify-self: flex-end; - width: 20px; - height: 20px; - border-color: $gray-700; - - &:checked { - border-color: var( --wp-admin-theme-color ); - } - - &::before { - width: 12px; - height: 12px; - margin: 3px; - background: var( --wp-admin-theme-color ); - } - - &:focus { - box-shadow: none; - } - } - } -} diff --git a/client/components/radio-card/test/__snapshots__/index.tsx.snap b/client/components/radio-card/test/__snapshots__/index.tsx.snap deleted file mode 100644 index f4429bb7ffd..00000000000 --- a/client/components/radio-card/test/__snapshots__/index.tsx.snap +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RadioCard Component renders RadioCard component with provided props 1`] = ` -
- - -
-`; diff --git a/client/components/radio-card/test/index.tsx b/client/components/radio-card/test/index.tsx deleted file mode 100644 index de4050684ca..00000000000 --- a/client/components/radio-card/test/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import user from '@testing-library/user-event'; - -/** - * Internal dependencies - */ -import RadioCard from '../'; - -const options = [ - { - label: 'Pineapple pizza', - value: 'pineapple', - icon: , - content:

Sweet pizza

, - }, - { - label: 'Pizza', - value: 'pizza', - icon: , - content:

The real pizza

, - }, -]; - -describe( 'RadioCard Component', () => { - it( 'renders RadioCard component with provided props', () => { - const { container } = render( - - ); - expect( container ).toMatchSnapshot(); - } ); - - it( 'changes the selected value when an option is clicked', () => { - const mockOnChange = jest.fn(); - render( - - ); - - user.click( screen.getByLabelText( /Pineapple/i ) ); - expect( mockOnChange ).toHaveBeenCalledWith( 'pineapple' ); - } ); -} ); diff --git a/client/components/test-mode-notice/index.tsx b/client/components/test-mode-notice/index.tsx index 80310707a46..f77dcaff4a7 100644 --- a/client/components/test-mode-notice/index.tsx +++ b/client/components/test-mode-notice/index.tsx @@ -59,7 +59,7 @@ const getNoticeContent = ( mixedString: sprintf( /* translators: %1$s: WooPayments */ __( - '{{strong}}%1$s is in dev mode.{{/strong}} You need to set up a live %1$s account before you can accept real transactions.', + '{{strong}}%1$s is in sandbox mode.{{/strong}} You need to set up a live %1$s account before you can accept real transactions.', 'woocommerce-payments' ), 'WooPayments' @@ -87,7 +87,7 @@ const getNoticeContent = ( // eslint-disable-next-line jsx-a11y/anchor-has-content diff --git a/client/components/woopay/save-user/checkout-page-save-user.js b/client/components/woopay/save-user/checkout-page-save-user.js index 8edd7c583d4..f2465a5711b 100644 --- a/client/components/woopay/save-user/checkout-page-save-user.js +++ b/client/components/woopay/save-user/checkout-page-save-user.js @@ -73,7 +73,8 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { ? {} : { save_user_in_woopay: isSaveDetailsChecked, - woopay_source_url: window.location.href, + woopay_source_url: + wcSettings?.storePages?.checkout?.permalink, woopay_is_blocks: true, woopay_viewport: `${ viewportWidth }x${ viewportHeight }`, woopay_user_phone_field: { @@ -91,6 +92,12 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { [ isSaveDetailsChecked, phoneNumber, viewportWidth, viewportHeight ] ); + const handleCountryDropdownClick = useCallback( () => { + wcpayTracks.recordUserEvent( + wcpayTracks.events.WOOPAY_SAVE_MY_INFO_COUNTRY_CLICK + ); + }, [] ); + const handleCheckboxClick = ( e ) => { const isChecked = e.target.checked; if ( isChecked ) { @@ -111,6 +118,15 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { ); }; + useEffect( () => { + // Record Tracks event when the mobile number is entered. + if ( isPhoneValid ) { + wcpayTracks.recordUserEvent( + wcpayTracks.events.WOOPAY_SAVE_MY_INFO_MOBILE_ENTER + ); + } + }, [ isPhoneValid ] ); + useEffect( () => { // Record Tracks event when user clicks on the info icon for the first time. if ( isInfoFlyoutVisible && ! hasShownInfoFlyout ) { @@ -291,7 +307,9 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { { value={ phoneNumber } onValueChange={ setPhoneNumber } onValidationChange={ onPhoneValidationChange } + onCountryDropdownClick={ + handleCountryDropdownClick + } inputProps={ { name: 'woopay_user_phone_field[no-country-code]', diff --git a/client/data/deposits/actions.js b/client/data/deposits/actions.js index 4513dae4d85..3b85184d364 100644 --- a/client/data/deposits/actions.js +++ b/client/data/deposits/actions.js @@ -98,10 +98,10 @@ export function updateInstantDeposit( data ) { }; } -export function* submitInstantDeposit( transactionIds ) { +export function* submitInstantDeposit( currency ) { try { yield dispatch( STORE_NAME ).startResolution( 'getInstantDeposit', [ - transactionIds, + currency, ] ); const deposit = yield apiFetch( { @@ -109,7 +109,7 @@ export function* submitInstantDeposit( transactionIds ) { method: 'POST', data: { type: 'instant', - transaction_ids: transactionIds, + currency, }, } ); @@ -153,7 +153,7 @@ export function* submitInstantDeposit( transactionIds ) { ); } finally { yield dispatch( STORE_NAME ).finishResolution( 'getInstantDeposit', [ - transactionIds, + currency, ] ); } } diff --git a/client/data/deposits/hooks.ts b/client/data/deposits/hooks.ts index bcb4878b06f..7a6178050bf 100644 --- a/client/data/deposits/hooks.ts +++ b/client/data/deposits/hooks.ts @@ -235,18 +235,18 @@ export const useDepositsSummary = ( { }; export const useInstantDeposit = ( - transactionIds: string[] + currency: string ): { inProgress: boolean; submit: () => void; deposit: unknown } => { const { deposit, inProgress } = useSelect( ( select ) => { const { getInstantDeposit, isResolving } = select( STORE_NAME ); return { - deposit: getInstantDeposit( [ transactionIds ] ), - inProgress: isResolving( 'getInstantDeposit', [ transactionIds ] ), + deposit: getInstantDeposit( [ currency ] ), + inProgress: isResolving( 'getInstantDeposit', [ currency ] ), }; } ); const { submitInstantDeposit } = useDispatch( STORE_NAME ); - const submit = () => submitInstantDeposit( transactionIds ); + const submit = () => submitInstantDeposit( currency ); return { deposit, inProgress, submit }; }; diff --git a/client/data/deposits/test/overviews.fixture.json b/client/data/deposits/test/overviews.fixture.json index 1a2106def2f..775953f30cb 100644 --- a/client/data/deposits/test/overviews.fixture.json +++ b/client/data/deposits/test/overviews.fixture.json @@ -97,8 +97,7 @@ "amount": 12345, "fee": 185.175, "net": 12159.83, - "fee_percentage": 1.5, - "transaction_ids": [ "txn_XYZ", "txn_ZXY" ] + "fee_percentage": 1.5 } ] }, diff --git a/client/deposits/instant-deposits/index.tsx b/client/deposits/instant-deposits/index.tsx index 67d8e22a9b2..9df289c2eac 100644 --- a/client/deposits/instant-deposits/index.tsx +++ b/client/deposits/instant-deposits/index.tsx @@ -33,9 +33,7 @@ const InstantDepositButton: React.FC< InstantDepositButtonProps > = ( { } ) => { const [ isModalOpen, setModalOpen ] = useState( false ); const buttonDisabled = isButtonDisabled( instantBalance ); - const { inProgress, submit } = useInstantDeposit( - instantBalance.transaction_ids - ); + const { inProgress, submit } = useInstantDeposit( instantBalance.currency ); const onClose = () => { setModalOpen( false ); }; diff --git a/client/deposits/instant-deposits/test/index.tsx b/client/deposits/instant-deposits/test/index.tsx index 2b58abf4a8b..4aec2f7b322 100644 --- a/client/deposits/instant-deposits/test/index.tsx +++ b/client/deposits/instant-deposits/test/index.tsx @@ -28,7 +28,6 @@ const mockInstantBalance = { fee: 123.45, net: 12221.55, fee_percentage: 1.5, - transaction_ids: [ 'txn_ABC123', 'txn_DEF456' ], currency: 'USD', } as AccountOverview.InstantBalance; @@ -37,7 +36,6 @@ const mockZeroInstantBalance = { fee: 0, net: 0, fee_percentage: 1.5, - transaction_ids: [], currency: 'USD', } as AccountOverview.InstantBalance; diff --git a/client/disputes/utils.ts b/client/disputes/utils.ts index 6e5acf2f437..d4cb2fc52d1 100644 --- a/client/disputes/utils.ts +++ b/client/disputes/utils.ts @@ -72,6 +72,11 @@ export const isInquiry = ( dispute: Pick< Dispute, 'status' > ): boolean => { return dispute.status.startsWith( 'warning' ); }; +export const isRefundable = ( status: DisputeStatus ): boolean => { + // Refundable dispute statuses are one of `warning_needs_response`, `warning_under_review`, `warning_closed` or `won`. + return isInquiry( { status } ) || 'won' === status; +}; + /** * Returns the dispute fee balance transaction for a dispute if it exists * and the deduction has not been reversed. diff --git a/client/onboarding/steps/mode-choice.tsx b/client/onboarding/steps/mode-choice.tsx index 00af3d247be..6fe5a312208 100644 --- a/client/onboarding/steps/mode-choice.tsx +++ b/client/onboarding/steps/mode-choice.tsx @@ -1,39 +1,38 @@ /** * External dependencies */ -import React, { useState } from 'react'; +import React from 'react'; import { Button } from '@wordpress/components'; -import { Icon, store, tool } from '@wordpress/icons'; +import { Icon, store } from '@wordpress/icons'; import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import RadioCard from 'components/radio-card'; import { useStepperContext } from 'components/stepper'; import { trackModeSelected } from '../tracking'; import strings from '../strings'; import InlineNotice from 'components/inline-notice'; -const DevModeNotice = () => ( +const SandboxModeNotice = () => ( - { strings.steps.mode.devModeNotice } + { strings.steps.mode.sandboxModeNotice } ); const ModeChoice: React.FC = () => { const { devMode } = wcpaySettings; - const liveStrings = strings.steps.mode.live; - const testStrings = strings.steps.mode.test; + const modeStrings = strings.steps.mode; - const [ selected, setSelected ] = useState< 'live' | 'test' >( 'live' ); const { nextStep } = useStepperContext(); - const handleContinue = () => { - trackModeSelected( selected ); + const handleContinue = ( mode: 'live' | 'test' ) => { + trackModeSelected( mode ); - if ( selected === 'live' ) return nextStep(); + // If live mode is selected, go to the next step of the flow. + if ( mode === 'live' ) return nextStep(); + // Else, redirect to the test mode Stripe flow. const { connectUrl } = wcpaySettings; const url = addQueryArgs( connectUrl, { test_mode: true, @@ -43,41 +42,42 @@ const ModeChoice: React.FC = () => { return ( <> - { devMode && } - void } - options={ [ - { - label: liveStrings.label, - value: 'live', - icon: , - content: ( -
- { liveStrings.note } -
- ), - }, - { - label: testStrings.label, - value: 'test', - icon: , - content: ( -
- { testStrings.note } -
- ), - }, - ] } - /> - + { devMode && } +
+
+ + { modeStrings.label } +
+
+
+ { modeStrings.note } +
+

{ modeStrings.tos }

+
+
+ +
+
+ +
+ +
); }; diff --git a/client/onboarding/steps/test/mode-choice.tsx b/client/onboarding/steps/test/mode-choice.tsx index 8c081182324..d6400d98936 100644 --- a/client/onboarding/steps/test/mode-choice.tsx +++ b/client/onboarding/steps/test/mode-choice.tsx @@ -27,7 +27,7 @@ declare const global: { }; describe( 'ModeChoice', () => { - it( 'displays test and live radio cards, notice for dev mode', () => { + it( 'displays test and live radio cards, notice for sandbox mode', () => { global.wcpaySettings = { connectUrl: 'https://wcpay.test/connect', devMode: true, @@ -35,14 +35,11 @@ describe( 'ModeChoice', () => { render( ); expect( - screen.getByText( strings.steps.mode.live.label ) - ).toBeInTheDocument(); - expect( - screen.getByText( strings.steps.mode.test.label ) + screen.getByText( strings.steps.mode.label ) ).toBeInTheDocument(); expect( screen.getByText( - 'Dev mode is enabled, only test accounts will be created. If you want to process live transactions, please disable it.' + 'Sandbox mode is enabled, only test accounts will be created. If you want to process live transactions, please disable it.' ) ).toBeInTheDocument(); } ); @@ -51,8 +48,7 @@ describe( 'ModeChoice', () => { nextStep = jest.fn(); render( ); - user.click( screen.getByText( strings.steps.mode.live.label ) ); - user.click( screen.getByRole( 'button' ) ); + user.click( screen.getByTestId( 'live-mode-button' ) ); expect( nextStep ).toHaveBeenCalled(); } ); @@ -70,8 +66,9 @@ describe( 'ModeChoice', () => { render( ); - user.click( screen.getByText( strings.steps.mode.test.label ) ); - user.click( screen.getByRole( 'button' ) ); + user.click( + screen.getByRole( 'button', { name: /Continue in sandbox mode/i } ) + ); expect( window.location.href ).toBe( `https://wcpay.test/connect?test_mode=true` diff --git a/client/onboarding/strings.tsx b/client/onboarding/strings.tsx index 04d201af815..a1578aacb61 100644 --- a/client/onboarding/strings.tsx +++ b/client/onboarding/strings.tsx @@ -6,6 +6,14 @@ import { __, sprintf } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; import React from 'react'; +const documentationUrls = { + sandboxMode: + 'https://woo.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/', + tos: 'https://wordpress.com/tos/', + merchantTerms: 'https://wordpress.com/tos/#more-woopay-specifically', + privacyPolicy: 'https://automattic.com/privacy/', +}; + export default { steps: { mode: { @@ -17,33 +25,21 @@ export default { 'Select the option that best fits your needs.', 'woocommerce-payments' ), - live: { - label: __( - 'I’d like to set up payments on my own store', - 'woocommerce-payments' - ), - note: __( - 'You’ll need to provide details to verify that you’re the owner of the account. If you’re setting up payments for someone else, choose the test payments option.', - 'woocommerce-payments' - ), - }, - test: { - label: __( - 'I’m building a store for someone else and would like to test payments', - 'woocommerce-payments' - ), - note: sprintf( - /* translators: %s: WooPayments */ - __( - 'This option will set up %s in development mode. You can use test data to set up, no personal information is required. When you’re ready to launch your store, switching to live payments is easy.', - 'woocommerce-payments' - ), - 'WooPayments' - ), + label: __( + 'I’d like to set up payments for my store', + 'woocommerce-payments' + ), + note: __( + 'You’ll need to provide details to verify that you’re the owner of the account. If you’re setting up payments for someone else, choose sandbox mode.', + 'woocommerce-payments' + ), + continue: { + live: __( 'Continue', 'woocommerce-payments' ), + test: __( 'Continue in sandbox mode', 'woocommerce-payments' ), }, - devModeNotice: interpolateComponents( { + sandboxModeNotice: interpolateComponents( { mixedString: __( - 'Dev mode is enabled, only test accounts will be created. If you want to process live transactions, please disable it. {{learnMoreLink}}Learn more{{/learnMoreLink}}', + 'Sandbox mode is enabled, only test accounts will be created. If you want to process live transactions, please disable it. {{learnMoreLink}}Learn more{{/learnMoreLink}}', 'woocommerce-payments' ), components: { @@ -51,15 +47,50 @@ export default { // Link content is in the format string above. Consider disabling jsx-a11y/anchor-has-content. // eslint-disable-next-line jsx-a11y/anchor-has-content ), }, } ), + tos: interpolateComponents( { + mixedString: sprintf( + __( + /* translators: %1$s: WooPayments, %2$s: WooPay */ + 'By using %1$s, you agree to the {{tosLink}}Terms of Service{{/tosLink}} (including %2$s {{merchantTermsLink}}merchant terms{{/merchantTermsLink}}) and acknowledge that you have read our {{privacyPolicyLink}}Privacy Policy{{/privacyPolicyLink}}.', + 'woocommerce-payments' + ), + 'WooPayments', + 'WooPay' + ), + components: { + tosLink: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + merchantTermsLink: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + privacyPolicyLink: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + }, + } ), }, personal: { heading: __( diff --git a/client/onboarding/style.scss b/client/onboarding/style.scss index 1c118b5bf3c..8dbbef819e4 100644 --- a/client/onboarding/style.scss +++ b/client/onboarding/style.scss @@ -106,6 +106,12 @@ body.wcpay-onboarding__body { padding: $gap-small $gap; } + .onboarding-mode__sandbox { + display: flex; + align-items: center; + justify-content: center; + } + .personal-details-notice { margin: 0; } @@ -124,4 +130,42 @@ body.wcpay-onboarding__body { margin: $gap 0; } } + + .wcpay-component-onboarding-card { + display: block; + padding: $gap-large; + background-color: #fff; + border-radius: 3px; + border: 1px solid $light-gray-500; + margin-bottom: $gap-large; + + &__label { + display: grid; + grid-template-columns: 24px 1fr 25px; + column-gap: $gap-small; + align-items: center; + font-weight: bold; + + &:not( :last-child ) { + margin-bottom: $gap; + } + + svg { + fill: #bbb; + } + } + + &__footer { + button { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + + p { + color: $gray-700; + } + } } diff --git a/client/order/index.js b/client/order/index.js index 1bfdfeee7ad..16ec2b60183 100644 --- a/client/order/index.js +++ b/client/order/index.js @@ -61,6 +61,9 @@ jQuery( function ( $ ) { const manualRefundsTip = getConfig( 'manualRefundsTip' ) ?? ''; const chargeId = getConfig( 'chargeId' ); const testMode = getConfig( 'testMode' ); + // Order and site are both in test mode, or both in live mode. + // '1' = true, '' = false, null = the order was created before the test mode meta was added, so we assume it matches. + const orderTestModeMatch = getConfig( 'orderTestModeMatch' ) !== ''; maybeShowOrderNotices(); @@ -175,7 +178,7 @@ jQuery( function ( $ ) { '#wcpay-order-payment-details-container' ); - // If the container doesn't exist (WC < 7.9), or the charge ID isn't present, don't render the notice. + // If the container doesn't exist (WC < 7.9) don't render notices. if ( ! container ) { return; } @@ -184,7 +187,7 @@ jQuery( function ( $ ) { <> { testMode && } - { chargeId && ( + { chargeId && orderTestModeMatch && ( { { label: strings.notice.actions.learnMore, url: - 'https://woo.com/document/woopayments/testing-and-troubleshooting/dev-mode/', + 'https://woo.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/', urlTarget: '_blank', }, ] diff --git a/client/overview/modal/progressive-onboarding-eligibility/index.tsx b/client/overview/modal/progressive-onboarding-eligibility/index.tsx index 6e16ac57a1f..b6c2b85054f 100644 --- a/client/overview/modal/progressive-onboarding-eligibility/index.tsx +++ b/client/overview/modal/progressive-onboarding-eligibility/index.tsx @@ -12,7 +12,7 @@ import { useDispatch } from '@wordpress/data'; * Internal dependencies */ import { trackEligibilityModalClosed } from 'onboarding/tracking'; -import HeaderImg from 'assets/images/illustrations/po-eligibility.svg'; +import ConfettiAnimation from 'components/confetti-animation'; import './style.scss'; const ProgressiveOnboardingEligibilityModal: React.FC = () => { @@ -70,18 +70,13 @@ const ProgressiveOnboardingEligibilityModal: React.FC = () => { className="wcpay-progressive-onboarding-eligibility-modal" onRequestClose={ handleDismiss } > -
- Header -
+

- { __( - 'You’re eligible to start selling now and fast-track the setup process.', - 'woocommerce-payments' - ) } + { __( 'You’re ready to sell.', 'woocommerce-payments' ) }

{ __( - 'Start selling now with these benefits or continue the process to set up deposits.', + 'Start selling now and fast track the setup process, or continue the process to set up deposits with WooPayments.', 'woocommerce-payments' ) }

@@ -97,7 +92,7 @@ const ProgressiveOnboardingEligibilityModal: React.FC = () => { { sprintf( /* translators: %s: WooPayments */ __( - '%s enables you to start processing payments right away.', + '%s enables you to start processing credit card payments right away.', 'woocommerce-payments' ), 'WooPayments' @@ -109,7 +104,7 @@ const ProgressiveOnboardingEligibilityModal: React.FC = () => { { __( 'Quick and easy setup', 'woocommerce-payments' ) } { __( - 'The setup process is super simple and ensures your store is ready to accept payments.', + 'The setup process is super simple and ensures your store is ready to accept card payments.', 'woocommerce-payments' ) } @@ -125,14 +120,11 @@ const ProgressiveOnboardingEligibilityModal: React.FC = () => {
- -
diff --git a/client/overview/modal/progressive-onboarding-eligibility/style.scss b/client/overview/modal/progressive-onboarding-eligibility/style.scss index 27d336e0743..1863d804d91 100644 --- a/client/overview/modal/progressive-onboarding-eligibility/style.scss +++ b/client/overview/modal/progressive-onboarding-eligibility/style.scss @@ -1,13 +1,17 @@ .wcpay-progressive-onboarding-eligibility-modal { - // fix for the modal being too short on smaller screens - @media ( max-height: 880px ) { - max-height: 100% !important; + &.components-modal__frame { + border-radius: 2px !important; + overflow: visible; } + .components-modal__content { box-sizing: border-box; max-width: 700px; - margin: 0 auto; + margin: 0; padding: $gap-larger; + display: flex; + flex-direction: column; + align-items: center; } .components-modal__header { @@ -18,26 +22,19 @@ left: initial; top: $gap-smaller; right: $gap-smaller; - } - } - - &__image { - margin: -$gap-larger; - margin-bottom: $gap-larger; - background-color: $studio-gray-0; - img { - display: block; - max-width: 100%; - max-height: 200px; - margin: 0 auto; + svg { + width: 32px; + height: 32px; + } } } &__heading { - @include wp-title-large; - text-align: center; - margin-bottom: 0; + font-size: 40px; + font-weight: 400; + line-height: 60px; + margin-bottom: $gap-smallest; } &__subheading { @@ -45,22 +42,28 @@ text-align: center; font-weight: normal; color: $gray-60; - margin-bottom: $gap-largest; + max-width: 480px; + margin: 0 0 $gap-large 0; } &__benefits { display: grid; grid-template-columns: repeat( 3, 1fr ); column-gap: $gap-largest; - text-align: center; + padding: 0 $gap; fill: $studio-woocommerce-purple-50; - @include wp-label; - color: $gray-700; + font-size: 14px; + line-height: 20px; + color: $gray-60; + + svg { + display: block; + margin: $gap-smaller auto; + } &__subtitle { @include wp-subtitle; - text-align: center; - margin-bottom: $gap-smaller; + margin: $gap-large 0 $gap-smallest; } @media screen and ( max-width: $break-small ) { @@ -71,15 +74,25 @@ &__footer { text-align: center; - margin-top: $gap-large; + margin: $gap-large 0; & :first-child { - margin-right: $gap-smaller; + margin-right: $gap; } button { - margin-top: $gap; - padding: $gap-smaller $gap; + margin-top: $gap-large; + padding: $gap-small $gap-large; + font-size: 14px; + line-height: 20px; + height: 40px; } } + + &__confetti { + position: absolute; + top: -28px; + left: -8px; + pointer-events: none; + } } diff --git a/client/overview/modal/progressive-onboarding-eligibility/test/index.test.tsx b/client/overview/modal/progressive-onboarding-eligibility/test/index.test.tsx index 3837b95f9c4..99e164f3b8f 100644 --- a/client/overview/modal/progressive-onboarding-eligibility/test/index.test.tsx +++ b/client/overview/modal/progressive-onboarding-eligibility/test/index.test.tsx @@ -36,8 +36,7 @@ describe( 'Progressive Onboarding Eligibility Modal', () => { const queryHeading = () => screen.queryByRole( 'heading', { - name: - 'You’re eligible to start selling now and fast-track the setup process.', + name: 'You’re ready to sell.', } ); expect( queryHeading() ).toBeInTheDocument(); @@ -55,14 +54,13 @@ describe( 'Progressive Onboarding Eligibility Modal', () => { user.click( screen.getByRole( 'button', { - name: 'Enable payments only', + name: 'Start selling', } ) ); expect( screen.queryByRole( 'heading', { - name: - 'You’re eligible to start selling now and fast-track the setup process.', + name: 'You’re ready to sell.', } ) ).not.toBeInTheDocument(); } ); @@ -85,7 +83,7 @@ describe( 'Progressive Onboarding Eligibility Modal', () => { user.click( screen.getByRole( 'button', { - name: 'Set up payments and deposits', + name: 'Start receiving deposits', } ) ); diff --git a/client/overview/strings.tsx b/client/overview/strings.tsx index 43ab5d47bb5..39ceda5d1e2 100644 --- a/client/overview/strings.tsx +++ b/client/overview/strings.tsx @@ -26,7 +26,7 @@ export default { mixedString: sprintf( /* translators: %1$s: WooPayments */ __( - '{{bold}}%1s is in dev mode.{{bold /}}. You need to set up a live %1s account before you can accept real transactions.', + '{{bold}}%1s is in sandbox mode.{{bold /}}. You need to set up a live %1s account before you can accept real transactions.', 'woocommerce-payments' ), 'WooPayments' diff --git a/client/overview/test/index.js b/client/overview/test/index.js index 2c677e27e31..1318b654b14 100644 --- a/client/overview/test/index.js +++ b/client/overview/test/index.js @@ -303,17 +303,12 @@ describe( 'Overview page', () => { render( ); expect( - screen.getByText( - 'You’re eligible to start selling now and fast-track the setup process.' - ) + screen.getByText( 'You’re ready to sell.' ) ).toBeInTheDocument(); } ); it( 'does not displays ProgressiveOnboardingEligibilityModal if showProgressiveOnboardingEligibilityModal is false', () => { - const query = () => - screen.queryByText( - 'You’re eligible to start selling now and fast-track the setup process.' - ); + const query = () => screen.queryByText( 'You’re ready to sell.' ); render( ); diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index 607bacb5f0a..c0bb0397a62 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -21,10 +21,6 @@ import { createInterpolateElement } from '@wordpress/element'; import HelpOutlineIcon from 'gridicons/dist/help-outline'; import _ from 'lodash'; -// This is a workaround for the position of the dropdown menu. At the same time underlines the need for a better solution. -import '../../../node_modules/@wordpress/components/src/dropdown-menu/style.scss'; -import '../../../node_modules/@wordpress/components/src/popover/style.scss'; - /** * Internal dependencies. */ @@ -48,6 +44,7 @@ import DisputeStatusChip from 'components/dispute-status-chip'; import { getDisputeFeeFormatted, isAwaitingResponse, + isRefundable, } from 'wcpay/disputes/utils'; import { useAuthorization } from 'wcpay/data'; import CaptureAuthorizationButton from 'wcpay/components/capture-authorization-button'; @@ -208,6 +205,19 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { const disputeFee = charge.dispute && getDisputeFeeFormatted( charge.dispute ); + // If this transaction is disputed, check if it is refundable. + const isDisputeRefundable = charge.dispute + ? isRefundable( charge.dispute.status ) + : true; + + // Partial refunds are done through the order page. If order number is not + // present, partial refund is not possible. + const isPartiallyRefundable = charge.order && charge.order.number; + + // Control menu only shows refund actions for now. In the future, it may show other actions. + const showControlMenu = + charge.captured && ! charge.refunded && isDisputeRefundable; + // Use the balance_transaction fee if available. If not (e.g. authorized but not captured), use the application_fee_amount. const transactionFee = charge.balance_transaction ? { @@ -484,7 +494,7 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
- { ! charge?.refunded && charge?.captured && ( + { showControlMenu && ( = ( { { ( { onClose } ) => ( @@ -521,7 +532,7 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( { 'woocommerce-payments' ) } - { charge.order && ( + { isPartiallyRefundable && ( { wcpayTracks.recordEvent( diff --git a/client/payment-details/summary/style.scss b/client/payment-details/summary/style.scss index 0ee051834fb..220e534985b 100755 --- a/client/payment-details/summary/style.scss +++ b/client/payment-details/summary/style.scss @@ -98,4 +98,31 @@ .payment-details__refund-controls { flex: 0 0 auto; + + .refund-controls__dropdown-menu { + /** + * HACK: The following styles are needed to make the dropdown menu + * appear in its expected position. The dropdown menu is positioned absolutely, so we need to make sure + * that the parent container is positioned relatively. + * This should be taken care of by the dropdown menu component's CSS, but we seem to be relying in outdated + * wordpress/components styles. + * + * Github issue: https://github.com/Automattic/woocommerce-payments/issues/8012 + */ + .components-popover { + position: fixed; + } + .components-popover__content { + position: absolute; + right: 100%; + } + + .components-tooltip { + .components-popover__content { + position: relative; + right: 0; + } + } + // END HACK + } } diff --git a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap index 4d582a000c7..c6c02760b87 100644 --- a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap @@ -701,13 +701,13 @@ exports[`PaymentDetailsSummary correctly renders a charge 1`] = ` class="payment-details__refund-controls" >
); diff --git a/client/types/account-overview.d.ts b/client/types/account-overview.d.ts index cf0acd923c1..7b02ef2a9c2 100644 --- a/client/types/account-overview.d.ts +++ b/client/types/account-overview.d.ts @@ -54,7 +54,6 @@ export interface InstantBalance { fee: number; net: number; fee_percentage: number; - transaction_ids: Array< string >; } export interface Overview { diff --git a/client/utils/index.js b/client/utils/index.js index d6e7a1c971d..f10cb308251 100644 --- a/client/utils/index.js +++ b/client/utils/index.js @@ -23,11 +23,11 @@ export const isInTestMode = ( fallback = false ) => { }; /** - * Returns true if WooPayments is in dev mode, false otherwise. + * Returns true if WooPayments is in dev/sandbox mode, false otherwise. * - * @param {boolean} fallback Fallback in case dev mode value can't be found (for example if the wcpaySettings are undefined). + * @param {boolean} fallback Fallback in case dev/sandbox mode value can't be found (for example if the wcpaySettings are undefined). * - * @return {boolean} True if in dev mode, false otherwise. Fallback value if test/dev mode value can't be found. + * @return {boolean} True if in dev/sandbox mode, false otherwise. Fallback value if dev/sandbox mode value can't be found. */ export const isInDevMode = ( fallback = false ) => { if ( typeof wcpaySettings === 'undefined' ) { diff --git a/composer.json b/composer.json index 0b2890b6228..e13f38f87ff 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,12 @@ "require": { "php": ">=7.2", "ext-json": "*", - "automattic/jetpack-connection": "1.51.7", + "automattic/jetpack-connection": "2.1.1", "automattic/jetpack-config": "1.15.2", "automattic/jetpack-autoloader": "2.11.18", - "automattic/jetpack-identity-crisis": "0.8.43", - "automattic/jetpack-sync": "1.47.7", - "woocommerce/subscriptions-core": "6.6.0" + "automattic/jetpack-identity-crisis": "0.14.1", + "automattic/jetpack-sync": "2.4.0", + "woocommerce/subscriptions-core": "6.7.1" }, "require-dev": { "composer/installers": "1.10.0", diff --git a/composer.lock b/composer.lock index 686f8f9aa92..5ad3d0b6b89 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2f207d4579d3db832302089bb6f3a11f", + "content-hash": "5bebf06cbd0a606b022f672f94e82881", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", - "version": "v1.4.22", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-a8c-mc-stats.git", - "reference": "d7fdf2fc7ae33d75e24e82d81269e33ec718446f" + "reference": "6ce7a1e1eba796643d7d32dc49057c7bb8e3233c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/d7fdf2fc7ae33d75e24e82d81269e33ec718446f", - "reference": "d7fdf2fc7ae33d75e24e82d81269e33ec718446f", + "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/6ce7a1e1eba796643d7d32dc49057c7bb8e3233c", + "reference": "6ce7a1e1eba796643d7d32dc49057c7bb8e3233c", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^4.0.0", "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { @@ -35,7 +38,7 @@ "link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.4.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -49,27 +52,30 @@ ], "description": "Used to record internal usage stats for Automattic. Not visible to site owners.", "support": { - "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v1.4.22" + "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v2.0.0" }, - "time": "2023-09-19T18:18:33+00:00" + "time": "2023-11-20T20:02:34+00:00" }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.2.25", + "version": "v0.3.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "d9566f47ab310d675779273eeead6d0ca64fff82" + "reference": "9c84adff57b0e39e812a9baac1b075f15b793f0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/d9566f47ab310d675779273eeead6d0ca64fff82", - "reference": "d9566f47ab310d675779273eeead6d0ca64fff82", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/9c84adff57b0e39e812a9baac1b075f15b793f0f", + "reference": "9c84adff57b0e39e812a9baac1b075f15b793f0f", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.11", - "automattic/jetpack-logo": "^1.6.3", + "automattic/jetpack-changelogger": "^4.0.3", + "automattic/jetpack-logo": "^2.0.0", "automattic/wordbless": "dev-master", "yoast/phpunit-polyfills": "1.1.0" }, @@ -85,7 +91,7 @@ "link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "0.2.x-dev" + "dev-trunk": "0.3.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-admin-menu.php" @@ -102,31 +108,32 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.25" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.3.1" }, - "time": "2023-11-14T16:36:17+00:00" + "time": "2023-11-24T21:14:18+00:00" }, { "name": "automattic/jetpack-assets", - "version": "v1.18.15", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-assets.git", - "reference": "8b0c0eaf371b17eaf58bc38ab0b12d1a2ac9811e" + "reference": "ae8944abdb7a8da7137dedb9b4fe2afd81ed2d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/8b0c0eaf371b17eaf58bc38ab0b12d1a2ac9811e", - "reference": "8b0c0eaf371b17eaf58bc38ab0b12d1a2ac9811e", + "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/ae8944abdb7a8da7137dedb9b4fe2afd81ed2d72", + "reference": "ae8944abdb7a8da7137dedb9b4fe2afd81ed2d72", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^1.6.23" + "automattic/jetpack-constants": "^2.0.0", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.11", + "automattic/jetpack-changelogger": "^4.0.5", "brain/monkey": "2.6.1", - "wikimedia/testing-access-wrapper": "^1.0 || ^2.0", + "wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0", "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { @@ -141,7 +148,7 @@ "link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.18.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -158,9 +165,9 @@ ], "description": "Asset management utilities for Jetpack ecosystem packages", "support": { - "source": "https://github.com/Automattic/jetpack-assets/tree/v1.18.15" + "source": "https://github.com/Automattic/jetpack-assets/tree/v2.0.4" }, - "time": "2023-11-14T16:36:44+00:00" + "time": "2024-01-04T15:59:44+00:00" }, { "name": "automattic/jetpack-autoloader", @@ -262,31 +269,32 @@ }, { "name": "automattic/jetpack-connection", - "version": "v1.51.7", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-connection.git", - "reference": "4c4bae836858957d9aaf6854cf4e24c3261242c4" + "reference": "92242d3133a9e9657597901fb4a3ad17a460c79b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/4c4bae836858957d9aaf6854cf4e24c3261242c4", - "reference": "4c4bae836858957d9aaf6854cf4e24c3261242c4", + "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/92242d3133a9e9657597901fb4a3ad17a460c79b", + "reference": "92242d3133a9e9657597901fb4a3ad17a460c79b", "shasum": "" }, "require": { - "automattic/jetpack-a8c-mc-stats": "^1.4.20", - "automattic/jetpack-admin-ui": "^0.2.19", - "automattic/jetpack-constants": "^1.6.22", - "automattic/jetpack-redirect": "^1.7.25", - "automattic/jetpack-roles": "^1.4.23", - "automattic/jetpack-status": "^1.16.4" + "automattic/jetpack-a8c-mc-stats": "^2.0.0", + "automattic/jetpack-admin-ui": "^0.3.1", + "automattic/jetpack-constants": "^2.0.0", + "automattic/jetpack-redirect": "^2.0.0", + "automattic/jetpack-roles": "^2.0.0", + "automattic/jetpack-status": "^2.0.2", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.2", + "automattic/jetpack-changelogger": "^4.0.5", "automattic/wordbless": "@dev", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.0.4" + "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -303,7 +311,7 @@ "link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.51.x-dev" + "dev-trunk": "2.1.x-dev" } }, "autoload": { @@ -319,26 +327,29 @@ ], "description": "Everything needed to connect to the Jetpack infrastructure", "support": { - "source": "https://github.com/Automattic/jetpack-connection/tree/v1.51.7" + "source": "https://github.com/Automattic/jetpack-connection/tree/v2.1.1" }, - "time": "2023-04-10T11:44:13+00:00" + "time": "2024-01-04T15:59:34+00:00" }, { "name": "automattic/jetpack-constants", - "version": "v1.6.23", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-constants.git", - "reference": "0825fb1fa94956f26adebc01be0d716a0fd3ade0" + "reference": "d4244e33d2d18902951af05ca5dbb689a23c9cdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/0825fb1fa94956f26adebc01be0d716a0fd3ade0", - "reference": "0825fb1fa94956f26adebc01be0d716a0fd3ade0", + "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/d4244e33d2d18902951af05ca5dbb689a23c9cdc", + "reference": "d4244e33d2d18902951af05ca5dbb689a23c9cdc", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.8", + "automattic/jetpack-changelogger": "^4.0.0", "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0" }, @@ -353,7 +364,7 @@ "link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.6.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -367,35 +378,36 @@ ], "description": "A wrapper for defining constants in a more testable way.", "support": { - "source": "https://github.com/Automattic/jetpack-constants/tree/v1.6.23" + "source": "https://github.com/Automattic/jetpack-constants/tree/v2.0.0" }, - "time": "2023-08-23T17:56:35+00:00" + "time": "2023-11-20T20:02:28+00:00" }, { "name": "automattic/jetpack-identity-crisis", - "version": "v0.8.43", + "version": "v0.14.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-identity-crisis.git", - "reference": "8a01e7ed271544d354c2192f575a6fe7dc2ba4d3" + "reference": "e858fe9b17aef3c4237863109e061d05528a7ae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-identity-crisis/zipball/8a01e7ed271544d354c2192f575a6fe7dc2ba4d3", - "reference": "8a01e7ed271544d354c2192f575a6fe7dc2ba4d3", + "url": "https://api.github.com/repos/Automattic/jetpack-identity-crisis/zipball/e858fe9b17aef3c4237863109e061d05528a7ae3", + "reference": "e858fe9b17aef3c4237863109e061d05528a7ae3", "shasum": "" }, "require": { - "automattic/jetpack-assets": "^1.18.1", - "automattic/jetpack-connection": "^1.51.7", - "automattic/jetpack-constants": "^1.6.22", - "automattic/jetpack-logo": "^1.6.1", - "automattic/jetpack-status": "^1.16.4" + "automattic/jetpack-assets": "^2.0.4", + "automattic/jetpack-connection": "^2.1.1", + "automattic/jetpack-constants": "^2.0.0", + "automattic/jetpack-logo": "^2.0.0", + "automattic/jetpack-status": "^2.0.2", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.2", + "automattic/jetpack-changelogger": "^4.0.5", "automattic/wordbless": "@dev", - "yoast/phpunit-polyfills": "1.0.4" + "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -412,7 +424,7 @@ "link-template": "https://github.com/Automattic/jetpack-identity-crisis/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.8.x-dev" + "dev-trunk": "0.14.x-dev" } }, "autoload": { @@ -426,26 +438,29 @@ ], "description": "Identity Crisis.", "support": { - "source": "https://github.com/Automattic/jetpack-identity-crisis/tree/v0.8.43" + "source": "https://github.com/Automattic/jetpack-identity-crisis/tree/v0.14.1" }, - "time": "2023-04-10T11:44:37+00:00" + "time": "2024-01-04T16:00:06+00:00" }, { "name": "automattic/jetpack-ip", - "version": "v0.1.6", + "version": "v0.2.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-ip.git", - "reference": "39a3b6084336a0a76e4f95f83c2306102e46990e" + "reference": "2c4c7c237ae8628b64edbe920f6ceef9be15d7dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/39a3b6084336a0a76e4f95f83c2306102e46990e", - "reference": "39a3b6084336a0a76e4f95f83c2306102e46990e", + "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/2c4c7c237ae8628b64edbe920f6ceef9be15d7dc", + "reference": "2c4c7c237ae8628b64edbe920f6ceef9be15d7dc", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^4.0.2", "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0" }, @@ -460,7 +475,7 @@ "link-template": "https://github.com/automattic/jetpack-ip/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.1.x-dev" + "dev-trunk": "0.2.x-dev" }, "textdomain": "jetpack-ip", "version-constants": { @@ -478,26 +493,29 @@ ], "description": "Utilities for working with IP addresses.", "support": { - "source": "https://github.com/Automattic/jetpack-ip/tree/v0.1.6" + "source": "https://github.com/Automattic/jetpack-ip/tree/v0.2.1" }, - "time": "2023-09-19T18:18:29+00:00" + "time": "2023-11-21T18:58:12+00:00" }, { "name": "automattic/jetpack-logo", - "version": "v1.6.3", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-logo.git", - "reference": "4fb83219cd579e2ad47441afc402fb867d1906ee" + "reference": "21890dd130cae1365d6e59cf01db74e453e72d10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-logo/zipball/4fb83219cd579e2ad47441afc402fb867d1906ee", - "reference": "4fb83219cd579e2ad47441afc402fb867d1906ee", + "url": "https://api.github.com/repos/Automattic/jetpack-logo/zipball/21890dd130cae1365d6e59cf01db74e453e72d10", + "reference": "21890dd130cae1365d6e59cf01db74e453e72d10", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^4.0.0", "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { @@ -511,7 +529,7 @@ "link-template": "https://github.com/Automattic/jetpack-logo/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.6.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -525,26 +543,29 @@ ], "description": "A logo for Jetpack", "support": { - "source": "https://github.com/Automattic/jetpack-logo/tree/v1.6.3" + "source": "https://github.com/Automattic/jetpack-logo/tree/v2.0.0" }, - "time": "2023-09-19T18:18:39+00:00" + "time": "2023-11-20T20:02:31+00:00" }, { "name": "automattic/jetpack-password-checker", - "version": "v0.2.14", + "version": "v0.3.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-password-checker.git", - "reference": "e15e0e01e363c25c2c6b105f4388b4b7d6f7b1db" + "reference": "43120a1ddc032a9141ff02cc3ac7a7eac936d9f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/e15e0e01e363c25c2c6b105f4388b4b7d6f7b1db", - "reference": "e15e0e01e363c25c2c6b105f4388b4b7d6f7b1db", + "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/43120a1ddc032a9141ff02cc3ac7a7eac936d9f9", + "reference": "43120a1ddc032a9141ff02cc3ac7a7eac936d9f9", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.8", + "automattic/jetpack-changelogger": "^4.0.0", "automattic/wordbless": "@dev", "yoast/phpunit-polyfills": "1.1.0" }, @@ -560,7 +581,7 @@ "link-template": "https://github.com/Automattic/jetpack-password-checker/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.2.x-dev" + "dev-trunk": "0.3.x-dev" } }, "autoload": { @@ -574,29 +595,30 @@ ], "description": "Password Checker.", "support": { - "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.2.14" + "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.3.0" }, - "time": "2023-08-23T17:56:39+00:00" + "time": "2023-11-20T20:02:33+00:00" }, { "name": "automattic/jetpack-redirect", - "version": "v1.7.27", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-redirect.git", - "reference": "43dd3ae2bef71281fe70f62733bfaa44c988f1b1" + "reference": "8f1bbfd4b046b8a0ae7b156007c2ef56a0ddbf76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/43dd3ae2bef71281fe70f62733bfaa44c988f1b1", - "reference": "43dd3ae2bef71281fe70f62733bfaa44c988f1b1", + "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/8f1bbfd4b046b8a0ae7b156007c2ef56a0ddbf76", + "reference": "8f1bbfd4b046b8a0ae7b156007c2ef56a0ddbf76", "shasum": "" }, "require": { - "automattic/jetpack-status": "^1.18.4" + "automattic/jetpack-status": "^2.0.0", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^4.0.0", "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0" }, @@ -611,7 +633,7 @@ "link-template": "https://github.com/Automattic/jetpack-redirect/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.7.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -625,26 +647,29 @@ ], "description": "Utilities to build URLs to the jetpack.com/redirect/ service", "support": { - "source": "https://github.com/Automattic/jetpack-redirect/tree/v1.7.27" + "source": "https://github.com/Automattic/jetpack-redirect/tree/v2.0.0" }, - "time": "2023-09-19T18:19:22+00:00" + "time": "2023-11-20T20:03:01+00:00" }, { "name": "automattic/jetpack-roles", - "version": "v1.4.25", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-roles.git", - "reference": "708b33f16a879fc2ab5939a972c968c9aeefbe38" + "reference": "967e52052a17123a23f4112da3d8e7e995467cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/708b33f16a879fc2ab5939a972c968c9aeefbe38", - "reference": "708b33f16a879fc2ab5939a972c968c9aeefbe38", + "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/967e52052a17123a23f4112da3d8e7e995467cb2", + "reference": "967e52052a17123a23f4112da3d8e7e995467cb2", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^4.0.0", "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0" }, @@ -659,7 +684,7 @@ "link-template": "https://github.com/Automattic/jetpack-roles/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.4.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -673,30 +698,31 @@ ], "description": "Utilities, related with user roles and capabilities.", "support": { - "source": "https://github.com/Automattic/jetpack-roles/tree/v1.4.25" + "source": "https://github.com/Automattic/jetpack-roles/tree/v2.0.0" }, - "time": "2023-09-19T18:18:38+00:00" + "time": "2023-11-20T20:02:32+00:00" }, { "name": "automattic/jetpack-status", - "version": "v1.19.0", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-status.git", - "reference": "3281c2311752e9df1b2809d8feacb7bf7b9b7b8d" + "reference": "a79c4bb2565ed888168b2b495a6cf38022b91b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/3281c2311752e9df1b2809d8feacb7bf7b9b7b8d", - "reference": "3281c2311752e9df1b2809d8feacb7bf7b9b7b8d", + "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/a79c4bb2565ed888168b2b495a6cf38022b91b7b", + "reference": "a79c4bb2565ed888168b2b495a6cf38022b91b7b", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^1.6.23" + "automattic/jetpack-constants": "^2.0.0", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.11", - "automattic/jetpack-ip": "^0.1.6", + "automattic/jetpack-changelogger": "^4.0.4", + "automattic/jetpack-ip": "^0.2.1", "brain/monkey": "2.6.1", "yoast/phpunit-polyfills": "1.1.0" }, @@ -711,7 +737,7 @@ "link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.19.x-dev" + "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -725,37 +751,38 @@ ], "description": "Used to retrieve information about the current status of Jetpack and the site overall.", "support": { - "source": "https://github.com/Automattic/jetpack-status/tree/v1.19.0" + "source": "https://github.com/Automattic/jetpack-status/tree/v2.0.2" }, - "time": "2023-11-13T13:50:12+00:00" + "time": "2023-12-03T23:57:53+00:00" }, { "name": "automattic/jetpack-sync", - "version": "v1.47.7", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-sync.git", - "reference": "d37f35bf8bf43ab1e1c665af2831e30814354d27" + "reference": "4b055c604fc90af519241cb0b3511ce4e3316f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/d37f35bf8bf43ab1e1c665af2831e30814354d27", - "reference": "d37f35bf8bf43ab1e1c665af2831e30814354d27", + "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/4b055c604fc90af519241cb0b3511ce4e3316f26", + "reference": "4b055c604fc90af519241cb0b3511ce4e3316f26", "shasum": "" }, "require": { - "automattic/jetpack-connection": "^1.51.7", - "automattic/jetpack-constants": "^1.6.22", - "automattic/jetpack-identity-crisis": "^0.8.43", - "automattic/jetpack-ip": "^0.1.2", - "automattic/jetpack-password-checker": "^0.2.13", - "automattic/jetpack-roles": "^1.4.23", - "automattic/jetpack-status": "^1.16.4" + "automattic/jetpack-connection": "^2.1.1", + "automattic/jetpack-constants": "^2.0.0", + "automattic/jetpack-identity-crisis": "^0.14.1", + "automattic/jetpack-ip": "^0.2.1", + "automattic/jetpack-password-checker": "^0.3.0", + "automattic/jetpack-roles": "^2.0.0", + "automattic/jetpack-status": "^2.0.2", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.2", + "automattic/jetpack-changelogger": "^4.0.5", "automattic/wordbless": "@dev", - "yoast/phpunit-polyfills": "1.0.4" + "yoast/phpunit-polyfills": "1.1.0" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -772,7 +799,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.47.x-dev" + "dev-trunk": "2.4.x-dev" } }, "autoload": { @@ -786,9 +813,9 @@ ], "description": "Everything needed to allow syncing to the WP.com infrastructure.", "support": { - "source": "https://github.com/Automattic/jetpack-sync/tree/v1.47.7" + "source": "https://github.com/Automattic/jetpack-sync/tree/v2.4.0" }, - "time": "2023-04-10T11:44:43+00:00" + "time": "2024-01-04T16:00:07+00:00" }, { "name": "composer/installers", @@ -940,16 +967,16 @@ }, { "name": "woocommerce/subscriptions-core", - "version": "6.6.0", + "version": "6.7.1", "source": { "type": "git", "url": "https://github.com/Automattic/woocommerce-subscriptions-core.git", - "reference": "5abcf9aac4e53ad9bdcf3752a34a04ae42261bac" + "reference": "81d809a476e87c260492d4cc0413818d85e123cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/5abcf9aac4e53ad9bdcf3752a34a04ae42261bac", - "reference": "5abcf9aac4e53ad9bdcf3752a34a04ae42261bac", + "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/81d809a476e87c260492d4cc0413818d85e123cc", + "reference": "81d809a476e87c260492d4cc0413818d85e123cc", "shasum": "" }, "require": { @@ -990,10 +1017,10 @@ "description": "Sell products and services with recurring payments in your WooCommerce Store.", "homepage": "https://github.com/Automattic/woocommerce-subscriptions-core", "support": { - "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.6.0", + "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.7.1", "issues": "https://github.com/Automattic/woocommerce-subscriptions-core/issues" }, - "time": "2023-12-20T07:19:09+00:00" + "time": "2024-01-17T01:56:28+00:00" } ], "packages-dev": [ @@ -1371,16 +1398,16 @@ }, { "name": "clue/utf8-react", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/clue/reactphp-utf8.git", - "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96" + "reference": "d5cd04d39cb5457aa5df830b7c4b301d2694217e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-utf8/zipball/8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", - "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", + "url": "https://api.github.com/repos/clue/reactphp-utf8/zipball/d5cd04d39cb5457aa5df830b7c4b301d2694217e", + "reference": "d5cd04d39cb5457aa5df830b7c4b301d2694217e", "shasum": "" }, "require": { @@ -1388,7 +1415,7 @@ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3" }, "require-dev": { - "phpunit/phpunit": "^9.3 ||^5.7 || ^4.8", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/stream": "^1.0 || ^0.7" }, "type": "library", @@ -1418,7 +1445,7 @@ ], "support": { "issues": "https://github.com/clue/reactphp-utf8/issues", - "source": "https://github.com/clue/reactphp-utf8/tree/v1.2.0" + "source": "https://github.com/clue/reactphp-utf8/tree/v1.3.0" }, "funding": [ { @@ -1430,7 +1457,7 @@ "type": "github" } ], - "time": "2020-11-06T11:48:09+00:00" + "time": "2023-12-06T14:52:17+00:00" }, { "name": "composer/package-versions-deprecated", @@ -2385,16 +2412,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -2435,9 +2462,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "openlss/lib-array2xml", @@ -3026,29 +3053,29 @@ }, { "name": "phpspec/prophecy", - "version": "v1.17.0", + "version": "v1.18.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2" + "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/15873c65b207b07765dbc3c95d20fdf4a320cbe2", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d4f454f7e1193933f04e6500de3e79191648ed0c", + "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { "phpspec/phpspec": "^6.0 || ^7.0", "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, "type": "library", "extra": { @@ -3081,6 +3108,7 @@ "keywords": [ "Double", "Dummy", + "dev", "fake", "mock", "spy", @@ -3088,22 +3116,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.17.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.18.0" }, - "time": "2023-02-02T15:41:36+00:00" + "time": "2023-12-07T16:22:33+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.2", + "version": "1.25.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bcad8d995980440892759db0c32acae7c8e79442" + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", - "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", "shasum": "" }, "require": { @@ -3135,29 +3163,29 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" }, - "time": "2023-09-26T12:28:12+00:00" + "time": "2024-01-04T17:06:16+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3207,7 +3235,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -3215,7 +3243,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4098,20 +4126,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -4143,7 +4171,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -4151,7 +4179,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -4425,20 +4453,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -4470,7 +4498,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -4478,7 +4506,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -4944,16 +4972,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", "shasum": "" }, "require": { @@ -4963,7 +4991,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/phpcs", @@ -4982,35 +5010,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" }, { "name": "symfony/console", - "version": "v5.4.31", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "11ac5f154e0e5c4c77af83ad11ead9165280b92a" + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/11ac5f154e0e5c4c77af83ad11ead9165280b92a", - "reference": "11ac5f154e0e5c4c77af83ad11ead9165280b92a", + "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", "shasum": "" }, "require": { @@ -5080,7 +5131,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.31" + "source": "https://github.com/symfony/console/tree/v5.4.34" }, "funding": [ { @@ -5096,7 +5147,7 @@ "type": "tidelift" } ], - "time": "2023-10-31T07:58:33+00:00" + "time": "2023-12-08T13:33:03+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5722,16 +5773,16 @@ }, { "name": "symfony/process", - "version": "v5.4.28", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a", "shasum": "" }, "require": { @@ -5764,7 +5815,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.28" + "source": "https://github.com/symfony/process/tree/v5.4.34" }, "funding": [ { @@ -5780,7 +5831,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T10:36:04+00:00" + "time": "2023-12-02T08:41:43+00:00" }, { "name": "symfony/service-contracts", @@ -5867,16 +5918,16 @@ }, { "name": "symfony/string", - "version": "v5.4.31", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "2765096c03f39ddf54f6af532166e42aaa05b24b" + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/2765096c03f39ddf54f6af532166e42aaa05b24b", - "reference": "2765096c03f39ddf54f6af532166e42aaa05b24b", + "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", "shasum": "" }, "require": { @@ -5933,7 +5984,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.31" + "source": "https://github.com/symfony/string/tree/v5.4.34" }, "funding": [ { @@ -5949,7 +6000,7 @@ "type": "tidelift" } ], - "time": "2023-11-09T08:19:44+00:00" + "time": "2023-12-09T13:20:28+00:00" }, { "name": "symfony/yaml", @@ -6028,16 +6079,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -6066,7 +6117,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -6074,7 +6125,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "vimeo/psalm", @@ -6646,5 +6697,5 @@ "platform-overrides": { "php": "7.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 0aa9f9dd03c..179c1b2ddc6 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -351,7 +351,7 @@ public function add_payments_menu() { } try { // Render full payments menu with sub-items only if the merchant completed the KYC (details_submitted = true). - $should_render_full_menu = $this->account->is_account_fully_onboarded(); + $should_render_full_menu = $this->account->is_stripe_connected() && $this->account->is_details_submitted(); } catch ( Exception $e ) { // There is an issue with connection, don't render full menu, user will get redirected to the connect page. $should_render_full_menu = false; @@ -723,6 +723,23 @@ public function enqueue_payments_scripts() { if ( $order && WC_Payment_Gateway_WCPay::GATEWAY_ID === $order->get_payment_method() ) { $refund_amount = $order->get_remaining_refund_amount(); + + // Check if the order's test mode meta matches the site's current test mode state. + // E.g. order and site are both in test mode, or both in live mode. + $order_mode = $order->get_meta( WC_Payments_Order_Service::WCPAY_MODE_META_KEY ); + if ( '' === $order_mode ) { + // If the order doesn't have a mode set, assume it was created before the order mode meta was added (< 6.9 PR#7651) and return null. + $order_test_mode_match = null; + } else { + $order_test_mode_match = ( + \WCPay\Constants\Order_Mode::PRODUCTION === $order_mode && + WC_Payments::mode()->is_live() + ) || ( + \WCPay\Constants\Order_Mode::TEST === $order_mode && + WC_Payments::mode()->is_test() + ); + } + wp_localize_script( 'WCPAY_ADMIN_ORDER_ACTIONS', 'wcpay_order_config', @@ -736,6 +753,7 @@ public function enqueue_payments_scripts() { 'chargeId' => $this->order_service->get_charge_id_for_order( $order ), 'hasOpenAuthorization' => $this->order_service->has_open_authorization( $order ), 'testMode' => \WCPay\Constants\Order_Mode::TEST === $order->get_meta( WC_Payments_Order_Service::WCPAY_MODE_META_KEY ), + 'orderTestModeMatch' => $order_test_mode_match, ] ); wp_localize_script( @@ -781,6 +799,7 @@ private function get_js_settings(): array { } $locale_info = include $path; + // Get symbols for those currencies without a short one. $symbols = get_woocommerce_currency_symbols(); $currency_data = []; @@ -810,7 +829,7 @@ private function get_js_settings(): array { try { $dev_mode = WC_Payments::mode()->is_dev(); } catch ( Exception $e ) { - Logger::log( sprintf( 'WooPayments JS settings: Could not determine if WCPay should be in dev mode! Message: %s', $e->getMessage() ), 'warning' ); + Logger::log( sprintf( 'WooPayments JS settings: Could not determine if WCPay should be in sandbox mode! Message: %s', $e->getMessage() ), 'warning' ); } $connect_url = WC_Payments_Account::get_connect_url(); diff --git a/includes/admin/class-wc-rest-payments-deposits-controller.php b/includes/admin/class-wc-rest-payments-deposits-controller.php index d4406ee19ca..f8a4fd7ec1c 100644 --- a/includes/admin/class-wc-rest-payments-deposits-controller.php +++ b/includes/admin/class-wc-rest-payments-deposits-controller.php @@ -185,6 +185,6 @@ static function ( $filter ) { */ public function manual_deposit( $request ) { $params = $request->get_params(); - return $this->forward_request( 'manual_deposit', [ $params['type'], $params['transaction_ids'] ] ); + return $this->forward_request( 'manual_deposit', [ $params['type'], $params['currency'] ] ); } } diff --git a/includes/admin/class-wc-rest-payments-orders-controller.php b/includes/admin/class-wc-rest-payments-orders-controller.php index a833fc9092f..e23f1ebfcff 100644 --- a/includes/admin/class-wc-rest-payments-orders-controller.php +++ b/includes/admin/class-wc-rest-payments-orders-controller.php @@ -189,19 +189,7 @@ public function capture_terminal_payment( WP_REST_Request $request ) { // Update the order: set the payment method and attach intent attributes. $order->set_payment_method( WC_Payment_Gateway_WCPay::GATEWAY_ID ); $order->set_payment_method_title( __( 'WooCommerce In-Person Payments', 'woocommerce-payments' ) ); - $intent_id = $intent->get_id(); - $intent_status = $intent->get_status(); - $charge = $intent->get_charge(); - $charge_id = $charge ? $charge->get_id() : null; - $this->order_service->attach_intent_info_to_order( - $order, - $intent_id, - $intent_status, - $intent->get_payment_method_id(), - $intent->get_customer_id(), - $charge_id, - $intent->get_currency() - ); + $this->order_service->attach_intent_info_to_order( $order, $intent ); $this->order_service->update_order_status_from_intent( $order, $intent ); // Certain payments (eg. Interac) are captured on the client-side (mobile app). diff --git a/includes/admin/class-wc-rest-payments-refunds-controller.php b/includes/admin/class-wc-rest-payments-refunds-controller.php index 26d3c14c670..1d0ebc59ac4 100644 --- a/includes/admin/class-wc-rest-payments-refunds-controller.php +++ b/includes/admin/class-wc-rest-payments-refunds-controller.php @@ -57,6 +57,7 @@ public function process_refund( $request ) { 'reason' => $reason, 'order_id' => $order_id, 'refund_payment' => true, + 'restock_items' => true, ] ); diff --git a/includes/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index 057af86e3f5..97c5087e46d 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Admin */ +use WCPay\Constants\Country_Code; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; use WCPay\Constants\Track_Events; @@ -388,7 +389,7 @@ public function validate_business_support_phone( string $value, WP_REST_Request } // Japan accounts require Japanese phone numbers. - if ( 'JP' === $this->account->get_account_country() ) { + if ( Country_Code::JAPAN === $this->account->get_account_country() ) { if ( '+81' !== substr( $value, 0, 3 ) ) { return new WP_Error( 'rest_invalid_pattern', @@ -676,7 +677,7 @@ private function update_is_manual_capture_enabled( WP_REST_Request $request ) { * @param WP_REST_Request $request Request object. */ private function update_is_test_mode_enabled( WP_REST_Request $request ) { - // avoiding updating test mode when dev mode is enabled. + // Avoid updating test mode when dev mode is enabled. if ( WC_Payments::mode()->is_dev() ) { return; } @@ -696,7 +697,7 @@ private function update_is_test_mode_enabled( WP_REST_Request $request ) { * @param WP_REST_Request $request Request object. */ private function update_is_debug_log_enabled( WP_REST_Request $request ) { - // avoiding updating test mode when dev mode is enabled. + // Avoid updating test mode when dev mode is enabled. if ( WC_Payments::mode()->is_dev() ) { return; } diff --git a/includes/class-compatibility-service.php b/includes/class-compatibility-service.php index 444b3383cb1..2f98acfd0aa 100644 --- a/includes/class-compatibility-service.php +++ b/includes/class-compatibility-service.php @@ -39,6 +39,7 @@ public function __construct( WC_Payments_API_Client $payments_api_client ) { */ public function init_hooks() { add_action( 'woocommerce_payments_account_refreshed', [ $this, 'update_compatibility_data' ] ); + add_action( 'after_switch_theme', [ $this, 'update_compatibility_data' ] ); } /** @@ -47,15 +48,42 @@ public function init_hooks() { * @return void */ public function update_compatibility_data() { + $active_plugins = get_option( 'active_plugins', [] ); + $post_types_count = $this->get_post_types_count(); try { $this->payments_api_client->update_compatibility_data( [ 'woopayments_version' => WCPAY_VERSION_NUMBER, 'woocommerce_version' => WC_VERSION, + 'blog_theme' => get_stylesheet(), + 'active_plugins' => $active_plugins, + 'post_types_count' => $post_types_count, ] ); } catch ( API_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // The exception is already logged if logging is on, nothing else needed. } } + + /** + * Gets the count of public posts for each post type. + * + * @return array<\WP_Post_Type|string, string> + */ + private function get_post_types_count(): array { + $post_types = get_post_types( + [ + 'public' => true, + ] + ); + + $post_types_count = []; + + foreach ( $post_types as $post_type ) { + $post_types_count[ $post_type ] = wp_count_posts( $post_type )->publish; + } + + return $post_types_count; + + } } diff --git a/includes/class-duplicate-payment-prevention-service.php b/includes/class-duplicate-payment-prevention-service.php index 99f093c5c09..71a0bfbcc10 100644 --- a/includes/class-duplicate-payment-prevention-service.php +++ b/includes/class-duplicate-payment-prevention-service.php @@ -102,9 +102,13 @@ public function check_payment_intent_attached_to_order_succeeded( WC_Order $orde return; } - $intent_meta_order_id_raw = $intent->get_metadata()['order_id'] ?? ''; - $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; - if ( $intent_meta_order_id !== $order->get_id() ) { + $intent_meta_order_id_raw = $intent->get_metadata()['order_id'] ?? ''; + $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; + $intent_meta_order_number_raw = $intent->get_metadata()['order_number'] ?? ''; + $intent_meta_order_number = is_numeric( $intent_meta_order_number_raw ) ? intval( $intent_meta_order_number_raw ) : 0; + $paid_on_woopay = filter_var( $intent->get_metadata()['paid_on_woopay'] ?? false, FILTER_VALIDATE_BOOLEAN ); + $is_woopay_order = $order->get_id() === $intent_meta_order_number; + if ( ! ( $paid_on_woopay && $is_woopay_order ) && $intent_meta_order_id !== $order->get_id() ) { return; } diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 32910b60cad..7d165269b4c 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -9,6 +9,7 @@ exit; // Exit if accessed directly. } +use WCPay\Constants\Country_Code; use WCPay\Constants\Fraud_Meta_Box_Type; use WCPay\Constants\Order_Mode; use WCPay\Constants\Order_Status; @@ -678,7 +679,7 @@ public function is_connected() { * @return bool */ public function is_account_partially_onboarded(): bool { - return $this->account->is_account_partially_onboarded(); + return $this->account->is_stripe_connected() && ! $this->account->is_details_submitted(); } /** @@ -1463,7 +1464,12 @@ public function process_payment_for_order( $cart, $payment_information, $schedul $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; if ( $intent_meta_order_id !== $order_id ) { throw new Intent_Authentication_Exception( - __( "We're not able to process this payment. Please try again later.", 'woocommerce-payments' ), + sprintf( + /* translators: %s: metadata. We do not need to translate WooPayMeta */ + esc_html( __( 'We\'re not able to process this payment. Please try again later. WooPayMeta: intent_meta_order_id: %1$s, order_id: %2$s', 'woocommerce-payments' ) ), + esc_attr( $intent_meta_order_id ), + esc_attr( $order_id ), + ), 'order_id_mismatch' ); } @@ -1684,7 +1690,7 @@ public function process_payment_for_order( $cart, $payment_information, $schedul } } - $this->order_service->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method, $customer_id, $charge_id, $currency ); + $this->order_service->attach_intent_info_to_order( $order, $intent ); $this->attach_exchange_info_to_order( $order, $charge_id ); if ( Intent_Status::SUCCEEDED === $status ) { $this->duplicate_payment_prevention_service->remove_session_processing_order( $order->get_id() ); @@ -1879,7 +1885,7 @@ public function process_redirect_payment( $order, $intent_id, $save_payment_meth } } - $this->order_service->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method_id, $customer_id, $charge_id, $currency ); + $this->order_service->attach_intent_info_to_order( $order, $intent ); $this->attach_exchange_info_to_order( $order, $charge_id ); if ( Intent_Status::SUCCEEDED === $status ) { $this->duplicate_payment_prevention_service->remove_session_processing_order( $order->get_id() ); @@ -2178,8 +2184,14 @@ public function process_refund( $order_id, $amount = null, $reason = '' ) { ); } + // Refund without an amount is a no-op, but required to succeed in + // case merchant needs it to re-stock order items. + if ( '0.00' === sprintf( '%0.2f', $amount ?? 0 ) ) { + return true; + } + // If the entered amount is not valid stop without making a request. - if ( $amount <= 0 || $amount > $order->get_total() ) { + if ( $amount < 0 || $amount > $order->get_total() ) { return new WP_Error( 'invalid-amount', __( 'The refund amount is not valid.', 'woocommerce-payments' ) @@ -2280,6 +2292,7 @@ static function ( $refund ) use ( $refund_amount ) { $wc_last_refund = WC_Payments_Utils::get_last_refund_from_order_id( $order_id ); if ( $wc_last_refund ) { $this->order_service->set_wcpay_refund_id_for_order( $wc_last_refund, $refund['id'] ); + $this->order_service->set_wcpay_refund_transaction_id_for_order( $wc_last_refund, $refund['balance_transaction'] ); $wc_last_refund->save_meta_data(); } @@ -2344,12 +2357,12 @@ public function generate_checkbox_html( $key, $data ) { if ( 'test_mode' === $key && $in_dev_mode ) { $data['custom_attributes']['disabled'] = 'disabled'; - $data['label'] = __( 'Dev mode is active so all transactions will be in test mode. This setting is only available to live accounts.', 'woocommerce-payments' ); + $data['label'] = __( 'Sandbox mode is active so all transactions will be in test mode. This setting is only available to live accounts.', 'woocommerce-payments' ); } if ( 'enable_logging' === $key && $in_dev_mode ) { $data['custom_attributes']['disabled'] = 'disabled'; - $data['label'] = __( 'Dev mode is active so logging is on by default.', 'woocommerce-payments' ); + $data['label'] = __( 'Sandbox mode is active so logging is on by default.', 'woocommerce-payments' ); } return parent::generate_checkbox_html( $key, $data ); @@ -2865,7 +2878,7 @@ protected function get_deposit_delay_days( int $default_value = 7 ): int { * * @return string code of the country. */ - protected function get_account_country( string $default_value = 'US' ): string { + protected function get_account_country( string $default_value = Country_Code::UNITED_STATES ): string { try { if ( $this->is_connected() ) { return $this->account->get_account_country() ?? $default_value; @@ -3413,7 +3426,7 @@ public function update_order_status() { $charge_id = ! empty( $charge ) ? $charge->get_id() : null; $this->attach_exchange_info_to_order( $order, $charge_id ); - $this->order_service->attach_intent_info_to_order( $order, $intent_id, $status, $intent->get_payment_method_id(), $intent->get_customer_id(), $charge_id, $intent->get_currency() ); + $this->order_service->attach_intent_info_to_order( $order, $intent ); $this->order_service->attach_transaction_fee_to_order( $order, $charge ); } else { // For $0 orders, fetch the Setup Intent instead. diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index fa4d3b45e45..db60f3df5d3 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -11,6 +11,7 @@ use Automattic\WooCommerce\Admin\Notes\DataStore; use Automattic\WooCommerce\Admin\Notes\Note; +use WCPay\Constants\Country_Code; use WCPay\Core\Server\Request\Get_Account; use WCPay\Core\Server\Request\Get_Account_Capital_Link; use WCPay\Core\Server\Request\Get_Account_Login_Data; @@ -199,6 +200,7 @@ public function is_stripe_account_valid(): bool { if ( ! $this->is_stripe_connected() ) { return false; } + $account = $this->get_cached_account_data(); if ( ! isset( $account['capabilities']['card_payments'] ) ) { @@ -224,33 +226,18 @@ public function is_account_rejected(): bool { } /** - * Checks if the account has not completed onboarding due to users abandoning the process half way. - * Returns true if the onboarding is started but did not finish. + * Checks if the account "details_submitted" flag is true. + * This is a proxy for telling if an account has completed onboarding. + * If the "details_submitted" flag is false, it means that the account has not + * yet finished the initial KYC. * - * @return bool True if the account is connected and details are not submitted, false otherwise. + * @return boolean True if the account is connected and details are not submitted, false otherwise. */ - public function is_account_partially_onboarded(): bool { - if ( ! $this->is_stripe_connected() ) { - return false; - } - + public function is_details_submitted(): bool { $account = $this->get_cached_account_data(); - return false === $account['details_submitted']; - } - /** - * Checks if the account has completed onboarding/KYC. - * Returns true if the onboarding/KYC is completed. - * - * @return bool True if the account is connected and details are submitted, false otherwise. - */ - public function is_account_fully_onboarded(): bool { - if ( ! $this->is_stripe_connected() ) { - return false; - } - - $account = $this->get_cached_account_data(); - return true === $account['details_submitted']; + $details_submitted = $account['details_submitted'] ?? false; + return true === $details_submitted; } /** @@ -258,7 +245,7 @@ public function is_account_fully_onboarded(): bool { * * @return array An array containing the status data, or [ 'error' => true ] on error or no connected account. */ - public function get_account_status_data() { + public function get_account_status_data(): array { $account = $this->get_cached_account_data(); if ( empty( $account ) ) { @@ -277,7 +264,7 @@ public function get_account_status_data() { return [ 'email' => $account['email'] ?? '', - 'country' => $account['country'] ?? 'US', + 'country' => $account['country'] ?? Country_Code::UNITED_STATES, 'status' => $account['status'], 'created' => $account['created'] ?? '', 'paymentsEnabled' => $account['payments_enabled'], @@ -858,31 +845,33 @@ public function maybe_redirect_settings_to_connect_or_overview(): bool { return false; } - // Account fully onboarded, don't redirect. - if ( $this->is_account_fully_onboarded() ) { - return false; - } - - // Account partially onboarded, redirect to overview. - if ( $this->is_account_partially_onboarded() ) { + // Not able to establish Stripe connection, redirect to the Connect page. + if ( ! $this->is_stripe_connected() ) { $this->redirect_to( admin_url( add_query_arg( [ 'page' => 'wc-admin', - 'path' => '/payments/overview', + 'path' => '/payments/connect', ], 'admin.php' ) ) ); + return true; + } + + if ( $this->is_details_submitted() ) { + // Account fully onboarded, don't redirect. + return false; } else { + // Account not yet fully onboarded so redirect to overview page. $this->redirect_to( admin_url( add_query_arg( [ 'page' => 'wc-admin', - 'path' => '/payments/connect', + 'path' => '/payments/overview', ], 'admin.php' ) @@ -918,7 +907,7 @@ public function maybe_redirect_onboarding_flow_to_overview(): bool { // We check it here after refreshing the cache, because merchant might have clicked back in browser (after Stripe KYC). // That will mean that no redirect from Stripe happened and user might be able to go through onboarding again if no webhook processed yet. - // That might cause issues if user selects dev onboarding after live one. + // That might cause issues if user selects sandbox onboarding after live one. // Shouldn't be called with force disconnected option enabled, otherwise we'll get current account data. if ( ! WC_Payments_Utils::force_disconnected_enabled() ) { $this->refresh_account_data(); @@ -966,7 +955,7 @@ public function maybe_handle_onboarding() { if ( isset( $_GET['wcpay-login'] ) && check_admin_referer( 'wcpay-login' ) ) { try { - if ( $this->is_account_partially_onboarded() ) { + if ( $this->is_stripe_connected() && ! $this->is_details_submitted() ) { $args = $_GET; $args['type'] = 'complete_kyc_link'; @@ -1499,18 +1488,18 @@ private function finalize_connection( $state, $mode ) { $event_properties ); - wp_safe_redirect( - add_query_arg( - [ - 'wcpay-state' => false, - 'wcpay-account-id' => false, - 'wcpay-live-publishable-key' => false, - 'wcpay-test-publishable-key' => false, - 'wcpay-mode' => false, - 'wcpay-connection-success' => '1', - ] - ) - ); + $params = [ + 'wcpay-state' => false, + 'wcpay-account-id' => false, + 'wcpay-live-publishable-key' => false, + 'wcpay-test-publishable-key' => false, + 'wcpay-mode' => false, + ]; + if ( empty( $_GET['wcpay-connection-error'] ) ) { + $params['wcpay-connection-success'] = '1'; + } + + wp_safe_redirect( add_query_arg( $params ) ); exit; } @@ -1795,7 +1784,7 @@ private function get_actioned_notes(): array { */ public function get_account_country() { $account = $this->get_cached_account_data(); - return $account['country'] ?? 'US'; + return $account['country'] ?? Country_Code::UNITED_STATES; } /** diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 4826252e7ec..98a647b5bff 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -277,15 +277,6 @@ public function get_payment_fields_js_config() { return apply_filters( 'wcpay_payment_fields_js_config', $payment_fields ); // nosemgrep: audit.php.wp.security.xss.query-arg -- server generated url is passed in. } - /** - * Checks if WooPay is enabled. - * - * @return bool - True if WooPay enabled, false otherwise. - */ - private function is_woopay_enabled() { - return WC_Payments_Features::is_woopay_eligible() && 'yes' === $this->gateway->get_option( 'platform_checkout', 'no' ) && WC_Payments_Features::is_woopay_express_checkout_enabled(); - } - /** * Gets payment method settings to pass to client scripts * @@ -354,13 +345,12 @@ public function payment_fields() { * but we need `$this->get_payment_fields_js_config` to be called * before `$this->saved_payment_methods()`. */ - $payment_fields = $this->get_payment_fields_js_config(); - $upe_object_name = 'wcpay_upe_config'; + $payment_fields = $this->get_payment_fields_js_config(); wp_enqueue_script( 'wcpay-upe-checkout' ); add_action( 'wp_footer', - function() use ( $payment_fields, $upe_object_name ) { - wp_localize_script( 'wcpay-upe-checkout', $upe_object_name, $payment_fields ); + function() use ( $payment_fields ) { + wp_localize_script( 'wcpay-upe-checkout', 'wcpay_upe_config', $payment_fields ); } ); diff --git a/includes/class-wc-payments-customer-service.php b/includes/class-wc-payments-customer-service.php index ecfabd4077e..824f7f7a2f9 100644 --- a/includes/class-wc-payments-customer-service.php +++ b/includes/class-wc-payments-customer-service.php @@ -520,6 +520,7 @@ public function get_prepared_customer_data() { $user_email = ''; $firstname = ''; $lastname = ''; + $billing_country = ''; if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $order_id = absint( $wp->query_vars['order-pay'] ); @@ -529,6 +530,7 @@ public function get_prepared_customer_data() { $firstname = $order->get_billing_first_name(); $lastname = $order->get_billing_last_name(); $user_email = $order->get_billing_email(); + $billing_country = $order->get_billing_country(); } } @@ -539,14 +541,15 @@ public function get_prepared_customer_data() { $firstname = $user->user_firstname; $lastname = $user->user_lastname; $user_email = get_user_meta( $user->ID, 'billing_email', true ); - $user_email = $user_email ? $user_email : $user->user_email; + $user_email = $user_email ?: $user->user_email; + $billing_country = get_user_meta( $user->ID, 'billing_country', true ); } } - $prepared_customer_data = [ + + return [ 'name' => $firstname . ' ' . $lastname, 'email' => $user_email, + 'billing_country' => $billing_country, ]; - - return $prepared_customer_data; } } diff --git a/includes/class-wc-payments-features.php b/includes/class-wc-payments-features.php index 904164421e0..9038a5c4898 100644 --- a/includes/class-wc-payments-features.php +++ b/includes/class-wc-payments-features.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments */ +use WCPay\Constants\Country_Code; + if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } @@ -310,7 +312,7 @@ public static function is_stripe_billing_eligible() { } $store_base_location = wc_get_base_location(); - return ! empty( $store_base_location['country'] ) && 'US' === $store_base_location['country']; + return ! empty( $store_base_location['country'] ) && Country_Code::UNITED_STATES === $store_base_location['country']; } /** diff --git a/includes/class-wc-payments-order-service.php b/includes/class-wc-payments-order-service.php index eab529f6d92..21b549768a7 100644 --- a/includes/class-wc-payments-order-service.php +++ b/includes/class-wc-payments-order-service.php @@ -83,6 +83,13 @@ class WC_Payments_Order_Service { */ const WCPAY_REFUND_ID_META_KEY = '_wcpay_refund_id'; + /** + * Meta key used to store WCPay refund transaction id. + * + * @const string + */ + const WCPAY_REFUND_TRANSACTION_ID_META_KEY = '_wcpay_refund_transaction_id'; + /** * Meta key used to store WCPay refund status. * @@ -106,6 +113,13 @@ class WC_Payments_Order_Service { */ const WCPAY_MODE_META_KEY = '_wcpay_mode'; + /** + * Meta key used to store payment transaction Id. + * + * @const string + */ + const WCPAY_PAYMENT_TRANSACTION_ID_META_KEY = '_wcpay_payment_transaction_id'; + /** * Client for making requests to the WooCommerce Payments API * @@ -524,6 +538,23 @@ public function set_charge_id_for_order( $order, $charge_id ) { $order->save_meta_data(); } + /** + * Set the payment metadata for payment transaction id. + * + * @param mixed $order The order. + * @param string $payment_transaction_id The value to be set. + * + * @throws Order_Not_Found_Exception + */ + public function set_payment_transaction_id_for_order( $order, $payment_transaction_id ) { + if ( ! isset( $payment_transaction_id ) || null === $payment_transaction_id ) { + return; + } + $order = $this->get_order( $order ); + $order->update_meta_data( self::WCPAY_PAYMENT_TRANSACTION_ID_META_KEY, $payment_transaction_id ); + $order->save_meta_data(); + } + /** * Get the payment metadata for charge id. * @@ -651,6 +682,20 @@ public function set_wcpay_refund_id_for_order( $order, $wcpay_refund_id ) { $order->save_meta_data(); } + /** + * Set the payment metadata for refund transaction id. + * + * @param WC_Order_Refund $order The order. + * @param string $wcpay_transaction_id The value to be set. + * + * @throws Order_Not_Found_Exception + */ + public function set_wcpay_refund_transaction_id_for_order( WC_Order_Refund $order, string $wcpay_transaction_id ) { + $order = $this->get_order( $order ); + $order->update_meta_data( self::WCPAY_REFUND_TRANSACTION_ID_META_KEY, $wcpay_transaction_id ); + $order->save_meta_data(); + } + /** * Get the payment metadata for refund id. * @@ -752,6 +797,35 @@ public function get_fraud_meta_box_type_for_order( $order ) : string { /** * Given the payment intent data, adds it to the given order as metadata and parses any notes that need to be added * + * @param WC_Order $order The order. + * @param WC_Payments_API_Payment_Intention|WC_Payments_API_Setup_Intention $intent The payment or setup intention object. + * + * @throws Order_Not_Found_Exception + */ + public function attach_intent_info_to_order( WC_Order $order, $intent ) { + // We don't want to allow metadata for a successful payment to be disrupted. + if ( Intent_Status::SUCCEEDED === $this->get_intention_status_for_order( $order ) ) { + return; + } + // first, let's prepare all the metadata needed for refunds, required for status change etc. + $intent_id = $intent->get_id(); + $intent_status = $intent->get_status(); + $payment_method = $intent->get_payment_method_id(); + $customer_id = $intent->get_customer_id(); + $currency = $intent instanceof WC_Payments_API_Payment_Intention ? $intent->get_currency() : $order->get_currency(); + $charge = $intent instanceof WC_Payments_API_Payment_Intention ? $intent->get_charge() : null; + $charge_id = $charge ? $charge->get_id() : null; + $payment_transaction = $charge ? $charge->get_balance_transaction() : null; + $payment_transaction_id = $payment_transaction['id'] ?? ''; + // next, save it in order meta. + $this->attach_intent_info_to_order__legacy( $order, $intent_id, $intent_status, $payment_method, $customer_id, $charge_id, $currency, $payment_transaction_id ); + } + + /** + * Legacy version of the attach_intent_info_to_order method. + * + * TODO: This method should ultimately be merged with `attach_intent_info_to_order` and then removed. + * * @param WC_Order $order The order. * @param string $intent_id The intent ID. * @param string $intent_status Intent status. @@ -759,10 +833,11 @@ public function get_fraud_meta_box_type_for_order( $order ) : string { * @param string $customer_id Customer ID. * @param string $charge_id Charge ID. * @param string $currency Currency code. + * @param string $payment_transaction_id The transaction ID of the linked charge. * * @throws Order_Not_Found_Exception */ - public function attach_intent_info_to_order( $order, $intent_id, $intent_status, $payment_method, $customer_id, $charge_id, $currency ) { + public function attach_intent_info_to_order__legacy( $order, $intent_id, $intent_status, $payment_method, $customer_id, $charge_id, $currency, $payment_transaction_id = null ) { // first, let's save all the metadata that needed for refunds, required for status change etc. $order->set_transaction_id( $intent_id ); $this->set_intent_id_for_order( $order, $intent_id ); @@ -771,6 +846,7 @@ public function attach_intent_info_to_order( $order, $intent_id, $intent_status, $this->set_intention_status_for_order( $order, $intent_status ); $this->set_customer_id_for_order( $order, $customer_id ); $this->set_wcpay_intent_currency_for_order( $order, $currency ); + $this->set_payment_transaction_id_for_order( $order, $payment_transaction_id ); $order->save(); } diff --git a/includes/class-wc-payments-payment-method-messaging-element.php b/includes/class-wc-payments-payment-method-messaging-element.php index 3d951d13cc5..93996df7d3d 100644 --- a/includes/class-wc-payments-payment-method-messaging-element.php +++ b/includes/class-wc-payments-payment-method-messaging-element.php @@ -10,7 +10,6 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -use WC_Payment_Gateway_WCPay; /** * WC_Payments_Payment_Method_Messaging_Element class. */ @@ -35,7 +34,7 @@ class WC_Payments_Payment_Method_Messaging_Element { * @param WC_Payment_Gateway_WCPay $gateway Gateway instance. * @return void */ - public function __construct( WC_Payments_Account $account, $gateway ) { + public function __construct( WC_Payments_Account $account, WC_Payment_Gateway_WCPay $gateway ) { $this->account = $account; $this->gateway = $gateway; } diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index 7a30adad550..135ea785bd4 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -13,6 +13,7 @@ exit; } +use WCPay\Constants\Country_Code; use WCPay\Exceptions\Invalid_Price_Exception; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Logger; @@ -94,6 +95,7 @@ public function init() { add_action( 'woocommerce_checkout_order_processed', [ $this, 'add_order_meta' ], 10, 2 ); add_filter( 'woocommerce_login_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); add_filter( 'woocommerce_registration_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); + add_filter( 'woocommerce_cart_needs_shipping_address', [ $this, 'filter_cart_needs_shipping_address' ], 11, 1 ); // Add a filter for the value of `wcpay_is_apple_pay_enabled`. // This option does not get stored in the database at all, and this function @@ -441,7 +443,7 @@ public function get_normalized_postal_code( $postcode, $country ) { * when passing it back from the shippingcontactselected object. This causes WC to invalidate * the postal code and not calculate shipping zones correctly. */ - if ( 'GB' === $country ) { + if ( Country_Code::UNITED_KINGDOM === $country ) { // Replaces a redacted string with something like LN10***. return str_pad( preg_replace( '/\s+/', '', $postcode ), 7, '*' ); } @@ -871,6 +873,19 @@ private function is_product_supported() { return apply_filters( 'wcpay_payment_request_is_product_supported', $is_supported, $product ); } + /** + * Determine wether to filter the cart needs shipping address. + * + * @param boolean $needs_shipping_address Whether the cart needs a shipping address. + */ + public function filter_cart_needs_shipping_address( $needs_shipping_address ) { + if ( $this->has_subscription_product() && wc_get_shipping_method_count( true, true ) === 0 ) { + return false; + } + + return $needs_shipping_address; + } + /** * Get cart details. */ @@ -925,6 +940,7 @@ public function ajax_get_shipping_options() { * @param boolean $itemized_display_items Indicates whether to show subtotals or itemized views. * * @return array Shipping options data. + * * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag */ public function get_shipping_options( $shipping_address, $itemized_display_items = false ) { diff --git a/includes/class-wc-payments-utils.php b/includes/class-wc-payments-utils.php index e49e6c65359..136f36b8d83 100644 --- a/includes/class-wc-payments-utils.php +++ b/includes/class-wc-payments-utils.php @@ -10,6 +10,7 @@ } use WCPay\Exceptions\{ Amount_Too_Small_Exception, API_Exception, Connection_Exception }; +use WCPay\Constants\Country_Code; /** * WC Payments Utils class @@ -223,44 +224,44 @@ public static function zero_decimal_currencies(): array { */ public static function supported_countries(): array { return [ - 'AE' => __( 'United Arab Emirates', 'woocommerce-payments' ), - 'AT' => __( 'Austria', 'woocommerce-payments' ), - 'AU' => __( 'Australia', 'woocommerce-payments' ), - 'BE' => __( 'Belgium', 'woocommerce-payments' ), - 'BG' => __( 'Bulgaria', 'woocommerce-payments' ), - 'CA' => __( 'Canada', 'woocommerce-payments' ), - 'CH' => __( 'Switzerland', 'woocommerce-payments' ), - 'CY' => __( 'Cyprus', 'woocommerce-payments' ), - 'CZ' => __( 'Czech Republic', 'woocommerce-payments' ), - 'DE' => __( 'Germany', 'woocommerce-payments' ), - 'DK' => __( 'Denmark', 'woocommerce-payments' ), - 'EE' => __( 'Estonia', 'woocommerce-payments' ), - 'FI' => __( 'Finland', 'woocommerce-payments' ), - 'ES' => __( 'Spain', 'woocommerce-payments' ), - 'FR' => __( 'France', 'woocommerce-payments' ), - 'HR' => __( 'Croatia', 'woocommerce-payments' ), - 'JP' => __( 'Japan', 'woocommerce-payments' ), - 'LU' => __( 'Luxembourg', 'woocommerce-payments' ), - 'GB' => __( 'United Kingdom (UK)', 'woocommerce-payments' ), - 'GR' => __( 'Greece', 'woocommerce-payments' ), - 'HK' => __( 'Hong Kong', 'woocommerce-payments' ), - 'HU' => __( 'Hungary', 'woocommerce-payments' ), - 'IE' => __( 'Ireland', 'woocommerce-payments' ), - 'IT' => __( 'Italy', 'woocommerce-payments' ), - 'LT' => __( 'Lithuania', 'woocommerce-payments' ), - 'LV' => __( 'Latvia', 'woocommerce-payments' ), - 'MT' => __( 'Malta', 'woocommerce-payments' ), - 'NL' => __( 'Netherlands', 'woocommerce-payments' ), - 'NO' => __( 'Norway', 'woocommerce-payments' ), - 'NZ' => __( 'New Zealand', 'woocommerce-payments' ), - 'PL' => __( 'Poland', 'woocommerce-payments' ), - 'PT' => __( 'Portugal', 'woocommerce-payments' ), - 'RO' => __( 'Romania', 'woocommerce-payments' ), - 'SE' => __( 'Sweden', 'woocommerce-payments' ), - 'SI' => __( 'Slovenia', 'woocommerce-payments' ), - 'SK' => __( 'Slovakia', 'woocommerce-payments' ), - 'SG' => __( 'Singapore', 'woocommerce-payments' ), - 'US' => __( 'United States (US)', 'woocommerce-payments' ), + Country_Code::UNITED_ARAB_EMIRATES => __( 'United Arab Emirates', 'woocommerce-payments' ), + Country_Code::AUSTRIA => __( 'Austria', 'woocommerce-payments' ), + Country_Code::AUSTRALIA => __( 'Australia', 'woocommerce-payments' ), + Country_Code::BELGIUM => __( 'Belgium', 'woocommerce-payments' ), + Country_Code::BULGARIA => __( 'Bulgaria', 'woocommerce-payments' ), + Country_Code::CANADA => __( 'Canada', 'woocommerce-payments' ), + Country_Code::SWITZERLAND => __( 'Switzerland', 'woocommerce-payments' ), + Country_Code::CYPRUS => __( 'Cyprus', 'woocommerce-payments' ), + Country_Code::CZECHIA => __( 'Czech Republic', 'woocommerce-payments' ), + Country_Code::GERMANY => __( 'Germany', 'woocommerce-payments' ), + Country_Code::DENMARK => __( 'Denmark', 'woocommerce-payments' ), + Country_Code::ESTONIA => __( 'Estonia', 'woocommerce-payments' ), + Country_Code::FINLAND => __( 'Finland', 'woocommerce-payments' ), + Country_Code::SPAIN => __( 'Spain', 'woocommerce-payments' ), + Country_Code::FRANCE => __( 'France', 'woocommerce-payments' ), + Country_Code::CROATIA => __( 'Croatia', 'woocommerce-payments' ), + Country_Code::JAPAN => __( 'Japan', 'woocommerce-payments' ), + Country_Code::LUXEMBOURG => __( 'Luxembourg', 'woocommerce-payments' ), + Country_Code::UNITED_KINGDOM => __( 'United Kingdom (UK)', 'woocommerce-payments' ), + Country_Code::GREECE => __( 'Greece', 'woocommerce-payments' ), + Country_Code::HONG_KONG => __( 'Hong Kong', 'woocommerce-payments' ), + Country_Code::HUNGARY => __( 'Hungary', 'woocommerce-payments' ), + Country_Code::IRELAND => __( 'Ireland', 'woocommerce-payments' ), + Country_Code::ITALY => __( 'Italy', 'woocommerce-payments' ), + Country_Code::LITHUANIA => __( 'Lithuania', 'woocommerce-payments' ), + Country_Code::LATVIA => __( 'Latvia', 'woocommerce-payments' ), + Country_Code::MALTA => __( 'Malta', 'woocommerce-payments' ), + Country_Code::NETHERLANDS => __( 'Netherlands', 'woocommerce-payments' ), + Country_Code::NORWAY => __( 'Norway', 'woocommerce-payments' ), + Country_Code::NEW_ZEALAND => __( 'New Zealand', 'woocommerce-payments' ), + Country_Code::POLAND => __( 'Poland', 'woocommerce-payments' ), + Country_Code::PORTUGAL => __( 'Portugal', 'woocommerce-payments' ), + Country_Code::ROMANIA => __( 'Romania', 'woocommerce-payments' ), + Country_Code::SWEDEN => __( 'Sweden', 'woocommerce-payments' ), + Country_Code::SLOVENIA => __( 'Slovenia', 'woocommerce-payments' ), + Country_Code::SLOVAKIA => __( 'Slovakia', 'woocommerce-payments' ), + Country_Code::SINGAPORE => __( 'Singapore', 'woocommerce-payments' ), + Country_Code::UNITED_STATES => __( 'United States (US)', 'woocommerce-payments' ), ]; } diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 0677eaa5a1b..b4b64d4ea23 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -414,6 +414,7 @@ public static function init() { include_once __DIR__ . '/exceptions/class-fraud-ruleset-exception.php'; include_once __DIR__ . '/exceptions/class-order-not-found-exception.php'; include_once __DIR__ . '/constants/class-base-constant.php'; + include_once __DIR__ . '/constants/class-country-code.php'; include_once __DIR__ . '/constants/class-fraud-meta-box-type.php'; include_once __DIR__ . '/constants/class-order-mode.php'; include_once __DIR__ . '/constants/class-order-status.php'; @@ -538,7 +539,7 @@ public static function init() { self::$card_gateway->init_hooks(); self::$wc_payments_checkout->init_hooks(); - self::$mode = new Mode( self::$card_gateway ); + self::$mode = new Mode(); self::$webhook_processing_service = new WC_Payments_Webhook_Processing_Service( self::$api_client, self::$db_helper, self::$account, self::$remote_note_service, self::$order_service, self::$in_person_payments_receipts_service, self::get_gateway(), self::$customer_service, self::$database_cache ); self::$webhook_reliability_service = new WC_Payments_Webhook_Reliability_Service( self::$api_client, self::$action_scheduler_service, self::$webhook_processing_service ); @@ -1357,7 +1358,7 @@ function wcpay_show_old_woocommerce_for_norway_notice() { 'WooPayments' ), [ - 'a1' => '', + 'a1' => '', ] ) ?> @@ -1723,10 +1724,10 @@ public static function wcpay_show_old_woocommerce_for_hungary_sweden_and_czech_r $notice, 'WooCommerce', 'WooPayments', - WC_VERSION + esc_html( WC_VERSION ) ), [ - 'a1' => '', + 'a1' => '', ] ) ?> diff --git a/includes/constants/class-country-code.php b/includes/constants/class-country-code.php new file mode 100644 index 00000000000..790059d65ef --- /dev/null +++ b/includes/constants/class-country-code.php @@ -0,0 +1,231 @@ + [], + Country_Code::AFGHANISTAN => [], // Angola. - 'AO' => [], + Country_Code::ANDORRA => [], // Argentina. - 'AR' => [ + Country_Code::ARGENTINA => [ 'C' => [ 'Ciudad Autónoma de Buenos Aires', 'Ciudad Autónoma de Buenos Aires', NULL ], 'B' => [ 'Buenos Aires', 'Buenos Aires', NULL ], 'K' => [ 'Catamarca', 'Catamarca', NULL ], @@ -78,9 +78,9 @@ class Payment_Request_Button_States { 'T' => [ 'Tucumán', 'Tucumán', NULL ], ], // Austria. - 'AT' => [], + Country_Code::AUSTRIA => [], // Australia. - 'AU' =>[ + Country_Code::AUSTRALIA =>[ 'ACT' => [ 'ACT', 'Australian Capital Territory', NULL ], 'NSW' => [ 'NSW', 'New South Wales', NULL ], 'NT' => [ 'NT', 'Northern Territory', NULL ], @@ -94,21 +94,21 @@ class Payment_Request_Button_States { // Aland Islands. 'AX' => [], // Bangladesh. - 'BD' => [], + Country_Code::BANGLADESH => [], // Belgium. - 'BE' => [], + Country_Code::BELGIUM => [], // Bulgaria. - 'BG' => [], + Country_Code::BULGARIA => [], // Bahrain. - 'BH' => [], + Country_Code::BAHRAIN => [], // Burundi. - 'BI' => [], + Country_Code::BURUNDI => [], // Benin. - 'BJ' => [], + Country_Code::BENIN => [], // Bolivia. - 'BO' => [], + Country_Code::BOLIVIA => [], // Brazil. - 'BR' => [ + Country_Code::BRAZIL => [ 'AC' => [ 'AC', 'Acre', NULL ], 'AL' => [ 'AL', 'Alagoas', NULL ], 'AP' => [ 'AP', 'Amapá', NULL ], @@ -138,7 +138,7 @@ class Payment_Request_Button_States { 'TO' => [ 'TO', 'Tocantins', NULL ], ], // Canada. - 'CA' => [ + Country_Code::CANADA => [ 'AB' => [ 'AB', 'Alberta', 'Alberta' ], 'BC' => [ 'BC', 'British Columbia', 'Colombie-Britannique' ], 'MB' => [ 'MB', 'Manitoba', 'Manitoba' ], @@ -154,9 +154,9 @@ class Payment_Request_Button_States { 'YT' => [ 'YT', 'Yukon', 'Yukon' ], ], // Switzerland. - 'CH' => [], + Country_Code::SWITZERLAND => [], // China. - 'CN' => [ + Country_Code::CHINA => [ 'CN1' => [ 'Yunnan Sheng', 'Yunnan Sheng', '云南省' ], 'CN2' => [ 'Beijing Shi', 'Beijing Shi', '北京市' ], 'CN3' => [ 'Tianjin Shi', 'Tianjin Shi', '天津市' ], @@ -193,19 +193,19 @@ class Payment_Request_Button_States { // [ 'Hong Kong', 'Hong Kong', '香港' ], ], // Czech Republic. - 'CZ' => [], + Country_Code::CZECHIA => [], // Germany. - 'DE' => [], + Country_Code::GERMANY => [], // Denmark. - 'DK' => [], + Country_Code::DENMARK => [], // Dominican Republic. - 'DO' => [], + Country_Code::DOMINICAN_REPUBLIC => [], // Algeria. - 'DZ' => [], + Country_Code::ALGERIA => [], // Estonia. - 'EE' => [], + Country_Code::ESTONIA => [], // Egypt. - 'EG' => [ + Country_Code::EGYPT => [ 'EGALX' => [ 'Alexandria Governorate', 'Alexandria Governorate', 'الإسكندرية' ], 'EGASN' => [ 'Aswan Governorate', 'Aswan Governorate', 'أسوان' ], 'EGAST' => [ 'Asyut Governorate', 'Asyut Governorate', 'أسيوط' ], @@ -235,7 +235,7 @@ class Payment_Request_Button_States { 'EGWAD' => [ 'New Valley Governorate', 'New Valley Governorate', 'الوادي الجديد' ], ], // Spain. - 'ES' => [ + Country_Code::SPAIN => [ 'C' => [ 'A Coruña', 'A Coruña', NULL ], 'VI' => [ 'Álava', 'Álava', NULL ], 'AB' => [ 'Albacete', 'Albacete', NULL ], @@ -290,19 +290,19 @@ class Payment_Request_Button_States { 'Z' => [ 'Zaragoza', 'Zaragoza', NULL ], ], // Finland. - 'FI' => [], + Country_Code::FINLAND => [], // France. - 'FR' => [], + Country_Code::FRANCE => [], // French Guiana. 'GF' => [], // Ghana. - 'GH' => [], + Country_Code::GHANA => [], // Guadeloupe. 'GP' => [], // Greece. - 'GR' => [], + Country_Code::GREECE => [], // Guatemala. - 'GT' => [], + Country_Code::GUATEMALA => [], // Hong Kong. 'HK' => [ 'HONG KONG' => [ 'Hong Kong Island', 'Hong Kong Island', '香港島' ], @@ -310,9 +310,9 @@ class Payment_Request_Button_States { 'NEW TERRITORIES' => [ 'New Territories', 'New Territories', '新界' ], ], // Hungary. - 'HU' => [], + Country_Code::HUNGARY => [], // Indonesia. - 'ID' => [ + Country_Code::INDONESIA => [ 'AC' => [ 'Aceh', 'Aceh', NULL ], 'SU' => [ 'Sumatera Utara', 'Sumatera Utara', NULL ], 'SB' => [ 'Sumatera Barat', 'Sumatera Barat', NULL ], @@ -351,7 +351,7 @@ class Payment_Request_Button_States { // [ 'Kalimantan Timur', 'Kalimantan Timur', NULL ], ], // Ireland. - 'IE' => [ + Country_Code::IRELAND => [ 'CW' => [ 'Co. Carlow', 'Co. Carlow', NULL ], 'CN' => [ 'Co. Cavan', 'Co. Cavan', NULL ], 'CE' => [ 'Co. Clare', 'Co. Clare', NULL ], @@ -380,11 +380,11 @@ class Payment_Request_Button_States { 'WW' => [ 'Co. Wicklow', 'Co. Wicklow', NULL ], ], // Israel. - 'IL' => [], + Country_Code::ISRAEL => [], // Isle of Man. 'IM' => [], // India. - 'IN' => [ + Country_Code::INDIA => [ 'AP' => [ 'Andhra Pradesh', 'Andhra Pradesh', NULL ], 'AR' => [ 'Arunachal Pradesh', 'Arunachal Pradesh', NULL ], 'AS' => [ 'Assam', 'Assam', NULL ], @@ -424,7 +424,7 @@ class Payment_Request_Button_States { 'PY' => [ 'Puducherry', 'Puducherry', NULL ], ], // Iran. - 'IR' => [ + Country_Code::IRAN => [ 'KHZ' => [ 'Khuzestan Province', 'Khuzestan Province', 'استان خوزستان' ], 'THR' => [ 'Tehran Province', 'Tehran Province', 'استان تهران' ], 'ILM' => [ 'Ilam Province', 'Ilam Province', 'استان ایلام' ], @@ -458,9 +458,9 @@ class Payment_Request_Button_States { 'SBN' => [ 'Sistan and Baluchestan Province', 'Sistan and Baluchestan Province', 'استان سیستان و بلوچستان' ], ], // Iceland. - 'IS' => [], + Country_Code::ICELAND => [], // Italy. - 'IT' => [ + Country_Code::ITALY => [ 'AG' => [ 'AG', 'Agrigento', NULL ], 'AL' => [ 'AL', 'Alessandria', NULL ], 'AN' => [ 'AN', 'Ancona', NULL ], @@ -570,7 +570,7 @@ class Payment_Request_Button_States { 'VT' => [ 'VT', 'Viterbo', NULL ], ], // Jamaica. - 'JM' => [ + Country_Code::JAMAICA => [ 'JM-01' => [ 'Kingston', 'Kingston', NULL ], 'JM-02' => [ 'St. Andrew', 'St. Andrew', NULL ], 'JM-03' => [ 'St. Thomas', 'St. Thomas', NULL ], @@ -587,7 +587,7 @@ class Payment_Request_Button_States { 'JM-14' => [ 'St. Catherine', 'St. Catherine', NULL ], ], // Japan. - 'JP' => [ + Country_Code::JAPAN => [ 'JP01' => [ 'Hokkaido', 'Hokkaido', '北海道' ], 'JP02' => [ 'Aomori', 'Aomori', '青森県' ], 'JP03' => [ 'Iwate', 'Iwate', '岩手県' ], @@ -637,29 +637,29 @@ class Payment_Request_Button_States { 'JP47' => [ 'Okinawa', 'Okinawa', '沖縄県' ], ], // Kenya. - 'KE' => [], + Country_Code::KENYA => [], // South Korea. - 'KR' => [], + Country_Code::SOUTH_KOREA => [], // Kuwait. - 'KW' => [], + Country_Code::KUWAIT => [], // Laos. - 'LA' => [], + Country_Code::LAOS => [], // Lebanon. - 'LB' => [], + Country_Code::LEBANON => [], // Sri Lanka. 'LK' => [], // Liberia. - 'LR' => [], + Country_Code::LIBERIA => [], // Luxembourg. - 'LU' => [], + Country_Code::LUXEMBOURG => [], // Moldova. - 'MD' => [], + Country_Code::MOLDOVA => [], // Martinique. 'MQ' => [], // Malta. - 'MT' => [], + Country_Code::MALTA => [], // Mexico. - 'MX' => [ + Country_Code::MEXICO => [ 'DF' => [ 'CDMX', 'Ciudad de México', NULL ], 'JA' => [ 'Jal.', 'Jalisco', NULL ], 'NL' => [ 'N.L.', 'Nuevo León', NULL ], @@ -694,7 +694,7 @@ class Payment_Request_Button_States { 'ZA' => [ 'Zac.', 'Zacatecas', NULL ], ], // Malaysia. - 'MY' => [ + Country_Code::MALAYSIA => [ 'JHR' => [ 'Johor', 'Johor', NULL ], 'KDH' => [ 'Kedah', 'Kedah', NULL ], 'KTN' => [ 'Kelantan', 'Kelantan', NULL ], @@ -713,7 +713,7 @@ class Payment_Request_Button_States { 'KUL' => [ 'Kuala Lumpur', 'Kuala Lumpur', NULL ], ], // Mozambique. - 'MZ' => [ + Country_Code::MOZAMBIQUE => [ 'MZP' => [ 'Cabo Delgado', 'Cabo Delgado', NULL ], 'MZG' => [ 'Gaza', 'Gaza', NULL ], 'MZI' => [ 'Inhambane', 'Inhambane', NULL ], @@ -727,9 +727,9 @@ class Payment_Request_Button_States { 'MZQ' => [ 'Zambezia', 'Zambezia', NULL ], ], // Namibia. - 'NA' => [], + Country_Code::NAMIBIA => [], // Nigeria. - 'NG' => [ + Country_Code::NIGERIA => [ 'AB' => [ 'Abia', 'Abia', NULL ], 'FC' => [ 'Federal Capital Territory', 'Federal Capital Territory', NULL ], 'AD' => [ 'Adamawa', 'Adamawa', NULL ], @@ -769,15 +769,15 @@ class Payment_Request_Button_States { 'ZA' => [ 'Zamfara', 'Zamfara', NULL ], ], // Netherlands. - 'NL' => [], + Country_Code::NETHERLANDS => [], // Norway. - 'NO' => [], + Country_Code::NORWAY => [], // Nepal. - 'NP' => [], + Country_Code::NEPAL => [], // New Zealand. - 'NZ' => [], + Country_Code::NEW_ZEALAND => [], // Peru. - 'PE' => [ + Country_Code::PERU => [ 'CAL' => [ 'Callao', 'Callao', NULL ], 'LMA' => [ 'Municipalidad Metropolitana de Lima', 'Municipalidad Metropolitana de Lima', NULL ], 'AMA' => [ 'Amazonas', 'Amazonas', NULL ], @@ -806,7 +806,7 @@ class Payment_Request_Button_States { 'UCA' => [ 'Ucayali', 'Ucayali', NULL ], ], // Philippines. - 'PH' => [ + Country_Code::PHILIPPINES => [ 'ABR' => [ 'Abra', 'Abra', NULL ], 'AGN' => [ 'Agusan del Norte', 'Agusan del Norte', NULL ], 'AGS' => [ 'Agusan del Sur', 'Agusan del Sur', NULL ], @@ -891,31 +891,31 @@ class Payment_Request_Button_States { '00' => [ 'Metro Manila', 'Metro Manila', NULL ], ], // Pakistan. - 'PK' => [], + Country_Code::PAKISTAN => [], // Poland. - 'PL' => [], + Country_Code::POLAND => [], // Puerto Rico. 'PR' => [], // Portugal. - 'PT' => [], + Country_Code::PORTUGAL => [], // Paraguay. - 'PY' => [], + Country_Code::PARAGUAY => [], // Reunion. 'RE' => [], // Romania. - 'RO' => [], + Country_Code::ROMANIA => [], // Serbia. - 'RS' => [], + Country_Code::SERBIA => [], // Sweden. - 'SE' => [], + Country_Code::SWEDEN => [], // Singapore. - 'SG' => [], + Country_Code::SINGAPORE => [], // Slovenia. - 'SI' => [], + Country_Code::SLOVENIA => [], // Slovakia. - 'SK' => [], + Country_Code::SLOVAKIA => [], // Thailand. - 'TH' => [ + Country_Code::THAILAND => [ 'TH-37' => [ 'Amnat Charoen', 'Amnat Charoen', 'อำนาจเจริญ' ], 'TH-15' => [ 'Ang Thong', 'Ang Thong', 'อ่างทอง' ], 'TH-14' => [ 'Phra Nakhon Si Ayutthaya', 'Phra Nakhon Si Ayutthaya', 'พระนครศรีอยุธยา' ], @@ -995,7 +995,7 @@ class Payment_Request_Button_States { 'TH-35' => [ 'Yasothon', 'Yasothon', 'ยโสธร' ], ], // Turkey. - 'TR' => [ + Country_Code::TURKEY => [ 'TR01' => [ 'Adana', 'Adana', NULL ], 'TR02' => [ 'Adıyaman', 'Adıyaman', NULL ], 'TR03' => [ 'Afyon', 'Afyon', NULL ], @@ -1079,13 +1079,13 @@ class Payment_Request_Button_States { 'TR81' => [ 'Düzce', 'Düzce', NULL ], ], // Tanzania. - 'TZ' => [], + Country_Code::TANZANIA => [], // Uganda. - 'UG' => [], + Country_Code::UGANDA => [], // United States Minor Outlying Islands. 'UM' => [], // United States. - 'US' => [ + Country_Code::UNITED_STATES => [ 'AL' => [ 'AL', 'Alabama', NULL ], 'AK' => [ 'AK', 'Alaska', NULL ], 'AZ' => [ 'AZ', 'Arizona', NULL ], @@ -1150,13 +1150,13 @@ class Payment_Request_Button_States { //[ 'VI', 'Virgin Islands', NULL ], ], // Vietnam. - 'VN' => [], + Country_Code::VIETNAM => [], // Mayotte. 'YT' => [], // South Africa. - 'ZA' => [], + Country_Code::SOUTH_AFRICA => [], // Zambia. - 'ZM' => [], + Country_Code::ZAMBIA => [], ]; // phpcs:enable } diff --git a/includes/core/README.md b/includes/core/README.md index 45fd0a68c42..59465c1b73e 100644 --- a/includes/core/README.md +++ b/includes/core/README.md @@ -59,13 +59,13 @@ This is a singular `WCPay\Core\Mode` object, accessible through `WC_Payments::mo During initialization, the following logic is used: -1. Dev mode would be entered if: +1. Sandbox mode would be entered if: - Either [WordPress's environment type](https://developer.wordpress.org/reference/functions/wp_get_environment_type/#description) is either `development` or `staging`. - or `WCPAY_DEV_MODE` is defined and set to boolean true. 2. Test mode is entered if: - - Either Dev mode is already enabled. + - Either Sandbox mode is already enabled. - or the gateway's test mode setting is on. -3. If the gateway is neither in dev or test mode, live mode is entered. +3. If the gateway is neither in sandbox or test mode, live mode is entered. To alter this behavior, you can use the `wcpay_dev_mode` and `wcpay_test_mode` filters, for example: diff --git a/includes/core/class-mode.php b/includes/core/class-mode.php index c8b12f88202..721b8ea689d 100644 --- a/includes/core/class-mode.php +++ b/includes/core/class-mode.php @@ -28,13 +28,6 @@ class Mode { */ private $dev_mode; - /** - * Holds the gateway class for settings. - * - * @var WC_Payment_Gateway_WCPay - */ - private $gateway; - /** * Indicates the WCPay version which introduced the class. * @@ -53,15 +46,6 @@ class Mode { 'staging', ]; - /** - * Stores the gateway for later retrieval of options. - * - * @param WC_Payment_Gateway_WCPay $gateway The active gateway. - */ - public function __construct( WC_Payment_Gateway_WCPay $gateway ) { - $this->gateway = $gateway; - } - /** * Initializes the working mode of WooPayments. * @@ -73,11 +57,6 @@ private function maybe_init() { return; } - // We need the gateway settings in order to determine test mode. - if ( ! isset( $this->gateway ) || empty( $this->gateway->settings ) ) { - throw new Exception( 'WooPayments\' working mode is not initialized yet. Wait for the `init` action.' ); - } - $dev_mode = ( // Plugin-specific dev mode. $this->is_wcpay_dev_mode_defined() @@ -87,18 +66,21 @@ private function maybe_init() { ); /** - * Allows WooCommerce to enter dev mode. + * Allows WooPayments to enter dev (aka sandbox) mode. * - * @see https://woo.com/document/woopayments/testing-and-troubleshooting/dev-mode/ + * @see https://woo.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/ * @param bool $dev_mode The pre-determined dev mode. */ $this->dev_mode = (bool) apply_filters( 'wcpay_dev_mode', $dev_mode ); - $test_mode_setting = 'yes' === $this->gateway->get_option( 'test_mode' ); - $test_mode = $this->dev_mode || $test_mode_setting; + // Getting the gateway settings directly from the database so the gateway doesn't need to be initialized. + $settings_option_name = 'woocommerce_' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '_settings'; + $wcpay_settings = get_option( $settings_option_name ); + $test_mode_setting = 'yes' === ( $wcpay_settings['test_mode'] ?? false ); + $test_mode = $this->dev_mode || $test_mode_setting; /** - * Allows WooCommerce to enter test mode. + * Allows WooPayments to enter test mode. * * @see https://woo.com/document/woopayments/testing-and-troubleshooting/testing/#enabling-test-mode * @param bool $test_mode The pre-determined test mode. diff --git a/includes/core/server/request/class-get-account-login-data.md b/includes/core/server/request/class-get-account-login-data.md index 4ea6a6f37e9..3f3675207e4 100644 --- a/includes/core/server/request/class-get-account-login-data.md +++ b/includes/core/server/request/class-get-account-login-data.md @@ -5,7 +5,7 @@ ## Description The `WCPay\Core\Server\Request\Get_Account_Login_Data` class is used to construct the request for getting one-time dashboard login url. -Note that this request sends the test_mode flag only when the site is in the dev mode. +Note that this request sends the test_mode flag only when the site is in sandbox mode. ## Parameters diff --git a/includes/core/server/request/class-get-account.md b/includes/core/server/request/class-get-account.md index 7cdd3cd4330..48606f63de9 100644 --- a/includes/core/server/request/class-get-account.md +++ b/includes/core/server/request/class-get-account.md @@ -5,7 +5,7 @@ ## Description The `WCPay\Core\Server\Request\Get_Account` class is used to construct the request for retrieving account data. -Note that this request sends the test_mode flag only when the site is in the dev mode. +Note that this request sends the test_mode flag only when the site is in sandbox mode. ## Parameters diff --git a/includes/multi-currency/CountryFlags.php b/includes/multi-currency/CountryFlags.php index 286e77dfbe2..5bc494b3dd4 100644 --- a/includes/multi-currency/CountryFlags.php +++ b/includes/multi-currency/CountryFlags.php @@ -7,6 +7,8 @@ namespace WCPay\MultiCurrency; +use WCPay\Constants\Country_Code; + defined( 'ABSPATH' ) || exit; /** @@ -15,257 +17,257 @@ class CountryFlags { const EMOJI_COUNTRIES_FLAGS = [ - 'AD' => '🇦🇩', - 'AE' => '🇦🇪', - 'AF' => '🇦🇫', - 'AG' => '🇦🇬', - 'AI' => '🇦🇮', - 'AL' => '🇦🇱', - 'AM' => '🇦🇲', - 'AO' => '🇦🇴', - 'AQ' => '🇦🇶', - 'AR' => '🇦🇷', - 'AS' => '🇦🇸', - 'AT' => '🇦🇹', - 'AU' => '🇦🇺', - 'AW' => '🇦🇼', - 'AX' => '🇦🇽', - 'AZ' => '🇦🇿', - 'BA' => '🇧🇦', - 'BB' => '🇧🇧', - 'BD' => '🇧🇩', - 'BE' => '🇧🇪', - 'BF' => '🇧🇫', - 'BG' => '🇧🇬', - 'BH' => '🇧🇭', - 'BI' => '🇧🇮', - 'BJ' => '🇧🇯', - 'BL' => '🇧🇱', - 'BM' => '🇧🇲', - 'BN' => '🇧🇳', - 'BO' => '🇧🇴', - 'BQ' => '🇧🇶', - 'BR' => '🇧🇷', - 'BS' => '🇧🇸', - 'BT' => '🇧🇹', - 'BV' => '🇧🇻', - 'BW' => '🇧🇼', - 'BY' => '🇧🇾', - 'BZ' => '🇧🇿', - 'CA' => '🇨🇦', - 'CC' => '🇨🇨', - 'CD' => '🇨🇩', - 'CF' => '🇨🇫', - 'CG' => '🇨🇬', - 'CH' => '🇨🇭', - 'CI' => '🇨🇮', - 'CK' => '🇨🇰', - 'CL' => '🇨🇱', - 'CM' => '🇨🇲', - 'CN' => '🇨🇳', - 'CO' => '🇨🇴', - 'CR' => '🇨🇷', - 'CU' => '🇨🇺', - 'CV' => '🇨🇻', - 'CW' => '🇨🇼', - 'CX' => '🇨🇽', - 'CY' => '🇨🇾', - 'CZ' => '🇨🇿', - 'DE' => '🇩🇪', - 'DJ' => '🇩🇯', - 'DK' => '🇩🇰', - 'DM' => '🇩🇲', - 'DO' => '🇩🇴', - 'DZ' => '🇩🇿', - 'EC' => '🇪🇨', - 'EE' => '🇪🇪', - 'EG' => '🇪🇬', - 'EH' => '🇪🇭', - 'ER' => '🇪🇷', - 'ES' => '🇪🇸', - 'ET' => '🇪🇹', - 'EU' => '🇪🇺', - 'FI' => '🇫🇮', - 'FJ' => '🇫🇯', - 'FK' => '🇫🇰', - 'FM' => '🇫🇲', - 'FO' => '🇫🇴', - 'FR' => '🇫🇷', - 'GA' => '🇬🇦', - 'GB' => '🇬🇧', - 'GD' => '🇬🇩', - 'GE' => '🇬🇪', - 'GF' => '🇬🇫', - 'GG' => '🇬🇬', - 'GH' => '🇬🇭', - 'GI' => '🇬🇮', - 'GL' => '🇬🇱', - 'GM' => '🇬🇲', - 'GN' => '🇬🇳', - 'GP' => '🇬🇵', - 'GQ' => '🇬🇶', - 'GR' => '🇬🇷', - 'GS' => '🇬🇸', - 'GT' => '🇬🇹', - 'GU' => '🇬🇺', - 'GW' => '🇬🇼', - 'GY' => '🇬🇾', - 'HK' => '🇭🇰', - 'HM' => '🇭🇲', - 'HN' => '🇭🇳', - 'HR' => '🇭🇷', - 'HT' => '🇭🇹', - 'HU' => '🇭🇺', - 'ID' => '🇮🇩', - 'IE' => '🇮🇪', - 'IL' => '🇮🇱', - 'IM' => '🇮🇲', - 'IN' => '🇮🇳', - 'IO' => '🇮🇴', - 'IQ' => '🇮🇶', - 'IR' => '🇮🇷', - 'IS' => '🇮🇸', - 'IT' => '🇮🇹', - 'JE' => '🇯🇪', - 'JM' => '🇯🇲', - 'JO' => '🇯🇴', - 'JP' => '🇯🇵', - 'KE' => '🇰🇪', - 'KG' => '🇰🇬', - 'KH' => '🇰🇭', - 'KI' => '🇰🇮', - 'KM' => '🇰🇲', - 'KN' => '🇰🇳', - 'KP' => '🇰🇵', - 'KR' => '🇰🇷', - 'KW' => '🇰🇼', - 'KY' => '🇰🇾', - 'KZ' => '🇰🇿', - 'LA' => '🇱🇦', - 'LB' => '🇱🇧', - 'LC' => '🇱🇨', - 'LI' => '🇱🇮', - 'LK' => '🇱🇰', - 'LR' => '🇱🇷', - 'LS' => '🇱🇸', - 'LT' => '🇱🇹', - 'LU' => '🇱🇺', - 'LV' => '🇱🇻', - 'LY' => '🇱🇾', - 'MA' => '🇲🇦', - 'MC' => '🇲🇨', - 'MD' => '🇲🇩', - 'ME' => '🇲🇪', - 'MF' => '🇲🇫', - 'MG' => '🇲🇬', - 'MH' => '🇲🇭', - 'MK' => '🇲🇰', - 'ML' => '🇲🇱', - 'MM' => '🇲🇲', - 'MN' => '🇲🇳', - 'MO' => '🇲🇴', - 'MP' => '🇲🇵', - 'MQ' => '🇲🇶', - 'MR' => '🇲🇷', - 'MS' => '🇲🇸', - 'MT' => '🇲🇹', - 'MU' => '🇲🇺', - 'MV' => '🇲🇻', - 'MW' => '🇲🇼', - 'MX' => '🇲🇽', - 'MY' => '🇲🇾', - 'MZ' => '🇲🇿', - 'NA' => '🇳🇦', - 'NC' => '🇳🇨', - 'NE' => '🇳🇪', - 'NF' => '🇳🇫', - 'NG' => '🇳🇬', - 'NI' => '🇳🇮', - 'NL' => '🇳🇱', - 'NO' => '🇳🇴', - 'NP' => '🇳🇵', - 'NR' => '🇳🇷', - 'NU' => '🇳🇺', - 'NZ' => '🇳🇿', - 'OM' => '🇴🇲', - 'PA' => '🇵🇦', - 'PE' => '🇵🇪', - 'PF' => '🇵🇫', - 'PG' => '🇵🇬', - 'PH' => '🇵🇭', - 'PK' => '🇵🇰', - 'PL' => '🇵🇱', - 'PM' => '🇵🇲', - 'PN' => '🇵🇳', - 'PR' => '🇵🇷', - 'PS' => '🇵🇸', - 'PT' => '🇵🇹', - 'PW' => '🇵🇼', - 'PY' => '🇵🇾', - 'QA' => '🇶🇦', - 'RE' => '🇷🇪', - 'RO' => '🇷🇴', - 'RS' => '🇷🇸', - 'RU' => '🇷🇺', - 'RW' => '🇷🇼', - 'SA' => '🇸🇦', - 'SB' => '🇸🇧', - 'SC' => '🇸🇨', - 'SD' => '🇸🇩', - 'SE' => '🇸🇪', - 'SG' => '🇸🇬', - 'SH' => '🇸🇭', - 'SI' => '🇸🇮', - 'SJ' => '🇸🇯', - 'SK' => '🇸🇰', - 'SL' => '🇸🇱', - 'SM' => '🇸🇲', - 'SN' => '🇸🇳', - 'SO' => '🇸🇴', - 'SR' => '🇸🇷', - 'SS' => '🇸🇸', - 'ST' => '🇸🇹', - 'SV' => '🇸🇻', - 'SX' => '🇸🇽', - 'SY' => '🇸🇾', - 'SZ' => '🇸🇿', - 'TC' => '🇹🇨', - 'TD' => '🇹🇩', - 'TF' => '🇹🇫', - 'TG' => '🇹🇬', - 'TH' => '🇹🇭', - 'TJ' => '🇹🇯', - 'TK' => '🇹🇰', - 'TL' => '🇹🇱', - 'TM' => '🇹🇲', - 'TN' => '🇹🇳', - 'TO' => '🇹🇴', - 'TR' => '🇹🇷', - 'TT' => '🇹🇹', - 'TV' => '🇹🇻', - 'TW' => '🇹🇼', - 'TZ' => '🇹🇿', - 'UA' => '🇺🇦', - 'UG' => '🇺🇬', - 'UM' => '🇺🇲', - 'US' => '🇺🇸', - 'UY' => '🇺🇾', - 'UZ' => '🇺🇿', - 'VA' => '🇻🇦', - 'VC' => '🇻🇨', - 'VE' => '🇻🇪', - 'VG' => '🇻🇬', - 'VI' => '🇻🇮', - 'VN' => '🇻🇳', - 'VU' => '🇻🇺', - 'WF' => '🇼🇫', - 'WS' => '🇼🇸', - 'XK' => '🇽🇰', - 'YE' => '🇾🇪', - 'YT' => '🇾🇹', - 'ZA' => '🇿🇦', - 'ZM' => '🇿🇲', - 'ZW' => '🇿🇼', + Country_Code::ANDORRA => '🇦🇩', + Country_Code::UNITED_ARAB_EMIRATES => '🇦🇪', + Country_Code::AFGHANISTAN => '🇦🇫', + Country_Code::ANTIGUA_AND_BARBUDA => '🇦🇬', + Country_Code::ANGUILLA => '🇦🇮', + Country_Code::ALBANIA => '🇦🇱', + Country_Code::ARMENIA => '🇦🇲', + Country_Code::ANGOLA => '🇦🇴', + Country_Code::ANTARCTICA => '🇦🇶', + Country_Code::ARGENTINA => '🇦🇷', + Country_Code::AMERICAN_SAMOA => '🇦🇸', + Country_Code::AUSTRIA => '🇦🇹', + Country_Code::AUSTRALIA => '🇦🇺', + Country_Code::ARUBA => '🇦🇼', + Country_Code::ALAND_ISLANDS => '🇦🇽', + Country_Code::AZERBAIJAN => '🇦🇿', + Country_Code::BOSNIA_AND_HERZEGOVINA => '🇧🇦', + Country_Code::BARBADOS => '🇧🇧', + Country_Code::BANGLADESH => '🇧🇩', + Country_Code::BELGIUM => '🇧🇪', + Country_Code::BURKINA_FASO => '🇧🇫', + Country_Code::BULGARIA => '🇧🇬', + Country_Code::BAHRAIN => '🇧🇭', + Country_Code::BURUNDI => '🇧🇮', + Country_Code::BENIN => '🇧🇯', + Country_Code::SAINT_BARTHELEMY => '🇧🇱', + Country_Code::BERMUDA => '🇧🇲', + Country_Code::BRUNEI => '🇧🇳', + Country_Code::BOLIVIA => '🇧🇴', + Country_Code::CARIBBEAN_NETHERLANDS => '🇧🇶', + Country_Code::BRAZIL => '🇧🇷', + Country_Code::BAHAMAS => '🇧🇸', + Country_Code::BHUTAN => '🇧🇹', + Country_Code::BOUVET_ISLAND => '🇧🇻', + Country_Code::BOTSWANA => '🇧🇼', + Country_Code::BELARUS => '🇧🇾', + Country_Code::BELIZE => '🇧🇿', + Country_Code::CANADA => '🇨🇦', + Country_Code::COCOS_KEELING_ISLANDS => '🇨🇨', + Country_Code::DEMOCRATIC_REPUBLIC_OF_THE_CONGO => '🇨🇩', + Country_Code::CENTRAL_AFRICAN_REPUBLIC => '🇨🇫', + Country_Code::CONGO => '🇨🇬', + Country_Code::SWITZERLAND => '🇨🇭', + Country_Code::IVORY_COAST => '🇨🇮', + Country_Code::COOK_ISLANDS => '🇨🇰', + Country_Code::CHILE => '🇨🇱', + Country_Code::CAMEROON => '🇨🇲', + Country_Code::CHINA => '🇨🇳', + Country_Code::COLOMBIA => '🇨🇴', + Country_Code::COSTA_RICA => '🇨🇷', + Country_Code::CUBA => '🇨🇺', + Country_Code::CABO_VERDE => '🇨🇻', + 'CW' => '🇨🇼', + 'CX' => '🇨🇽', + Country_Code::CYPRUS => '🇨🇾', + Country_Code::CZECHIA => '🇨🇿', + Country_Code::GERMANY => '🇩🇪', + Country_Code::DJIBOUTI => '🇩🇯', + Country_Code::DENMARK => '🇩🇰', + Country_Code::DOMINICA => '🇩🇲', + Country_Code::DOMINICAN_REPUBLIC => '🇩🇴', + Country_Code::ALGERIA => '🇩🇿', + Country_Code::ECUADOR => '🇪🇨', + Country_Code::ESTONIA => '🇪🇪', + Country_Code::EGYPT => '🇪🇬', + 'EH' => '🇪🇭', + Country_Code::ERITREA => '🇪🇷', + Country_Code::SPAIN => '🇪🇸', + Country_Code::ETHIOPIA => '🇪🇹', + 'EU' => '🇪🇺', + Country_Code::FINLAND => '🇫🇮', + Country_Code::FIJI => '🇫🇯', + 'FK' => '🇫🇰', + Country_Code::MICRONESIA => '🇫🇲', + 'FO' => '🇫🇴', + Country_Code::FRANCE => '🇫🇷', + Country_Code::GABON => '🇬🇦', + Country_Code::UNITED_KINGDOM => '🇬🇧', + Country_Code::GRENADA => '🇬🇩', + Country_Code::GEORGIA => '🇬🇪', + 'GF' => '🇬🇫', + 'GG' => '🇬🇬', + Country_Code::GHANA => '🇬🇭', + Country_Code::GIBRALTAR => '🇬🇮', + 'GL' => '🇬🇱', + Country_Code::GAMBIA => '🇬🇲', + Country_Code::GUINEA => '🇬🇳', + 'GP' => '🇬🇵', + Country_Code::EQUATORIAL_GUINEA => '🇬🇶', + Country_Code::GREECE => '🇬🇷', + 'GS' => '🇬🇸', + Country_Code::GUATEMALA => '🇬🇹', + 'GU' => '🇬🇺', + Country_Code::GUINEA_BISSAU => '🇬🇼', + Country_Code::GUYANA => '🇬🇾', + Country_Code::HONG_KONG => '🇭🇰', + 'HM' => '🇭🇲', + Country_Code::HONDURAS => '🇭🇳', + Country_Code::CROATIA => '🇭🇷', + Country_Code::HAITI => '🇭🇹', + Country_Code::HUNGARY => '🇭🇺', + Country_Code::INDONESIA => '🇮🇩', + Country_Code::IRELAND => '🇮🇪', + Country_Code::ISRAEL => '🇮🇱', + 'IM' => '🇮🇲', + Country_Code::INDIA => '🇮🇳', + Country_Code::BRITISH_INDIAN_OCEAN_TERRITORY => '🇮🇴', + Country_Code::IRAQ => '🇮🇶', + Country_Code::IRAN => '🇮🇷', + Country_Code::ICELAND => '🇮🇸', + Country_Code::ITALY => '🇮🇹', + 'JE' => '🇯🇪', + Country_Code::JAMAICA => '🇯🇲', + Country_Code::JORDAN => '🇯🇴', + Country_Code::JAPAN => '🇯🇵', + Country_Code::KENYA => '🇰🇪', + Country_Code::KYRGYZSTAN => '🇰🇬', + Country_Code::CAMBODIA => '🇰🇭', + Country_Code::KIRIBATI => '🇰🇮', + Country_Code::COMOROS => '🇰🇲', + Country_Code::SAINT_KITTS_AND_NEVIS => '🇰🇳', + Country_Code::NORTH_KOREA => '🇰🇵', + Country_Code::SOUTH_KOREA => '🇰🇷', + Country_Code::KUWAIT => '🇰🇼', + 'KY' => '🇰🇾', + Country_Code::KAZAKHSTAN => '🇰🇿', + Country_Code::LAOS => '🇱🇦', + Country_Code::LEBANON => '🇱🇧', + Country_Code::SAINT_LUCIA => '🇱🇨', + Country_Code::LIECHTENSTEIN => '🇱🇮', + Country_Code::SRI_LANKA => '🇱🇰', + Country_Code::LIBERIA => '🇱🇷', + Country_Code::LESOTHO => '🇱🇸', + Country_Code::LITHUANIA => '🇱🇹', + Country_Code::LUXEMBOURG => '🇱🇺', + Country_Code::LATVIA => '🇱🇻', + Country_Code::LIBYA => '🇱🇾', + Country_Code::MOROCCO => '🇲🇦', + Country_Code::MONACO => '🇲🇨', + Country_Code::MOLDOVA => '🇲🇩', + Country_Code::MONTENEGRO => '🇲🇪', + 'MF' => '🇲🇫', + Country_Code::MADAGASCAR => '🇲🇬', + Country_Code::MARSHALL_ISLANDS => '🇲🇭', + Country_Code::NORTH_MACEDONIA => '🇲🇰', + Country_Code::MALI => '🇲🇱', + Country_Code::MYANMAR => '🇲🇲', + Country_Code::MONGOLIA => '🇲🇳', + 'MO' => '🇲🇴', + 'MP' => '🇲🇵', + 'MQ' => '🇲🇶', + Country_Code::MAURITANIA => '🇲🇷', + 'MS' => '🇲🇸', + Country_Code::MALTA => '🇲🇹', + Country_Code::MAURITIUS => '🇲🇺', + Country_Code::MALDIVES => '🇲🇻', + Country_Code::MALAWI => '🇲🇼', + Country_Code::MEXICO => '🇲🇽', + Country_Code::MALAYSIA => '🇲🇾', + Country_Code::MOZAMBIQUE => '🇲🇿', + Country_Code::NAMIBIA => '🇳🇦', + 'NC' => '🇳🇨', + Country_Code::NIGER => '🇳🇪', + 'NF' => '🇳🇫', + Country_Code::NIGERIA => '🇳🇬', + Country_Code::NICARAGUA => '🇳🇮', + Country_Code::NETHERLANDS => '🇳🇱', + Country_Code::NORWAY => '🇳🇴', + Country_Code::NEPAL => '🇳🇵', + Country_Code::NAURU => '🇳🇷', + 'NU' => '🇳🇺', + Country_Code::NEW_ZEALAND => '🇳🇿', + Country_Code::OMAN => '🇴🇲', + Country_Code::PANAMA => '🇵🇦', + Country_Code::PERU => '🇵🇪', + 'PF' => '🇵🇫', + Country_Code::PAPUA_NEW_GUINEA => '🇵🇬', + Country_Code::PHILIPPINES => '🇵🇭', + Country_Code::PAKISTAN => '🇵🇰', + Country_Code::POLAND => '🇵🇱', + 'PM' => '🇵🇲', + 'PN' => '🇵🇳', + 'PR' => '🇵🇷', + Country_Code::PALESTINE => '🇵🇸', + Country_Code::PORTUGAL => '🇵🇹', + Country_Code::PALAU => '🇵🇼', + Country_Code::PARAGUAY => '🇵🇾', + Country_Code::QATAR => '🇶🇦', + 'RE' => '🇷🇪', + Country_Code::ROMANIA => '🇷🇴', + Country_Code::SERBIA => '🇷🇸', + Country_Code::RUSSIA => '🇷🇺', + Country_Code::RWANDA => '🇷🇼', + Country_Code::SAUDI_ARABIA => '🇸🇦', + Country_Code::SOLOMON_ISLANDS => '🇸🇧', + Country_Code::SEYCHELLES => '🇸🇨', + Country_Code::SUDAN => '🇸🇩', + Country_Code::SWEDEN => '🇸🇪', + Country_Code::SINGAPORE => '🇸🇬', + 'SH' => '🇸🇭', + Country_Code::SLOVENIA => '🇸🇮', + 'SJ' => '🇸🇯', + Country_Code::SLOVAKIA => '🇸🇰', + Country_Code::SIERRA_LEONE => '🇸🇱', + Country_Code::SAN_MARINO => '🇸🇲', + Country_Code::SENEGAL => '🇸🇳', + Country_Code::SOMALIA => '🇸🇴', + Country_Code::SURINAME => '🇸🇷', + Country_Code::SOUTH_SUDAN => '🇸🇸', + Country_Code::SAO_TOME_AND_PRINCIPE => '🇸🇹', + Country_Code::EL_SALVADOR => '🇸🇻', + 'SX' => '🇸🇽', + Country_Code::SYRIA => '🇸🇾', + Country_Code::ESWATINI => '🇸🇿', + 'TC' => '🇹🇨', + Country_Code::CHAD => '🇹🇩', + 'TF' => '🇹🇫', + Country_Code::TOGO => '🇹🇬', + Country_Code::THAILAND => '🇹🇭', + Country_Code::TAJIKISTAN => '🇹🇯', + 'TK' => '🇹🇰', + Country_Code::EAST_TIMOR => '🇹🇱', + Country_Code::TURKMENISTAN => '🇹🇲', + Country_Code::TUNISIA => '🇹🇳', + Country_Code::TONGA => '🇹🇴', + Country_Code::TURKEY => '🇹🇷', + Country_Code::TRINIDAD_AND_TOBAGO => '🇹🇹', + Country_Code::TUVALU => '🇹🇻', + Country_Code::TAIWAN => '🇹🇼', + Country_Code::TANZANIA => '🇹🇿', + Country_Code::UKRAINE => '🇺🇦', + Country_Code::UGANDA => '🇺🇬', + 'UM' => '🇺🇲', + Country_Code::UNITED_STATES => '🇺🇸', + Country_Code::URUGUAY => '🇺🇾', + Country_Code::UZBEKISTAN => '🇺🇿', + Country_Code::VATICAN_CITY => '🇻🇦', + Country_Code::SAINT_VINCENT_AND_THE_GRENADINES => '🇻🇨', + Country_Code::VENEZUELA => '🇻🇪', + 'VG' => '🇻🇬', + 'VI' => '🇻🇮', + Country_Code::VIETNAM => '🇻🇳', + Country_Code::VANUATU => '🇻🇺', + 'WF' => '🇼🇫', + Country_Code::SAMOA => '🇼🇸', + Country_Code::KOSOVO => '🇽🇰', + Country_Code::YEMEN => '🇾🇪', + 'YT' => '🇾🇹', + Country_Code::SOUTH_AFRICA => '🇿🇦', + Country_Code::ZAMBIA => '🇿🇲', + Country_Code::ZIMBABWE => '🇿🇼', ]; /** diff --git a/includes/multi-currency/Helpers/OrderMetaHelper.php b/includes/multi-currency/Helpers/OrderMetaHelper.php index 64e53e3c80a..208fefc2c26 100644 --- a/includes/multi-currency/Helpers/OrderMetaHelper.php +++ b/includes/multi-currency/Helpers/OrderMetaHelper.php @@ -325,14 +325,19 @@ public function display_meta_box_content( $order ) { /** * Appends our parameter to the edit post link if needed. * - * @param string $url The current edit post link. + * @param string|null $url The current edit post link. * - * @return string + * @return string|null */ - public function maybe_update_edit_post_link( $url ): string { + public function maybe_update_edit_post_link( $url ): ?string { + if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) { + return null; + } + if ( $this->is_feature_enabled() ) { $url .= '&wcpay_mc_meta_helper=1'; } + return $url; } diff --git a/includes/multi-currency/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php index c862cc9f131..bf3df9ad5fd 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -12,6 +12,7 @@ use WC_Payments_Utils; use WC_Payments_API_Client; use WC_Payments_Localization_Service; +use WCPay\Constants\Country_Code; use WCPay\Exceptions\API_Exception; use WCPay\Database_Cache; use WCPay\Logger; @@ -1020,7 +1021,7 @@ public function display_geolocation_currency_update_notice() { echo \WC_Payments_Utils::esc_interpolated_html( $message, [ - 'a' => '', + 'a' => '', ] ); echo ' ' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . '

'; @@ -1392,8 +1393,8 @@ private function simulate_client_currency() { $countries = WC_Payments_Utils::supported_countries(); $predefined_simulation_currencies = [ - 'USD' => $countries['US'], - 'GBP' => $countries['GB'], + 'USD' => $countries[ Country_Code::UNITED_STATES ], + 'GBP' => $countries[ Country_Code::UNITED_KINGDOM ], ]; $simulation_currency = 'USD' === get_option( 'woocommerce_currency', 'USD' ) ? 'GBP' : 'USD'; diff --git a/includes/payment-methods/class-affirm-payment-method.php b/includes/payment-methods/class-affirm-payment-method.php index 68c4f785ddd..4be64bf653d 100644 --- a/includes/payment-methods/class-affirm-payment-method.php +++ b/includes/payment-methods/class-affirm-payment-method.php @@ -9,6 +9,7 @@ use WC_Payments_Token_Service; use WC_Payments_Utils; +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\MultiCurrency; /** @@ -33,19 +34,19 @@ public function __construct( $token_service ) { $this->accept_only_domestic_payment = true; $this->limits_per_currency = [ 'CAD' => [ - 'CA' => [ + Country_Code::CANADA => [ 'min' => 5000, 'max' => 3000000, ], // Represents CAD 50 - 30,000 CAD. ], 'USD' => [ - 'US' => [ + Country_Code::UNITED_STATES => [ 'min' => 5000, 'max' => 3000000, ], // Represents USD 50 - 30,000 USD. ], ]; - $this->countries = [ 'US', 'CA' ]; + $this->countries = [ Country_Code::UNITED_STATES, Country_Code::CANADA ]; } /** diff --git a/includes/payment-methods/class-afterpay-payment-method.php b/includes/payment-methods/class-afterpay-payment-method.php index 628ca444075..9984ae212b7 100644 --- a/includes/payment-methods/class-afterpay-payment-method.php +++ b/includes/payment-methods/class-afterpay-payment-method.php @@ -9,6 +9,7 @@ use WC_Payments_Token_Service; use WC_Payments_Utils; +use WCPay\Constants\Country_Code; /** * Afterpay Payment Method class extending UPE base class @@ -32,31 +33,31 @@ public function __construct( $token_service ) { $this->accept_only_domestic_payment = true; $this->limits_per_currency = [ 'AUD' => [ - 'AU' => [ + Country_Code::AUSTRALIA => [ 'min' => 100, 'max' => 200000, ], // Represents AUD 1 - 2,000 AUD. ], 'CAD' => [ - 'CA' => [ + Country_Code::CANADA => [ 'min' => 100, 'max' => 200000, ], // Represents CAD 1 - 2,000 CAD. ], 'NZD' => [ - 'NZ' => [ + Country_Code::NEW_ZEALAND => [ 'min' => 100, 'max' => 200000, ], // Represents NZD 1 - 2,000 NZD. ], 'GBP' => [ - 'GB' => [ + Country_Code::UNITED_KINGDOM => [ 'min' => 100, 'max' => 120000, ], // Represents GBP 1 - 1,200 GBP. ], 'USD' => [ - 'US' => [ + Country_Code::UNITED_STATES => [ 'min' => 100, 'max' => 400000, ], // Represents USD 1 - 4,000 USD. diff --git a/includes/payment-methods/class-klarna-payment-method.php b/includes/payment-methods/class-klarna-payment-method.php index 0300a2dfd74..9127cd16d48 100644 --- a/includes/payment-methods/class-klarna-payment-method.php +++ b/includes/payment-methods/class-klarna-payment-method.php @@ -9,6 +9,7 @@ use WC_Payments_Token_Service; use WC_Payments_Utils; +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\MultiCurrency; /** @@ -31,68 +32,68 @@ public function __construct( $token_service ) { $this->icon_url = plugins_url( 'assets/images/payment-methods/klarna.svg', WCPAY_PLUGIN_FILE ); $this->currencies = [ 'USD', 'GBP', 'EUR', 'DKK', 'NOK', 'SEK' ]; $this->accept_only_domestic_payment = true; - $this->countries = [ 'US', 'GB', 'AT', 'DE', 'NL', 'BE', 'ES', 'IT', 'IE', 'DK', 'FI', 'NO', 'SE' ]; + $this->countries = [ Country_Code::UNITED_STATES, Country_Code::UNITED_KINGDOM, Country_Code::AUSTRIA, Country_Code::GERMANY, Country_Code::NETHERLANDS, Country_Code::BELGIUM, Country_Code::SPAIN, Country_Code::ITALY, Country_Code::IRELAND, Country_Code::DENMARK, Country_Code::FINLAND, Country_Code::NORWAY, Country_Code::SWEDEN ]; $this->limits_per_currency = [ 'USD' => [ - 'US' => [ + Country_Code::UNITED_STATES => [ 'min' => 0, 'max' => 1000000, ], ], 'GBP' => [ - 'GB' => [ + Country_Code::UNITED_KINGDOM => [ 'min' => 0, 'max' => 1150000, ], ], 'EUR' => [ - 'AT' => [ + Country_Code::AUSTRIA => [ 'min' => 1, 'max' => 1000000, ], - 'BE' => [ + Country_Code::BELGIUM => [ 'min' => 1, 'max' => 1000000, ], - 'DE' => [ + Country_Code::GERMANY => [ 'min' => 1, 'max' => 1000000, ], - 'NL' => [ + Country_Code::NETHERLANDS => [ 'min' => 1, 'max' => 1500000, ], - 'FI' => [ + Country_Code::FINLAND => [ 'min' => 0, 'max' => 1000000, ], - 'ES' => [ + Country_Code::SPAIN => [ 'min' => 0, 'max' => 1000000, ], - 'IE' => [ + Country_Code::IRELAND => [ 'min' => 0, 'max' => 400000, ], - 'IT' => [ + Country_Code::ITALY => [ 'min' => 0, 'max' => 1000000, ], ], 'DKK' => [ - 'DK' => [ + Country_Code::DENMARK => [ 'min' => 100, 'max' => 100000000, ], ], 'NOK' => [ - 'NO' => [ + Country_Code::NORWAY => [ 'min' => 0, 'max' => 100000000, ], ], 'SEK' => [ - 'SE' => [ + Country_Code::SWEDEN => [ 'min' => 0, 'max' => 15000000, ], diff --git a/includes/subscriptions/class-wc-payments-invoice-service.php b/includes/subscriptions/class-wc-payments-invoice-service.php index c4d0cd38fb5..2e2b23663d2 100644 --- a/includes/subscriptions/class-wc-payments-invoice-service.php +++ b/includes/subscriptions/class-wc-payments-invoice-service.php @@ -299,15 +299,7 @@ public function get_and_attach_intent_info_to_order( $order, $intent_id ) { $charge = $intent_object->get_charge(); - $this->order_service->attach_intent_info_to_order( - $order, - $intent_id, - $intent_object->get_status(), - $intent_object->get_payment_method_id(), - $intent_object->get_customer_id(), - $charge ? $charge->get_id() : null, - $intent_object->get_currency() - ); + $this->order_service->attach_intent_info_to_order( $order, $intent_object ); } /** diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index 637126b3fa4..18fb4a6a9c2 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -251,15 +251,15 @@ public function get_deposits_summary( array $filters = [] ) { * Trigger a manual deposit. * * @param string $type Type of deposit. Only "instant" is supported for now. - * @param string $transaction_ids Comma-separated list of transaction IDs that will be associated with this deposit. + * @param string $currency The deposit currency. * @return array The new deposit object. * @throws API_Exception - Exception thrown on request failure. */ - public function manual_deposit( $type, $transaction_ids ) { + public function manual_deposit( $type, $currency ) { return $this->request( [ - 'type' => $type, - 'transaction_ids' => $transaction_ids, + 'type' => $type, + 'currency' => $currency, ], self::DEPOSITS_API, self::POST diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 398219468be..42a5ec09c67 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -462,6 +462,7 @@ private static function get_init_session_request( $order_id = null, $key = null, WC()->customer->set_billing_email( $email ); WC()->customer->save(); + $woopay_adapted_extensions->init(); $request['adapted_extensions'] = $woopay_adapted_extensions->get_adapted_extensions_data( $email ); if ( ! is_user_logged_in() && count( $request['adapted_extensions'] ) > 0 ) { diff --git a/package-lock.json b/package-lock.json index 056a7547a52..ffbacceb596 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "7.0.0", + "version": "7.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "7.0.0", + "version": "7.1.0", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { @@ -16,6 +16,7 @@ "@stripe/stripe-js": "1.15.1", "@woocommerce/explat": "2.3.0", "@woocommerce/number": "2.4.0", + "canvas-confetti": "1.9.2", "crypto-js": "4.1.1", "debug": "4.1.1", "intl-tel-input": "17.0.15", @@ -29,6 +30,7 @@ "@testing-library/react": "11.2.5", "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "10.4.1", + "@types/canvas-confetti": "1.6.4", "@types/intl-tel-input": "17.0.4", "@types/lodash": "4.14.170", "@types/react": "17.0.2", @@ -8435,6 +8437,12 @@ "@types/node": "*" } }, + "node_modules/@types/canvas-confetti": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz", + "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==", + "dev": true + }, "node_modules/@types/cheerio": { "version": "0.22.31", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", @@ -19327,6 +19335,15 @@ } ] }, + "node_modules/canvas-confetti": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.2.tgz", + "integrity": "sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/capital-case": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", @@ -50103,6 +50120,12 @@ "@types/node": "*" } }, + "@types/canvas-confetti": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz", + "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==", + "dev": true + }, "@types/cheerio": { "version": "0.22.31", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", @@ -58895,6 +58918,11 @@ "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", "dev": true }, + "canvas-confetti": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.2.tgz", + "integrity": "sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==" + }, "capital-case": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", diff --git a/package.json b/package.json index 32b6a95a35f..0152e41271c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "7.0.0", + "version": "7.1.0", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", @@ -75,6 +75,7 @@ "@stripe/stripe-js": "1.15.1", "@woocommerce/explat": "2.3.0", "@woocommerce/number": "2.4.0", + "canvas-confetti": "1.9.2", "crypto-js": "4.1.1", "debug": "4.1.1", "intl-tel-input": "17.0.15", @@ -88,6 +89,7 @@ "@testing-library/react": "11.2.5", "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "10.4.1", + "@types/canvas-confetti": "1.6.4", "@types/intl-tel-input": "17.0.4", "@types/lodash": "4.14.170", "@types/react": "17.0.2", diff --git a/readme.txt b/readme.txt index 0c4dc6d82e0..9ff761232e3 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.4 Requires PHP: 7.3 -Stable tag: 7.0.0 +Stable tag: 7.1.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,55 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 7.1.0 - 2024-01-25 = +* Add - Add active plugins array to compatibility data. +* Add - Add post_types and their counts as an array to compatibility data. +* Add - Add the active theme name of the blog to the compatibility service +* Add - Expose the refund transaction ID in WooCommerce Order Refund API +* Add - Select the proper payment element when using saved Stripe Link tokens or choosing to use Stripe Link for new email. +* Add - Track filtering interactions on the Transactions page. +* Fix - Allow subscription purchase via Payment Request when no shipping methods are present. +* Fix - Allow zero-amount refunds for backwards compatibility with basic payment gateway and to allow re-stock of refunded orders. +* Fix - Checking if wcpayPaymentRequestPayForOrderParams before using it in Pay for Order page +* Fix - Checkout error when page URL is too long +* Fix - Comment: Fix QIT security tests errors. +* Fix - Fix incorrect test mode notice when left KYC early after going live from builder mode +* Fix - Fix network error that occurs when viewing an test mode order with test mode disabled, and vice versa. +* Fix - fix pay-for-order quirks and 3DS behavior +* Fix - Fix Safe Mode message reversed host +* Fix - Fix Stripe Link autofill on checkout. +* Fix - Fix Stripe Link button alignment in the Checkout Block +* Fix - Hide the transaction details refund menu for ineligble disputed transactions +* Fix - Improve clarity & readability of disputed order notice (not all text bold). +* Fix - Prevent possible fatal when using get_edit_post_link filter. +* Fix - Re-render WooPay button when cart updates, when checkout updates. +* Fix - Reinstate first deposit waiting period notice in payments overview (fix bug) +* Fix - Remove unnecessary import statement which leads to a warning when first loaded +* Fix - Resolved an error that would occur with WC 8.5.0 when editing a subscription customer from the admin dashboard. +* Fix - Resolved an issue that caused ordering the Admin Subscriptions List Table to not work when HPOS is enabled. +* Fix - Restock order items when performing full refund from transaction details page +* Fix - Reverting to manual styling over native WordPress components to fix CSS defects on Analytics page +* Fix - Send metadata in error message +* Fix - Show the correct number of days in the new account waiting period notice. +* Fix - Update WooPay tablet breakpoint. +* Fix - Verify that order exists before offering "Partial refund" option on transaction details page. +* Update - Changed the edit subscription product "Expire after" (Subscription length) so it more clearly describes when a subscription will automatically stop renewing. +* Update - Pass currency parameter and not transaction_ids parameter when creating instant deposit. +* Update - Store balance transaction ID in order metadata. +* Update - Updated BNPL sorting in settings for consistency with onboarding. +* Update - Update references to dev mode to use sandbox mode terminology. +* Update - Updates to the styling of the onboarding mode selection page. +* Update - Update style of notices within the deposits section of the settings screen. +* Dev - Added enum class for country codes +* Dev - Add new Tracks events to WooPay Save My Info checkbox +* Dev - Allow test pipelines to pass by slightly adjusting HTML selectors +* Dev - Merge UPE tests into the single and main gateway test file for unit and E2E tests. +* Dev - Place order button Tracks +* Dev - Track payment-request-button loads +* Dev - Update jetpack dependencies for syncing. +* Dev - Updates to account status logic to streamline it. +* Dev - Update subscriptions-core to 6.7.1. + = 7.0.0 - 2024-01-03 = * Add - Add Account Management tools with reset account functionality for partially onboarded accounts. * Add - Adding Compatibility Service to assist with flagging possible compatibility issues in the future. diff --git a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php index 544e8e5a1e3..2bb57a967d1 100644 --- a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php @@ -9,7 +9,6 @@ use WC_Payments_Account; use WC_Payments_Order_Service; -use WC_Payment_Gateway_WCPay; use WCPay\Core\Mode; use WCPay\Internal\DependencyManagement\AbstractServiceProvider; use WCPay\Internal\Logger; @@ -44,8 +43,7 @@ public function register(): void { $container->add( 'wc_get_logger', 'wc_get_logger' ); $container->addShared( Logger::class ) ->addArgument( 'wc_get_logger' ) - ->addArgument( Mode::class ) - ->addArgument( WC_Payment_Gateway_WCPay::class ); + ->addArgument( Mode::class ); $container->addShared( OrderService::class ) ->addArgument( WC_Payments_Order_Service::class ) diff --git a/src/Internal/Logger.php b/src/Internal/Logger.php index 517f9f2fd6d..c22f17c6cfa 100644 --- a/src/Internal/Logger.php +++ b/src/Internal/Logger.php @@ -34,33 +34,20 @@ class Logger { */ private $mode; - /** - * WC_Payment_Gateway_WCPay - * - * @var WC_Payment_Gateway_WCPay - */ - private $gateway; - /** * Logger constructor. * - * @param WC_Logger_Interface $wc_logger WC_Logger_Interface. - * @param Mode $mode Mode. - * @param WC_Payment_Gateway_WCPay $gateway WC_Payment_Gateway_WCPay. + * @param WC_Logger_Interface $wc_logger WC_Logger_Interface. + * @param Mode $mode Mode. */ - public function __construct( WC_Logger_Interface $wc_logger, Mode $mode, WC_Payment_Gateway_WCPay $gateway ) { + public function __construct( WC_Logger_Interface $wc_logger, Mode $mode ) { $this->wc_logger = $wc_logger; $this->mode = $mode; - $this->gateway = $gateway; } /** * Add a log entry. * - * Note that this depends on WC_Payments gateway property to be initialized as - * we need this to access the plugins debug setting to figure out if the setting - * is turned on. - * * @param string $message Log message. * @param string $level One of the following: * 'emergency': System is unusable. @@ -80,7 +67,7 @@ public function log( $message, $level = 'info' ) : void { } /** - * Checks if the gateway setting logging toggle is enabled. + * Checks if the setting logging toggle is enabled. * * @return bool Depending on the enable_logging setting. */ @@ -92,7 +79,12 @@ public function can_log() { } catch ( Exception $e ) { return false; } - return 'yes' === $this->gateway->get_option( 'enable_logging' ); + + // Getting the gateway settings directly from the database so the gateway doesn't need to be initialized. + $settings_option_name = 'woocommerce_' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '_settings'; + $wcpay_settings = get_option( $settings_option_name ); + + return 'yes' === ( $wcpay_settings['enable_logging'] ?? false ); } /** diff --git a/src/Internal/Service/OrderService.php b/src/Internal/Service/OrderService.php index 25e962cdadd..57265fcecc9 100644 --- a/src/Internal/Service/OrderService.php +++ b/src/Internal/Service/OrderService.php @@ -186,21 +186,25 @@ public function update_order_from_successful_intent( ) { $order = $this->get_order( $order_id ); - $charge = null; - $charge_id = null; + $charge = null; + $charge_id = null; + $payment_transaction_id = null; if ( $intent instanceof WC_Payments_API_Payment_Intention ) { - $charge = $intent->get_charge(); - $charge_id = $intent->get_charge()->get_id(); + $charge = $intent->get_charge(); + $charge_id = $intent->get_charge()->get_id(); + $payment_transaction = $charge ? $charge->get_balance_transaction() : null; + $payment_transaction_id = $payment_transaction['id'] ?? ''; } - $this->legacy_service->attach_intent_info_to_order( + $this->legacy_service->attach_intent_info_to_order__legacy( $order, $intent->get_id(), $intent->get_status(), $context->get_payment_method()->get_id(), $context->get_customer_id(), $charge_id, - $context->get_currency() + $context->get_currency(), + $payment_transaction_id, ); $this->legacy_service->attach_transaction_fee_to_order( $order, $charge ); @@ -253,7 +257,7 @@ public function update_order_from_intent_that_requires_action( ) { $order = $this->get_order( $order_id ); - $this->legacy_service->attach_intent_info_to_order( + $this->legacy_service->attach_intent_info_to_order__legacy( $order, $intent->get_id(), $intent->get_status(), diff --git a/tests/e2e/config/jest.config.js b/tests/e2e/config/jest.config.js index 8e6c3a446f8..b150629cdf1 100644 --- a/tests/e2e/config/jest.config.js +++ b/tests/e2e/config/jest.config.js @@ -11,8 +11,6 @@ const e2ePaths = { wcpay: path.resolve( __dirname, '../specs/wcpay' ), subscriptions: path.resolve( __dirname, '../specs/subscriptions' ), blocks: path.resolve( __dirname, '../specs/blocks' ), - upe: path.resolve( __dirname, '../specs/upe' ), - upeSplit: path.resolve( __dirname, '../specs/upe-split' ), }; // Allow E2E tests to run specific tests - wcpay, subscriptions, blocks, all (default). diff --git a/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js b/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js index 41b4f569c80..5ce9187b684 100644 --- a/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js +++ b/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js @@ -19,7 +19,9 @@ let subscriptionId; const testSelectors = { subscriptionIdField: '.woocommerce-orders-table__cell-subscription-id > a', subscriptionRenewButton: 'a.button.subscription_renewal_early', - wcNotice: '.woocommerce .woocommerce-notices-wrapper .woocommerce-message', + wcNotice: 'div.wc-block-components-notice-banner', + wcOldNotice: + '.woocommerce .woocommerce-notices-wrapper .woocommerce-message', }; describeif( RUN_SUBSCRIPTIONS_TESTS )( @@ -65,10 +67,11 @@ describeif( RUN_SUBSCRIPTIONS_TESTS )( ); // Place an order to renew a subscription - await page.waitForSelector( testSelectors.wcNotice ); - await expect( page ).toMatchElement( testSelectors.wcNotice, { - text: 'Complete checkout to renew now.', - } ); + await shopperWCP.waitForSubscriptionsErrorBanner( + 'Complete checkout to renew now.', + testSelectors.wcNotice, + testSelectors.wcOldNotice + ); await page.waitForNavigation( { waitUntil: 'networkidle0' } ); await shopper.placeOrder(); diff --git a/tests/e2e/specs/subscriptions/shopper/shopper-subscriptions-manage-payments.spec.js b/tests/e2e/specs/subscriptions/shopper/shopper-subscriptions-manage-payments.spec.js index 3b1dc31781f..236c792e8bb 100644 --- a/tests/e2e/specs/subscriptions/shopper/shopper-subscriptions-manage-payments.spec.js +++ b/tests/e2e/specs/subscriptions/shopper/shopper-subscriptions-manage-payments.spec.js @@ -24,7 +24,8 @@ const testSelectors = { subscriptionIdField: '.woocommerce-orders-table__cell-subscription-id > a', subscriptionChangePaymentButton: '.subscription_details a.button.change_payment_method', - wcNotice: '.woocommerce .woocommerce-message', + wcNotice: 'div.wc-block-components-notice-banner', + wcOldNotice: '.woocommerce .woocommerce-message', pageTitle: 'h1.entry-title', newPaymentMethodCheckbox: 'input#wc-woocommerce_payments-payment-token-new', subscriptionPaymentMethod: '.subscription-payment-method', @@ -93,10 +94,11 @@ describeif( RUN_SUBSCRIPTIONS_TESTS )( await fillCardDetails( page, newCard ); await shopper.placeOrder(); - await page.waitForSelector( testSelectors.wcNotice, { - text: 'Payment method updated.', - } ); - + await shopperWCP.waitForSubscriptionsErrorBanner( + 'Payment method updated.', + testSelectors.wcNotice, + testSelectors.wcOldNotice + ); // Verify the new payment method has been set await page.waitForSelector( testSelectors.subscriptionPaymentMethod, @@ -133,9 +135,11 @@ describeif( RUN_SUBSCRIPTIONS_TESTS )( ); await checkboxes[ 0 ].click(); await shopper.placeOrder(); - await page.waitForSelector( testSelectors.wcNotice, { - text: 'Payment method updated.', - } ); + await shopperWCP.waitForSubscriptionsErrorBanner( + 'Payment method updated.', + testSelectors.wcNotice, + testSelectors.wcOldNotice + ); // Verify the new payment method has been set await page.waitForSelector( diff --git a/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js b/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js deleted file mode 100644 index 78642474eb1..00000000000 --- a/tests/e2e/specs/upe-split/shopper/shopper-upe-enabled-all-flows.spec.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; - -/** - * Internal dependencies - */ -import { merchantWCP, shopperWCP } from '../../../utils/flows'; -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; -const { shopper, merchant, uiUnblocked } = require( '@woocommerce/e2e-utils' ); - -const MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS = 20000; -const UPE_METHOD_CHECKBOXES = [ - '#inspector-checkbox-control-3', // bancontact - '#inspector-checkbox-control-4', // eps - '#inspector-checkbox-control-5', // giropay - '#inspector-checkbox-control-6', // ideal - '#inspector-checkbox-control-7', // sofort -]; -const card = config.get( 'cards.basic' ); - -describe( 'Enabled Split UPE', () => { - beforeAll( async () => { - await merchant.login(); - await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); - await merchant.logout(); - await shopper.login(); - await shopperWCP.changeAccountCurrencyTo( 'EUR' ); - } ); - - afterAll( async () => { - await shopperWCP.changeAccountCurrencyTo( 'USD' ); - await shopperWCP.logout(); - await merchant.login(); - await merchantWCP.disablePaymentMethod( UPE_METHOD_CHECKBOXES ); - await merchant.logout(); - } ); - - describe( 'Shortcode checkout', () => { - it( 'should save the card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await shopperWCP.selectNewPaymentMethod(); - await fillCardDetails( page, card ); - await shopperWCP.toggleSavePaymentMethod(); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - - // validate that the payment method has been added to the customer. - await shopperWCP.goToPaymentMethods(); - await expect( page ).toMatch( card.label ); - await expect( page ).toMatch( - `${ card.expires.month }/${ card.expires.year }` - ); - } ); - - it( 'should process a payment with the saved card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await shopper.goToCheckout(); - await uiUnblocked(); - await shopperWCP.selectSavedPaymentMethod( - `${ card.label } (expires ${ card.expires.month }/${ card.expires.year })` - ); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - } ); - - it( 'should delete the card', async () => { - await shopperWCP.goToPaymentMethods(); - await shopperWCP.deleteSavedPaymentMethod( card.label ); - await expect( page ).toMatch( 'Payment method deleted' ); - } ); - } ); - - describe( 'My Account', () => { - let timeAdded; - it( 'should add the card as a new payment method', async () => { - await shopperWCP.goToPaymentMethods(); - await shopperWCP.addNewPaymentMethod( 'basic', card ); - - // Take note of the time when we added this card - timeAdded = Date.now(); - - // Verify that the card was added - await expect( page ).not.toMatch( - 'You cannot add a new payment method so soon after the previous one. Please wait for 20 seconds.' - ); - await expect( page ).toMatch( 'Payment method successfully added' ); - await expect( page ).toMatch( - `${ card.expires.month }/${ card.expires.year }` - ); - } ); - - it( 'should be able to delete the card', async () => { - await shopperWCP.deleteSavedPaymentMethod( card.label ); - await expect( page ).toMatch( 'Payment method deleted.' ); - } ); - - afterAll( async () => { - // Make sure that at least 20s had already elapsed since the last card was added. - // Otherwise, you will get the error message, - // "You cannot add a new payment method so soon after the previous one." - const timeTestFinished = Date.now(); - const elapsedWaitTime = timeTestFinished - timeAdded; - const remainingWaitTime = - MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS > elapsedWaitTime - ? MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS - elapsedWaitTime - : 0; - - await new Promise( ( r ) => setTimeout( r, remainingWaitTime ) ); - } ); - } ); -} ); diff --git a/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js b/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js deleted file mode 100644 index c5ba0caa81e..00000000000 --- a/tests/e2e/specs/upe/shopper/shopper-upe-enabled-all-flows.spec.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; - -/** - * Internal dependencies - */ -import { merchantWCP, shopperWCP } from '../../../utils/flows'; -import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; -const { shopper, merchant } = require( '@woocommerce/e2e-utils' ); - -const MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS = 20000; - -const sepaPaymentMethod = '#inspector-checkbox-control-8'; -const card = config.get( 'cards.basic' ); - -describe( 'Enabled UPE', () => { - beforeAll( async () => { - await merchant.login(); - // enable SEPA - await merchantWCP.enablePaymentMethod( [ sepaPaymentMethod ] ); - await merchant.logout(); - await shopper.login(); - await shopperWCP.changeAccountCurrencyTo( 'EUR' ); - } ); - - afterAll( async () => { - await shopperWCP.changeAccountCurrencyTo( 'USD' ); - await shopperWCP.logout(); - await merchant.login(); - //disable SEPA - await merchantWCP.disablePaymentMethod( [ sepaPaymentMethod ] ); - await merchant.logout(); - } ); - - describe( 'Shortcode checkout', () => { - it( 'should save the card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await shopperWCP.selectNewPaymentMethod(); - await fillCardDetails( page, card ); - await shopperWCP.toggleSavePaymentMethod(); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - - // validate that the payment method has been added to the customer. - await shopperWCP.goToPaymentMethods(); - await expect( page ).toMatch( card.label ); - await expect( page ).toMatch( - `${ card.expires.month }/${ card.expires.year }` - ); - } ); - - it( 'should process a payment with the saved card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await shopperWCP.selectSavedPaymentMethod( - `${ card.label } (expires ${ card.expires.month }/${ card.expires.year })` - ); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - } ); - - it( 'should delete the card', async () => { - await shopperWCP.goToPaymentMethods(); - await shopperWCP.deleteSavedPaymentMethod( card.label ); - await expect( page ).toMatch( 'Payment method deleted' ); - } ); - } ); - - describe( 'My Account', () => { - let timeAdded; - it( 'should add the card as a new payment method', async () => { - await shopperWCP.goToPaymentMethods(); - await shopperWCP.addNewPaymentMethod( 'basic', card ); - - // Take note of the time when we added this card - timeAdded = Date.now(); - - // Verify that the card was added - await expect( page ).not.toMatch( - 'You cannot add a new payment method so soon after the previous one. Please wait for 20 seconds.' - ); - await expect( page ).toMatch( 'Payment method successfully added' ); - await expect( page ).toMatch( - `${ card.expires.month }/${ card.expires.year }` - ); - } ); - - it( 'should be able to delete the card', async () => { - await shopperWCP.deleteSavedPaymentMethod( card.label ); - await expect( page ).toMatch( 'Payment method deleted.' ); - } ); - - afterAll( async () => { - // Make sure that at least 20s had already elapsed since the last card was added. - // Otherwise, you will get the error message, - // "You cannot add a new payment method so soon after the previous one." - const timeTestFinished = Date.now(); - const elapsedWaitTime = timeTestFinished - timeAdded; - const remainingWaitTime = - MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS > elapsedWaitTime - ? MIN_WAIT_TIME_BETWEEN_PAYMENT_METHODS - elapsedWaitTime - : 0; - - await new Promise( ( r ) => setTimeout( r, remainingWaitTime ) ); - } ); - } ); -} ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js new file mode 100644 index 00000000000..eb6820c4272 --- /dev/null +++ b/tests/e2e/specs/wcpay/merchant/merchant-disputes-view-details-via-order-notice.spec.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import config from 'config'; + +/** + * Internal dependencies + */ +import { fillCardDetails, setupProductCheckout } from '../../../utils/payments'; + +const { merchant, shopper } = require( '@woocommerce/e2e-utils' ); + +describe( 'Disputes > View dispute details via disputed order notice', () => { + beforeAll( async () => { + await page.goto( config.get( 'url' ), { waitUntil: 'networkidle0' } ); + + // Place an order to dispute later + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + const card = config.get( 'cards.disputed-fraudulent' ); + await fillCardDetails( page, card ); + await shopper.placeOrder(); + await expect( page ).toMatch( 'Order received' ); + + // Get the order ID + const orderIdField = await page.$( + '.woocommerce-order-overview__order.order > strong' + ); + const orderId = await orderIdField.evaluate( ( el ) => el.innerText ); + + await merchant.login(); + await merchant.goToOrder( orderId ); + } ); + + afterAll( async () => { + await merchant.logout(); + } ); + + it( 'should navigate to dispute details when disputed order notice button clicked', async () => { + // If WC < 7.9, return early since the order dispute notice is not present. + const orderPaymentDetailsContainer = await page.$( + '#wcpay-order-payment-details-container' + ); + if ( ! orderPaymentDetailsContainer ) { + // eslint-disable-next-line no-console + console.log( + 'Skipping test since the order dispute notice is not present in WC < 7.9' + ); + return; + } + + // Click the order dispute notice. + await expect( page ).toClick( '[type="button"]', { + text: 'Respond now', + } ); + + await page.waitForNavigation( { + waitUntil: 'networkidle0', + } ); + + // Verify we see the dispute details on the transaction details page. + await expect( page ).toMatchElement( '.dispute-notice', { + text: 'The cardholder claims this is an unauthorized transaction', + } ); + } ); +} ); diff --git a/tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-bnpls-checkout.spec.js similarity index 100% rename from tests/e2e/specs/upe-split/shopper/shopper-bnpls-checkout.spec.js rename to tests/e2e/specs/wcpay/shopper/shopper-bnpls-checkout.spec.js diff --git a/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js index 715732b40f9..e1e959effd4 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-checkout-failures.spec.js @@ -2,18 +2,21 @@ * External dependencies */ import config from 'config'; -/** - * Internal dependencies - */ -import { shopperWCP } from '../../../utils'; import { clearCardDetails, fillCardDetails, setupProductCheckout, } from '../../../utils/payments'; +import { shopperWCP } from '../../../utils'; const { uiUnblocked } = require( '@woocommerce/e2e-utils' ); +const notice = 'div.wc-block-components-notice-banner'; +const oldNotice = 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li'; + +const waitForBanner = async ( errorText ) => { + return shopperWCP.waitForErrorBanner( errorText, notice, oldNotice ); +}; describe( 'Shopper > Checkout > Failures with various cards', () => { beforeAll( async () => { @@ -32,12 +35,7 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { await fillCardDetails( page, declinedCard ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( - page - ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { text: 'Error: Your card was declined.' } - ); + await waitForBanner( 'Error: Your card was declined.' ); await clearCardDetails(); } ); @@ -74,12 +72,7 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { await fillCardDetails( page, cardInsufficientFunds ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( - page - ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { text: 'Error: Your card has insufficient funds.' } - ); + await waitForBanner( 'Error: Your card has insufficient funds.' ); await clearCardDetails(); } ); @@ -88,12 +81,7 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { await fillCardDetails( page, cardExpired ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( - page - ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { text: 'Error: Your card has expired.' } - ); + await waitForBanner( 'Error: Your card has expired.' ); await clearCardDetails(); } ); @@ -102,12 +90,7 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { await fillCardDetails( page, cardIncorrectCVC ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( - page - ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { text: "Error: Your card's security code is incorrect." } - ); + await waitForBanner( "Error: Your card's security code is incorrect." ); await clearCardDetails(); } ); @@ -116,12 +99,8 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { await fillCardDetails( page, cardProcessingError ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( page ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { - text: - 'Error: An error occurred while processing your card. Try again in a little bit.', - } + await waitForBanner( + 'Error: An error occurred while processing your card. Try again in a little bit.' ); await clearCardDetails(); } ); @@ -151,7 +130,7 @@ describe( 'Shopper > Checkout > Failures with various cards', () => { ); await expect( page ).toMatch( declined3dsCardError, - 'Error: Your card was declined.' + 'Your card has been declined.' ); } ); } ); diff --git a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-checkout-purchase-with-upe-methods.spec.js similarity index 62% rename from tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js rename to tests/e2e/specs/wcpay/shopper/shopper-checkout-purchase-with-upe-methods.spec.js index e1062f6d1ca..3f122226c13 100644 --- a/tests/e2e/specs/upe-split/shopper/shopper-deferred-intent-creation-upe-enabled.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-checkout-purchase-with-upe-methods.spec.js @@ -8,17 +8,15 @@ import config from 'config'; */ import { merchantWCP, shopperWCP } from '../../../utils/flows'; import { - confirmCardAuthentication, - fillCardDetails, setupProductCheckout, - selectGiropayOnCheckout, - completeGiropayPayment, + selectOnCheckout, + completeRedirectedPayment, } from '../../../utils/payments'; -import { uiUnblocked } from '@woocommerce/e2e-utils/build/page-utils'; const { shopper, merchant } = require( '@woocommerce/e2e-utils' ); const UPE_METHOD_CHECKBOXES = [ - '#inspector-checkbox-control-7', // giropay + "//label[contains(text(), 'Bancontact')]/preceding-sibling::span/input[@type='checkbox']", + "//label[contains(text(), 'giropay')]/preceding-sibling::span/input[@type='checkbox']", ]; const card = config.get( 'cards.basic' ); const card2 = config.get( 'cards.basic2' ); @@ -30,6 +28,7 @@ describe( 'Enabled UPE with deferred intent creation', () => { await merchantWCP.enablePaymentMethod( UPE_METHOD_CHECKBOXES ); await merchant.logout(); await shopper.login(); + await shopperWCP.changeAccountCurrencyTo( 'EUR' ); } ); afterAll( async () => { @@ -41,88 +40,30 @@ describe( 'Enabled UPE with deferred intent creation', () => { describe( 'Enabled UPE with deferred intent creation', () => { it( 'should successfully place order with Giropay', async () => { - await shopperWCP.goToShopWithCurrency( 'EUR' ); await setupProductCheckout( config.get( 'addresses.customer.billing' ) ); - await selectGiropayOnCheckout( page ); + await selectOnCheckout( 'giropay', page ); await shopper.placeOrder(); - await completeGiropayPayment( page, 'success' ); + await completeRedirectedPayment( page, 'success' ); await page.waitForNavigation( { waitUntil: 'networkidle0', } ); await expect( page ).toMatch( 'Order received' ); } ); - it( 'should successfully place order with the default card', async () => { + it( 'should successfully place order with Bancontact', async () => { await setupProductCheckout( config.get( 'addresses.customer.billing' ) ); - await fillCardDetails( page, card ); + await selectOnCheckout( 'bancontact', page ); await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - } ); - - it( 'should process a payment with authentication for the 3DS card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await fillCardDetails( page, config.get( 'cards.3ds' ) ); - await shopper.placeOrder(); - await confirmCardAuthentication( page, '3DS' ); + await completeRedirectedPayment( page, 'success' ); await page.waitForNavigation( { waitUntil: 'networkidle0', } ); await expect( page ).toMatch( 'Order received' ); } ); - - it( 'should successfully save the card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await fillCardDetails( page, card ); - await shopperWCP.toggleSavePaymentMethod(); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - - // validate that the payment method has been added to the customer. - await shopperWCP.goToPaymentMethods(); - await expect( page ).toMatch( card.label ); - await expect( page ).toMatch( - `${ card.expires.month }/${ card.expires.year }` - ); - } ); - - it( 'should process a payment with the saved card', async () => { - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - await shopper.goToCheckout(); - await uiUnblocked(); - await shopperWCP.selectSavedPaymentMethod( - `${ card.label } (expires ${ card.expires.month }/${ card.expires.year })` - ); - await shopper.placeOrder(); - await expect( page ).toMatch( 'Order received' ); - } ); - - it( 'should delete the card', async () => { - await shopperWCP.goToPaymentMethods(); - await shopperWCP.deleteSavedPaymentMethod( card.label ); - await expect( page ).toMatch( 'Payment method deleted' ); - } ); - - it( 'should not allow guest user to save the card', async () => { - await shopperWCP.logout(); - await setupProductCheckout( - config.get( 'addresses.customer.billing' ) - ); - - await expect( page ).not.toMatchElement( - 'input#wc-woocommerce_payments-new-payment-method' - ); - await shopper.login(); - } ); } ); describe( 'My Account', () => { diff --git a/tests/e2e/specs/wcpay/shopper/shopper-checkout-save-card-and-purchase.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-checkout-save-card-and-purchase.spec.js index df04f592763..341b52cf516 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-checkout-save-card-and-purchase.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-checkout-save-card-and-purchase.spec.js @@ -87,6 +87,17 @@ describe( 'Saved cards ', () => { await shopperWCP.deleteSavedPaymentMethod( card.label ); await expect( page ).toMatch( 'Payment method deleted' ); } ); + + it( 'should not allow guest user to save the card', async () => { + await shopperWCP.logout(); + await setupProductCheckout( + config.get( 'addresses.customer.billing' ) + ); + + await expect( page ).not.toMatchElement( + 'input#wc-woocommerce_payments-new-payment-method' + ); + } ); } ); } ); diff --git a/tests/e2e/specs/wcpay/shopper/shopper-myaccount-payment-methods-add-fail.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-myaccount-payment-methods-add-fail.spec.js index 3db157447e7..f3e3762f48d 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-myaccount-payment-methods-add-fail.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-myaccount-payment-methods-add-fail.spec.js @@ -9,7 +9,10 @@ const { shopper } = require( '@woocommerce/e2e-utils' ); * Internal dependencies */ import { shopperWCP } from '../../../utils/flows'; -const { fillCardDetails } = require( '../../../utils/payments' ); +const { + fillCardDetails, + confirmCardAuthentication, +} = require( '../../../utils/payments' ); const cards = Object.entries( config.get( 'cards' ) ); const invalidCards = cards.filter( ( [ cardType ] ) => @@ -46,6 +49,9 @@ describe( 'Payment Methods', () => { await expect( page ).toClick( 'button', { text: 'Add payment method', } ); + if ( cardType === 'declined-3ds' ) { + await confirmCardAuthentication( page, '3DS2' ); + } await expect( page ).toMatchElement( '.woocommerce-error', { timeout: 30000, } ); diff --git a/tests/e2e/specs/wcpay/shopper/shopper-myaccount-save-card-and-checkout.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-myaccount-save-card-and-checkout.spec.js index 9eaea6e86ac..8def9e54824 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-myaccount-save-card-and-checkout.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-myaccount-save-card-and-checkout.spec.js @@ -37,6 +37,9 @@ describe( 'Saved cards ', () => { await expect( page ).toMatch( 'Payment method successfully added' ); + await expect( page ).toMatch( + `${ card.expires.month }/${ card.expires.year }` + ); } ); it( 'should process a payment with the saved card', async () => { diff --git a/tests/e2e/specs/wcpay/shopper/shopper-pay-for-order.spec.js b/tests/e2e/specs/wcpay/shopper/shopper-pay-for-order.spec.js index d9b421ad602..af9491a7885 100644 --- a/tests/e2e/specs/wcpay/shopper/shopper-pay-for-order.spec.js +++ b/tests/e2e/specs/wcpay/shopper/shopper-pay-for-order.spec.js @@ -26,11 +26,10 @@ describe( 'Shopper > Pay for Order', () => { await fillCardDetails( page, declinedCard ); await expect( page ).toClick( '#place_order' ); await uiUnblocked(); - await expect( - page - ).toMatchElement( - 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li', - { text: 'Error: Your card was declined.' } + await shopperWCP.waitForErrorBanner( + 'Error: Your card was declined.', + 'div.wc-block-components-notice-banner', + 'div.woocommerce-NoticeGroup > ul.woocommerce-error > li' ); // after the card has been declined, go to the order page and pay with a basic card diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js index 4eeededd5fb..3399d5b9ffe 100644 --- a/tests/e2e/utils/flows.js +++ b/tests/e2e/utils/flows.js @@ -281,10 +281,11 @@ export const shopperWCP = { await uiUnblocked(); } - await page.waitForSelector( '.cart-empty.woocommerce-info' ); - await expect( page ).toMatchElement( '.cart-empty.woocommerce-info', { - text: 'Your cart is currently empty.', - } ); + await shopperWCP.waitForErrorBanner( + 'Your cart is currently empty.', + 'div.wc-block-components-notice-banner', + '.cart-empty.woocommerce-info' + ); }, goToProductPageBySlug: async ( productSlug ) => { @@ -297,6 +298,46 @@ export const shopperWCP = { await shopperWCP.goToProductPageBySlug( productSlug ); await shopper.addToCart(); }, + + waitForErrorBanner: async ( + errorText, + noticeSelector, + oldNoticeSelector + ) => { + const errorBannerToCheck = ( async () => { + await expect( page ).toMatchElement( noticeSelector, { + text: errorText, + } ); + } )(); + + const oldErrorBannerToCheck = ( async () => { + await expect( page ).toMatchElement( oldNoticeSelector, { + text: errorText, + } ); + } )(); + + await Promise.race( [ errorBannerToCheck, oldErrorBannerToCheck ] ); + }, + + waitForSubscriptionsErrorBanner: async ( + errorText, + errorSelector, + oldErrorSelector + ) => { + const errorBannerToCheck = ( async () => { + return page.waitForSelector( errorSelector, { + text: errorText, + } ); + } )(); + + const oldErrorBannerToCheck = ( async () => { + return page.waitForSelector( oldErrorSelector, { + text: errorText, + } ); + } )(); + + await Promise.race( [ errorBannerToCheck, oldErrorBannerToCheck ] ); + }, }; // The generic flows will be moved to their own package soon (more details in p7bje6-2gV-p2), so we're diff --git a/tests/e2e/utils/payments.js b/tests/e2e/utils/payments.js index 3c8084c0811..dc09c6d11f0 100644 --- a/tests/e2e/utils/payments.js +++ b/tests/e2e/utils/payments.js @@ -44,36 +44,6 @@ export async function fillCardDetails( page, card ) { if ( zip !== null ) { await zip.type( '90210', { delay: 20 } ); } - } else { - await page.waitForSelector( '.__PrivateStripeElement' ); - const frameHandle = await page.waitForSelector( - '#payment #wcpay-card-element iframe[name^="__privateStripeFrame"]' - ); - const stripeFrame = await frameHandle.contentFrame(); - - const cardNumberInput = await stripeFrame.waitForSelector( - '[name="cardnumber"]', - { timeout: 30000 } - ); - await cardNumberInput.type( card.number, { delay: 20 } ); - await page.waitFor( 1000 ); - - const cardDateInput = await stripeFrame.waitForSelector( - '[name="exp-date"]', - { timeout: 30000 } - ); - - await cardDateInput.type( card.expires.month + card.expires.year, { - delay: 20, - } ); - await page.waitFor( 1000 ); - - const cardCvcInput = await stripeFrame.waitForSelector( - '[name="cvc"]', - { timeout: 30000 } - ); - await cardCvcInput.type( card.cvc, { delay: 20 } ); - await page.waitFor( 1000 ); } } @@ -298,26 +268,31 @@ export async function setupCheckout( billingDetails ) { } /** - * Selects the Giropay payment method on the checkout page. + * Selects the payment method on the checkout page. * + * @param {*} paymentMethod The payment method to select. * @param {*} page The page reference object. */ -export async function selectGiropayOnCheckout( page ) { - await page.$( '#payment .payment_method_woocommerce_payments_giropay' ); - const giropayRadioLabel = await page.waitForSelector( - '#payment .payment_method_woocommerce_payments_giropay label' +export async function selectOnCheckout( paymentMethod, page ) { + await page.$( + '#payment .payment_method_woocommerce_payments_' + paymentMethod + ); + const radioLabel = await page.waitForSelector( + '#payment .payment_method_woocommerce_payments_' + + paymentMethod + + ' label' ); - giropayRadioLabel.click(); + radioLabel.click(); await page.waitFor( 1000 ); } /** - * Authorizes or fails a Giropay payment. + * Authorizes or fails a redirected payment. * * @param {*} page The page reference object. * @param {string} action Either of 'success' or 'failure'. */ -export async function completeGiropayPayment( page, action ) { +export async function completeRedirectedPayment( page, action ) { await page.$( '.actions .common-ButtonGroup' ); const actionButton = await page.waitForSelector( `.actions .common-ButtonGroup a[name=${ action }]` diff --git a/tests/js/jest-test-file-setup.js b/tests/js/jest-test-file-setup.js index 5956c6c748e..c4d9395a9a1 100644 --- a/tests/js/jest-test-file-setup.js +++ b/tests/js/jest-test-file-setup.js @@ -89,6 +89,11 @@ global.wcSettings = { CA: 'Canada', UK: 'United Kingdom', }, + storePages: { + checkout: { + permalink: 'http://localhost/', + }, + }, }; global.wpApiSettings = { diff --git a/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php b/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php index ff5971fdc49..2774d3e1b04 100644 --- a/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php +++ b/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WooCommerce\Payments\Tasks\WC_Payments_Task_Disputes; /** @@ -45,7 +46,7 @@ public function test_disputes_task_with_single_dispute_outside_7days() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+9 days' ) ), @@ -72,7 +73,7 @@ public function test_disputes_task_with_single_dispute_within_7days() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+6 days' ) ), @@ -104,7 +105,7 @@ public function test_disputes_task_with_single_dispute_within_24h() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+23 hours' ) ), @@ -136,7 +137,7 @@ public function test_disputes_task_with_multiple_disputes_within_7days() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+6 days' ) ), @@ -153,7 +154,7 @@ public function test_disputes_task_with_multiple_disputes_within_7days() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'warning_needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+3 days' ) ), @@ -183,7 +184,7 @@ public function test_disputes_task_with_multiple_disputes_within_24h() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+23 hours' ) ), @@ -200,7 +201,7 @@ public function test_disputes_task_with_multiple_disputes_within_24h() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'warning_needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+23 hours' ) ), @@ -217,7 +218,7 @@ public function test_disputes_task_with_multiple_disputes_within_24h() { 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'warning_needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+9 days' ) ), @@ -247,7 +248,7 @@ public function test_disputes_task_with_multiple_disputes_within_7days_multicurr 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+6 days' ) ), @@ -264,7 +265,7 @@ public function test_disputes_task_with_multiple_disputes_within_7days_multicurr 'order_number' => 14, 'customer_name' => 'customer', 'customer_email' => 'email@email.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'status' => 'warning_needs_response', 'created' => gmdate( 'Y-m-d H:i:s', strtotime( '-14 days' ) ), 'due_by' => gmdate( 'Y-m-d H:i:s', strtotime( '+3 days' ) ), diff --git a/tests/unit/admin/test-class-wc-payments-admin.php b/tests/unit/admin/test-class-wc-payments-admin.php index 65b75d854fe..1f828d877b8 100644 --- a/tests/unit/admin/test-class-wc-payments-admin.php +++ b/tests/unit/admin/test-class-wc-payments-admin.php @@ -134,19 +134,13 @@ public function tear_down() { parent::tear_down(); } - /** - * @dataProvider feature_flag_combinations_not_causing_settings_badge_render_provider - * - * @param bool $is_upe_settings_preview_enabled - * @param bool $is_upe_enabled - */ - public function test_it_does_not_render_settings_badge( $is_upe_settings_preview_enabled, $is_upe_enabled ) { + public function test_it_does_not_render_settings_badge(): void { global $submenu; $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); @@ -161,7 +155,7 @@ public function test_it_does_not_render_payments_badge_if_stripe_is_connected() $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); @@ -180,7 +174,7 @@ public function test_it_refreshes_the_cache_if_get_param_exists() { ]; // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->mock_account->expects( $this->once() )->method( 'refresh_account_data' ); $this->payments_admin->add_payments_menu(); @@ -195,7 +189,7 @@ public function test_it_renders_payments_badge_if_activation_date_is_older_than_ $this->mock_current_user_is_admin(); // Make sure we render the menu without submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( false ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( false ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( false ); update_option( 'wcpay_activation_timestamp', time() - ( 3 * DAY_IN_SECONDS ) ); $this->payments_admin->add_payments_menu(); @@ -210,7 +204,7 @@ public function test_it_does_not_render_payments_badge_if_activation_date_is_les $this->mock_current_user_is_admin(); // Make sure we render the menu without submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( false ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( false ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( false ); update_option( 'wcpay_menu_badge_hidden', 'no' ); update_option( 'wcpay_activation_timestamp', time() - ( DAY_IN_SECONDS * 2 ) ); @@ -221,15 +215,6 @@ public function test_it_does_not_render_payments_badge_if_activation_date_is_les $this->assertArrayNotHasKey( 'wc-admin&path=/payments/overview', $item_names_by_urls ); } - public function feature_flag_combinations_not_causing_settings_badge_render_provider() { - return [ - [ false, false ], - [ false, true ], - [ true, false ], - [ true, true ], - ]; - } - private function mock_current_user_is_admin() { $admin_user = self::factory()->user->create( [ 'role' => 'administrator' ] ); wp_set_current_user( $admin_user ); @@ -514,7 +499,7 @@ public function test_disputes_notification_badge_display() { $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); @@ -556,7 +541,7 @@ public function test_disputes_notification_badge_no_display() { $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); @@ -600,7 +585,7 @@ public function test_transactions_notification_badge_display() { $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); @@ -646,7 +631,7 @@ public function test_transactions_notification_badge_no_display() { $this->mock_current_user_is_admin(); // Make sure we render the menu with submenu items. - $this->mock_account->method( 'is_account_fully_onboarded' )->willReturn( true ); + $this->mock_account->method( 'is_details_submitted' )->willReturn( true ); $this->mock_account->method( 'is_stripe_connected' )->willReturn( true ); $this->payments_admin->add_payments_menu(); diff --git a/tests/unit/admin/test-class-wc-rest-payments-accounts-controller.php b/tests/unit/admin/test-class-wc-rest-payments-accounts-controller.php index 7b5886e6eed..42560d5da90 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-accounts-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-accounts-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Core\Server\Request\Get_Account; use WCPay\Core\Server\Response; use WCPay\Exceptions\API_Exception; @@ -76,7 +77,7 @@ public function test_get_account_data_with_connected_account() { new Response( [ 'is_live' => true, - 'country' => 'DE', + 'country' => Country_Code::GERMANY, 'status' => 'complete', 'store_currencies' => [ 'default' => 'EUR' ], ] @@ -89,7 +90,7 @@ public function test_get_account_data_with_connected_account() { $this->assertSame( 200, $response->status ); $this->assertTrue( $response_data['test_mode'] ); $this->assertSame( 'complete', $response_data['status'] ); - $this->assertSame( 'DE', $response_data['country'] ); + $this->assertSame( Country_Code::GERMANY, $response_data['country'] ); $this->assertSame( 'EUR', $response_data['store_currencies']['default'] ); } @@ -114,7 +115,7 @@ public function test_get_account_data_without_connected_account_and_enabled_onbo $this->assertTrue( $response_data['test_mode'] ); $this->assertSame( 'NOACCOUNT', $response_data['status'] ); // The default country and currency have changed in WC 5.3, hence multiple options in assertions. - $this->assertContains( $response_data['country'], [ 'US', 'GB' ] ); + $this->assertContains( $response_data['country'], [ Country_Code::UNITED_STATES, Country_Code::UNITED_KINGDOM ] ); $this->assertContains( $response_data['store_currencies']['default'], [ 'USD', 'GBP' ] ); } @@ -138,7 +139,7 @@ public function test_get_account_data_without_connected_account_and_disabled_onb $this->assertTrue( $response_data['test_mode'] ); $this->assertSame( 'ONBOARDING_DISABLED', $response_data['status'] ); // The default country and currency have changed in WC 5.3, hence multiple options in assertions. - $this->assertContains( $response_data['country'], [ 'US', 'GB' ] ); + $this->assertContains( $response_data['country'], [ Country_Code::UNITED_STATES, Country_Code::UNITED_KINGDOM ] ); $this->assertContains( $response_data['store_currencies']['default'], [ 'USD', 'GBP' ] ); } diff --git a/tests/unit/admin/test-class-wc-rest-payments-customer-controller.php b/tests/unit/admin/test-class-wc-rest-payments-customer-controller.php index 11b9eb84669..0ab08a25597 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-customer-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-customer-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; /** * WC_REST_Payments_Customer_Controller_Test unit tests. @@ -55,7 +56,7 @@ public function test_get_customer_payment_methods_endpoint_will_return_correct_r 'address_postal_code_check' => 'unchecked', 'cvc_check' => 'pass', ], - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'exp_month' => 11, 'exp_year' => 2030, 'fingerprint' => 'RSTUvWXZa1b2c3Y4', @@ -105,7 +106,7 @@ private function get_base_payment_method_data() { 'billing_details' => [ 'address' => [ 'city' => 'Los Angeles', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'line1' => '123 Sunset Blvd', 'line2' => 'Apt 456', 'postal_code' => '90028', diff --git a/tests/unit/admin/test-class-wc-rest-payments-onboarding-controller.php b/tests/unit/admin/test-class-wc-rest-payments-onboarding-controller.php index 7fd44cfa063..ee220f5b43c 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-onboarding-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-onboarding-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; /** * WC_REST_Payments_Onboarding_Controller unit tests. @@ -105,7 +106,7 @@ public function test_get_required_verification_information() { $request = new WP_REST_Request( 'GET' ); $request->set_url_params( [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'type' => 'company', 'structure' => 'sole_proprietor', ] @@ -131,7 +132,7 @@ public function test_get_progressive_onboarding_eligible() { $request->set_body_params( [ 'business' => [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'type' => 'company', 'mcc' => 'most_popular__software_services', ], @@ -170,7 +171,7 @@ public function test_get_progressive_onboarding_not_eligible() { [ 'business' => [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'type' => 'company', 'mcc' => 'most_popular__software_services', ], diff --git a/tests/unit/admin/test-class-wc-rest-payments-orders-controller.php b/tests/unit/admin/test-class-wc-rest-payments-orders-controller.php index 3a76bcba3ea..3d44fc9bfa5 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-orders-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-orders-controller.php @@ -114,12 +114,7 @@ public function test_capture_terminal_payment_success() { ->method( 'attach_intent_info_to_order' ) ->with( $this->isInstanceOf( WC_Order::class ), - $this->mock_intent_id, - Intent_Status::REQUIRES_CAPTURE, - 'pm_mock', - 'cus_mock', - $this->mock_charge_id, - 'USD' + $mock_intent, ); $request = new WP_REST_Request( 'POST' ); @@ -171,12 +166,7 @@ public function test_capture_terminal_payment_succeeded_intent() { ->method( 'attach_intent_info_to_order' ) ->with( $this->isInstanceOf( WC_Order::class ), - $this->mock_intent_id, - Intent_Status::SUCCEEDED, - 'pm_mock', - 'cus_mock', - $this->mock_charge_id, - 'USD' + $mock_intent, ); $this->mock_gateway @@ -236,12 +226,7 @@ public function test_capture_terminal_payment_completed_order() { ->method( 'attach_intent_info_to_order' ) ->with( $this->isInstanceOf( WC_Order::class ), - $this->mock_intent_id, - Intent_Status::SUCCEEDED, - 'pm_mock', - 'cus_mock', - $this->mock_charge_id, - 'USD' + $mock_intent, ); $this->mock_gateway diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index 8c1628a9f91..0f8714c6b6b 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -8,6 +8,7 @@ use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\RestApi; use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Constants\Payment_Method; use WCPay\Database_Cache; use WCPay\Duplicate_Payment_Prevention_Service; @@ -741,7 +742,7 @@ public function account_business_support_address_validation_provider() { [ [ 'city' => 'test city', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], $request, 'account_business_support_address', diff --git a/tests/unit/contants/test-class-country-code.php b/tests/unit/contants/test-class-country-code.php new file mode 100644 index 00000000000..bf3f67a2ecf --- /dev/null +++ b/tests/unit/contants/test-class-country-code.php @@ -0,0 +1,67 @@ +assertEquals( 'AU', Country_Code::AUSTRALIA ); + $this->assertEquals( 'AT', Country_Code::AUSTRIA ); + $this->assertEquals( 'BE', Country_Code::BELGIUM ); + $this->assertEquals( 'BR', Country_Code::BRAZIL ); + $this->assertEquals( 'BG', Country_Code::BULGARIA ); + $this->assertEquals( 'CA', Country_Code::CANADA ); + $this->assertEquals( 'HR', Country_Code::CROATIA ); + $this->assertEquals( 'CY', Country_Code::CYPRUS ); + $this->assertEquals( 'CZ', Country_Code::CZECHIA ); + $this->assertEquals( 'DK', Country_Code::DENMARK ); + $this->assertEquals( 'EE', Country_Code::ESTONIA ); + $this->assertEquals( 'FI', Country_Code::FINLAND ); + $this->assertEquals( 'FR', Country_Code::FRANCE ); + $this->assertEquals( 'DE', Country_Code::GERMANY ); + $this->assertEquals( 'GH', Country_Code::GHANA ); + $this->assertEquals( 'GI', Country_Code::GIBRALTAR ); + $this->assertEquals( 'GR', Country_Code::GREECE ); + $this->assertEquals( 'HK', Country_Code::HONG_KONG ); + $this->assertEquals( 'HU', Country_Code::HUNGARY ); + $this->assertEquals( 'IN', Country_Code::INDIA ); + $this->assertEquals( 'ID', Country_Code::INDONESIA ); + $this->assertEquals( 'IE', Country_Code::IRELAND ); + $this->assertEquals( 'IT', Country_Code::ITALY ); + $this->assertEquals( 'JP', Country_Code::JAPAN ); + $this->assertEquals( 'KE', Country_Code::KENYA ); + $this->assertEquals( 'LV', Country_Code::LATVIA ); + $this->assertEquals( 'LI', Country_Code::LIECHTENSTEIN ); + $this->assertEquals( 'LT', Country_Code::LITHUANIA ); + $this->assertEquals( 'LU', Country_Code::LUXEMBOURG ); + $this->assertEquals( 'MY', Country_Code::MALAYSIA ); + $this->assertEquals( 'MT', Country_Code::MALTA ); + $this->assertEquals( 'MX', Country_Code::MEXICO ); + $this->assertEquals( 'NL', Country_Code::NETHERLANDS ); + $this->assertEquals( 'NZ', Country_Code::NEW_ZEALAND ); + $this->assertEquals( 'NG', Country_Code::NIGERIA ); + $this->assertEquals( 'NO', Country_Code::NORWAY ); + $this->assertEquals( 'PL', Country_Code::POLAND ); + $this->assertEquals( 'PT', Country_Code::PORTUGAL ); + $this->assertEquals( 'RO', Country_Code::ROMANIA ); + $this->assertEquals( 'SG', Country_Code::SINGAPORE ); + $this->assertEquals( 'SK', Country_Code::SLOVAKIA ); + $this->assertEquals( 'SI', Country_Code::SLOVENIA ); + $this->assertEquals( 'ZA', Country_Code::SOUTH_AFRICA ); + $this->assertEquals( 'ES', Country_Code::SPAIN ); + $this->assertEquals( 'SE', Country_Code::SWEDEN ); + $this->assertEquals( 'CH', Country_Code::SWITZERLAND ); + $this->assertEquals( 'TH', Country_Code::THAILAND ); + $this->assertEquals( 'AE', Country_Code::UNITED_ARAB_EMIRATES ); + $this->assertEquals( 'GB', Country_Code::UNITED_KINGDOM ); + $this->assertEquals( 'US', Country_Code::UNITED_STATES ); + } +} diff --git a/tests/unit/core/server/request/test-class-list-transactions-request.php b/tests/unit/core/server/request/test-class-list-transactions-request.php index 0d932deafcf..899e87ea4df 100644 --- a/tests/unit/core/server/request/test-class-list-transactions-request.php +++ b/tests/unit/core/server/request/test-class-list-transactions-request.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception; use WCPay\Core\Server\Request\List_Transactions; @@ -70,8 +71,8 @@ public function test_list_transactions_request_will_be_date() { $device_is_not = 'android'; $channel = 'online'; $channel_is_not = 'in_person'; - $country = 'US'; - $country_is_not = 'CA'; + $country = Country_Code::UNITED_STATES; + $country_is_not = Country_Code::CANADA; $risk_level = '0'; $risk_level_is_not = '1'; $search = [ 'search' ]; @@ -151,8 +152,8 @@ public function test_list_transactions_request_will_be_date_using_from_rest_requ $device_is_not = 'android'; $channel = 'online'; $channel_is_not = 'in_person'; - $country = 'US'; - $country_is_not = 'CA'; + $country = Country_Code::UNITED_STATES; + $country_is_not = Country_Code::CANADA; $risk_level = '0'; $risk_level_is_not = '1'; $search = [ 'search' ]; diff --git a/tests/unit/core/service/test-class-wc-payments-customer-service-api.php b/tests/unit/core/service/test-class-wc-payments-customer-service-api.php index a1b8847a41e..298549c6873 100644 --- a/tests/unit/core/service/test-class-wc-payments-customer-service-api.php +++ b/tests/unit/core/service/test-class-wc-payments-customer-service-api.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Database_Cache; use WCPay\Exceptions\API_Exception; use WCPay\Core\WC_Payments_Customer_Service_API; @@ -474,7 +475,7 @@ private function get_mock_customer_data() { 'postal_code' => '09876', 'city' => 'City', 'state' => 'State', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'shipping' => [ 'name' => 'Shipping Ship', @@ -484,7 +485,7 @@ private function get_mock_customer_data() { 'postal_code' => '76543', 'city' => 'City2', 'state' => 'State2', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], ], ]; diff --git a/tests/unit/core/test-class-mode.php b/tests/unit/core/test-class-mode.php index 0fac6a3f300..668b13c5e14 100644 --- a/tests/unit/core/test-class-mode.php +++ b/tests/unit/core/test-class-mode.php @@ -28,11 +28,7 @@ class Core_Mode_Test extends WCPAY_UnitTestCase { public function setUp() : void { parent::setUp(); - $this->mock_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class ); - $this->mock_gateway->settings = [ 'empty' => false ]; - $this->mode = $this->getMockBuilder( Mode::class ) - ->setConstructorArgs( [ $this->mock_gateway ] ) ->setMethods( [ 'is_wcpay_dev_mode_defined', 'get_wp_environment_type' ] ) ->getMock(); } @@ -44,17 +40,8 @@ public function tearDown() : void { parent::tearDown(); } - public function test_throw_exception_if_uninitialized() { - $this->mock_gateway->settings = []; - $this->expectException( Exception::class ); - $this->mode->is_live(); - } - public function test_init_defaults_to_live_mode() { - $this->mock_gateway->expects( $this->once() ) - ->method( 'get_option' ) - ->with( 'test_mode' ) - ->willReturn( 'no' ); + update_option( 'woocommerce_woocommerce_payments_settings', [ 'test_mode' => 'no' ] ); $this->assertTrue( $this->mode->is_live() ); } @@ -76,10 +63,7 @@ public function test_init_enters_dev_mode_through_filter() { } public function test_init_enters_test_mode_with_gateway_test_mode_settings() { - $this->mock_gateway->expects( $this->once() ) - ->method( 'get_option' ) - ->with( 'test_mode' ) - ->willReturn( 'yes' ); + update_option( 'woocommerce_woocommerce_payments_settings', [ 'test_mode' => 'yes' ] ); // Reset and check. $this->assertFalse( $this->mode->is_dev() ); diff --git a/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php b/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php index 271acc2bb9a..327187167e4 100644 --- a/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php +++ b/tests/unit/fraud-prevention/test-class-buyer-fingerprinting-service.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\Fraud_Prevention\Buyer_Fingerprinting_Service; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; @@ -45,7 +46,7 @@ public function test_it_hashes_using_sha512() { public function test_it_hashes_order_info() { $fingerprint = 'abc123'; - $ip_country = 'GB'; + $ip_country = Country_Code::UNITED_KINGDOM; add_filter( 'woocommerce_geolocate_ip', function() use ( $ip_country ) { diff --git a/tests/unit/fraud-prevention/test-class-fraud-risk-tools.php b/tests/unit/fraud-prevention/test-class-fraud-risk-tools.php index 7af9c4b76a1..249e56badd2 100644 --- a/tests/unit/fraud-prevention/test-class-fraud-risk-tools.php +++ b/tests/unit/fraud-prevention/test-class-fraud-risk-tools.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\Fraud_Prevention\Fraud_Risk_Tools; /** @@ -331,7 +332,7 @@ public function test_it_gets_high_protection_empty_allowed_countries_settings() public function test_it_gets_the_correct_for_specific_allowed_selling_locations_type() { update_option( 'woocommerce_allowed_countries', 'specific' ); - update_option( 'woocommerce_specific_allowed_countries', [ 'US', 'CA' ] ); + update_option( 'woocommerce_specific_allowed_countries', [ Country_Code::UNITED_STATES, Country_Code::CANADA ] ); $settings = $this->fraud_risk_tools->get_standard_protection_settings(); @@ -340,7 +341,7 @@ public function test_it_gets_the_correct_for_specific_allowed_selling_locations_ public function test_it_gets_the_correct_for_all_except_selling_locations_type() { update_option( 'woocommerce_allowed_countries', 'all_except' ); - update_option( 'woocommerce_all_except_countries', [ 'US', 'CA' ] ); + update_option( 'woocommerce_all_except_countries', [ Country_Code::UNITED_STATES, Country_Code::CANADA ] ); $settings = $this->fraud_risk_tools->get_standard_protection_settings(); diff --git a/tests/unit/helpers/class-wc-helper-intention.php b/tests/unit/helpers/class-wc-helper-intention.php index 06130bb3b7e..3879db1f32d 100644 --- a/tests/unit/helpers/class-wc-helper-intention.php +++ b/tests/unit/helpers/class-wc-helper-intention.php @@ -38,7 +38,15 @@ public static function create_charge( $data = [] ) { 'amount_captured' => 5000, 'amount_refunded' => 0, 'application_fee_amount' => 0, - 'balance_transaction' => 'txn_mock', + 'balance_transaction' => [ + 'id' => 'txn_mock', + 'amount' => 5000, + 'available_on' => 1703808000, + 'created' => new DateTime( '2022-05-20 19:05:38' ), + 'currency' => 'usd', + 'exchange_rate' => null, + 'fee' => 82, + ], 'billing_details' => [], 'currency' => 'usd', 'dispute' => [], diff --git a/tests/unit/helpers/class-wc-helper-order.php b/tests/unit/helpers/class-wc-helper-order.php index 93499fb901f..f7f02e23d74 100644 --- a/tests/unit/helpers/class-wc-helper-order.php +++ b/tests/unit/helpers/class-wc-helper-order.php @@ -5,6 +5,7 @@ * @package WooCommerce/Tests */ +use WCPay\Constants\Country_Code; use WCPay\Constants\Order_Status; /** @@ -96,7 +97,7 @@ public static function create_order( $customer_id = 1, $total = 50, $product = n $order->set_billing_city( 'WooCity' ); $order->set_billing_state( 'NY' ); $order->set_billing_postcode( '12345' ); - $order->set_billing_country( 'US' ); + $order->set_billing_country( Country_Code::UNITED_STATES ); $order->set_billing_email( 'admin@example.org' ); $order->set_billing_phone( '555-32123' ); diff --git a/tests/unit/migrations/test-class-update-service-data-from-server.php b/tests/unit/migrations/test-class-update-service-data-from-server.php index 998fcb39dc3..b7a7ca0c7f4 100644 --- a/tests/unit/migrations/test-class-update-service-data-from-server.php +++ b/tests/unit/migrations/test-class-update-service-data-from-server.php @@ -8,6 +8,7 @@ namespace WCPay\Migrations; use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPAY_UnitTestCase; /** @@ -74,7 +75,7 @@ public function test_does_nothing_if_account_data_contains_giropay_fees() { 'discount' => [], ], ], - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ] ) ); @@ -106,7 +107,7 @@ public function test_updates_service_data_if_account_data_does_not_contain_girop 'discount' => [], ], ], - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ] ) ); diff --git a/tests/unit/multi-currency/test-class-country-flags.php b/tests/unit/multi-currency/test-class-country-flags.php index cd3adff8feb..0ebe63fb8ef 100644 --- a/tests/unit/multi-currency/test-class-country-flags.php +++ b/tests/unit/multi-currency/test-class-country-flags.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\CountryFlags; /** @@ -12,7 +13,7 @@ */ class Country_Flags_Test extends WCPAY_UnitTestCase { public function test_get_by_country_returns_emoji_flag() { - $this->assertEquals( CountryFlags::get_by_country( 'US' ), '🇺🇸' ); + $this->assertEquals( CountryFlags::get_by_country( Country_Code::UNITED_STATES ), '🇺🇸' ); } public function test_get_by_country_returns_empty_string() { diff --git a/tests/unit/multi-currency/test-class-frontend-prices.php b/tests/unit/multi-currency/test-class-frontend-prices.php index db34cc1b40d..8c76d8a8ec4 100644 --- a/tests/unit/multi-currency/test-class-frontend-prices.php +++ b/tests/unit/multi-currency/test-class-frontend-prices.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\FrontendPrices unit tests. */ @@ -206,7 +208,7 @@ function() { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; @@ -250,7 +252,7 @@ function() { ); WC()->session->init(); - WC()->customer->set_location( 'US', 'CA' ); + WC()->customer->set_location( Country_Code::UNITED_STATES, 'CA' ); $shipping_method = new \WC_Shipping_Flat_Rate(); $shipping_method->tax_status = 'taxable'; diff --git a/tests/unit/multi-currency/test-class-geolocation.php b/tests/unit/multi-currency/test-class-geolocation.php index 0c663f17fcf..386787f6a3c 100644 --- a/tests/unit/multi-currency/test-class-geolocation.php +++ b/tests/unit/multi-currency/test-class-geolocation.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WCPay\MultiCurrency\Geolocation unit tests. */ @@ -37,10 +39,10 @@ public function test_get_country_by_customer_location_returns_geolocation_countr add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); - $this->assertSame( 'CA', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::CANADA, $this->geolocation->get_country_by_customer_location() ); } public function test_get_country_by_customer_location_returns_default_country_when_no_geolocation() { @@ -54,20 +56,20 @@ function() { add_filter( 'woocommerce_customer_default_location', function() { - return 'BR'; + return Country_Code::BRAZIL; } ); - $this->assertSame( 'BR', $this->geolocation->get_country_by_customer_location() ); + $this->assertSame( Country_Code::BRAZIL, $this->geolocation->get_country_by_customer_location() ); } public function test_get_currency_by_customer_location_returns_geolocation_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); @@ -75,7 +77,7 @@ function() { } public function test_get_currency_by_customer_location_returns_default_currency_code() { - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'BR' )->willReturn( [ 'currency_code' => 'BRL' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::BRAZIL )->willReturn( [ 'currency_code' => 'BRL' ] ); add_filter( 'woocommerce_geolocate_ip', @@ -86,7 +88,7 @@ function() { add_filter( 'woocommerce_customer_default_location', function() { - return 'BR'; + return Country_Code::BRAZIL; } ); diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index e5a360e2f5a..a6964e951ad 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\MultiCurrency\Utils; use WCPay\Database_Cache; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyException; @@ -451,11 +452,11 @@ function() { public function test_update_selected_currency_by_geolocation_does_not_set_session_cookie() { update_option( 'wcpay_multi_currency_enable_auto_currency', 'yes' ); - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); @@ -472,11 +473,11 @@ public function test_update_selected_currency_by_geolocation_updates_session_whe add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); $this->multi_currency->update_selected_currency_by_geolocation(); @@ -489,11 +490,11 @@ public function test_update_selected_currency_by_geolocation_displays_notice() { add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); $this->multi_currency->update_selected_currency_by_geolocation(); @@ -512,14 +513,14 @@ public function test_update_selected_currency_by_geolocation_does_not_update_if_ add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); // Arrange: Set the expected calls and retruns for our mock classes. $this->mock_localization_service ->method( 'get_country_locale_data' ) - ->with( 'CA' ) + ->with( Country_Code::CANADA ) ->willReturn( [ 'currency_code' => 'CAD' ] ); $this->mock_utils @@ -542,11 +543,11 @@ public function test_display_geolocation_currency_update_notice() { add_filter( 'woocommerce_geolocate_ip', function() { - return 'CA'; + return Country_Code::CANADA; } ); - $this->mock_localization_service->method( 'get_country_locale_data' )->with( 'CA' )->willReturn( [ 'currency_code' => 'CAD' ] ); + $this->mock_localization_service->method( 'get_country_locale_data' )->with( Country_Code::CANADA )->willReturn( [ 'currency_code' => 'CAD' ] ); $this->multi_currency->display_geolocation_currency_update_notice(); @@ -554,11 +555,11 @@ function() { } public function test_display_geolocation_currency_update_notice_does_not_display_if_using_default_currency() { - WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, 'US' ); + WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, Country_Code::UNITED_STATES ); add_filter( 'woocommerce_geolocate_ip', function() { - return 'US'; + return Country_Code::UNITED_STATES; } ); @@ -572,7 +573,7 @@ public function test_display_geolocation_currency_update_notice_does_not_display add_filter( 'woocommerce_geolocate_ip', function() { - return 'US'; + return Country_Code::UNITED_STATES; } ); diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php deleted file mode 100644 index 7c013e2b858..00000000000 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ /dev/null @@ -1,1026 +0,0 @@ - 'success', - 'payment_needed' => true, - 'redirect' => 'testURL/key=mock_order_key', - ]; - - /** - * WC_Payments_Localization_Service instance. - * - * @var WC_Payments_Localization_Service - */ - private $mock_localization_service; - - /** - * Mock Fraud Service. - * - * @var WC_Payments_Fraud_Service|MockObject; - */ - private $mock_fraud_service; - - /** - * Pre-test setup - */ - public function set_up() { - parent::set_up(); - - // Arrange: Mock WC_Payments_API_Client so we can configure the - // return value of create_and_confirm_intention(). - // Note that we cannot use createStub here since it's not defined in PHPUnit 6.5. - $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get_payment_method', - 'is_server_connected', - 'get_timeline', - ] - ) - ->getMock(); - - $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' ); - $this->mock_wcpay_account->method( 'get_account_default_currency' )->willReturn( 'USD' ); - - // Mock the main class's cache service. - $this->_cache = WC_Payments::get_database_cache(); - $this->mock_cache = $this->createMock( Database_Cache::class ); - WC_Payments::set_database_cache( $this->mock_cache ); - - $payment_methods = [ - 'link' => [ - 'base' => 0.1, - ], - ]; - - $this->mock_wcpay_account - ->expects( $this->any() ) - ->method( 'get_fees' ) - ->willReturn( $payment_methods ); - - $this->mock_woopay_utilities = $this->createMock( WooPay_Utilities::class ); - - // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. - $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) - ->disableOriginalConstructor() - ->getMock(); - - // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. - $this->mock_token_service = $this->getMockBuilder( 'WC_Payments_Token_Service' ) - ->disableOriginalConstructor() - ->onlyMethods( [ 'add_payment_method_to_user' ] ) - ->getMock(); - - // Arrange: Mock WC_Payments_Action_Scheduler_Service so its methods aren't called directly. - $this->mock_action_scheduler_service = $this->getMockBuilder( 'WC_Payments_Action_Scheduler_Service' ) - ->disableOriginalConstructor() - ->getMock(); - - $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); - - $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); - $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); - - $this->mock_payment_methods = []; - $payment_method_classes = [ - CC_Payment_Method::class, - Giropay_Payment_Method::class, - Sofort_Payment_Method::class, - Bancontact_Payment_Method::class, - EPS_Payment_Method::class, - P24_Payment_Method::class, - Ideal_Payment_Method::class, - Sepa_Payment_Method::class, - Becs_Payment_Method::class, - Link_Payment_Method::class, - Affirm_Payment_Method::class, - Afterpay_Payment_Method::class, - ]; - - $this->mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); - foreach ( $payment_method_classes as $payment_method_class ) { - $mock_payment_method = $this->getMockBuilder( $payment_method_class ) - ->setConstructorArgs( [ $this->mock_token_service ] ) - ->onlyMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) - ->getMock(); - $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; - } - - $this->mock_order_service = $this->getMockBuilder( WC_Payments_Order_Service::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - ] - ) - ->onlyMethods( - [ - 'get_payment_method_id_for_order', - ] - ) - ->getMock(); - - $this->mock_payment_method = $this->getMockBuilder( $payment_method_class ) - ->setConstructorArgs( [ $this->mock_token_service ] ) - ->onlyMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) - ->getMock(); - $this->mock_payment_methods[ $this->mock_payment_method->get_id() ] = $this->mock_payment_method; - - // Arrange: Mock WC_Payment_Gateway_WCPay so that some of its methods can be - // mocked, and their return values can be used for testing. - $this->mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_method, - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->mock_order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->setMethods( - [ - 'get_return_url', - 'manage_customer_details_for_order', - 'parent_process_payment', - 'get_upe_enabled_payment_method_statuses', - 'is_payment_recurring', - ] - ) - ->getMock(); - - // Arrange: Set the return value of get_return_url() so it can be used in a test later. - $this->mock_gateway - ->expects( $this->any() ) - ->method( 'get_return_url' ) - ->will( - $this->returnValue( $this->return_url ) - ); - $this->mock_gateway - ->expects( $this->any() ) - ->method( 'parent_process_payment' ) - ->will( - $this->returnValue( $this->mock_payment_result ) - ); - - // Arrange: Define a $_POST array which includes the payment method, - // so that get_payment_method_from_request() does not throw error. - $_POST = [ - 'wcpay-payment-method' => 'pm_mock', - 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, - ]; - - // Mock the level3 service to always return an empty array. - $mock_level3_service = $this->createMock( Level3Service::class ); - $mock_level3_service->expects( $this->any() ) - ->method( 'get_data_from_order' ) - ->willReturn( [] ); - wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service ); - - // Mock the order service to always return an empty array for meta. - $mock_order_service = $this->createMock( OrderService::class ); - $mock_order_service->expects( $this->any() ) - ->method( 'get_payment_metadata' ) - ->willReturn( [] ); - wcpay_get_test_container()->replace( OrderService::class, $mock_order_service ); - } - - /** - * Cleanup after tests. - * - * @return void - */ - public function tear_down() { - parent::tear_down(); - WC_Payments::set_database_cache( $this->_cache ); - wcpay_get_test_container()->reset_all_replacements(); - } - - public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { - $order = WC_Helper_Order::create_order(); - $_POST = $this->setup_saved_payment_method(); - $intent = WC_Helper_Intention::create_intention(); - - $this->mock_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) - ); - $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - - $this->set_cart_contains_subscription_items( false ); - - $result = $this->mock_gateway->process_payment( $order->get_id() ); - - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( $this->return_url, $result['redirect'] ); - } - - public function test_process_payment_returns_correct_redirect_when_using_payment_request() { - $order = WC_Helper_Order::create_order(); - $intent = WC_Helper_Intention::create_intention(); - $_POST['payment_request_type'] = 'google_pay'; - - $this->mock_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) - ); - $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - $this->set_cart_contains_subscription_items( false ); - - $result = $this->mock_gateway->process_payment( $order->get_id() ); - - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( $this->return_url, $result['redirect'] ); - } - - public function is_proper_intent_used_with_order_returns_false() { - $this->assertFalse( $this->mock_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); - } - - public function test_process_redirect_payment_intent_processing() { - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $save_payment_method = false; - $user = wp_get_current_user(); - $intent_status = Intent_Status::PROCESSING; - $intent_metadata = [ 'order_id' => (string) $order_id ]; - $charge_id = 'ch_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'pi_mock'; - $payment_method_id = 'pm_mock'; - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $payment_intent = WC_Helper_Intention::create_intention( - [ - 'status' => $intent_status, - 'metadata' => $intent_metadata, - ] - ); - - $this->mock_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $request = $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->will( $this->returnValue( $payment_intent ) ); - - $this->set_cart_contains_subscription_items( false ); - - $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - $note = wc_get_order_notes( - [ - 'order_id' => $order_id, - 'limit' => 1, - ] - )[0]; - - $this->assertStringContainsString( 'authorized', $note->content ); - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); - } - - public function test_process_redirect_payment_intent_succeded() { - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $save_payment_method = false; - $user = wp_get_current_user(); - $intent_status = Intent_Status::SUCCEEDED; - $intent_metadata = [ 'order_id' => (string) $order_id ]; - $charge_id = 'ch_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'pi_mock'; - $payment_method_id = 'pm_mock'; - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $payment_intent = WC_Helper_Intention::create_intention( - [ - 'status' => $intent_status, - 'metadata' => $intent_metadata, - ] - ); - - $this->mock_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $request = $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->will( $this->returnValue( $payment_intent ) ); - - $this->set_cart_contains_subscription_items( false ); - - $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); - } - - public function test_validate_order_id_received_vs_intent_meta_order_id_throw_exception() { - $order = WC_Helper_Order::create_order(); - $intent_metadata = [ 'order_id' => (string) ( $order->get_id() + 100 ) ]; - - $this->expectException( Process_Payment_Exception::class ); - $this->expectExceptionMessage( "We're not able to process this payment due to the order ID mismatch. Please try again later." ); - - \PHPUnit_Utils::call_method( - $this->mock_gateway, - 'validate_order_id_received_vs_intent_meta_order_id', - [ $order, $intent_metadata ] - ); - } - - public function test_validate_order_id_received_vs_intent_meta_order_id_returning_void() { - $order = WC_Helper_Order::create_order(); - $intent_metadata = [ 'order_id' => (string) ( $order->get_id() ) ]; - - $res = \PHPUnit_Utils::call_method( - $this->mock_gateway, - 'validate_order_id_received_vs_intent_meta_order_id', - [ $order, $intent_metadata ] - ); - - $this->assertSame( null, $res ); - } - - public function test_correct_payment_method_title_for_order() { - $order = WC_Helper_Order::create_order(); - - $visa_credit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'credit', - ], - ]; - $visa_debit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'debit', - ], - ]; - $mastercard_credit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'mastercard', - 'funding' => 'credit', - ], - ]; - $eps_details = [ - 'type' => 'eps', - ]; - $giropay_details = [ - 'type' => 'giropay', - ]; - $p24_details = [ - 'type' => 'p24', - ]; - $sofort_details = [ - 'type' => 'sofort', - ]; - $bancontact_details = [ - 'type' => 'bancontact', - ]; - $sepa_details = [ - 'type' => 'sepa_debit', - ]; - $ideal_details = [ - 'type' => 'ideal', - ]; - $becs_details = [ - 'type' => 'au_becs_debit', - ]; - - $charge_payment_method_details = [ - $visa_credit_details, - $visa_debit_details, - $mastercard_credit_details, - $giropay_details, - $sofort_details, - $bancontact_details, - $eps_details, - $p24_details, - $ideal_details, - $sepa_details, - $becs_details, - ]; - - $expected_payment_method_titles = [ - 'Visa credit card', - 'Visa debit card', - 'Mastercard credit card', - 'giropay', - 'Sofort', - 'Bancontact', - 'EPS', - 'Przelewy24 (P24)', - 'iDEAL', - 'SEPA Direct Debit', - 'BECS Direct Debit', - ]; - - foreach ( $charge_payment_method_details as $i => $payment_method_details ) { - $this->mock_gateway->set_payment_method_title_for_order( $order, $payment_method_details['type'], $payment_method_details ); - $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); - } - } - - public function test_payment_methods_show_correct_default_outputs() { - $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); - $this->mock_token_service->expects( $this->any() ) - ->method( 'add_payment_method_to_user' ) - ->will( - $this->returnValue( $mock_token ) - ); - - $mock_user = 'mock_user'; - $mock_payment_method_id = 'pm_mock'; - - $mock_visa_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'debit', - ], - ]; - $mock_mastercard_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'mastercard', - 'funding' => 'credit', - ], - ]; - $mock_giropay_details = [ - 'type' => 'giropay', - ]; - $mock_p24_details = [ - 'type' => 'p24', - ]; - $mock_sofort_details = [ - 'type' => 'sofort', - ]; - $mock_bancontact_details = [ - 'type' => 'bancontact', - ]; - $mock_eps_details = [ - 'type' => 'eps', - ]; - $mock_sepa_details = [ - 'type' => 'sepa_debit', - ]; - $mock_ideal_details = [ - 'type' => 'ideal', - ]; - $mock_becs_details = [ - 'type' => 'au_becs_debit', - ]; - $mock_affirm_details = [ - 'type' => 'affirm', - ]; - $mock_afterpay_details = [ - 'type' => 'afterpay_clearpay', - ]; - - $this->set_cart_contains_subscription_items( false ); - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $p24_method = $this->mock_payment_methods['p24']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - $affirm_method = $this->mock_payment_methods['affirm']; - $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; - - $this->assertEquals( 'card', $card_method->get_id() ); - $this->assertEquals( 'Credit card / debit card', $card_method->get_title() ); - $this->assertEquals( 'Visa debit card', $card_method->get_title( $mock_visa_details ) ); - $this->assertEquals( 'Mastercard credit card', $card_method->get_title( $mock_mastercard_details ) ); - $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); - $this->assertTrue( $card_method->is_reusable() ); - $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); - - $this->assertEquals( 'giropay', $giropay_method->get_id() ); - $this->assertEquals( 'giropay', $giropay_method->get_title() ); - $this->assertEquals( 'giropay', $giropay_method->get_title( $mock_giropay_details ) ); - $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $giropay_method->is_reusable() ); - - $this->assertEquals( 'p24', $p24_method->get_id() ); - $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title() ); - $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( $mock_p24_details ) ); - $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $p24_method->is_reusable() ); - - $this->assertEquals( 'sofort', $sofort_method->get_id() ); - $this->assertEquals( 'Sofort', $sofort_method->get_title() ); - $this->assertEquals( 'Sofort', $sofort_method->get_title( $mock_sofort_details ) ); - $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sofort_method->is_reusable() ); - - $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); - $this->assertEquals( 'Bancontact', $bancontact_method->get_title() ); - $this->assertEquals( 'Bancontact', $bancontact_method->get_title( $mock_bancontact_details ) ); - $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $bancontact_method->is_reusable() ); - - $this->assertEquals( 'eps', $eps_method->get_id() ); - $this->assertEquals( 'EPS', $eps_method->get_title() ); - $this->assertEquals( 'EPS', $eps_method->get_title( $mock_eps_details ) ); - $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $eps_method->is_reusable() ); - - $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); - $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title() ); - $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( $mock_sepa_details ) ); - $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sepa_method->is_reusable() ); - - $this->assertEquals( 'ideal', $ideal_method->get_id() ); - $this->assertEquals( 'iDEAL', $ideal_method->get_title() ); - $this->assertEquals( 'iDEAL', $ideal_method->get_title( $mock_ideal_details ) ); - $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $ideal_method->is_reusable() ); - - $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); - $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title() ); - $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( $mock_becs_details ) ); - $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $becs_method->is_reusable() ); - - $this->assertSame( 'affirm', $affirm_method->get_id() ); - $this->assertSame( 'Affirm', $affirm_method->get_title() ); - $this->assertSame( 'Affirm', $affirm_method->get_title( $mock_affirm_details ) ); - $this->assertTrue( $affirm_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $affirm_method->is_reusable() ); - - $this->assertSame( 'afterpay_clearpay', $afterpay_method->get_id() ); - $this->assertSame( 'Afterpay', $afterpay_method->get_title() ); - $this->assertSame( 'Afterpay', $afterpay_method->get_title( $mock_afterpay_details ) ); - $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $afterpay_method->is_reusable() ); - } - - public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { - $this->set_cart_contains_subscription_items( true ); - - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $p24_method = $this->mock_payment_methods['p24']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - $affirm_method = $this->mock_payment_methods['affirm']; - $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; - - $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $giropay_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sofort_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $bancontact_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $eps_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sepa_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $p24_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $ideal_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $becs_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $affirm_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $afterpay_method->is_enabled_at_checkout( 'US' ) ); - } - - public function test_only_valid_payment_methods_returned_for_currency() { - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $p24_method = $this->mock_payment_methods['p24']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - $affirm_method = $this->mock_payment_methods['affirm']; - $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; - - WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; - - $account_domestic_currency = 'USD'; - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - // BNPLs can accept only domestic payments. - $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = 'USD'; - - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = 'AUD'; - $this->assertTrue( $becs_method->is_currency_valid( $account_domestic_currency ) ); - - // BNPLs can accept only domestic payments. - WC_Helper_Site_Currency::$mock_site_currency = 'USD'; - $account_domestic_currency = 'CAD'; - $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = ''; - } - - public function test_payment_method_compares_correct_currency() { - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $p24_method = $this->mock_payment_methods['p24']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - $affirm_method = $this->mock_payment_methods['affirm']; - $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; - - WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; - $account_domestic_currency = 'USD'; - - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - - global $wp; - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $wp->query_vars = [ 'order-pay' => strval( $order_id ) ]; - $order->set_currency( 'USD' ); - - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); - - $wp->query_vars = []; - } - - public function test_create_token_from_setup_intent_adds_token() { - $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); - $mock_setup_intent_id = 'si_mock'; - $mock_user = wp_get_current_user(); - - $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $mock_setup_intent_id ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( - WC_Helper_Intention::create_setup_intention( - [ - 'id' => $mock_setup_intent_id, - 'payment_method' => 'pm_mock', - ] - ) - ); - - $this->mock_token_service->expects( $this->once() ) - ->method( 'add_payment_method_to_user' ) - ->with( 'pm_mock', $mock_user ) - ->will( - $this->returnValue( $mock_token ) - ); - - $this->assertEquals( $mock_token, $this->mock_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); - } - - public function test_exception_will_be_thrown_if_phone_number_is_invalid() { - $order = WC_Helper_Order::create_order(); - $order->set_billing_phone( '+1123456789123456789123' ); - $order->save(); - $this->expectException( Exception::class ); - $this->expectExceptionMessage( 'Invalid phone number.' ); - $this->mock_gateway->process_payment( $order->get_id() ); - } - - public function test_remove_link_payment_method_if_card_disabled() { - $this->mock_gateway->settings['upe_enabled_payment_method_ids'] = [ 'link' ]; - - $this->mock_gateway - ->expects( $this->once() ) - ->method( 'get_upe_enabled_payment_method_statuses' ) - ->will( - $this->returnValue( [ 'link_payments' => [ 'status' => 'active' ] ] ) - ); - - $this->assertSame( $this->mock_gateway->get_payment_method_ids_enabled_at_checkout(), [] ); - } - - /** - * @dataProvider available_payment_methods_provider - */ - public function test_get_upe_available_payment_methods( $payment_methods, $expected_result ) { - $mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $mock_wcpay_account - ->expects( $this->any() ) - ->method( 'get_fees' ) - ->willReturn( $payment_methods ); - - $gateway = new WC_Payment_Gateway_WCPay( - $this->mock_api_client, - $mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_method, - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->mock_order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service - ); - - $this->assertEquals( $expected_result, $gateway->get_upe_available_payment_methods() ); - } - - public function available_payment_methods_provider() { - return [ - 'card only' => [ - [ 'card' => [ 'base' => 0.1 ] ], - [ 'card' ], - ], - 'no match with fees' => [ - [ 'some_other_payment_method' => [ 'base' => 0.1 ] ], - [], - ], - 'multiple matches with fees' => [ - [ - 'card' => [ 'base' => 0.1 ], - 'bancontact' => [ 'base' => 0.2 ], - ], - [ 'card', 'bancontact' ], - ], - 'no fees no methods' => [ - [], - [], - ], - ]; - } - - /** - * Helper function to mock subscriptions for internal UPE payment methods. - */ - private function set_cart_contains_subscription_items( $cart_contains_subscriptions ) { - foreach ( $this->mock_payment_methods as $mock_payment_method ) { - $mock_payment_method->expects( $this->any() ) - ->method( 'is_subscription_item_in_cart' ) - ->will( - $this->returnValue( $cart_contains_subscriptions ) - ); - } - } - - private function setup_saved_payment_method() { - $token = WC_Helper_Token::create_token( 'pm_mock' ); - - return [ - 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, - 'wc-' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' => (string) $token->get_id(), - ]; - } - - private function set_get_upe_enabled_payment_method_statuses_return_value( $return_value = null ) { - if ( null === $return_value ) { - $return_value = [ - 'card_payments' => [ - 'status' => 'active', - ], - ]; - } - $this->mock_gateway - ->expects( $this->any() ) - ->method( 'get_upe_enabled_payment_method_statuses' ) - ->will( $this->returnValue( $return_value ) ); - } -} diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php deleted file mode 100644 index e788e467a14..00000000000 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ /dev/null @@ -1,1290 +0,0 @@ - 'success', - 'payment_needed' => true, - 'redirect' => 'testURL/key=mock_order_key', - ]; - - /** - * WC_Payments_Localization_Service instance. - * - * @var WC_Payments_Localization_Service - */ - private $mock_localization_service; - - /** - * Mock Fraud Service. - * - * @var WC_Payments_Fraud_Service|MockObject - */ - private $mock_fraud_service; - - /** - * Mapping for payment ID to payment method. - * - * @var array - */ - private $payment_method_classes = [ - Payment_Method::CARD => CC_Payment_Method::class, - Payment_Method::GIROPAY => Giropay_Payment_Method::class, - Payment_Method::SOFORT => Sofort_Payment_Method::class, - Payment_Method::BANCONTACT => Bancontact_Payment_Method::class, - Payment_Method::EPS => EPS_Payment_Method::class, - Payment_Method::P24 => P24_Payment_Method::class, - Payment_Method::IDEAL => Ideal_Payment_Method::class, - Payment_Method::SEPA => Sepa_Payment_Method::class, - Payment_Method::BECS => Becs_Payment_Method::class, - Payment_Method::LINK => Link_Payment_Method::class, - ]; - - /** - * Pre-test setup - */ - public function set_up() { - parent::set_up(); - - $this->mock_payment_gateways = []; - $this->mock_payment_methods = []; - - // Mock the main class's cache service. - $this->_cache = WC_Payments::get_database_cache(); - $this->mock_cache = $this->createMock( Database_Cache::class ); - WC_Payments::set_database_cache( $this->mock_cache ); - - // Arrange: Mock WC_Payments_API_Client so we can configure the - // return value of create_and_confirm_intention(). - // Note that we cannot use createStub here since it's not defined in PHPUnit 6.5. - $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) - ->disableOriginalConstructor() - ->setMethods( - [ - 'create_intention', - 'create_setup_intention', - 'update_intention', - 'get_intent', - 'get_payment_method', - 'is_server_connected', - 'get_charge', - 'get_timeline', - ] - ) - ->getMock(); - - $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' ); - $this->mock_wcpay_account->method( 'get_account_default_currency' )->willReturn( 'USD' ); - - $payment_methods = [ - 'link' => [ - 'base' => 0.1, - ], - ]; - - $this->mock_wcpay_account - ->expects( $this->any() ) - ->method( 'get_fees' ) - ->willReturn( $payment_methods ); - - $this->mock_woopay_utilities = $this->createMock( WooPay_Utilities::class ); - - // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. - $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) - ->disableOriginalConstructor() - ->getMock(); - - // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. - $this->mock_token_service = $this->getMockBuilder( 'WC_Payments_Token_Service' ) - ->disableOriginalConstructor() - ->setMethods( [ 'add_payment_method_to_user' ] ) - ->getMock(); - - // Arrange: Mock WC_Payments_Action_Scheduler_Service so its methods aren't called directly. - $this->mock_action_scheduler_service = $this->getMockBuilder( 'WC_Payments_Action_Scheduler_Service' ) - ->disableOriginalConstructor() - ->getMock(); - - $this->mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); - $this->order_service = new WC_Payments_Order_Service( $this->mock_api_client ); - - $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); - - $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); - $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); - - // Arrange: Define a $_POST array which includes the payment method, - // so that get_payment_method_from_request() does not throw error. - $_POST = [ - 'wcpay-payment-method' => 'pm_mock', - 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, - ]; - - $get_payment_gateway_by_id_return_value_map = []; - - foreach ( $this->payment_method_classes as $payment_method_id => $payment_method_class ) { - $mock_payment_method = $this->getMockBuilder( $payment_method_class ) - ->setConstructorArgs( [ $this->mock_token_service ] ) - ->setMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) - ->getMock(); - $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; - - $mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $mock_payment_method, - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->setMethods( - [ - 'get_return_url', - 'manage_customer_details_for_order', - 'parent_process_payment', - 'get_upe_enabled_payment_method_statuses', - 'is_payment_recurring', - 'get_payment_method_ids_enabled_at_checkout', - 'wc_payments_get_payment_gateway_by_id', - 'get_selected_payment_method', - 'get_upe_enabled_payment_method_ids', - ] - ) - ->getMock(); - - // Arrange: Set the return value of get_return_url() so it can be used in a test later. - $mock_gateway - ->expects( $this->any() ) - ->method( 'get_return_url' ) - ->will( - $this->returnValue( $this->return_url ) - ); - $mock_gateway - ->expects( $this->any() ) - ->method( 'parent_process_payment' ) - ->will( - $this->returnValue( $this->mock_payment_result ) - ); - - $this->mock_payment_gateways[ $payment_method_id ] = $mock_gateway; - - $get_payment_gateway_by_id_return_value_map[] = [ $payment_method_id, $mock_gateway ]; - - WC_Helper_Site_Currency::$mock_site_currency = ''; - } - - foreach ( $this->mock_payment_gateways as $id => $mock_gateway ) { - $mock_gateway->expects( $this->any() ) - ->method( 'wc_payments_get_payment_gateway_by_id' ) - ->will( - $this->returnValueMap( $get_payment_gateway_by_id_return_value_map ) - ); - } - - // Mock the level3 service to always return an empty array. - $mock_level3_service = $this->createMock( Level3Service::class ); - $mock_level3_service->expects( $this->any() ) - ->method( 'get_data_from_order' ) - ->willReturn( [] ); - wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service ); - - // Mock the order service to always return an empty array for meta. - $mock_order_service = $this->createMock( OrderService::class ); - $mock_order_service->expects( $this->any() ) - ->method( 'get_payment_metadata' ) - ->willReturn( [] ); - wcpay_get_test_container()->replace( OrderService::class, $mock_order_service ); - } - - /** - * Cleanup after tests. - * - * @return void - */ - public function tear_down() { - parent::tear_down(); - WC_Payments::set_database_cache( $this->_cache ); - wcpay_get_test_container()->reset_all_replacements(); - } - - /** - * Test the UI
container that will hold the payment method. - * - * @return void - */ - public function test_display_gateway_html_for_multiple_gateways() { - foreach ( $this->mock_payment_gateways as $payment_method_id => $mock_payment_gateway ) { - /** - * This tests each payment method output separately without concatenating the output - * into 1 single buffer. Each iteration has 1 assertion. - */ - ob_start(); - $mock_payment_gateway->display_gateway_html(); - $actual_output = ob_get_contents(); - ob_end_clean(); - - $this->assertStringContainsString( '
', $actual_output ); - } - } - - public function test_should_not_use_stripe_platform_on_checkout_page_for_upe() { - $payment_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - $this->assertFalse( $payment_gateway->should_use_stripe_platform_on_checkout_page() ); - } - - public function test_link_payment_method_requires_mandate_data() { - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - - $mock_upe_gateway - ->expects( $this->once() ) - ->method( 'get_upe_enabled_payment_method_ids' ) - ->will( - $this->returnValue( [ 'link' ] ) - ); - - $this->assertTrue( $mock_upe_gateway->is_mandate_data_required() ); - } - - public function test_sepa_debit_payment_method_requires_mandate_data() { - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - $this->assertTrue( $mock_upe_gateway->is_mandate_data_required() ); - } - - public function test_non_required_mandate_data() { - $mock_gateway_not_requiring_mandate_data = $this->mock_payment_gateways[ Payment_Method::GIROPAY ]; - $this->assertFalse( $mock_gateway_not_requiring_mandate_data->is_mandate_data_required() ); - } - - public function test_non_reusable_payment_method_is_not_available_when_subscription_is_in_cart() { - $non_reusable_payment_method = Payment_Method::BANCONTACT; - $payment_gateway = $this->mock_payment_gateways[ $non_reusable_payment_method ]; - - $this->set_cart_contains_subscription_items( true ); - - $this->assertFalse( $payment_gateway->is_available() ); - } - - public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { - $mock_card_payment_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - $user = wp_get_current_user(); - $customer_id = 'cus_mock'; - - $order = WC_Helper_Order::create_order(); - $_POST = $this->setup_saved_payment_method(); - $mock_card_payment_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - $mock_card_payment_gateway->expects( $this->any() ) - ->method( 'get_upe_enabled_payment_method_ids' ) - ->will( - $this->returnValue( [ Payment_Method::CARD ] ) - ); - $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( - WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ) - ); - - $this->set_cart_contains_subscription_items( false ); - - $result = $mock_card_payment_gateway->process_payment( $order->get_id() ); - - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( $this->return_url, $result['redirect'] ); - } - - public function test_upe_process_payment_check_session_order_redirect_to_previous_order() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - - $response = [ - 'dummy_result' => 'xyz', - ]; - - // Arrange the order is being processed. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange the DPPs to return a redirect. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_against_session_processing_order' ) - ->with( wc_get_order( $order ) ) - ->willReturn( $response ); - - // Act: process the order but redirect to the previous/session paid order. - $result = $mock_upe_gateway->process_payment( $order_id ); - - // Assert: the result of check_against_session_processing_order. - $this->assertSame( $response, $result ); - } - - public function test_process_redirect_payment_intent_processing() { - - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - $order = WC_Helper_Order::create_order(); - - $order_id = $order->get_id(); - $save_payment_method = false; - $user = wp_get_current_user(); - $intent_status = Intent_Status::PROCESSING; - $intent_metadata = [ 'order_id' => (string) $order_id ]; - $charge_id = 'ch_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'pi_mock'; - $payment_method_id = 'pm_mock'; - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $card_method = $this->mock_payment_methods['card']; - - $payment_intent = WC_Helper_Intention::create_intention( - [ - 'status' => $intent_status, - 'metadata' => $intent_metadata, - ] - ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_selected_payment_method' ) - ->willReturn( $card_method ); - - $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); - - $this->set_cart_contains_subscription_items( false ); - - $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - $note = wc_get_order_notes( - [ - 'order_id' => $order_id, - 'limit' => 1, - ] - )[0]; - - $this->assertStringContainsString( 'authorized', $note->content ); - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); - } - - public function test_process_redirect_payment_intent_succeded() { - - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - $order = WC_Helper_Order::create_order(); - - $order_id = $order->get_id(); - $save_payment_method = false; - $user = wp_get_current_user(); - $intent_status = Intent_Status::SUCCEEDED; - $intent_metadata = [ 'order_id' => (string) $order_id ]; - $charge_id = 'ch_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'pi_mock'; - $payment_method_id = 'pm_mock'; - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $card_method = $this->mock_payment_methods['card']; - - $payment_intent = WC_Helper_Intention::create_intention( - [ - 'status' => $intent_status, - 'metadata' => $intent_metadata, - ] - ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_selected_payment_method' ) - ->willReturn( $card_method ); - - $this->set_cart_contains_subscription_items( false ); - - $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); - } - - public function is_proper_intent_used_with_order_returns_false() { - $this->assertFalse( $this->mock_upe_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); - } - - public function test_process_redirect_setup_intent_succeded() { - - $order = WC_Helper_Order::create_order(); - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - - $order_id = $order->get_id(); - $save_payment_method = true; - $user = wp_get_current_user(); - $intent_status = Intent_Status::SUCCEEDED; - $client_secret = 'cs_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'si_mock'; - $payment_method_id = 'pm_mock'; - $token = WC_Helper_Token::create_token( $payment_method_id ); - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $card_method = $this->mock_payment_methods['card']; - - $order->set_shipping_total( 0 ); - $order->set_shipping_tax( 0 ); - $order->set_cart_tax( 0 ); - $order->set_total( 0 ); - $order->save(); - - $setup_intent = WC_Helper_Intention::create_setup_intention( - [ - 'id' => 'pi_mock', - 'client_secret' => $client_secret, - 'status' => $intent_status, - 'payment_method' => $payment_method_id, - 'payment_method_options' => [ - 'card' => [ - 'request_three_d_secure' => 'automatic', - ], - ], - 'last_setup_error' => [], - ] - ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $intent_id ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $setup_intent ); - - $this->mock_token_service->expects( $this->once() ) - ->method( 'add_payment_method_to_user' ) - ->will( - $this->returnValue( $token ) - ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_selected_payment_method' ) - ->willReturn( $card_method ); - - $this->set_cart_contains_subscription_items( true ); - - $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); - $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); - } - - public function test_process_redirect_payment_save_payment_token() { - - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $save_payment_method = true; - $user = wp_get_current_user(); - $intent_status = Intent_Status::PROCESSING; - $intent_metadata = [ 'order_id' => (string) $order_id ]; - $charge_id = 'ch_mock'; - $customer_id = 'cus_mock'; - $intent_id = 'pi_mock'; - $payment_method_id = 'pm_mock'; - $token = WC_Helper_Token::create_token( $payment_method_id ); - - // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. - $order->update_meta_data( '_intent_id', $intent_id ); - $order->save(); - - $card_method = $this->mock_payment_methods['card']; - - $payment_intent = WC_Helper_Intention::create_intention( - [ - 'status' => $intent_status, - 'metadata' => $intent_metadata, - ] - ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'manage_customer_details_for_order' ) - ->will( - $this->returnValue( [ $user, $customer_id ] ) - ); - - $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); - - $this->mock_token_service->expects( $this->once() ) - ->method( 'add_payment_method_to_user' ) - ->will( - $this->returnValue( $token ) - ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_selected_payment_method' ) - ->willReturn( $card_method ); - - $this->set_cart_contains_subscription_items( false ); - - $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); - - $result_order = wc_get_order( $order_id ); - $note = wc_get_order_notes( - [ - 'order_id' => $order_id, - 'limit' => 1, - ] - )[0]; - - $this->assertStringContainsString( 'authorized', $note->content ); - $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); - $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); - $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); - $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); - $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); - $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); - $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); - } - - public function test_correct_payment_method_title_for_order() { - $order = WC_Helper_Order::create_order(); - - $visa_credit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'credit', - ], - ]; - $visa_debit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'debit', - ], - ]; - $mastercard_credit_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'mastercard', - 'funding' => 'credit', - ], - ]; - $eps_details = [ - 'type' => 'eps', - ]; - $giropay_details = [ - 'type' => 'giropay', - ]; - $p24_details = [ - 'type' => 'p24', - ]; - $sofort_details = [ - 'type' => 'sofort', - ]; - $bancontact_details = [ - 'type' => 'bancontact', - ]; - $sepa_details = [ - 'type' => 'sepa_debit', - ]; - $ideal_details = [ - 'type' => 'ideal', - ]; - $becs_details = [ - 'type' => 'au_becs_debit', - ]; - - $charge_payment_method_details = [ - $visa_credit_details, - $visa_debit_details, - $mastercard_credit_details, - $giropay_details, - $sofort_details, - $bancontact_details, - $eps_details, - $p24_details, - $ideal_details, - $sepa_details, - $becs_details, - ]; - - $expected_payment_method_titles = [ - 'Visa credit card', - 'Visa debit card', - 'Mastercard credit card', - 'giropay', - 'Sofort', - 'Bancontact', - 'EPS', - 'Przelewy24 (P24)', - 'iDEAL', - 'SEPA Direct Debit', - 'BECS Direct Debit', - ]; - - foreach ( $charge_payment_method_details as $i => $payment_method_details ) { - $payment_method_id = $payment_method_details['type']; - $mock_upe_gateway = $this->mock_payment_gateways[ $payment_method_id ]; - $payment_method = $this->mock_payment_methods[ $payment_method_id ]; - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_selected_payment_method' ) - ->willReturn( $payment_method ); - $mock_upe_gateway->set_payment_method_title_for_order( $order, $payment_method_id, $payment_method_details ); - $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); - } - } - - public function test_payment_methods_show_correct_default_outputs() { - $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); - $this->mock_token_service->expects( $this->any() ) - ->method( 'add_payment_method_to_user' ) - ->will( - $this->returnValue( $mock_token ) - ); - - $mock_user = 'mock_user'; - $mock_payment_method_id = 'pm_mock'; - - $mock_visa_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'visa', - 'funding' => 'debit', - ], - ]; - $mock_mastercard_details = [ - 'type' => 'card', - 'card' => [ - 'network' => 'mastercard', - 'funding' => 'credit', - ], - ]; - $mock_giropay_details = [ - 'type' => 'giropay', - ]; - $mock_p24_details = [ - 'type' => 'p24', - ]; - $mock_sofort_details = [ - 'type' => 'sofort', - ]; - $mock_bancontact_details = [ - 'type' => 'bancontact', - ]; - $mock_eps_details = [ - 'type' => 'eps', - ]; - $mock_sepa_details = [ - 'type' => 'sepa_debit', - ]; - $mock_ideal_details = [ - 'type' => 'ideal', - ]; - $mock_becs_details = [ - 'type' => 'au_becs_debit', - ]; - - $this->set_cart_contains_subscription_items( false ); - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $p24_method = $this->mock_payment_methods['p24']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - - $this->assertEquals( 'card', $card_method->get_id() ); - $this->assertEquals( 'Credit card / debit card', $card_method->get_title() ); - $this->assertEquals( 'Visa debit card', $card_method->get_title( $mock_visa_details ) ); - $this->assertEquals( 'Mastercard credit card', $card_method->get_title( $mock_mastercard_details ) ); - $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); - $this->assertTrue( $card_method->is_reusable() ); - $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); - - $this->assertEquals( 'giropay', $giropay_method->get_id() ); - $this->assertEquals( 'giropay', $giropay_method->get_title() ); - $this->assertEquals( 'giropay', $giropay_method->get_title( $mock_giropay_details ) ); - $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $giropay_method->is_reusable() ); - - $this->assertEquals( 'p24', $p24_method->get_id() ); - $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title() ); - $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( $mock_p24_details ) ); - $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $p24_method->is_reusable() ); - - $this->assertEquals( 'sofort', $sofort_method->get_id() ); - $this->assertEquals( 'Sofort', $sofort_method->get_title() ); - $this->assertEquals( 'Sofort', $sofort_method->get_title( $mock_sofort_details ) ); - $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sofort_method->is_reusable() ); - - $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); - $this->assertEquals( 'Bancontact', $bancontact_method->get_title() ); - $this->assertEquals( 'Bancontact', $bancontact_method->get_title( $mock_bancontact_details ) ); - $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $bancontact_method->is_reusable() ); - - $this->assertEquals( 'eps', $eps_method->get_id() ); - $this->assertEquals( 'EPS', $eps_method->get_title() ); - $this->assertEquals( 'EPS', $eps_method->get_title( $mock_eps_details ) ); - $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $eps_method->is_reusable() ); - - $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); - $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title() ); - $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( $mock_sepa_details ) ); - $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sepa_method->is_reusable() ); - - $this->assertEquals( 'ideal', $ideal_method->get_id() ); - $this->assertEquals( 'iDEAL', $ideal_method->get_title() ); - $this->assertEquals( 'iDEAL', $ideal_method->get_title( $mock_ideal_details ) ); - $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $ideal_method->is_reusable() ); - - $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); - $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title() ); - $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( $mock_becs_details ) ); - $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $becs_method->is_reusable() ); - } - - public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { - // Setup $this->mock_payment_methods. - - $this->set_cart_contains_subscription_items( true ); - - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $p24_method = $this->mock_payment_methods['p24']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - - $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $giropay_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sofort_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $bancontact_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $eps_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $sepa_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $p24_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $ideal_method->is_enabled_at_checkout( 'US' ) ); - $this->assertFalse( $becs_method->is_enabled_at_checkout( 'US' ) ); - } - - public function test_only_valid_payment_methods_returned_for_currency() { - // Setup $this->mock_payment_methods. - - $card_method = $this->mock_payment_methods['card']; - $giropay_method = $this->mock_payment_methods['giropay']; - $sofort_method = $this->mock_payment_methods['sofort']; - $bancontact_method = $this->mock_payment_methods['bancontact']; - $eps_method = $this->mock_payment_methods['eps']; - $sepa_method = $this->mock_payment_methods['sepa_debit']; - $p24_method = $this->mock_payment_methods['p24']; - $ideal_method = $this->mock_payment_methods['ideal']; - $becs_method = $this->mock_payment_methods['au_becs_debit']; - - WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; - $account_domestic_currency = 'USD'; - - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = 'USD'; - - $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); - $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = 'AUD'; - $this->assertTrue( $becs_method->is_currency_valid( $account_domestic_currency ) ); - - WC_Helper_Site_Currency::$mock_site_currency = ''; - } - - public function test_create_token_from_setup_intent_adds_token() { - - $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); - $mock_setup_intent_id = 'si_mock'; - $mock_user = wp_get_current_user(); - - $this->mock_token_service - ->method( 'add_payment_method_to_user' ) - ->with( 'pm_mock', $mock_user ) - ->will( - $this->returnValue( $mock_token ) - ); - - foreach ( $this->mock_payment_gateways as $mock_upe_gateway ) { - $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $mock_setup_intent_id ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( - WC_Helper_Intention::create_setup_intention( - [ - 'id' => $mock_setup_intent_id, - 'payment_method' => 'pm_mock', - ] - ) - ); - $this->assertEquals( $mock_token, $mock_upe_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); - } - } - - /** - * Test get_payment_method_types with regular checkout post request context. - * - * @return void - */ - public function test_get_payment_methods_with_request_context() { - $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_methods[ Payment_Method::CARD ], - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) - ->getMock(); - - $order = WC_Helper_Order::create_order(); - $payment_information = new Payment_Information( 'pm_mock', $order ); - - $_POST['payment_method'] = 'woocommerce_payments'; - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'get_payment_methods_from_gateway_id' ) - ->with( 'woocommerce_payments' ) - ->will( - $this->returnValue( [ Payment_Method::CARD ] ) - ); - - $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); - - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - - unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification - } - - /** - * Test get_payment_method_types without post request context. - * - * @return void - */ - public function test_get_payment_methods_without_request_context() { - $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_methods[ Payment_Method::CARD ], - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) - ->getMock(); - - $token = WC_Helper_Token::create_token( 'pm_mock' ); - $order = WC_Helper_Order::create_order(); - $payment_information = new Payment_Information( 'pm_mock', $order, null, $token ); - - unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'get_payment_methods_from_gateway_id' ) - ->with( $token->get_gateway_id(), $order->get_id() ) - ->will( - $this->returnValue( [ Payment_Method::CARD ] ) - ); - - $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); - - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - } - - /** - * Test get_payment_method_types without post request context or saved token. - * - * @return void - */ - public function test_get_payment_methods_without_request_context_or_token() { - $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_methods[ Payment_Method::CARD ], - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->setMethods( - [ - 'get_payment_methods_from_gateway_id', - 'get_payment_method_ids_enabled_at_checkout', - ] - ) - ->getMock(); - - $payment_information = new Payment_Information( 'pm_mock' ); - - unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification - - $gateway = WC_Payments::get_gateway(); - WC_Payments::set_gateway( $mock_upe_gateway ); - - $mock_upe_gateway->expects( $this->never() ) - ->method( 'get_payment_methods_from_gateway_id' ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'get_payment_method_ids_enabled_at_checkout' ) - ->willReturn( [ Payment_Method::CARD ] ); - - $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); - - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - - WC_Payments::set_gateway( $gateway ); - } - - /** - * Test get_payment_methods_from_gateway_id function with UPE enabled. - * - * @return void - */ - public function test_get_payment_methods_from_gateway_id_upe() { - WC_Helper_Order::create_order(); - $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_methods[ Payment_Method::CARD ], - $this->mock_payment_methods, - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - ] - ) - ->onlyMethods( - [ - 'get_upe_enabled_payment_method_ids', - 'get_payment_method_ids_enabled_at_checkout', - ] - ) - ->getMock(); - - $gateway = WC_Payments::get_gateway(); - WC_Payments::set_gateway( $mock_upe_gateway ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_upe_enabled_payment_method_ids' ) - ->will( - $this->returnValue( [ Payment_Method::CARD, Payment_Method::LINK ] ) - ); - - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::BANCONTACT ); - $this->assertSame( [ Payment_Method::BANCONTACT ], $payment_methods ); - - $mock_upe_gateway->expects( $this->any() ) - ->method( 'get_payment_method_ids_enabled_at_checkout' ) - ->will( - $this->onConsecutiveCalls( - [ Payment_Method::CARD, Payment_Method::LINK ], - [ Payment_Method::CARD ] - ) - ); - - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); - $this->assertSame( [ Payment_Method::CARD, Payment_Method::LINK ], $payment_methods ); - - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - - WC_Payments::set_gateway( $gateway ); - } - - /** - * Helper function to mock subscriptions for internal UPE payment methods. - */ - private function set_cart_contains_subscription_items( $cart_contains_subscriptions ) { - foreach ( $this->mock_payment_methods as $mock_payment_method ) { - $mock_payment_method->expects( $this->any() ) - ->method( 'is_subscription_item_in_cart' ) - ->will( - $this->returnValue( $cart_contains_subscriptions ) - ); - } - } - - private function setup_saved_payment_method() { - $token = WC_Helper_Token::create_token( 'pm_mock' ); - - return [ - 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, - 'wc-' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' => (string) $token->get_id(), - ]; - } - - private function set_get_upe_enabled_payment_method_statuses_return_value( $mock_payment_gateway, $return_value = null ) { - if ( null === $return_value ) { - $return_value = [ - 'card_payments' => [ - 'status' => 'active', - ], - ]; - } - $mock_payment_gateway - ->expects( $this->any() ) - ->method( 'get_upe_enabled_payment_method_statuses' ) - ->will( $this->returnValue( $return_value ) ); - } -} diff --git a/tests/unit/reports/test-class-wc-rest-payments-reports-authorizations-controller.php b/tests/unit/reports/test-class-wc-rest-payments-reports-authorizations-controller.php index 58f7bc1acaf..302ec1efadd 100644 --- a/tests/unit/reports/test-class-wc-rest-payments-reports-authorizations-controller.php +++ b/tests/unit/reports/test-class-wc-rest-payments-reports-authorizations-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Exceptions\Connection_Exception; use WCPay\Core\Server\Request\List_Authorizations; @@ -196,7 +197,7 @@ private function get_authorizations_list_from_server() { 'source_identifier' => '4242', 'customer_name' => 'Test One', 'customer_email' => 'test1@woo.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'fees' => 312, 'currency' => 'eur', 'risk_level' => 0, @@ -221,7 +222,7 @@ private function get_authorizations_list_from_server() { 'source_identifier' => '4242', 'customer_name' => 'Test Two', 'customer_email' => 'test2@woo.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'fees' => 98, 'currency' => 'eur', 'risk_level' => 0, @@ -246,7 +247,7 @@ private function get_authorizations_list_from_server() { 'source_identifier' => '4242', 'customer_name' => 'Test One', 'customer_email' => 'test1@woo.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'fees' => 312, 'currency' => 'eur', 'risk_level' => 0, @@ -277,7 +278,7 @@ private function get_authorizations_list() { 'customer' => [ 'name' => 'Test One', 'email' => 'test1@woo.com', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'net_amount' => 6988, 'order_id' => 123, @@ -298,7 +299,7 @@ private function get_authorizations_list() { 'customer' => [ 'name' => 'Test Two', 'email' => 'test2@woo.com', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'net_amount' => 1702, 'order_id' => 456, @@ -319,7 +320,7 @@ private function get_authorizations_list() { 'customer' => [ 'name' => 'Test One', 'email' => 'test1@woo.com', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'net_amount' => 6988, 'order_id' => 789, diff --git a/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php b/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php index aefee308132..46c2b21452c 100644 --- a/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php +++ b/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Exceptions\Connection_Exception; use WCPay\Core\Server\Request\List_Transactions; @@ -200,7 +201,7 @@ private function get_transactions_list_from_server() { 'source_identifier' => '3184', 'customer_name' => 'Test Customer1', 'customer_email' => 'test1@woo.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'amount' => 2583, 'net' => 2426, 'fees' => 157, @@ -233,7 +234,7 @@ private function get_transactions_list_from_server() { 'source_identifier' => '3184', 'customer_name' => 'Test Customer2', 'customer_email' => 'test2@woo.com', - 'customer_country' => 'US', + 'customer_country' => Country_Code::UNITED_STATES, 'amount' => 2583, 'net' => 2452, 'fees' => 131, @@ -282,7 +283,7 @@ private function get_transactions_list() { 'customer' => [ 'name' => 'Test Customer1', 'email' => 'test1@woo.com', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'net_amount' => 2426, 'order_id' => 123, @@ -308,7 +309,7 @@ private function get_transactions_list() { 'customer' => [ 'name' => 'Test Customer2', 'email' => 'test2@woo.com', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'net_amount' => 2452, 'order_id' => 275, diff --git a/tests/unit/src/Internal/LoggerTest.php b/tests/unit/src/Internal/LoggerTest.php index 849d9d5dd3f..97765d6a8ea 100644 --- a/tests/unit/src/Internal/LoggerTest.php +++ b/tests/unit/src/Internal/LoggerTest.php @@ -42,13 +42,6 @@ class LoggerTest extends WCPAY_UnitTestCase { */ private $mode; - /** - * WC_Payment_Gateway_WCPay - * - * @var WC_Payment_Gateway_WCPay|MockObject - */ - private $mock_gateway; - /** * Sets up the logger. */ @@ -57,13 +50,11 @@ protected function setUp(): void { $this->mock_wc_logger = $this->createMock( WC_Logger::class ); $this->mode = $this->createMock( Mode::class ); - $this->mock_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class ); $this->sut = $this->getMockBuilder( Logger::class ) ->setConstructorArgs( [ $this->mock_wc_logger, $this->mode, - $this->mock_gateway, ] ) ->onlyMethods( [ 'can_log' ] ) @@ -134,8 +125,7 @@ public function provider_log_levels() { public function test_can_log_dev_mode() { $this->sut = new Logger( $this->mock_wc_logger, - $this->mode, - $this->mock_gateway + $this->mode ); $this->mode->expects( $this->once() ) ->method( 'is_dev' ) @@ -149,8 +139,7 @@ public function test_can_log_dev_mode() { public function test_can_log_exception() { $this->sut = new Logger( $this->mock_wc_logger, - $this->mode, - $this->mock_gateway + $this->mode ); $this->mode->expects( $this->once() ) ->method( 'is_dev' ) @@ -164,16 +153,12 @@ public function test_can_log_exception() { public function test_can_log_no_option() { $this->sut = new Logger( $this->mock_wc_logger, - $this->mode, - $this->mock_gateway + $this->mode ); $this->mode->expects( $this->once() ) ->method( 'is_dev' ) ->willReturn( false ); - $this->mock_gateway->expects( $this->once() ) - ->method( 'get_option' ) - ->with( 'enable_logging' ) - ->willReturn( null ); + update_option( 'woocommerce_woocommerce_payments_settings', [] ); $this->assertFalse( $this->sut->can_log() ); } @@ -183,16 +168,12 @@ public function test_can_log_no_option() { public function test_can_log_disabled() { $this->sut = new Logger( $this->mock_wc_logger, - $this->mode, - $this->mock_gateway + $this->mode ); $this->mode->expects( $this->once() ) ->method( 'is_dev' ) ->willReturn( false ); - $this->mock_gateway->expects( $this->once() ) - ->method( 'get_option' ) - ->with( 'enable_logging' ) - ->willReturn( 'no' ); + update_option( 'woocommerce_woocommerce_payments_settings', [ 'enable_logging' => 'no' ] ); $this->assertFalse( $this->sut->can_log() ); } @@ -202,16 +183,12 @@ public function test_can_log_disabled() { public function test_can_log_enabled() { $this->sut = new Logger( $this->mock_wc_logger, - $this->mode, - $this->mock_gateway + $this->mode ); $this->mode->expects( $this->once() ) ->method( 'is_dev' ) ->willReturn( false ); - $this->mock_gateway->expects( $this->once() ) - ->method( 'get_option' ) - ->with( 'enable_logging' ) - ->willReturn( 'yes' ); + update_option( 'woocommerce_woocommerce_payments_settings', [ 'enable_logging' => 'yes' ] ); $this->assertTrue( $this->sut->can_log() ); } } diff --git a/tests/unit/src/Internal/Service/Level3ServiceTest.php b/tests/unit/src/Internal/Service/Level3ServiceTest.php index 553c54f862c..5911201c99b 100644 --- a/tests/unit/src/Internal/Service/Level3ServiceTest.php +++ b/tests/unit/src/Internal/Service/Level3ServiceTest.php @@ -12,6 +12,7 @@ use WC_Order; use WC_Order_Item_Product; use WC_Order_Item_Fee; +use WCPay\Constants\Country_Code; use WCPAY_UnitTestCase; use WC_Payments_Account; use WCPay\Internal\Service\Level3Service; @@ -235,7 +236,7 @@ public function test_full_level3_data() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', false, false, 1, 1, 30, true ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -266,7 +267,7 @@ public function test_full_level3_data_with_product_id_longer_than_12_characters( ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', false, false, 1, 1, 123456789123456 ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -305,7 +306,7 @@ public function test_full_level3_data_with_fee() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', true ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -344,7 +345,7 @@ public function test_full_level3_data_with_negative_price_product() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', false, true, 1, 1 ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -353,7 +354,7 @@ public function test_full_level3_data_with_negative_price_product() { public function test_us_store_level_3_data() { // Use a non-us customer postcode to ensure it's not included in the level3 data. - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '9000' ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -384,7 +385,7 @@ public function test_us_customer_level_3_data() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '9000' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012' ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -394,7 +395,7 @@ public function test_us_customer_level_3_data() { public function test_non_us_customer_level_3_data() { $expected_data = []; - $this->mock_account->method( 'get_account_country' )->willReturn( 'CA' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::CANADA ); $this->mock_level_3_order( 'K0A' ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -425,7 +426,7 @@ public function test_full_level3_data_with_float_quantity() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', false, false, 3.7 ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -456,7 +457,7 @@ public function test_full_level3_data_with_float_quantity_zero() { ->with( 'get_option', 'woocommerce_store_postcode' ) ->willReturn( '94110' ); - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', false, false, 0.4 ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); @@ -505,7 +506,7 @@ public function test_level3_data_bundle() { } public function test_level3_data_bundle_for_orders_with_more_than_200_items() { - $this->mock_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); $this->mock_level_3_order( '98012', true, false, 1, 500 ); $level_3_data = $this->sut->get_data_from_order( $this->order_id ); diff --git a/tests/unit/src/Internal/Service/OrderServiceTest.php b/tests/unit/src/Internal/Service/OrderServiceTest.php index 63f40cf074f..3faae6b6ccd 100644 --- a/tests/unit/src/Internal/Service/OrderServiceTest.php +++ b/tests/unit/src/Internal/Service/OrderServiceTest.php @@ -332,7 +332,7 @@ public function test_update_order_from_successful_intent( $intent ) { ->willReturn( $mock_charge ); } - // Prepare all parameters for `attach_intent_info_to_order`. + // Prepare all parameters for `attach_intent_info_to_order__legacy`. $intent->expects( $this->once() ) ->method( 'get_id' ) ->willReturn( $intent_id ); @@ -355,7 +355,7 @@ public function test_update_order_from_successful_intent( $intent ) { ->willReturn( 'prod' ); $this->mock_legacy_service->expects( $this->once() ) - ->method( 'attach_intent_info_to_order' ) + ->method( 'attach_intent_info_to_order__legacy' ) ->with( $mock_order, $intent_id, @@ -412,7 +412,7 @@ public function test_update_order_from_intent_that_requires_action() { $mock_intent->expects( $this->once() )->method( 'get_status' )->willReturn( $intent_status ); $this->mock_legacy_service->expects( $this->once() ) - ->method( 'attach_intent_info_to_order' ) + ->method( 'attach_intent_info_to_order__legacy' ) ->with( $mock_order, $intent_id, diff --git a/tests/unit/test-class-compatibility-service.php b/tests/unit/test-class-compatibility-service.php index 70e86b5d2dc..8debd6b802d 100644 --- a/tests/unit/test-class-compatibility-service.php +++ b/tests/unit/test-class-compatibility-service.php @@ -40,16 +40,42 @@ public function set_up() { public function test_registers_woocommerce_filters_properly() { $priority = has_filter( 'woocommerce_payments_account_refreshed', [ $this->compatibility_service, 'update_compatibility_data' ] ); $this->assertEquals( 10, $priority ); + $priority = has_action( 'after_switch_theme', [ $this->compatibility_service, 'update_compatibility_data' ] ); + $this->assertEquals( 10, $priority ); } public function test_update_compatibility_data() { + $stylesheet = 'my_theme_name'; + add_filter( + 'stylesheet', + function( $theme ) use ( $stylesheet ) { + return $stylesheet; + } + ); + // Arrange: Create the expected value to be passed to update_compatibility_data. $expected = [ 'woopayments_version' => WCPAY_VERSION_NUMBER, 'woocommerce_version' => WC_VERSION, + 'blog_theme' => $stylesheet, + 'active_plugins' => [ + 'woocommerce/woocommerce.php', + 'woocommerce-payments/woocommerce-payments.php', + ], + 'post_types_count' => [ + 'post' => 1, + 'page' => 6, + 'attachment' => 0, + 'product' => 12, + ], ]; // Arrange/Assert: Set the expectations for update_compatibility_data. + add_filter( 'option_active_plugins', [ $this, 'active_plugins_filter_return' ] ); + + // Arrange: Insert test posts. + $post_ids = $this->insert_test_posts( $expected['post_types_count'] ); + $this->mock_api_client ->expects( $this->once() ) ->method( 'update_compatibility_data' ) @@ -57,5 +83,108 @@ public function test_update_compatibility_data() { // Act: Call the method we're testing. $this->compatibility_service->update_compatibility_data(); + + remove_filter( 'option_active_plugins', [ $this, 'active_plugins_filter_return' ] ); + + // Clean up: Delete the test posts. + $this->delete_test_posts( $post_ids ); + } + + public function test_update_compatibility_data_active_plugins_false() { + $stylesheet = 'my_theme_name'; + add_filter( + 'stylesheet', + function( $theme ) use ( $stylesheet ) { + return $stylesheet; + } + ); + + // Arrange: Create the expected value to be passed to update_compatibility_data. + $expected = [ + 'woopayments_version' => WCPAY_VERSION_NUMBER, + 'woocommerce_version' => WC_VERSION, + 'blog_theme' => $stylesheet, + 'active_plugins' => [], + 'post_types_count' => [ + 'post' => 1, + 'page' => 6, + 'attachment' => 0, + 'product' => 12, + ], + ]; + + $this->break_active_plugins_option(); + + // Arrange: Insert test posts. + $post_ids = $this->insert_test_posts( $expected['post_types_count'] ); + + // Arrange/Assert: Set the expectations for update_compatibility_data. + $this->mock_api_client + ->expects( $this->once() ) + ->method( 'update_compatibility_data' ) + ->with( $expected ); + + // Act: Call the method we're testing. + $this->compatibility_service->update_compatibility_data(); + + $this->fix_active_plugins_option(); + + // Clean up: Delete the test posts. + $this->delete_test_posts( $post_ids ); + } + + + public function active_plugins_filter_return() { + return [ + 'woocommerce/woocommerce.php', + 'woocommerce-payments/woocommerce-payments.php', + ]; + } + + private function break_active_plugins_option() { + update_option( 'temp_active_plugins', get_option( 'active_plugins' ) ); + delete_option( 'active_plugins' ); + } + + private function fix_active_plugins_option() { + update_option( 'active_plugins', get_option( 'temp_active_plugins' ) ); + delete_option( 'temp_active_plugins' ); + } + + /** + * Insert test posts for use during a unit test. + * + * @param array $post_types Assoc array of post types as keys and the number of posts to create for each. + * + * @return array Array of post IDs that were created. + */ + private function insert_test_posts( array $post_types ): array { + $post_ids = []; + foreach ( $post_types as $post_type => $count ) { + $title_content = 'This is a ' . $post_type . ' test post'; + for ( $i = 0; $i < $count; $i++ ) { + $post_ids[] = (int) wp_insert_post( + [ + 'post_title' => $title_content, + 'post_content' => $title_content, + 'post_type' => $post_type, + 'post_status' => 'publish', + ] + ); + } + } + + return $post_ids; + } + + /** + * Delete test posts that were created during a unit test. + * + * @param array $post_ids Array of post IDs to delete. + */ + private function delete_test_posts( array $post_ids ) { + foreach ( $post_ids as $post_id ) { + wp_delete_post( (int) $post_id, true ); + } } } diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php index 1ce7132f420..b064ef08f90 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php @@ -304,7 +304,7 @@ public function test_intent_status_success() { $this->mock_order_service ->expects( $this->once() ) ->method( 'attach_intent_info_to_order' ) - ->with( $mock_order, $intent_id, $status, 'pm_mock', $customer_id, $charge_id, 'USD' ); + ->with( $mock_order, $intent ); $this->mock_order_service ->expects( $this->once() ) @@ -471,7 +471,7 @@ public function test_intent_status_requires_capture() { $this->mock_order_service ->expects( $this->once() ) ->method( 'attach_intent_info_to_order' ) - ->with( $mock_order, $intent_id, $status, 'pm_mock', $customer_id, $charge_id, 'USD' ); + ->with( $mock_order, $intent ); // Assert: The Order_Service is called correctly. $this->mock_order_service @@ -919,7 +919,7 @@ public function test_intent_status_requires_action() { $this->mock_order_service ->expects( $this->once() ) ->method( 'attach_intent_info_to_order' ) - ->with( $mock_order, $intent_id, $status, 'pm_mock', $customer_id, $charge_id, 'USD' ); + ->with( $mock_order, $intent ); $this->mock_order_service ->expects( $this->once() ) @@ -1035,7 +1035,7 @@ public function test_setup_intent_status_requires_action() { $this->mock_order_service ->expects( $this->once() ) ->method( 'attach_intent_info_to_order' ) - ->with( $mock_order, $intent_id, $status, 'pm_mock', $customer_id, '', 'USD' ); + ->with( $mock_order, $intent ); // Assert: Order status was not updated. $mock_order diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php index 7350621aedc..f42ef677dbb 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php @@ -766,7 +766,7 @@ public function test_process_refund_on_uncaptured_payment() { $this->assertEquals( 'uncaptured-payment', $result->get_error_code() ); } - public function test_process_refund_on_invalid_amount() { + public function test_process_refund_on_zero_amount() { $intent_id = 'pi_xxxxxxxxxxxxx'; $charge_id = 'ch_yyyyyyyyyyyyy'; @@ -778,9 +778,19 @@ public function test_process_refund_on_invalid_amount() { $order_id = $order->get_id(); $result = $this->wcpay_gateway->process_refund( $order_id, 0 ); + $this->assertSame( true, $result ); + } - $this->assertInstanceOf( WP_Error::class, $result ); - $this->assertEquals( 'invalid-amount', $result->get_error_code() ); + public function test_process_refund_on_invalid_amount() { + $intent_id = 'pi_xxxxxxxxxxxxx'; + $charge_id = 'ch_yyyyyyyyyyyyy'; + + $order = WC_Helper_Order::create_order(); + $order->update_meta_data( '_intent_id', $intent_id ); + $order->update_meta_data( '_charge_id', $charge_id ); + $order->save(); + + $order_id = $order->get_id(); $result = $this->wcpay_gateway->process_refund( $order_id, - 5 ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 02505f94d51..62dd5c2f83a 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -15,9 +15,11 @@ use WCPay\Core\Server\Request\Get_Setup_Intention; use WCPay\Constants\Order_Status; use WCPay\Constants\Intent_Status; +use WCPay\Constants\Payment_Method; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Exceptions\Amount_Too_Small_Exception; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\Process_Payment_Exception; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Internal\Payment\Factor; use WCPay\Internal\Payment\Router; @@ -26,8 +28,20 @@ use WCPay\Internal\Service\OrderService; use WCPay\Internal\Service\PaymentProcessingService; use WCPay\Payment_Information; +use WCPay\Payment_Methods\Affirm_Payment_Method; +use WCPay\Payment_Methods\Afterpay_Payment_Method; +use WCPay\Payment_Methods\Bancontact_Payment_Method; +use WCPay\Payment_Methods\Becs_Payment_Method; use WCPay\Payment_Methods\CC_Payment_Method; +use WCPay\Payment_Methods\Eps_Payment_Method; +use WCPay\Payment_Methods\Giropay_Payment_Method; +use WCPay\Payment_Methods\Ideal_Payment_Method; +use WCPay\Payment_Methods\Klarna_Payment_Method; +use WCPay\Payment_Methods\Link_Payment_Method; +use WCPay\Payment_Methods\P24_Payment_Method; use WCPay\Payment_Methods\Sepa_Payment_Method; +use WCPay\Payment_Methods\Sofort_Payment_Method; +use WCPay\Payment_Methods\WC_Helper_Site_Currency; use WCPay\WooPay\WooPay_Utilities; use WCPay\Session_Rate_Limiter; @@ -45,9 +59,30 @@ class WC_Payment_Gateway_WCPay_Test extends WCPAY_UnitTestCase { /** * System under test. * + * The card gateway is predominantly used for testing compared to other gateways, + * therefore it is assigned its own variable. + * * @var WC_Payment_Gateway_WCPay */ - private $wcpay_gateway; + private $card_gateway; + + /** + * Arrays of system under test. + * + * Useful when testing operations involving multiple gateways. + * + * @var WC_Payment_Gateway_WCPay[] + */ + private $gateways; + + /** + * Arrays of payment methods. + * + * Useful when testing operations involving multiple payment methods. + * + * @var WCPay\Payment_Methods\UPE_Payment_Method[] + */ + private $payment_methods; /** * Mock WC_Payments_API_Client. @@ -168,16 +203,6 @@ public function set_up() { $this->mock_api_client->expects( $this->any() )->method( 'get_blog_id' )->willReturn( 1234567 ); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $this->mock_wcpay_account - ->expects( $this->any() ) - ->method( 'get_fees' ) - ->willReturn( - [ - 'card' => [ - 'base' => 0.1, - ], - ] - ); // Mock the main class's cache service. $this->_cache = WC_Payments::get_database_cache(); @@ -211,22 +236,11 @@ public function set_up() { ->setMethods( [ 'is_subscription_item_in_cart' ] ) ->getMock(); - $this->wcpay_gateway = new WC_Payment_Gateway_WCPay( - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_method, - [ 'card' => $this->mock_payment_method ], - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service - ); + $this->init_gateways(); - WC_Payments::set_gateway( $this->wcpay_gateway ); + // Replace the main class's gateway for testing purposes. + $this->_gateway = WC_Payments::get_gateway(); + WC_Payments::set_gateway( $this->card_gateway ); $this->woopay_utilities = new WooPay_Utilities(); @@ -256,9 +270,12 @@ public function tear_down() { // Restore the cache service in the main class. WC_Payments::set_database_cache( $this->_cache ); + // Restore the gateway in the main class. + WC_Payments::set_gateway( $this->_gateway ); + // Fall back to an US store. update_option( 'woocommerce_store_postcode', '94110' ); - $this->wcpay_gateway->update_option( 'saved_cards', 'yes' ); + $this->card_gateway->update_option( 'saved_cards', 'yes' ); // Some tests simulate payment method parameters. $payment_method_keys = [ @@ -277,6 +294,890 @@ public function tear_down() { wcpay_get_test_container()->reset_all_replacements(); } + public function test_process_redirect_payment_intent_processing() { + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::PROCESSING; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $request = $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->will( $this->returnValue( $payment_intent ) ); + + $this->mock_customer_service + ->expects( $this->once() ) + ->method( 'get_customer_id_by_user_id' ) + ->will( $this->returnValue( $customer_id ) ); + + $this->card_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + $note = wc_get_order_notes( + [ + 'order_id' => $order_id, + 'limit' => 1, + ] + )[0]; + + $this->assertStringContainsString( 'authorized', $note->content ); + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); + } + + public function test_process_redirect_payment_intent_succeded() { + $order = WC_Helper_Order::create_order(); + + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::SUCCEEDED; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $payment_intent ); + + $this->mock_customer_service + ->expects( $this->once() ) + ->method( 'get_customer_id_by_user_id' ) + ->will( $this->returnValue( $customer_id ) ); + + $this->card_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); + } + + public function test_validate_order_id_received_vs_intent_meta_order_id_throw_exception() { + $order = WC_Helper_Order::create_order(); + $intent_metadata = [ 'order_id' => (string) ( $order->get_id() + 100 ) ]; + + $this->expectException( Process_Payment_Exception::class ); + $this->expectExceptionMessage( "We're not able to process this payment due to the order ID mismatch. Please try again later." ); + + \PHPUnit_Utils::call_method( + $this->card_gateway, + 'validate_order_id_received_vs_intent_meta_order_id', + [ $order, $intent_metadata ] + ); + } + + public function test_validate_order_id_received_vs_intent_meta_order_id_returning_void() { + $order = WC_Helper_Order::create_order(); + $intent_metadata = [ 'order_id' => (string) ( $order->get_id() ) ]; + + $res = \PHPUnit_Utils::call_method( + $this->card_gateway, + 'validate_order_id_received_vs_intent_meta_order_id', + [ $order, $intent_metadata ] + ); + + $this->assertSame( null, $res ); + } + + public function test_correct_payment_method_title_for_order() { + $order = WC_Helper_Order::create_order(); + + $visa_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'credit', + ], + ]; + $visa_debit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mastercard_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $eps_details = [ + 'type' => 'eps', + ]; + $giropay_details = [ + 'type' => 'giropay', + ]; + $p24_details = [ + 'type' => 'p24', + ]; + $sofort_details = [ + 'type' => 'sofort', + ]; + $bancontact_details = [ + 'type' => 'bancontact', + ]; + $sepa_details = [ + 'type' => 'sepa_debit', + ]; + $ideal_details = [ + 'type' => 'ideal', + ]; + $becs_details = [ + 'type' => 'au_becs_debit', + ]; + + $charge_payment_method_details = [ + $visa_credit_details, + $visa_debit_details, + $mastercard_credit_details, + $giropay_details, + $sofort_details, + $bancontact_details, + $eps_details, + $p24_details, + $ideal_details, + $sepa_details, + $becs_details, + ]; + + $expected_payment_method_titles = [ + 'Visa credit card', + 'Visa debit card', + 'Mastercard credit card', + 'giropay', + 'Sofort', + 'Bancontact', + 'EPS', + 'Przelewy24 (P24)', + 'iDEAL', + 'SEPA Direct Debit', + 'BECS Direct Debit', + ]; + + foreach ( $charge_payment_method_details as $i => $payment_method_details ) { + $this->card_gateway->set_payment_method_title_for_order( $order, $payment_method_details['type'], $payment_method_details ); + $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); + } + } + + public function test_payment_methods_show_correct_default_outputs() { + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $this->mock_token_service->expects( $this->any() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $mock_token ) + ); + + $mock_user = 'mock_user'; + $mock_payment_method_id = 'pm_mock'; + + $mock_visa_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mock_mastercard_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $mock_giropay_details = [ + 'type' => 'giropay', + ]; + $mock_p24_details = [ + 'type' => 'p24', + ]; + $mock_sofort_details = [ + 'type' => 'sofort', + ]; + $mock_bancontact_details = [ + 'type' => 'bancontact', + ]; + $mock_eps_details = [ + 'type' => 'eps', + ]; + $mock_sepa_details = [ + 'type' => 'sepa_debit', + ]; + $mock_ideal_details = [ + 'type' => 'ideal', + ]; + $mock_becs_details = [ + 'type' => 'au_becs_debit', + ]; + $mock_affirm_details = [ + 'type' => 'affirm', + ]; + $mock_afterpay_details = [ + 'type' => 'afterpay_clearpay', + ]; + + $card_method = $this->payment_methods['card']; + $giropay_method = $this->payment_methods['giropay']; + $p24_method = $this->payment_methods['p24']; + $sofort_method = $this->payment_methods['sofort']; + $bancontact_method = $this->payment_methods['bancontact']; + $eps_method = $this->payment_methods['eps']; + $sepa_method = $this->payment_methods['sepa_debit']; + $ideal_method = $this->payment_methods['ideal']; + $becs_method = $this->payment_methods['au_becs_debit']; + $affirm_method = $this->payment_methods['affirm']; + $afterpay_method = $this->payment_methods['afterpay_clearpay']; + + $this->assertEquals( 'card', $card_method->get_id() ); + $this->assertEquals( 'Credit card / debit card', $card_method->get_title() ); + $this->assertEquals( 'Visa debit card', $card_method->get_title( $mock_visa_details ) ); + $this->assertEquals( 'Mastercard credit card', $card_method->get_title( $mock_mastercard_details ) ); + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertTrue( $card_method->is_reusable() ); + $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); + + $this->assertEquals( 'giropay', $giropay_method->get_id() ); + $this->assertEquals( 'giropay', $giropay_method->get_title() ); + $this->assertEquals( 'giropay', $giropay_method->get_title( $mock_giropay_details ) ); + $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_reusable() ); + + $this->assertEquals( 'p24', $p24_method->get_id() ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title() ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( $mock_p24_details ) ); + $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_reusable() ); + + $this->assertEquals( 'sofort', $sofort_method->get_id() ); + $this->assertEquals( 'Sofort', $sofort_method->get_title() ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( $mock_sofort_details ) ); + $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_reusable() ); + + $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title() ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( $mock_bancontact_details ) ); + $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_reusable() ); + + $this->assertEquals( 'eps', $eps_method->get_id() ); + $this->assertEquals( 'EPS', $eps_method->get_title() ); + $this->assertEquals( 'EPS', $eps_method->get_title( $mock_eps_details ) ); + $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_reusable() ); + + $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title() ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( $mock_sepa_details ) ); + $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_reusable() ); + + $this->assertEquals( 'ideal', $ideal_method->get_id() ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title() ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( $mock_ideal_details ) ); + $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_reusable() ); + + $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title() ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( $mock_becs_details ) ); + $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_reusable() ); + + $this->assertSame( 'affirm', $affirm_method->get_id() ); + $this->assertSame( 'Affirm', $affirm_method->get_title() ); + $this->assertSame( 'Affirm', $affirm_method->get_title( $mock_affirm_details ) ); + $this->assertTrue( $affirm_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $affirm_method->is_reusable() ); + + $this->assertSame( 'afterpay_clearpay', $afterpay_method->get_id() ); + $this->assertSame( 'Afterpay', $afterpay_method->get_title() ); + $this->assertSame( 'Afterpay', $afterpay_method->get_title( $mock_afterpay_details ) ); + $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $afterpay_method->is_reusable() ); + } + + public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { + // Simulate is_changing_payment_method_for_subscription being true so that is_enabled_at_checkout() checks if the payment method is reusable(). + $_GET['change_payment_method'] = 10; + WC_Subscriptions::set_wcs_is_subscription( + function ( $order ) { + return true; + } + ); + + $card_method = $this->payment_methods['card']; + $giropay_method = $this->payment_methods['giropay']; + $sofort_method = $this->payment_methods['sofort']; + $bancontact_method = $this->payment_methods['bancontact']; + $eps_method = $this->payment_methods['eps']; + $sepa_method = $this->payment_methods['sepa_debit']; + $p24_method = $this->payment_methods['p24']; + $ideal_method = $this->payment_methods['ideal']; + $becs_method = $this->payment_methods['au_becs_debit']; + $affirm_method = $this->payment_methods['affirm']; + $afterpay_method = $this->payment_methods['afterpay_clearpay']; + + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $affirm_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $afterpay_method->is_enabled_at_checkout( 'US' ) ); + } + + public function test_only_valid_payment_methods_returned_for_currency() { + $card_method = $this->payment_methods['card']; + $giropay_method = $this->payment_methods['giropay']; + $sofort_method = $this->payment_methods['sofort']; + $bancontact_method = $this->payment_methods['bancontact']; + $eps_method = $this->payment_methods['eps']; + $sepa_method = $this->payment_methods['sepa_debit']; + $p24_method = $this->payment_methods['p24']; + $ideal_method = $this->payment_methods['ideal']; + $becs_method = $this->payment_methods['au_becs_debit']; + $affirm_method = $this->payment_methods['affirm']; + $afterpay_method = $this->payment_methods['afterpay_clearpay']; + + WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; + + $account_domestic_currency = 'USD'; + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + // BNPLs can accept only domestic payments. + $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'AUD'; + $this->assertTrue( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + // BNPLs can accept only domestic payments. + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + $account_domestic_currency = 'CAD'; + $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = ''; + } + + public function test_payment_method_compares_correct_currency() { + $card_method = $this->payment_methods['card']; + $giropay_method = $this->payment_methods['giropay']; + $sofort_method = $this->payment_methods['sofort']; + $bancontact_method = $this->payment_methods['bancontact']; + $eps_method = $this->payment_methods['eps']; + $sepa_method = $this->payment_methods['sepa_debit']; + $p24_method = $this->payment_methods['p24']; + $ideal_method = $this->payment_methods['ideal']; + $becs_method = $this->payment_methods['au_becs_debit']; + $affirm_method = $this->payment_methods['affirm']; + $afterpay_method = $this->payment_methods['afterpay_clearpay']; + + WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; + $account_domestic_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + global $wp; + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $wp->query_vars = [ 'order-pay' => strval( $order_id ) ]; + $order->set_currency( 'USD' ); + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + $wp->query_vars = []; + } + + public function test_create_token_from_setup_intent_adds_token() { + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $mock_setup_intent_id = 'si_mock'; + $mock_user = wp_get_current_user(); + + $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $mock_setup_intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_setup_intention( + [ + 'id' => $mock_setup_intent_id, + 'payment_method' => 'pm_mock', + ] + ) + ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->with( 'pm_mock', $mock_user ) + ->will( + $this->returnValue( $mock_token ) + ); + + $this->assertEquals( $mock_token, $this->card_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); + } + + public function test_exception_will_be_thrown_if_phone_number_is_invalid() { + $order = WC_Helper_Order::create_order(); + $order->set_billing_phone( '+1123456789123456789123' ); + $order->save(); + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'Invalid phone number.' ); + $this->card_gateway->process_payment( $order->get_id() ); + } + + public function test_remove_link_payment_method_if_card_disabled() { + $link_gateway = $this->get_gateway( Payment_Method::LINK ); + $link_gateway->settings['upe_enabled_payment_method_ids'] = [ 'link' ]; + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_cached_account_data' ) + ->willReturn( + [ + 'capabilities' => [ + 'link_payments' => 'active', + ], + 'capability_requirements' => [ + 'link_payments' => [], + ], + ] + ); + + $this->assertSame( $link_gateway->get_payment_method_ids_enabled_at_checkout(), [] ); + } + + /** + * @dataProvider available_payment_methods_provider + */ + public function test_get_upe_available_payment_methods( $payment_methods, $expected_result ) { + $this->mock_wcpay_account + ->expects( $this->once() ) + ->method( 'get_fees' ) + ->willReturn( $payment_methods ); + + $this->assertEquals( $expected_result, $this->card_gateway->get_upe_available_payment_methods() ); + } + + public function available_payment_methods_provider() { + return [ + 'card only' => [ + [ 'card' => [ 'base' => 0.1 ] ], + [ 'card' ], + ], + 'no match with fees' => [ + [ 'some_other_payment_method' => [ 'base' => 0.1 ] ], + [], + ], + 'multiple matches with fees' => [ + [ + 'card' => [ 'base' => 0.1 ], + 'bancontact' => [ 'base' => 0.2 ], + ], + [ 'card', 'bancontact' ], + ], + 'no fees no methods' => [ + [], + [], + ], + ]; + } + + // This test uses a mock of the gateway class due to get_selected_payment_method()'s reliance on the static method WC_Payments::get_payment_method_map(), which can't be mocked. Refactoring the gateway class to avoid using this static method would allow mocking it in tests. + public function test_process_redirect_setup_intent_succeded() { + $order = WC_Helper_Order::create_order(); + + /** @var WC_Payment_Gateway_WCPay */ + $mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->payment_methods['card'], + [ $this->payment_methods ], + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->onlyMethods( + [ + 'manage_customer_details_for_order', + 'get_selected_payment_method', + ] + ) + ->getMock(); + + $order_id = $order->get_id(); + $save_payment_method = true; + $user = wp_get_current_user(); + $intent_status = Intent_Status::SUCCEEDED; + $client_secret = 'cs_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'si_mock'; + $payment_method_id = 'pm_mock'; + $token = WC_Helper_Token::create_token( $payment_method_id ); + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->payment_methods['card']; + + $order->set_shipping_total( 0 ); + $order->set_shipping_tax( 0 ); + $order->set_cart_tax( 0 ); + $order->set_total( 0 ); + $order->save(); + + $setup_intent = WC_Helper_Intention::create_setup_intention( + [ + 'id' => $intent_id, + 'client_secret' => $client_secret, + 'status' => $intent_status, + 'payment_method' => $payment_method_id, + 'payment_method_options' => [ + 'card' => [ + 'request_three_d_secure' => 'automatic', + ], + ], + 'last_setup_error' => [], + ] + ); + + $mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $setup_intent ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $token ) + ); + + $mock_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + // Simulate is_changing_payment_method_for_subscription being true so that is_enabled_at_checkout() checks if the payment method is reusable(). + $_GET['change_payment_method'] = 10; + WC_Subscriptions::set_wcs_is_subscription( + function ( $order ) { + return true; + } + ); + + $mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); + $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); + } + + // This test uses a mock of the gateway class due to get_selected_payment_method()'s reliance on the static method WC_Payments::get_payment_method_map(), which can't be mocked. Refactoring the gateway class to avoid using this static method would allow mocking it in tests. + public function test_process_redirect_payment_save_payment_token() { + /** @var WC_Payment_Gateway_WCPay */ + $mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->payment_methods['card'], + [ $this->payment_methods ], + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->onlyMethods( + [ + 'manage_customer_details_for_order', + 'get_selected_payment_method', + ] + ) + ->getMock(); + + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $save_payment_method = true; + $user = wp_get_current_user(); + $intent_status = Intent_Status::PROCESSING; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + $token = WC_Helper_Token::create_token( $payment_method_id ); + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->payment_methods['card']; + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $payment_intent ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $token ) + ); + + $mock_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + // Simulate is_changing_payment_method_for_subscription being true so that is_enabled_at_checkout() checks if the payment method is reusable(). + $_GET['change_payment_method'] = 10; + WC_Subscriptions::set_wcs_is_subscription( + function ( $order ) { + return true; + } + ); + + $mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + $note = wc_get_order_notes( + [ + 'order_id' => $order_id, + 'limit' => 1, + ] + )[0]; + + $this->assertStringContainsString( 'authorized', $note->content ); + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); + $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); + } + + public function test_get_payment_methods_with_post_request_context() { + $order = WC_Helper_Order::create_order(); + $payment_information = new Payment_Information( 'pm_mock', $order ); + + $_POST['payment_method'] = 'woocommerce_payments'; + + $payment_methods = $this->card_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + public function test_get_payment_methods_without_post_request_context() { + $token = WC_Helper_Token::create_token( 'pm_mock' ); + $order = WC_Helper_Order::create_order(); + $payment_information = new Payment_Information( 'pm_mock', $order, null, $token ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + + $payment_methods = $this->card_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + } + + public function test_get_payment_methods_without_request_context_or_token() { + $payment_information = new Payment_Information( 'pm_mock' ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + + $gateway = WC_Payments::get_gateway(); + WC_Payments::set_gateway( $this->card_gateway ); + + $payment_methods = $this->card_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + WC_Payments::set_gateway( $gateway ); + } + + public function test_get_payment_methods_from_gateway_id_upe() { + WC_Helper_Order::create_order(); + + $gateway = WC_Payments::get_gateway(); + + $payment_methods = $this->card_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::BANCONTACT ); + $this->assertSame( [ Payment_Method::BANCONTACT ], $payment_methods ); + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_cached_account_data' ) + ->willReturn( + [ + 'capabilities' => [ + 'link_payments' => 'active', + 'card_payments' => 'active', + ], + 'capability_requirements' => [ + 'link_payments' => [], + 'card_payments' => [], + ], + ] + ); + $this->card_gateway->settings['upe_enabled_payment_method_ids'] = [ Payment_Method::LINK, Payment_Method::CARD ]; + WC_Payments::set_gateway( $this->card_gateway ); + $payment_methods = $this->card_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $this->assertSame( [ Payment_Method::CARD, Payment_Method::LINK ], $payment_methods ); + + $this->card_gateway->settings['upe_enabled_payment_method_ids'] = [ Payment_Method::CARD ]; + $payment_methods = $this->card_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + WC_Payments::set_gateway( $gateway ); + } + + public function test_display_gateway_html() { + foreach ( $this->gateways as $gateway ) { + /** + * This tests each payment method output separately without concatenating the output + * into 1 single buffer. Each iteration has 1 assertion. + */ + ob_start(); + $gateway->display_gateway_html(); + $actual_output = ob_get_contents(); + ob_end_clean(); + + $this->assertStringContainsString( '
', $actual_output ); + } + } + + public function test_should_not_use_stripe_platform_on_checkout_page_for_non_card() { + foreach ( $this->get_gateways_excluding( [ Payment_Method::CARD ] ) as $gateway ) { + $this->assertFalse( $gateway->should_use_stripe_platform_on_checkout_page() ); + } + } + public function test_attach_exchange_info_to_order_with_no_conversion() { $charge_id = 'ch_mock'; @@ -290,7 +1191,7 @@ public function test_attach_exchange_info_to_order_with_no_conversion() { ->method( 'get_account_default_currency' ) ->willReturn( 'usd' ); - $this->wcpay_gateway->attach_exchange_info_to_order( $order, $charge_id ); + $this->card_gateway->attach_exchange_info_to_order( $order, $charge_id ); // The meta key should not be set. $this->assertEquals( '', $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate' ) ); @@ -309,7 +1210,7 @@ public function test_attach_exchange_info_to_order_with_different_account_curren ->method( 'get_account_default_currency' ) ->willReturn( 'jpy' ); - $this->wcpay_gateway->attach_exchange_info_to_order( $order, $charge_id ); + $this->card_gateway->attach_exchange_info_to_order( $order, $charge_id ); // The meta key should not be set. $this->assertEquals( '', $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate' ) ); @@ -345,7 +1246,7 @@ public function test_attach_exchange_info_to_order_with_zero_decimal_order_curre ] ); - $this->wcpay_gateway->attach_exchange_info_to_order( $order, $charge_id ); + $this->card_gateway->attach_exchange_info_to_order( $order, $charge_id ); $this->assertEquals( 0.009414, $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate' ) ); } @@ -378,7 +1279,7 @@ public function test_attach_exchange_info_to_order_with_different_order_currency ] ); - $this->wcpay_gateway->attach_exchange_info_to_order( $order, $charge_id ); + $this->card_gateway->attach_exchange_info_to_order( $order, $charge_id ); $this->assertEquals( 0.853, $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate' ) ); } @@ -394,7 +1295,7 @@ function ( $output ) { } ); - $this->wcpay_gateway->save_payment_method_checkbox(); + $this->card_gateway->save_payment_method_checkbox(); } public function test_save_payment_method_checkbox_not_displayed_when_force_checked() { @@ -408,13 +1309,13 @@ function ( $output ) { } ); - $this->wcpay_gateway->save_payment_method_checkbox( true ); + $this->card_gateway->save_payment_method_checkbox( true ); } public function test_save_payment_method_checkbox_not_displayed_when_stripe_platform_account_used() { // Setup the test so that should_use_stripe_platform_on_checkout_page returns true. $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => true ] ); - $this->wcpay_gateway->update_option( 'platform_checkout', 'yes' ); + $this->card_gateway->update_option( 'platform_checkout', 'yes' ); add_filter( 'woocommerce_is_checkout', '__return_true' ); WC()->session->init(); WC()->cart->add_to_cart( WC_Helper_Product::create_simple_product()->get_id(), 1 ); @@ -430,7 +1331,7 @@ function ( $output ) { } ); - $this->wcpay_gateway->save_payment_method_checkbox( false ); + $this->card_gateway->save_payment_method_checkbox( false ); remove_filter( 'woocommerce_is_checkout', '__return_true' ); WC()->cart->empty_cart(); @@ -457,7 +1358,7 @@ public function test_capture_charge_success() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention() ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $notes = wc_get_order_notes( [ @@ -509,7 +1410,7 @@ public function test_capture_charge_success_non_usd() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention( [ 'currency' => 'eur' ] ) ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $notes = wc_get_order_notes( [ @@ -558,7 +1459,7 @@ public function test_capture_charge_failure() { ->method( 'format_response' ) ->willReturn( $mock_intent ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $note = wc_get_order_notes( [ @@ -610,7 +1511,7 @@ public function test_capture_charge_failure_non_usd() { ->method( 'format_response' ) ->willReturn( $mock_intent ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $note = wc_get_order_notes( [ @@ -664,7 +1565,7 @@ public function test_capture_charge_api_failure() { ->method( 'format_response' ) ->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $note = wc_get_order_notes( [ @@ -723,7 +1624,7 @@ public function test_capture_charge_api_failure_non_usd() { ->method( 'format_response' ) ->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $note = wc_get_order_notes( [ @@ -778,7 +1679,7 @@ public function test_capture_charge_expired() { ->method( 'format_response' ) ->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) ); - $result = $this->wcpay_gateway->capture_charge( $order ); + $result = $this->card_gateway->capture_charge( $order ); $note = wc_get_order_notes( [ @@ -833,7 +1734,7 @@ public function test_capture_charge_metadata() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention() ); - $result = $this->wcpay_gateway->capture_charge( $order, true, [] ); + $result = $this->card_gateway->capture_charge( $order, true, [] ); $note = wc_get_order_notes( [ @@ -883,7 +1784,7 @@ public function test_capture_charge_without_level3() { ->expects( $this->never() ) ->method( 'get_account_country' ); // stand-in for get_level3_data_from_order. - $result = $this->wcpay_gateway->capture_charge( $order, false ); + $result = $this->card_gateway->capture_charge( $order, false ); $notes = wc_get_order_notes( [ @@ -1075,7 +1976,7 @@ public function test_cancel_authorization_handles_api_exception_when_canceling() ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::CANCELED ] ) ); - $this->wcpay_gateway->cancel_authorization( $order ); + $this->card_gateway->cancel_authorization( $order ); $note = wc_get_order_notes( [ @@ -1110,7 +2011,7 @@ public function test_cancel_authorization_handles_all_api_exceptions() { ->method( 'format_response' ) ->will( $this->throwException( new API_Exception( 'ignore this', 'test', 123 ) ) ); - $this->wcpay_gateway->cancel_authorization( $order ); + $this->card_gateway->cancel_authorization( $order ); $note = wc_get_order_notes( [ @@ -1125,7 +2026,7 @@ public function test_cancel_authorization_handles_all_api_exceptions() { } public function test_add_payment_method_no_method() { - $result = $this->wcpay_gateway->add_payment_method(); + $result = $this->card_gateway->add_payment_method(); $this->assertEquals( 'error', $result['result'] ); } @@ -1159,7 +2060,7 @@ public function test_create_and_confirm_setup_intent_existing_customer() { ) ); - $result = $this->wcpay_gateway->create_and_confirm_setup_intent(); + $result = $this->card_gateway->create_and_confirm_setup_intent(); $this->assertSame( 'seti_mock_123', $result->get_id() ); } @@ -1186,13 +2087,13 @@ public function test_create_and_confirm_setup_intent_no_customer() { ) ); - $result = $this->wcpay_gateway->create_and_confirm_setup_intent(); + $result = $this->card_gateway->create_and_confirm_setup_intent(); $this->assertSame( 'seti_mock_123', $result->get_id() ); } public function test_add_payment_method_no_intent() { - $result = $this->wcpay_gateway->add_payment_method(); + $result = $this->card_gateway->add_payment_method(); $this->assertEquals( 'error', $result['result'] ); } @@ -1222,7 +2123,7 @@ public function test_add_payment_method_success() { ->method( 'add_payment_method_to_user' ) ->with( 'pm_mock', wp_get_current_user() ); - $result = $this->wcpay_gateway->add_payment_method(); + $result = $this->card_gateway->add_payment_method(); $this->assertEquals( 'success', $result['result'] ); } @@ -1241,7 +2142,7 @@ public function test_add_payment_method_no_customer() { ->expects( $this->never() ) ->method( 'add_payment_method_to_user' ); - $result = $this->wcpay_gateway->add_payment_method(); + $result = $this->card_gateway->add_payment_method(); $this->assertEquals( 'error', $result['result'] ); } @@ -1263,7 +2164,7 @@ public function test_add_payment_method_cancelled_intent() { ->expects( $this->never() ) ->method( 'add_payment_method_to_user' ); - $result = $this->wcpay_gateway->add_payment_method(); + $result = $this->card_gateway->add_payment_method(); $this->assertEquals( 'error', $result['result'] ); wc_clear_notices(); @@ -1278,7 +2179,7 @@ public function test_schedule_order_tracking_with_wrong_payment_gateway() { ->expects( $this->never() ) ->method( 'schedule_job' ); - $this->wcpay_gateway->schedule_order_tracking( $order->get_id(), $order ); + $this->card_gateway->schedule_order_tracking( $order->get_id(), $order ); } public function test_schedule_order_tracking_with_sift_disabled() { @@ -1297,7 +2198,7 @@ public function test_schedule_order_tracking_with_sift_disabled() { ] ); - $this->wcpay_gateway->schedule_order_tracking( $order->get_id(), $order ); + $this->card_gateway->schedule_order_tracking( $order->get_id(), $order ); } public function test_schedule_order_tracking_with_no_payment_method_id() { @@ -1319,7 +2220,7 @@ public function test_schedule_order_tracking_with_no_payment_method_id() { ] ); - $this->wcpay_gateway->schedule_order_tracking( $order->get_id(), $order ); + $this->card_gateway->schedule_order_tracking( $order->get_id(), $order ); } public function test_schedule_order_tracking() { @@ -1343,7 +2244,7 @@ public function test_schedule_order_tracking() { ] ); - $this->wcpay_gateway->schedule_order_tracking( $order->get_id(), $order ); + $this->card_gateway->schedule_order_tracking( $order->get_id(), $order ); } public function test_schedule_order_tracking_on_already_created_order() { @@ -1367,12 +2268,12 @@ public function test_schedule_order_tracking_on_already_created_order() { ] ); - $this->wcpay_gateway->schedule_order_tracking( $order->get_id(), $order ); + $this->card_gateway->schedule_order_tracking( $order->get_id(), $order ); } public function test_outputs_payments_settings_screen() { ob_start(); - $this->wcpay_gateway->output_payments_settings_screen(); + $this->card_gateway->output_payments_settings_screen(); $output = ob_get_clean(); $this->assertStringMatchesFormat( '%aid="wcpay-account-settings-container"%a', $output ); } @@ -1380,7 +2281,7 @@ public function test_outputs_payments_settings_screen() { public function test_outputs_express_checkout_settings_screen() { $_GET['method'] = 'foo'; ob_start(); - $this->wcpay_gateway->output_payments_settings_screen(); + $this->card_gateway->output_payments_settings_screen(); $output = ob_get_clean(); $this->assertStringMatchesFormat( '%aid="wcpay-express-checkout-settings-container"%a', $output ); $this->assertStringMatchesFormat( '%adata-method-id="foo"%a', $output ); @@ -1394,11 +2295,11 @@ public function test_outputs_express_checkout_settings_screen() { public function test_validate_account_statement_descriptor_field( $is_valid, $value, $expected = null ) { $key = 'account_statement_descriptor'; if ( $is_valid ) { - $validated_value = $this->wcpay_gateway->validate_account_statement_descriptor_field( $key, $value ); + $validated_value = $this->card_gateway->validate_account_statement_descriptor_field( $key, $value ); $this->assertEquals( $expected ?? $value, $validated_value ); } else { $this->expectExceptionMessage( 'Customer bank statement is invalid.' ); - $this->wcpay_gateway->validate_account_statement_descriptor_field( $key, $value ); + $this->card_gateway->validate_account_statement_descriptor_field( $key, $value ); } } @@ -1435,14 +2336,14 @@ public function test_payment_request_form_field_defaults() { 'cart', 'checkout', ], - $this->wcpay_gateway->get_option( 'payment_request_button_locations' ) + $this->card_gateway->get_option( 'payment_request_button_locations' ) ); $this->assertEquals( 'medium', - $this->wcpay_gateway->get_option( 'payment_request_button_size' ) + $this->card_gateway->get_option( 'payment_request_button_size' ) ); - $form_fields = $this->wcpay_gateway->get_form_fields(); + $form_fields = $this->card_gateway->get_form_fields(); $this->assertEquals( [ @@ -1472,7 +2373,7 @@ public function test_payment_gateway_enabled_for_supported_currency() { ] ) ); - $this->assertTrue( $this->wcpay_gateway->is_available_for_current_currency() ); + $this->assertTrue( $this->card_gateway->is_available_for_current_currency() ); } public function test_payment_gateway_enabled_for_empty_supported_currency_list() { @@ -1482,7 +2383,7 @@ public function test_payment_gateway_enabled_for_empty_supported_currency_list() [] ) ); - $this->assertTrue( $this->wcpay_gateway->is_available_for_current_currency() ); + $this->assertTrue( $this->card_gateway->is_available_for_current_currency() ); } public function test_payment_gateway_disabled_for_unsupported_currency() { @@ -1493,7 +2394,7 @@ public function test_payment_gateway_disabled_for_unsupported_currency() { ] ) ); - $this->assertFalse( $this->wcpay_gateway->is_available_for_current_currency() ); + $this->assertFalse( $this->card_gateway->is_available_for_current_currency() ); } public function test_process_payment_for_order_not_from_request() { @@ -1519,7 +2420,7 @@ public function test_process_payment_for_order_not_from_request() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => 'success' ] ) ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } public function test_process_payment_for_order_rejects_with_cached_minimum_amount() { @@ -1534,10 +2435,10 @@ public function test_process_payment_for_order_rejects_with_cached_minimum_amoun $this->expectException( Exception::class ); $this->expectExceptionMessage( 'The selected payment method requires a total amount of at least $0.50.' ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } - public function test_mandate_data_not_added_to_payment_intent_if_not_required() { + public function test_set_mandate_data_to_payment_intent_if_not_required() { $payment_method = 'woocommerce_payments_sepa_debit'; $order = WC_Helper_Order::create_order(); $order->set_currency( 'USD' ); @@ -1555,13 +2456,13 @@ public function test_mandate_data_not_added_to_payment_intent_if_not_required() $request->expects( $this->never() ) ->method( 'set_mandate_data' ); - // Mandate data is required for SEPA and Stripe Link only, $this->wcpay_gateway is created with card hence mandate data should not be added. - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + // Mandate data is required for SEPA and Stripe Link only, $this->card_gateway is created with card hence mandate data should not be added. + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } - public function test_mandate_data_added_to_payment_intent_if_required() { + public function test_set_mandate_data_to_payment_intent_if_required() { // Mandate data is required for SEPA and Stripe Link, hence creating the gateway with a SEPA payment method should add mandate data. - $gateway = $this->create_gateway_with( new Sepa_Payment_Method( $this->mock_token_service ) ); + $gateway = $this->get_gateway( Payment_Method::SEPA ); $payment_method = 'woocommerce_payments_sepa_debit'; $order = WC_Helper_Order::create_order(); $order->set_currency( 'USD' ); @@ -1593,9 +2494,9 @@ function ( $data ) { $gateway->process_payment_for_order( WC()->cart, $pi ); } - public function test_mandate_data_not_added_to_setup_intent_request_when_link_is_disabled() { + public function test_set_mandate_data_with_setup_intent_request_when_link_is_disabled() { // Disabled link is reflected in upe_enabled_payment_method_ids: when link is disabled, the array contains only card. - $this->wcpay_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card' ]; + $this->card_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card' ]; $payment_method = 'woocommerce_payments'; $order = WC_Helper_Order::create_order(); @@ -1631,11 +2532,11 @@ public function test_mandate_data_not_added_to_setup_intent_request_when_link_is $request->expects( $this->never() ) ->method( 'set_mandate_data' ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } - public function test_mandate_data_added_to_setup_intent_request_when_link_is_enabled() { - $this->wcpay_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card', 'link' ]; + public function test_set_mandate_data_with_setup_intent_request_when_link_is_enabled() { + $this->card_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card', 'link' ]; $payment_method = 'woocommerce_payments'; $order = WC_Helper_Order::create_order(); @@ -1681,8 +2582,38 @@ function ( $data ) { ) ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); - $this->wcpay_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card' ]; + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->settings['upe_enabled_payment_method_ids'] = [ 'card' ]; + } + + public function test_is_mandate_data_required_card_and_link() { + $this->card_gateway->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::LINK ] ); + $this->assertTrue( $this->card_gateway->is_mandate_data_required() ); + } + + public function test_is_mandate_data_required_sepa() { + $sepa = $this->get_gateway( Payment_Method::SEPA ); + $this->assertTrue( $sepa->is_mandate_data_required() ); + } + + public function test_is_mandate_data_required_returns_false() { + foreach ( $this->get_gateways_excluding( [ Payment_Method::SEPA, Payment_Method::CARD ] ) as $gateway ) { + $this->assertFalse( $gateway->is_mandate_data_required() ); + } + } + + public function test_non_reusable_gateways_not_available_when_changing_payment_method_for_card() { + // Simulate is_changing_payment_method_for_subscription being true so that is_enabled_at_checkout() checks if the payment method is reusable(). + $_GET['change_payment_method'] = 10; + WC_Subscriptions::set_wcs_is_subscription( + function ( $order ) { + return true; + } + ); + + foreach ( $this->get_gateways_excluding( [ Payment_Method::CARD ] ) as $gateway ) { + $this->assertFalse( $gateway->is_available() ); + } } public function test_process_payment_for_order_cc_payment_method() { @@ -1705,7 +2636,7 @@ public function test_process_payment_for_order_cc_payment_method() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => 'success' ] ) ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } public function test_process_payment_for_order_upe_payment_method() { @@ -1728,7 +2659,7 @@ public function test_process_payment_for_order_upe_payment_method() { ->method( 'format_response' ) ->willReturn( WC_Helper_Intention::create_intention( [ 'status' => 'success' ] ) ); - $this->wcpay_gateway->process_payment_for_order( WC()->cart, $pi ); + $this->card_gateway->process_payment_for_order( WC()->cart, $pi ); } public function test_process_payment_caches_mimimum_amount_and_displays_error_upon_exception() { @@ -1780,7 +2711,7 @@ public function test_process_payment_caches_mimimum_amount_and_displays_error_up $this->expectExceptionMessage( $message ); try { - $this->wcpay_gateway->process_payment( $order->get_id() ); + $this->card_gateway->process_payment( $order->get_id() ); } catch ( Exception $e ) { $this->assertEquals( '6000', get_transient( 'wcpay_minimum_amount_usd' ) ); throw $e; @@ -1799,7 +2730,7 @@ public function test_process_payment_rejects_if_missing_fraud_prevention_token() $this->expectException( Exception::class ); $this->expectExceptionMessage( "We're not able to process this payment. Please refresh the page and try again." ); - $this->wcpay_gateway->process_payment( $order->get_id() ); + $this->card_gateway->process_payment( $order->get_id() ); } public function test_process_payment_rejects_if_invalid_fraud_prevention_token() { @@ -1822,7 +2753,7 @@ public function test_process_payment_rejects_if_invalid_fraud_prevention_token() $this->expectException( Exception::class ); $this->expectExceptionMessage( "We're not able to process this payment. Please refresh the page and try again." ); - $this->wcpay_gateway->process_payment( $order->get_id() ); + $this->card_gateway->process_payment( $order->get_id() ); } public function test_process_payment_continues_if_valid_fraud_prevention_token() { @@ -1872,7 +2803,7 @@ public function test_get_upe_enabled_payment_method_statuses_with_empty_cache() 'requirements' => [], ], ], - $this->wcpay_gateway->get_upe_enabled_payment_method_statuses() + $this->card_gateway->get_upe_enabled_payment_method_statuses() ); } @@ -1906,7 +2837,7 @@ public function test_get_upe_enabled_payment_method_statuses_with_cache() { 'requirements' => [], ], ], - $this->wcpay_gateway->get_upe_enabled_payment_method_statuses() + $this->card_gateway->get_upe_enabled_payment_method_statuses() ); } @@ -1920,36 +2851,36 @@ public function test_woopay_form_field_defaults() { 'cart', 'checkout', ], - $this->wcpay_gateway->get_option( 'platform_checkout_button_locations' ) + $this->card_gateway->get_option( 'platform_checkout_button_locations' ) ); $this->assertEquals( 'By placing this order, you agree to our [terms] and understand our [privacy_policy].', - $this->wcpay_gateway->get_option( 'platform_checkout_custom_message' ) + $this->card_gateway->get_option( 'platform_checkout_custom_message' ) ); } public function test_is_woopay_enabled_returns_true() { $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => true ] ); - $this->wcpay_gateway->update_option( 'platform_checkout', 'yes' ); + $this->card_gateway->update_option( 'platform_checkout', 'yes' ); wp_set_current_user( 1 ); add_filter( 'woocommerce_is_checkout', '__return_true' ); - $this->assertTrue( $this->woopay_utilities->should_enable_woopay( $this->wcpay_gateway ) ); + $this->assertTrue( $this->woopay_utilities->should_enable_woopay( $this->card_gateway ) ); remove_filter( 'woocommerce_is_checkout', '__return_true' ); } public function test_should_use_stripe_platform_on_checkout_page_not_woopay_eligible() { $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => false ] ); - $this->assertFalse( $this->wcpay_gateway->should_use_stripe_platform_on_checkout_page() ); + $this->assertFalse( $this->card_gateway->should_use_stripe_platform_on_checkout_page() ); } public function test_should_use_stripe_platform_on_checkout_page_not_woopay() { $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => true ] ); - $this->wcpay_gateway->update_option( 'platform_checkout', 'no' ); + $this->card_gateway->update_option( 'platform_checkout', 'no' ); - $this->assertFalse( $this->wcpay_gateway->should_use_stripe_platform_on_checkout_page() ); + $this->assertFalse( $this->card_gateway->should_use_stripe_platform_on_checkout_page() ); } public function is_woopay_falsy_value_provider() { @@ -1971,13 +2902,13 @@ public function test_is_in_dev_mode() { $mode = WC_Payments::mode(); $mode->dev(); - $this->assertTrue( $this->wcpay_gateway->is_in_dev_mode() ); + $this->assertTrue( $this->card_gateway->is_in_dev_mode() ); $mode->test(); - $this->assertFalse( $this->wcpay_gateway->is_in_dev_mode() ); + $this->assertFalse( $this->card_gateway->is_in_dev_mode() ); $mode->live(); - $this->assertFalse( $this->wcpay_gateway->is_in_dev_mode() ); + $this->assertFalse( $this->card_gateway->is_in_dev_mode() ); } /** @@ -1987,13 +2918,13 @@ public function test_is_in_test_mode() { $mode = WC_Payments::mode(); $mode->dev(); - $this->assertTrue( $this->wcpay_gateway->is_in_test_mode() ); + $this->assertTrue( $this->card_gateway->is_in_test_mode() ); $mode->test(); - $this->assertTrue( $this->wcpay_gateway->is_in_test_mode() ); + $this->assertTrue( $this->card_gateway->is_in_test_mode() ); $mode->live(); - $this->assertFalse( $this->wcpay_gateway->is_in_test_mode() ); + $this->assertFalse( $this->card_gateway->is_in_test_mode() ); } /** @@ -2060,7 +2991,7 @@ public function test_should_use_new_process_requires_dev_mode() { $mock_router->expects( $this->never() ) ->method( 'should_use_new_payment_process' ); - $this->assertFalse( $this->wcpay_gateway->should_use_new_process( $order ) ); + $this->assertFalse( $this->card_gateway->should_use_new_process( $order ) ); } public function test_should_use_new_process_returns_false_if_feature_unavailable() { @@ -2078,7 +3009,7 @@ public function test_should_use_new_process_returns_false_if_feature_unavailable ->willReturn( false ); // Act: Call the method. - $result = $this->wcpay_gateway->should_use_new_process( $order ); + $result = $this->card_gateway->should_use_new_process( $order ); $this->assertFalse( $result ); } @@ -2099,7 +3030,7 @@ public function test_should_use_new_process_uses_the_new_process() { ->willReturn( true ); // Act: Call the method. - $result = $this->wcpay_gateway->should_use_new_process( $order ); + $result = $this->card_gateway->should_use_new_process( $order ); $this->assertTrue( $result ); } @@ -2110,7 +3041,7 @@ public function test_should_use_new_process_adds_base_factor() { $order = WC_Helper_Order::create_order( 1, 0 ); $this->expect_router_factor( Factor::NEW_PAYMENT_PROCESS(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_no_payment() { @@ -2120,7 +3051,7 @@ public function test_should_use_new_process_determines_positive_no_payment() { $order = WC_Helper_Order::create_order( 1, 0 ); $this->expect_router_factor( Factor::NO_PAYMENT(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_no_payment() { @@ -2132,7 +3063,7 @@ public function test_should_use_new_process_determines_negative_no_payment() { $order->save(); $this->expect_router_factor( Factor::NO_PAYMENT(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_no_payment_when_saving_pm() { @@ -2145,7 +3076,7 @@ public function test_should_use_new_process_determines_negative_no_payment_when_ $_POST['wc-woocommerce_payments-new-payment-method'] = 'pm_XYZ'; $this->expect_router_factor( Factor::NO_PAYMENT(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_use_saved_pm() { @@ -2160,7 +3091,7 @@ public function test_should_use_new_process_determines_positive_use_saved_pm() { $_POST['wc-woocommerce_payments-payment-token'] = $token->get_id(); $this->expect_router_factor( Factor::USE_SAVED_PM(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_use_saved_pm() { @@ -2174,7 +3105,7 @@ public function test_should_use_new_process_determines_negative_use_saved_pm() { $_POST['wc-woocommerce_payments-payment-token'] = 'new'; $this->expect_router_factor( Factor::USE_SAVED_PM(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_save_pm() { @@ -2186,7 +3117,7 @@ public function test_should_use_new_process_determines_positive_save_pm() { $_POST['wc-woocommerce_payments-new-payment-method'] = '1'; $this->expect_router_factor( Factor::SAVE_PM(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_save_pm_for_subscription() { @@ -2198,7 +3129,7 @@ public function test_should_use_new_process_determines_positive_save_pm_for_subs WC_Subscriptions::$wcs_order_contains_subscription = '__return_true'; $this->expect_router_factor( Factor::SAVE_PM(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_save_pm() { @@ -2214,7 +3145,7 @@ public function test_should_use_new_process_determines_negative_save_pm() { $_POST['wc-woocommerce_payments-payment-token'] = $token->get_id(); $this->expect_router_factor( Factor::SAVE_PM(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_subscription_signup() { @@ -2226,7 +3157,7 @@ public function test_should_use_new_process_determines_positive_subscription_sig WC_Subscriptions::$wcs_order_contains_subscription = '__return_true'; $this->expect_router_factor( Factor::SUBSCRIPTION_SIGNUP(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_subscription_signup() { @@ -2238,7 +3169,7 @@ public function test_should_use_new_process_determines_negative_subscription_sig WC_Subscriptions::$wcs_order_contains_subscription = '__return_false'; $this->expect_router_factor( Factor::SUBSCRIPTION_SIGNUP(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_positive_woopay_payment() { @@ -2250,7 +3181,7 @@ public function test_should_use_new_process_determines_positive_woopay_payment() $_POST['platform-checkout-intent'] = 'pi_ZYX'; $this->expect_router_factor( Factor::WOOPAY_PAYMENT(), true ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_should_use_new_process_determines_negative_woopay_payment() { @@ -2263,7 +3194,7 @@ public function test_should_use_new_process_determines_negative_woopay_payment() unset( $_POST['platform-checkout-intent'] ); $this->expect_router_factor( Factor::WOOPAY_PAYMENT(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } /** @@ -2281,7 +3212,7 @@ public function test_should_use_new_process_determines_negative_wcpay_subscripti add_filter( 'wcpay_is_wcpay_subscriptions_enabled', '__return_true' ); $this->expect_router_factor( Factor::WCPAY_SUBSCRIPTION_SIGNUP(), false ); - $this->wcpay_gateway->should_use_new_process( $order ); + $this->card_gateway->should_use_new_process( $order ); } public function test_new_process_payment() { @@ -2306,7 +3237,7 @@ public function test_new_process_payment() { ->with( $order->get_id() ) ->willReturn( $mock_state ); - $result = $this->wcpay_gateway->process_payment( $order->get_id() ); + $result = $this->card_gateway->process_payment( $order->get_id() ); $this->assertSame( [ 'result' => 'success', @@ -2316,6 +3247,55 @@ public function test_new_process_payment() { ); } + public function test_process_payment_returns_correct_redirect() { + $order = WC_Helper_Order::create_order(); + $_POST = [ 'wcpay-payment-method' => 'pm_mock' ]; + + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ) + ); + + $this->mock_token_service + ->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->willReturn( new WC_Payment_Token_CC() ); + + $result = $this->card_gateway->process_payment( $order->get_id() ); + + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $order->get_checkout_order_received_url(), $result['redirect'] ); + } + + public function test_process_payment_returns_correct_redirect_when_using_payment_request() { + $order = WC_Helper_Order::create_order(); + $_POST['payment_request_type'] = 'google_pay'; + $_POST = [ 'wcpay-payment-method' => 'pm_mock' ]; + + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ) + ); + + $this->mock_token_service + ->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->willReturn( new WC_Payment_Token_CC() ); + + $result = $this->card_gateway->process_payment( $order->get_id() ); + + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $order->get_checkout_order_received_url(), $result['redirect'] ); + } + + public function is_proper_intent_used_with_order_returns_false() { + $this->assertFalse( $this->card_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); + } + /** * Sets up the expectation for a certain factor for the new payment * process to be either set or unset. @@ -2359,20 +3339,74 @@ private function create_charge_object() { return new WC_Payments_API_Charge( $this->mock_charge_id, 1500, $created ); } - private function create_gateway_with( $payment_method ) { - return new WC_Payment_Gateway_WCPay( - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $payment_method, - [ $payment_method ], - $this->mock_rate_limiter, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service + private function init_payment_methods() { + $payment_methods = []; + + $payment_method_classes = [ + CC_Payment_Method::class, + Bancontact_Payment_Method::class, + Sepa_Payment_Method::class, + Giropay_Payment_Method::class, + Sofort_Payment_Method::class, + P24_Payment_Method::class, + Ideal_Payment_Method::class, + Becs_Payment_Method::class, + Eps_Payment_Method::class, + Link_Payment_Method::class, + Affirm_Payment_Method::class, + Afterpay_Payment_Method::class, + Klarna_Payment_Method::class, + ]; + + foreach ( $payment_method_classes as $payment_method_class ) { + $payment_method = new $payment_method_class( $this->mock_token_service ); + $payment_methods[ $payment_method->get_id() ] = new $payment_method_class( $this->mock_token_service ); + } + $this->payment_methods = $payment_methods; + } + + private function init_gateways() { + $this->init_payment_methods(); + $gateways = []; + + foreach ( $this->payment_methods as $payment_method ) { + $gateways[] = new WC_Payment_Gateway_WCPay( + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $payment_method, + $this->payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service + ); + } + + $this->gateways = $gateways; + $this->card_gateway = $gateways[0]; + } + + private function get_gateways_excluding( $excluded_payment_method_ids ) { + return array_filter( + $this->gateways, + function( $gateway ) use ( $excluded_payment_method_ids ) { + return ! in_array( $gateway->get_payment_method()->get_id(), $excluded_payment_method_ids, true ); + } ); } + + private function get_gateway( $payment_method_id ) { + return ( array_values( + array_filter( + $this->gateways, + function( $gateway ) use ( $payment_method_id ) { + return $payment_method_id === $gateway->get_payment_method()->get_id(); + } + ) + ) )[0] ?? null; + } } diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index 35a94949c56..a872511ba2d 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -931,53 +931,39 @@ public function test_is_account_rejected_returns_false_on_error() { $this->assertFalse( $this->wcpay_account->is_account_rejected() ); } - public function test_is_account_partially_onboarded_returns_true() { - $this->mock_database_cache->expects( $this->exactly( 2 ) )->method( 'get_or_add' )->willReturn( - [ - 'account_id' => 'acc_test', - 'live_publishable_key' => 'pk_test_', - 'test_publishable_key' => 'pk_live_', - 'has_pending_requirements' => true, - 'current_deadline' => 12345, - 'is_live' => true, - 'status' => 'restricted', - 'details_submitted' => false, - ] - ); - - $this->assertTrue( $this->wcpay_account->is_account_partially_onboarded() ); - - } - - public function test_is_account_partially_onboarded_returns_false() { - $this->mock_database_cache->expects( $this->exactly( 2 ) )->method( 'get_or_add' )->willReturn( - [ - 'account_id' => 'acc_test', - 'live_publishable_key' => 'pk_test_', - 'test_publishable_key' => 'pk_live_', - 'has_pending_requirements' => true, - 'current_deadline' => 12345, - 'is_live' => true, - 'status' => 'restricted', - 'details_submitted' => true, - ] - ); - - $this->assertFalse( $this->wcpay_account->is_account_partially_onboarded() ); + /** + * Test the is_details_submitted method. + * + * @param bool $details_submitted Whether details_submitted is true for the account. + * + * @return void + * + * @dataProvider is_details_submitted_provider + */ + public function test_is_details_submitted( bool $details_submitted ): void { + $this->mock_database_cache->expects( $this->once() ) + ->method( 'get_or_add' ) + ->willReturn( + [ + 'account_id' => 'acc_test', + 'live_publishable_key' => 'pk_test_', + 'test_publishable_key' => 'pk_live_', + 'has_pending_requirements' => true, + 'current_deadline' => 12345, + 'is_live' => true, + 'status' => 'restricted', + 'details_submitted' => $details_submitted, + ] + ); + $this->assertEquals( $details_submitted, $this->wcpay_account->is_details_submitted() ); } - public function test_is_account_partially_onboarded_returns_false_when_stripe_not_connected() { - $this->mock_empty_cache(); - - $this->mock_wcpay_request( Get_Account::class ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willThrowException( - new API_Exception( 'test', 'wcpay_account_not_found', 401 ) - ); - - $this->assertFalse( $this->wcpay_account->is_account_partially_onboarded() ); + public function is_details_submitted_provider(): array { + return [ + [ true ], + [ false ], + ]; } public function test_is_account_partially_onboarded_returns_false_if_account_not_connected() { diff --git a/tests/unit/test-class-wc-payments-customer-service.php b/tests/unit/test-class-wc-payments-customer-service.php index 4260bf25d40..5d7c14fc34b 100644 --- a/tests/unit/test-class-wc-payments-customer-service.php +++ b/tests/unit/test-class-wc-payments-customer-service.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Database_Cache; use WCPay\Exceptions\API_Exception; @@ -485,7 +486,7 @@ public function test_update_payment_method_with_billing_details_from_order() { 'billing_details' => [ 'address' => [ 'city' => 'WooCity', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'line1' => 'WooAddress', 'postal_code' => '12345', 'state' => 'NY', @@ -589,7 +590,7 @@ private function get_mock_wc_object_for_customer_data( $object_class, $mock_retu 'get_billing_postcode' => '09876', 'get_billing_city' => 'City', 'get_billing_state' => 'State', - 'get_billing_country' => 'US', + 'get_billing_country' => Country_Code::UNITED_STATES, 'get_shipping_first_name' => 'Shipping', 'get_shipping_last_name' => 'Ship', 'get_shipping_address_1' => '2 Street St', @@ -597,7 +598,7 @@ private function get_mock_wc_object_for_customer_data( $object_class, $mock_retu 'get_shipping_postcode' => '76543', 'get_shipping_city' => 'City2', 'get_shipping_state' => 'State2', - 'get_shipping_country' => 'US', + 'get_shipping_country' => Country_Code::UNITED_STATES, ], $mock_return_overrides ); @@ -622,7 +623,7 @@ private function get_mock_customer_data( $overrides = [] ) { 'postal_code' => '09876', 'city' => 'City', 'state' => 'State', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], 'shipping' => [ 'name' => 'Shipping Ship', @@ -632,7 +633,7 @@ private function get_mock_customer_data( $overrides = [] ) { 'postal_code' => '76543', 'city' => 'City2', 'state' => 'State2', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], ], ], @@ -670,7 +671,7 @@ public function test_get_customer_id_for_order() { 'postal_code' => '12345', 'city' => 'WooCity', 'state' => 'NY', - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ], ]; diff --git a/tests/unit/test-class-wc-payments-incentives-service.php b/tests/unit/test-class-wc-payments-incentives-service.php index 0cbaa3b4ae8..4884198caaa 100644 --- a/tests/unit/test-class-wc-payments-incentives-service.php +++ b/tests/unit/test-class-wc-payments-incentives-service.php @@ -261,7 +261,7 @@ function() use ( $response ) { 'tc_url' => 'incentive_tc_url', ], // This is the hash of the test store context: - // 'country' => 'US', + // 'country' => Country_Code::UNITED_STATES, // 'locale' => 'en_US', // 'has_orders' => false, // 'has_payments' => false, diff --git a/tests/unit/test-class-wc-payments-localization-service.php b/tests/unit/test-class-wc-payments-localization-service.php index c002d6cc8a4..32be4fc17fd 100644 --- a/tests/unit/test-class-wc-payments-localization-service.php +++ b/tests/unit/test-class-wc-payments-localization-service.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WC_Payments_Localization_Service_Test unit tests. */ @@ -156,7 +158,7 @@ public function test_get_country_locale_data() { ], ], ], - $this->localization_service->get_country_locale_data( 'BR' ) + $this->localization_service->get_country_locale_data( Country_Code::BRAZIL ) ); } diff --git a/tests/unit/test-class-wc-payments-onboarding-service.php b/tests/unit/test-class-wc-payments-onboarding-service.php index 8cb3f11ac99..982a363cb92 100644 --- a/tests/unit/test-class-wc-payments-onboarding-service.php +++ b/tests/unit/test-class-wc-payments-onboarding-service.php @@ -6,6 +6,7 @@ */ use PHPUnit\Framework\MockObject\MockObject; +use WCPay\Constants\Country_Code; use WCPay\Database_Cache; /** @@ -40,7 +41,7 @@ class WC_Payments_Onboarding_Service_Test extends WCPAY_UnitTestCase { */ private $mock_business_types = [ [ - 'key' => 'US', + 'key' => Country_Code::UNITED_STATES, 'name' => 'United States (US)', 'types' => [ [ @@ -146,12 +147,12 @@ public function test_get_required_verification_information() { $this->mock_api_client ->expects( $this->once() ) ->method( 'get_onboarding_required_verification_information' ) - ->with( 'US', 'company', 'sole_propietorship' ) + ->with( Country_Code::UNITED_STATES, 'company', 'sole_propietorship' ) ->willReturn( $mock_requirements ); $this->assertEquals( $mock_requirements, - $this->onboarding_service->get_required_verification_information( 'US', 'company', 'sole_propietorship' ) + $this->onboarding_service->get_required_verification_information( Country_Code::UNITED_STATES, 'company', 'sole_propietorship' ) ); } diff --git a/tests/unit/test-class-wc-payments-order-service.php b/tests/unit/test-class-wc-payments-order-service.php index 1107c94fcd5..b1d1f030ff0 100644 --- a/tests/unit/test-class-wc-payments-order-service.php +++ b/tests/unit/test-class-wc-payments-order-service.php @@ -1155,6 +1155,12 @@ public function test_set_wcpay_refund_id() { $this->assertEquals( $this->order->get_meta( '_wcpay_refund_id', true ), $wcpay_refund_id ); } + public function set_wcpay_refund_transaction_id_for_order() { + $wcpay_refund_transaction_id = 'txn_mock'; + $this->order_service->set_wcpay_refund_transaction_id_for_order( $this->order, $wcpay_refund_transaction_id ); + $this->assertSame( $this->order->get_meta( WC_Payments_Order_Service::WCPAY_REFUND_TRANSACTION_ID_META_KEY, true ), $wcpay_refund_transaction_id ); + } + public function test_get_wcpay_refund_id() { $wcpay_refund_id = 'ri_1234'; $this->order->update_meta_data( '_wcpay_refund_id', $wcpay_refund_id ); @@ -1205,18 +1211,40 @@ public function test_get_fraud_meta_box_type() { $this->assertEquals( $fraud_meta_box_type_from_service, $fraud_meta_box_type ); } + public function test_set_payment_transaction_id_for_order() { + $transaction_id = 'txn_mock'; + $this->order_service->set_payment_transaction_id_for_order( $this->order, $transaction_id ); + $this->assertSame( $this->order->get_meta( '_wcpay_payment_transaction_id', true ), $transaction_id ); + } + public function test_attach_intent_info_to_order() { - $intent_id = 'pi_mock'; - $intent_status = 'succeeded'; - $payment_method = 'woocommerce_payments'; - $customer_id = 'cus_12345'; - $charge_id = 'ch_mock'; - $currency = 'USD'; - $this->order_service->attach_intent_info_to_order( $this->order, $intent_id, $intent_status, $payment_method, $customer_id, $charge_id, $currency ); + $intent_id = 'pi_mock'; + $intent = WC_Helper_Intention::create_intention( [ 'id' => $intent_id ] ); + $this->order_service->attach_intent_info_to_order( $this->order, $intent ); $this->assertEquals( $intent_id, $this->order->get_meta( '_intent_id', true ) ); } + public function test_attach_intent_info_to_order_after_successful_payment() { + $intent = WC_Helper_Intention::create_intention( + [ + 'id' => 'pi_mock', + 'status' => Intent_Status::SUCCEEDED, + ] + ); + $this->order_service->attach_intent_info_to_order( $this->order, $intent ); + + $another_intent = WC_Helper_Intention::create_intention( + [ + 'id' => 'pi_mock_2', + 'status' => Intent_Status::CANCELED, + ] + ); + $this->order_service->attach_intent_info_to_order( $this->order, $another_intent ); + + $this->assertEquals( Intent_Status::SUCCEEDED, $this->order->get_meta( '_intention_status', true ) ); + } + /** * Several methods use the private method get_order to get the order being worked on. If an order is not found * then an exception is thrown. This test attempt to confirm that exception gets thrown. diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index e4d7412bf15..c774cf5573f 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; @@ -14,7 +15,7 @@ */ class WC_Payments_Payment_Request_Button_Handler_Test extends WCPAY_UnitTestCase { const SHIPPING_ADDRESS = [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'state' => 'CA', 'postcode' => '94110', 'city' => 'San Francisco', @@ -153,6 +154,7 @@ function() { public function tear_down() { parent::tear_down(); + WC_Subscriptions_Cart::set_cart_contains_subscription( false ); WC()->cart->empty_cart(); WC()->session->cleanup_sessions(); $this->zone->delete(); @@ -549,4 +551,20 @@ public function test_get_product_data_returns_the_same_as_build_display_items_wi 'Failed asserting total amount are the same for get_product_data and build_display_items' ); } + + public function test_filter_cart_needs_shipping_address_returns_false() { + sleep( 1 ); + $this->zone->delete_shipping_method( $this->flat_rate_id ); + $this->zone->delete_shipping_method( $this->local_pickup_id ); + + WC_Subscriptions_Cart::set_cart_contains_subscription( true ); + + $this->assertFalse( $this->pr->filter_cart_needs_shipping_address( true ) ); + } + + public function test_filter_cart_needs_shipping_address_returns_true() { + WC_Subscriptions_Cart::set_cart_contains_subscription( true ); + + $this->assertTrue( $this->pr->filter_cart_needs_shipping_address( true ) ); + } } diff --git a/tests/unit/wc-payment-api/models/test-class-wc-payments-api-charge.php b/tests/unit/wc-payment-api/models/test-class-wc-payments-api-charge.php index 5471fbfc5b8..1f06e1dd7bb 100644 --- a/tests/unit/wc-payment-api/models/test-class-wc-payments-api-charge.php +++ b/tests/unit/wc-payment-api/models/test-class-wc-payments-api-charge.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; + /** * WC_Payments_API_Charge unit tests. */ @@ -24,7 +26,7 @@ public function test_payments_api_charge_model_serializes_correctly() { 'address_postal_code_check' => null, 'cvc_check' => null, ], - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'exp_month' => 1, 'exp_year' => 2022, 'fingerprint' => 'mock', diff --git a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php index d6320c14bd0..b9b8e5b79ee 100644 --- a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php +++ b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\Constants\Intent_Status; use WCPay\Exceptions\API_Exception; use WCPay\Internal\Logger; @@ -518,7 +519,7 @@ public function test_create_terminal_location_validation_values() { $this->payments_api_client->create_terminal_location( 'Example', [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, ] ); } @@ -527,7 +528,7 @@ public function test_create_terminal_location_success() { $location = [ 'display_name' => 'Example', 'address' => [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'line1' => 'Some Str. 2', ], 'metadata' => [], @@ -795,8 +796,7 @@ public function test_redacting_params( $request_arguments, $logger_num_calls, .. $mock_logger = $this->getMockBuilder( 'WC_Logger' ) ->setMethods( [ 'log' ] ) ->getMock(); - $mock_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class ); - $mock_internal_logger = new Logger( $mock_logger, WC_Payments::mode(), $mock_gateway ); + $mock_internal_logger = new Logger( $mock_logger, WC_Payments::mode() ); wcpay_get_test_container()->replace( Logger::class, $mock_internal_logger ); WC_Payments::mode()->dev(); @@ -918,7 +918,7 @@ public function test_get_onboarding_po_eligible() { $po_eligible = $this->payments_api_client->get_onboarding_po_eligible( [ - 'country' => 'US', + 'country' => Country_Code::UNITED_STATES, 'type' => 'company', 'mcc' => 'most_popular__software_services', ], diff --git a/tests/unit/woopay/class-woopay-scheduler-test.php b/tests/unit/woopay/class-woopay-scheduler-test.php index 05d689527e3..0fa6db45784 100644 --- a/tests/unit/woopay/class-woopay-scheduler-test.php +++ b/tests/unit/woopay/class-woopay-scheduler-test.php @@ -5,6 +5,7 @@ * @package WooCommerce\Payments\Tests */ +use WCPay\Constants\Country_Code; use WCPay\WooPay\WooPay_Scheduler; use WCPay\WooPay\WooPay_Utilities; @@ -198,7 +199,7 @@ public function test_update_adapted_extensions_and_available_countries_list() { 'test-extension', 'test-extension-2', ]; - $available_countries = [ 'US', 'BR' ]; + $available_countries = [ Country_Code::UNITED_STATES, Country_Code::BRAZIL ]; $this->mock_api_response( [], $adapted_extensions, $available_countries ); diff --git a/woocommerce-payments.php b/woocommerce-payments.php index 7f453eb5c35..d1db912b2e6 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -12,7 +12,7 @@ * WC tested up to: 8.4.0 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 7.0.0 + * Version: 7.1.0 * * @package WooCommerce\Payments */ @@ -333,7 +333,16 @@ function wcpay_get_jetpack_idc_custom_content(): array { $urls = Automattic\Jetpack\Identity_Crisis::get_mismatched_urls(); if ( false !== $urls ) { $current_url = untrailingslashit( $urls['current_url'] ); - $wpcom_url = untrailingslashit( $urls['wpcom_url'] ); + /** + * Undo the reverse the Jetpack IDC library is doing since we want to display the URL. + * + * @see https://github.com/Automattic/jetpack-identity-crisis/blob/trunk/src/class-identity-crisis.php#L471 + */ + $idc_sync_error = Automattic\Jetpack\Identity_Crisis::check_identity_crisis(); + if ( is_array( $idc_sync_error ) && ! empty( $idc_sync_error['reversed_url'] ) ) { + $urls['wpcom_url'] = strrev( $urls['wpcom_url'] ); + } + $wpcom_url = untrailingslashit( $urls['wpcom_url'] ); $custom_content['migrateCardBodyText'] = sprintf( /* translators: %1$s: The current site domain name. %2$s: The original site domain name. Please keep hostname tags in your translation so that they can be formatted properly. %3$s: WooPayments. */