Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into fix/6567-user-set-…
Browse files Browse the repository at this point in the history
…date-and-time-formatting-arent-respected-in-react-components
  • Loading branch information
mgascam committed Dec 19, 2024
2 parents 7a83982 + 5005b6b commit d7d0e00
Show file tree
Hide file tree
Showing 28 changed files with 585 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: dev

Remove hooks from customer and token services to dedicated methods
4 changes: 4 additions & 0 deletions changelog/feat-9810-add-seller-message
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Add seller_message to failed order notes
4 changes: 4 additions & 0 deletions changelog/fix-9114-level3-rounding
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fix

Add a rounding entry to Level 3 data for rare cases where rounding errors break calculations.
4 changes: 4 additions & 0 deletions changelog/fix-method-title-availability
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fix

Set payment method title once title is known.
5 changes: 5 additions & 0 deletions changelog/fix-tokenized-cart-subscription-signup-fee
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: fix
Comment: fix: tokenized cart subscription signup fee price


4 changes: 4 additions & 0 deletions changelog/load-checkout-scripts-on-checkout-if-not-loaded
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

Load checkout scripts when they are not previously loaded on checkout page.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: update

Update error messages for payment authorization actions to provide more specific and user-friendly feedback.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: update

Add failure reason to failed payments in the timeline.
102 changes: 86 additions & 16 deletions client/data/authorizations/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,51 @@ import {
import { STORE_NAME } from '../constants';
import { ApiError } from 'wcpay/types/errors';

const getErrorMessage = ( apiError: {
code?: string;
message?: string;
} ): string => {
// Map specific error codes to user-friendly messages
const errorMessages: Record< string, string > = {
wcpay_missing_order: __(
'The order could not be found.',
'woocommerce-payments'
),
wcpay_refunded_order_uncapturable: __(
'Payment cannot be processed for partially or fully refunded orders.',
'woocommerce-payments'
),
wcpay_intent_order_mismatch: __(
'The payment cannot be processed due to a mismatch with order details.',
'woocommerce-payments'
),
wcpay_payment_uncapturable: __(
'This payment cannot be processed in its current state.',
'woocommerce-payments'
),
wcpay_capture_error: __(
'The payment capture failed to complete.',
'woocommerce-payments'
),
wcpay_cancel_error: __(
'The payment cancellation failed to complete.',
'woocommerce-payments'
),
wcpay_server_error: __(
'An unexpected error occurred. Please try again later.',
'woocommerce-payments'
),
};

return (
errorMessages[ apiError.code ?? '' ] ??
__(
'Unable to process the payment. Please try again later.',
'woocommerce-payments'
)
);
};

export function updateAuthorizations(
query: Query,
data: Authorization[]
Expand Down Expand Up @@ -165,17 +210,29 @@ export function* submitCaptureAuthorization(
)
);
} catch ( error ) {
const baseErrorMessage = sprintf(
// translators: %s Order id
__(
'There has been an error capturing the payment for order #%s.',
'woocommerce-payments'
),
orderId
);

const apiError = error as {
code?: string;
message?: string;
data?: {
status?: number;
};
};

const errorDetails = getErrorMessage( apiError );

yield controls.dispatch(
'core/notices',
'createErrorNotice',
sprintf(
// translators: %s Order id
__(
'There has been an error capturing the payment for order #%s. Please try again later.',
'woocommerce-payments'
),
orderId
)
`${ baseErrorMessage } ${ errorDetails }`
);
} finally {
yield controls.dispatch(
Expand All @@ -184,6 +241,7 @@ export function* submitCaptureAuthorization(
'getAuthorization',
[ paymentIntentId ]
);

yield controls.dispatch(
STORE_NAME,
'setIsRequestingAuthorization',
Expand Down Expand Up @@ -278,17 +336,29 @@ export function* submitCancelAuthorization(
)
);
} catch ( error ) {
const baseErrorMessage = sprintf(
// translators: %s Order id
__(
'There has been an error canceling the payment for order #%s.',
'woocommerce-payments'
),
orderId
);

const apiError = error as {
code?: string;
message?: string;
data?: {
status?: number;
};
};

const errorDetails = getErrorMessage( apiError );

yield controls.dispatch(
'core/notices',
'createErrorNotice',
sprintf(
// translators: %s Order id
__(
'There has been an error canceling the payment for order #%s. Please try again later.',
'woocommerce-payments'
),
orderId
)
`${ baseErrorMessage } ${ errorDetails }`
);
} finally {
yield controls.dispatch(
Expand Down
159 changes: 157 additions & 2 deletions client/data/authorizations/test/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
updateAuthorization,
} from '../actions';
import authorizationsFixture from './authorizations.fixture.json';
import { STORE_NAME } from 'wcpay/data/constants';

describe( 'Authorizations actions', () => {
describe( 'submitCaptureAuthorization', () => {
Expand Down Expand Up @@ -153,10 +154,117 @@ describe( 'Authorizations actions', () => {
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error capturing the payment for order #42. Please try again later.'
'There has been an error capturing the payment for order #42. Unable to process the payment. Please try again later.'
)
);
} );

describe( 'error handling', () => {
it( 'should create error notice with API error message', () => {
const generator = submitCaptureAuthorization( 'pi_123', 123 );

// Mock the start of the capture process
expect( generator.next().value ).toEqual(
controls.dispatch(
STORE_NAME,
'startResolution',
'getAuthorization',
[ 'pi_123' ]
)
);

expect( generator.next().value ).toEqual(
controls.dispatch(
STORE_NAME,
'setIsRequestingAuthorization',
true
)
);

// Mock API error response
const apiError = {
code: 'wcpay_refunded_order_uncapturable',
message:
'Payment cannot be captured for partially or fully refunded orders.',
data: { status: 400 },
};

// Simulate API error
expect( generator.throw( apiError ).value ).toEqual(
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error capturing the payment for order #123. Payment cannot be processed for partially or fully refunded orders.'
)
);

// Verify cleanup in finally block
expect( generator.next().value ).toEqual(
controls.dispatch(
STORE_NAME,
'finishResolution',
'getAuthorization',
[ 'pi_123' ]
)
);

expect( generator.next().value ).toEqual(
controls.dispatch(
STORE_NAME,
'setIsRequestingAuthorization',
false
)
);
} );

it( 'should create error notice with fallback message when API error has no message', () => {
const generator = submitCaptureAuthorization( 'pi_123', 123 );

// Skip initial dispatch calls
generator.next();
generator.next();

// Mock API error without message
const apiError = {
code: 'unknown_error',
data: { status: 500 },
};

expect( generator.throw( apiError ).value ).toEqual(
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error capturing the payment for order #123. Unable to process the payment. Please try again later.'
)
);
} );

it( 'should show default error notice for unknown error code', () => {
const generator = submitCaptureAuthorization(
'pi_unknown',
999
);

// Start the generator to the point where it would throw an error
generator.next();
generator.next();

// Mock an API error with an unknown error code
const apiError = {
code: 'unknown_error_code',
data: { status: 500 },
};

// Expect the default error message to be dispatched
expect( generator.throw( apiError ).value ).toEqual(
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error capturing the payment for order #999. Unable to process the payment. Please try again later.'
)
);
} );
} );
} );

