diff --git a/.env b/.env index 7832763df9..b0129d312a 100644 --- a/.env +++ b/.env @@ -12,6 +12,7 @@ CREDIT_HELP_LINK_URL='' CSRF_TOKEN_API_PATH='' DISCOVERY_API_BASE_URL='' DISCUSSIONS_MFE_BASE_URL='' +DISCOUNT_CODE_INFO_URL='' ECOMMERCE_BASE_URL='' ENABLE_JUMPNAV='true' ENABLE_NOTICES='' diff --git a/.env.development b/.env.development index 5dd532769a..7f9cdfc709 100644 --- a/.env.development +++ b/.env.development @@ -12,6 +12,7 @@ CREDIT_HELP_LINK_URL='https://help.edx.org/edxlearner/s/article/Can-I-receive-co CSRF_TOKEN_API_PATH='/csrf/api/v1/token' DISCOVERY_API_BASE_URL='http://localhost:18381' DISCUSSIONS_MFE_BASE_URL='http://localhost:2002' +DISCOUNT_CODE_INFO_URL='' ECOMMERCE_BASE_URL='http://localhost:18130' ENABLE_JUMPNAV='true' ENABLE_NOTICES='' diff --git a/.env.test b/.env.test index b17621b0e3..42e4756f9e 100644 --- a/.env.test +++ b/.env.test @@ -12,6 +12,7 @@ CREDIT_HELP_LINK_URL='https://help.edx.org/edxlearner/s/article/Can-I-receive-co CSRF_TOKEN_API_PATH='/csrf/api/v1/token' DISCOVERY_API_BASE_URL='http://localhost:18381' DISCUSSIONS_MFE_BASE_URL='http://localhost:2002' +DISCOUNT_CODE_INFO_URL='' ECOMMERCE_BASE_URL='http://localhost:18130' ENABLE_JUMPNAV='true' ENABLE_NOTICES='' diff --git a/src/index.jsx b/src/index.jsx index b3748ca688..1b5cacc2af 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -171,6 +171,7 @@ initialize({ CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null, CREDIT_HELP_LINK_URL: process.env.CREDIT_HELP_LINK_URL || null, DISCUSSIONS_MFE_BASE_URL: process.env.DISCUSSIONS_MFE_BASE_URL || null, + DISCOUNT_CODE_INFO_URL: process.env.DISCOUNT_CODE_INFO_URL || null, ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null, ENTERPRISE_LEARNER_PORTAL_URL: process.env.ENTERPRISE_LEARNER_PORTAL_URL || null, ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null, diff --git a/src/shared/streak-celebration/StreakCelebrationModal.jsx b/src/shared/streak-celebration/StreakCelebrationModal.jsx index f3b33b5feb..4845aaaec0 100644 --- a/src/shared/streak-celebration/StreakCelebrationModal.jsx +++ b/src/shared/streak-celebration/StreakCelebrationModal.jsx @@ -49,6 +49,16 @@ async function calculateVoucherDiscount(voucher, sku, username) { .then(res => camelCaseObject(res)); } +async function getDiscountCodeInfo(code, courseId) { + const params = new URLSearchParams(); + params.append('code', code); + params.append('course_run_key', courseId); + const url = `${getConfig().DISCOUNT_CODE_INFO_URL}?${params.toString()}`; + + return getAuthenticatedHttpClient().get(url) + .then(res => camelCaseObject(res)); +} + const CloseText = ({ intl }) => ( {intl.formatMessage(messages.streakButton)} @@ -83,34 +93,48 @@ const StreakModal = ({ // Ask ecommerce to calculate discount savings useEffect(() => { - if (streakDiscountCouponEnabled && verifiedMode && getConfig().ECOMMERCE_BASE_URL) { - calculateVoucherDiscount(discountCode, verifiedMode.sku, username) - .then( - (result) => { + (async () => { + let newDiscountPercentage = 0; + try { + if (streakDiscountCouponEnabled && verifiedMode) { + if (getConfig().DISCOUNT_CODE_INFO_URL) { + const result = await getDiscountCodeInfo(discountCode, courseId); + const { isApplicable, discountPercentage } = result.data; + if (isApplicable) { + // Just store the percent, because ecommerce doesn't give us the + // currency symbol to use, so we want to use the symbol that LMS + // gives us. And we don't want to assume ecommerce's currency is + // the same as the LMS. So we'll keep using the values in + // verifiedMode, just multiplied by the received percentage. + newDiscountPercentage = discountPercentage; + sendTrackEvent('edx.bi.course.streak_discount_enabled', { + course_id: courseId, + sku: verifiedMode.sku, + }); + } + } else if (getConfig().ECOMMERCE_BASE_URL) { + const result = await calculateVoucherDiscount(discountCode, verifiedMode.sku, username); const { totalInclTax, totalInclTaxExclDiscounts } = result.data; if (totalInclTaxExclDiscounts && totalInclTax !== totalInclTaxExclDiscounts) { // Just store the percent (rather than using these values directly), because ecommerce doesn't give us // the currency symbol to use, so we want to use the symbol that LMS gives us. And I don't want to assume // ecommerce's currency is the same as the LMS. So we'll keep using the values in verifiedMode, just // multiplied by the calculated percentage. - setDiscountPercent(1 - totalInclTax / totalInclTaxExclDiscounts); + newDiscountPercentage = 1 - totalInclTax / totalInclTaxExclDiscounts; sendTrackEvent('edx.bi.course.streak_discount_enabled', { course_id: courseId, sku: verifiedMode.sku, }); - } else { - setDiscountPercent(0); } - }, - () => { - // ignore any errors - we just won't show the discount to the user then - setDiscountPercent(0); - }, - ); - } else { - setDiscountPercent(0); - } - // eslint-disable-next-line react-hooks/exhaustive-deps + } + } + } catch { + // ignore any errors - we just won't show the discount to the user then + } finally { + setDiscountPercent(newDiscountPercentage); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [streakDiscountCouponEnabled, username, verifiedMode]); if (!isStreakCelebrationOpen) { diff --git a/src/shared/streak-celebration/StreakCelebrationModal.test.jsx b/src/shared/streak-celebration/StreakCelebrationModal.test.jsx index 345f36a0ee..c71b9946a4 100644 --- a/src/shared/streak-celebration/StreakCelebrationModal.test.jsx +++ b/src/shared/streak-celebration/StreakCelebrationModal.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Factory } from 'rosie'; -import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { camelCaseObject, getConfig, mergeConfig } from '@edx/frontend-platform'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { breakpoints } from '@openedx/paragon'; @@ -34,6 +34,19 @@ describe('Loaded Tab Page', () => { }); } + function setDiscountViaDiscountCodeInfo(percent) { + const discountURLParams = new URLSearchParams(); + discountURLParams.append('code', 'ZGY11119949'); + discountURLParams.append('course_run_key', courseMetadata.id); + const discountURL = `${getConfig().DISCOUNT_CODE_INFO_URL}?${discountURLParams.toString()}`; + + mockData.streakDiscountCouponEnabled = true; + axiosMock.onGet(discountURL).reply(200, { + isApplicable: true, + discountPercentage: percent / 100, + }); + } + function setDiscountError() { mockData.streakDiscountCouponEnabled = true; axiosMock.onGet(calculateUrl).reply(500); @@ -105,4 +118,22 @@ describe('Loaded Tab Page', () => { sku: mockData.verifiedMode.sku, }); }); + + it('shows discount version of streak celebration modal when discount available and info fetched using DISCOUNT_CODE_INFO_URL', async () => { + mergeConfig({ DISCOUNT_CODE_INFO_URL: 'http://localhost:8140/lms/discount-code-info/' }); + + global.innerWidth = breakpoints.extraSmall.maxWidth; + setDiscountViaDiscountCodeInfo(14); + await renderModal(); + + const endDateText = `Ends ${new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toLocaleDateString({ timeZone: 'UTC' })}.`; + expect(screen.getByText('You’ve unlocked a 14% off discount when you upgrade this course for a limited time only.', { exact: false })).toBeInTheDocument(); + expect(screen.getByText(endDateText, { exact: false })).toBeInTheDocument(); + expect(screen.getByText('Continue with course')).toBeInTheDocument(); + expect(screen.queryByText('Keep it up')).not.toBeInTheDocument(); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.course.streak_discount_enabled', { + course_id: mockData.courseId, + sku: mockData.verifiedMode.sku, + }); + }); });