diff --git a/src/feedback/data/sagas.js b/src/feedback/data/sagas.js index 4a5bfe57c..767d3d967 100644 --- a/src/feedback/data/sagas.js +++ b/src/feedback/data/sagas.js @@ -1,9 +1,12 @@ import { put } from 'redux-saga/effects'; +import { getConfig, ensureConfig } from '@edx/frontend-platform'; import { logError, logInfo } from '@edx/frontend-platform/logging'; import { addMessage, clearMessages } from './actions'; import { MESSAGE_TYPES } from './constants'; +ensureConfig(['ECOMMERCE_BASE_URL'], 'Alert saga'); + export function* handleErrors(e, clearExistingMessages) { if (clearExistingMessages) { yield put(clearMessages()); @@ -56,6 +59,25 @@ export function* handleMessages(messages, clearExistingMessages, url) { return null; } +/** + * handleSDNCheckFailure + * log the SDN check failure error message + * and redirect user to SDN failure page +*/ +const handleSDNCheckFailure = (error) => { + const setLocation = href => { global.location.href = href; }; + logError(error, { + messagePrefix: 'SDN Check Error', + paymentMethod: 'Stripe', + paymentErrorType: 'SDN Check Submit Api', + program_uuid: error?.data?.program_uuid, + }); + if (getConfig().ENVIRONMENT !== 'test') { + // SDN failure: redirect to Ecommerce SDN error page. + setLocation(`${getConfig().ECOMMERCE_BASE_URL}/payment/sdn/failure/`); + } +}; + /** * Handle Subscription Errors */ @@ -70,15 +92,19 @@ export function* handleSubscriptionErrors(e, clearExistingMessages) { if (e.errors !== undefined) { for (let i = 0; i < e.errors.length; i++) { // eslint-disable-line no-plusplus const error = e.errors[i]; - const customErrors = [ - 'empty_subscription', - 'embargo_error', - 'basket_changed_error', - 'program_unavailable', - 'ineligible_program', - 'payment_attachment_error', - ]; - if (error.code !== 'empty_subscription') { + if (error.code === 'sdn_check_failure') { + handleSDNCheckFailure(error); + } else if (error.code === 'empty_subscription') { + // do nothing as empty placeholder message will be rendered by Subscription Page + } else { + const customErrors = [ + 'embargo_error', + 'basket_changed_error', + 'program_unavailable', + 'ineligible_program', + 'payment_attachment_error', + ]; + if (customErrors.includes(error.code)) { if (error.code !== 'create-paymentMethod') { // already logged error logInfo('API Error', error.code); diff --git a/src/subscription/checkout/submit-button/SubscriptionSubmitButton.jsx b/src/subscription/checkout/submit-button/SubscriptionSubmitButton.jsx index 851904ec9..b6aeda656 100644 --- a/src/subscription/checkout/submit-button/SubscriptionSubmitButton.jsx +++ b/src/subscription/checkout/submit-button/SubscriptionSubmitButton.jsx @@ -15,7 +15,9 @@ const SubscriptionSubmitButton = ({ if (disabled) { submitButtonState = 'disabled'; } if (isProcessing) { submitButtonState = 'processing'; } // handle submitted state - if (status === 'trialing' || status === 'succeeded') { submitButtonState = 'success'; } + if (status === 'trialing' || status === 'succeeded') { + submitButtonState = 'success'; + } return (
@@ -27,7 +29,7 @@ const SubscriptionSubmitButton = ({ { { intl.formatMessage(messages[`subscription.confirmation.modal.${subscriptionState}.heading`], { - programTitle, + programTitle: {programTitle}, }) } diff --git a/src/subscription/confirmation-modal/ConfirmationModal.test.jsx b/src/subscription/confirmation-modal/ConfirmationModal.test.jsx index 0af2da793..b4f8c332e 100644 --- a/src/subscription/confirmation-modal/ConfirmationModal.test.jsx +++ b/src/subscription/confirmation-modal/ConfirmationModal.test.jsx @@ -15,6 +15,18 @@ import { subscriptionStatusReceived } from '../data/status/actions'; import { camelCaseObject } from '../../payment/data/utils'; +function getCustomTextContent(content, node) { + // eslint-disable-next-line no-shadow + // The textContent property sets or returns the text content of the specified node, and all its descendants. + const hasText = (elem) => elem.textContent === this.searchFor; + const nodeHasText = hasText(node); + const childrenDontHaveText = Array.from(node.children).every( + (child) => !hasText(child), + ); + + return nodeHasText && childrenDontHaveText; +} + /** * ConfirmationModal Test */ @@ -43,7 +55,7 @@ describe('', () => { ); store.dispatch(fetchSubscriptionDetails.fulfill()); }); - const heading = getByText(`Congratulations! Your subscription to ${subscriptionDetails.programTitle} has started.`); + const heading = getByText(getCustomTextContent.bind({ searchFor: `Congratulations! Your subscription to ${subscriptionDetails.programTitle} has started.` })); expect(heading).toBeInTheDocument(); }); @@ -62,7 +74,7 @@ describe('', () => { store.dispatch(fetchSubscriptionDetails.fulfill()); }); - const heading = getByText(`Congratulations! Your 7-day free trial of ${subscriptionDetails.programTitle} has started.`); + const heading = getByText(getCustomTextContent.bind({ searchFor: `Congratulations! Your 7-day free trial of ${subscriptionDetails.programTitle} has started.` })); expect(heading).toBeInTheDocument(); const gotoDashboardLink = getByRole('link', { name: 'Go to dashboard' }); diff --git a/src/subscription/data/service.js b/src/subscription/data/service.js index 73ae34979..6662619ab 100644 --- a/src/subscription/data/service.js +++ b/src/subscription/data/service.js @@ -30,6 +30,7 @@ export function handleDetailsApiError(requestError) { errors.push({ code: error_code, userMessage: user_message, + data: this?.payload, }); } const apiError = new Error(); @@ -50,6 +51,6 @@ export async function postDetails(postData) { `${getConfig().SUBSCRIPTIONS_BASE_URL}/api/v1/stripe-checkout/`, postData, ) - .catch(handleDetailsApiError); + .catch(handleDetailsApiError.bind({ payload: postData })); return transformSubscriptionDetails(data); }