describe( 'submitCancelAuthorization', () => {
Expand Down Expand Up @@ -294,9 +402,56 @@ describe( 'Authorizations actions', () => {
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error canceling the payment for order #42. Please try again later.'
'There has been an error canceling the payment for order #42. Unable to process the payment. Please try again later.'
)
);
} );

describe( 'error handling', () => {
it( 'should create error notice with API error message', () => {
const generator = submitCancelAuthorization( 'pi_123', 123 );

// Skip initial dispatch calls
generator.next();
generator.next();

// Mock API error response
const apiError = {
code: 'wcpay_payment_uncapturable',
message: 'The payment cannot be canceled at this time.',
data: { status: 400 },
};

expect( generator.throw( apiError ).value ).toEqual(
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error canceling the payment for order #123. This payment cannot be processed in its current state.'
)
);
} );

it( 'should create error notice with fallback message when API error has no message', () => {
const generator = submitCancelAuthorization( 'pi_123', 123 );

// Skip initial dispatch calls
generator.next();
generator.next();

// Mock API error without message
const apiError = {
code: 'unknown_error',
data: { status: 500 },
};

expect( generator.throw( apiError ).value ).toEqual(
controls.dispatch(
'core/notices',
'createErrorNotice',
'There has been an error canceling the payment for order #123. Unable to process the payment. Please try again later.'
)
);
} );
} );
} );
} );
Loading

0 comments on commit d7d0e00

Please sign in to comment.