chore(auth&payments): Remove feature flag

This commit is contained in:
Lisa Chan 2023-08-07 10:29:59 -04:00
Родитель f05170cc21
Коммит a39d69372b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9052E177BBC5E764
16 изменённых файлов: 78 добавлений и 356 удалений

Просмотреть файл

@ -903,14 +903,6 @@ const convictConf = convict({
format: String, format: String,
doc: 'Stripe API key for direct Stripe integration', doc: 'Stripe API key for direct Stripe integration',
}, },
stripeInvoiceImmediately: {
enabled: {
default: false,
doc: 'Enables immediate invoicing for stripe in all subscription upgrades',
env: 'SUBSCRIPTIONS_STRIPE_INVOICE_IMMEDIATELY',
format: Boolean,
},
},
stripeWebhookPayloadLimit: { stripeWebhookPayloadLimit: {
default: 1048576, default: 1048576,
env: 'STRIPE_WEBHOOK_PAYLOAD_LIMIT', env: 'STRIPE_WEBHOOK_PAYLOAD_LIMIT',

Просмотреть файл

@ -4,7 +4,6 @@
import * as invoiceDTO from 'fxa-shared/dto/auth/payments/invoice'; import * as invoiceDTO from 'fxa-shared/dto/auth/payments/invoice';
import { InvoicePreview } from 'fxa-shared/subscriptions/types'; import { InvoicePreview } from 'fxa-shared/subscriptions/types';
import { Stripe } from 'stripe'; import { Stripe } from 'stripe';
import { config } from '../../config';
/** /**
* Formats a Stripe Invoice to the FirstInvoicePreview DTO format. * Formats a Stripe Invoice to the FirstInvoicePreview DTO format.
@ -51,10 +50,7 @@ export function stripeInvoiceToFirstInvoicePreviewDTO(
}; };
} }
if ( if (invoice[1]) {
invoice[1] &&
config.getProperties().subscriptions.stripeInvoiceImmediately
) {
const proration = invoice[1].lines.data.find( const proration = invoice[1].lines.data.find(
(lineItem) => lineItem.proration (lineItem) => lineItem.proration
); );

Просмотреть файл

@ -689,11 +689,7 @@ export class StripeHelper extends StripeHelperBase {
); );
let proratedInvoice; let proratedInvoice;
if ( if (isUpgrade && requestObject.subscription_items?.length) {
isUpgrade &&
requestObject.subscription_items?.length &&
this.config.subscriptions.stripeInvoiceImmediately
) {
try { try {
requestObject.subscription_proration_behavior = 'always_invoice'; requestObject.subscription_proration_behavior = 'always_invoice';
requestObject.subscription_proration_date = Math.floor( requestObject.subscription_proration_date = Math.floor(
@ -1454,7 +1450,10 @@ export class StripeHelper extends StripeHelperBase {
expand: ['data.customer', 'data.subscription'], expand: ['data.customer', 'data.subscription'],
})) { })) {
const subscription = invoice.subscription as Stripe.Subscription; const subscription = invoice.subscription as Stripe.Subscription;
if (subscription && ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) { if (
subscription &&
ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)
) {
yield invoice; yield invoice;
} }
} }
@ -1974,11 +1973,6 @@ export class StripeHelper extends StripeHelperBase {
plan_change_date: moment().unix(), plan_change_date: moment().unix(),
}; };
const prorationBehavior = this.config.subscriptions.stripeInvoiceImmediately
.enabled
? 'always_invoice'
: 'create_prorations';
const updatedSubscription = await this.updateSubscriptionAndBackfill( const updatedSubscription = await this.updateSubscriptionAndBackfill(
subscription, subscription,
{ {
@ -1989,7 +1983,7 @@ export class StripeHelper extends StripeHelperBase {
plan: newPlanId, plan: newPlanId,
}, },
], ],
proration_behavior: prorationBehavior, proration_behavior: 'always_invoice',
metadata: updatedMetadata, metadata: updatedMetadata,
} }
); );
@ -3137,7 +3131,6 @@ export class StripeHelper extends StripeHelperBase {
id: invoiceId, id: invoiceId,
number: invoiceNumber, number: invoiceNumber,
currency: paymentProratedCurrency, currency: paymentProratedCurrency,
total: invoiceTotalNewInCents,
amount_due: invoiceAmountDue, amount_due: invoiceAmountDue,
} = invoice; } = invoice;
@ -3164,87 +3157,8 @@ export class StripeHelper extends StripeHelperBase {
subscriptionId: subscription.id, subscriptionId: subscription.id,
}); });
const { const { total: nextInvoiceTotal, currency: nextInvoiceCurrency } =
total: nextInvoiceTotal, nextInvoice || {};
currency: nextInvoiceCurrency,
lines: nextInvoiceLines,
} = nextInvoice || {};
// https://stripe.com/docs/api/invoices/object#invoice_object-lines
// as per the Stripe docs, one of the individual line items includes
// new plan pricing and tax information without additional fees
const upcomingInvoiceLineItem = nextInvoiceLines?.data?.find(
(line) => line.type === 'subscription'
);
const {
amount: upcomingInvoiceAmount,
discount_amounts: upcomingInvoiceDiscountAmounts,
tax_amounts: upcomingInvoiceTaxAmounts,
} = upcomingInvoiceLineItem || {};
const {
productPaymentCycleNew: productPaymentCycleNew,
invoiceTotalOldInCents: invoiceTotalOldInCents,
} = baseDetails;
// remove everything between asterisk in FXA-7796
// **************** //
const sameBillingCycle = productPaymentCycleOld === productPaymentCycleNew;
const sameBillingCondition = sameBillingCycle && !!upcomingInvoiceLineItem;
const invoiceImmediately =
this.config.subscriptions.stripeInvoiceImmediately.enabled;
// used for newTotalInCents
const newListPriceTotalWithoutTax =
sameBillingCondition && !!upcomingInvoiceAmount
? upcomingInvoiceAmount
: undefined;
// used for newTotalInCents
const newListPriceDiscounts =
(sameBillingCondition &&
upcomingInvoiceDiscountAmounts?.reduce(
(acc, discount) =>
discount.amount ? acc + discount.amount : acc + 0,
0
)) ||
0;
// used for newTotalInCents
const newListPriceTotalTaxes = sameBillingCondition
? upcomingInvoiceTaxAmounts?.reduce(
(acc, tax) => (tax.inclusive ? acc + 0 : acc + tax.amount),
0
)
: undefined;
// if same billing cycle and line item exists, add amount and tax for new total
// else return the next invoice total
const newTotalInCents =
sameBillingCycle &&
!!newListPriceTotalWithoutTax &&
!!newListPriceTotalTaxes
? newListPriceTotalWithoutTax -
newListPriceDiscounts +
newListPriceTotalTaxes
: nextInvoiceTotal;
// calculated proration
// if same billing cycle, return next invoice total minus new plan price
// else return amount due on current invoice
const paymentProratedInCents = sameBillingCycle
? nextInvoiceTotal - newTotalInCents
: invoiceAmountDue;
// calculated old total
// if same billing cycle, get new invoiceTotal
// else get current invoiceTotal
const oldTotalInCents = sameBillingCycle
? invoiceTotalNewInCents
: invoiceTotalOldInCents;
// **************** //
return { return {
...baseDetails, ...baseDetails,
@ -3253,25 +3167,14 @@ export class StripeHelper extends StripeHelperBase {
productNameOld, productNameOld,
productIconURLOld, productIconURLOld,
productPaymentCycleOld, productPaymentCycleOld,
// remove condition and keep invoiceTotalOldInCents as value in FXA-7796 paymentAmountOldInCents: baseDetails.invoiceTotalOldInCents,
paymentAmountOldInCents: !invoiceImmediately
? oldTotalInCents
: invoiceTotalOldInCents,
paymentAmountOldCurrency: planOld.currency, paymentAmountOldCurrency: planOld.currency,
// remove condition and keep nextInvoiceTotal as value in FXA-7796 paymentAmountNewInCents: nextInvoiceTotal,
paymentAmountNewInCents: !invoiceImmediately
? newTotalInCents
: nextInvoiceTotal,
paymentAmountNewCurrency: nextInvoiceCurrency, paymentAmountNewCurrency: nextInvoiceCurrency,
invoiceNumber, invoiceNumber,
invoiceId, invoiceId,
// remove condition and keep invoiceAmountDue as value in FXA-7796 paymentProratedInCents: invoiceAmountDue,
paymentProratedInCents: !invoiceImmediately
? paymentProratedInCents
: invoiceAmountDue,
paymentProratedCurrency, paymentProratedCurrency,
// remove in FXA-7796
invoiceImmediately: invoiceImmediately,
}; };
} }

Просмотреть файл

@ -14,8 +14,6 @@ subscriptionUpgrade-upgrade-info = You have successfully upgraded from { $produc
# $productPaymentCycleNew (String) - The interval of time from the end of one payment statement date to the next payment statement date of the new subscription, e.g. month # $productPaymentCycleNew (String) - The interval of time from the end of one payment statement date to the next payment statement date of the new subscription, e.g. month
# $productPaymentCycleOld (String) - The interval of time from the end of one payment statement date to the next payment statement date of the old subscription, e.g. month # $productPaymentCycleOld (String) - The interval of time from the end of one payment statement date to the next payment statement date of the old subscription, e.g. month
# $paymentProrated (String) - The one time fee to reflect the higher charge for the remainder of the payment cycle, including currency, e.g. $10.00 # $paymentProrated (String) - The one time fee to reflect the higher charge for the remainder of the payment cycle, including currency, e.g. $10.00
# remove subscriptionUpgrade-content-charge-info in FXA-7796; additionally remove in subscriptionUpgrade/index.txt
subscriptionUpgrade-content-charge-info = Starting with your next bill, your charge will change from { $paymentAmountOld } per { $productPaymentCycleOld } to { $paymentAmountNew } per { $productPaymentCycleNew }. At that time you will also be charged a one-time fee of { $paymentProrated } to reflect the higher charge for the remainder of this { $productPaymentCycleOld }.
subscriptionUpgrade-content-charge-info-different-cycle = You will be charged a one-time fee of { $paymentProrated } to reflect your subscriptions higher price for the remainder of this { $productPaymentCycleOld }. Starting with your next bill, your charge will change from { $paymentAmountOld } per { $productPaymentCycleOld } to { $paymentAmountNew } per { $productPaymentCycleNew }. subscriptionUpgrade-content-charge-info-different-cycle = You will be charged a one-time fee of { $paymentProrated } to reflect your subscriptions higher price for the remainder of this { $productPaymentCycleOld }. Starting with your next bill, your charge will change from { $paymentAmountOld } per { $productPaymentCycleOld } to { $paymentAmountNew } per { $productPaymentCycleNew }.
# Variables: # Variables:

Просмотреть файл

@ -9,22 +9,15 @@
<mj-text css-class="text-header"> <mj-text css-class="text-header">
<span data-l10n-id="subscriptionUpgrade-title">Thank you for upgrading!</span> <span data-l10n-id="subscriptionUpgrade-title">Thank you for upgrading!</span>
</mj-text> </mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text css-class="text-body"> <mj-text css-class="text-body">
<span data-l10n-id="subscriptionUpgrade-upgrade-info" data-l10n-args="<%= JSON.stringify({productName, productNameOld}) %>">You have successfully upgraded from <%- productNameOld %> to <%- productName %>.</span> <span data-l10n-id="subscriptionUpgrade-upgrade-info" data-l10n-args="<%= JSON.stringify({productName, productNameOld}) %>">You have successfully upgraded from <%- productNameOld %> to <%- productName %>.</span>
</mj-text> </mj-text>
<mj-text css-class="text-body"> <mj-text css-class="text-body">
<% if (!locals.invoiceImmediately && locals.productPaymentCycleOld === locals.productPaymentCycleNew) { %> <span data-l10n-id="subscriptionUpgrade-content-charge-info-different-cycle" data-l10n-args="<%= JSON.stringify({paymentAmountNew, paymentAmountOld, paymentProrated, productPaymentCycleNew, productPaymentCycleOld}) %>">You will be charged a one-time fee of <%- paymentProrated %> to reflect your subscriptions higher price for the remainder of this <%- productPaymentCycleOld %>. Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>.</span>
<span data-l10n-id="subscriptionUpgrade-content-charge-info" data-l10n-args="<%= JSON.stringify({paymentAmountNew, paymentAmountOld, paymentProrated, productPaymentCycleNew, productPaymentCycleOld}) %>">Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>. At that time you will also be charged a one-time fee of <%- paymentProrated %> to reflect the higher charge for the remainder of this <%- productPaymentCycleOld %>.</span>
<% } else { %>
<span data-l10n-id="subscriptionUpgrade-content-charge-info-different-cycle" data-l10n-args="<%= JSON.stringify({paymentAmountNew, paymentAmountOld, paymentProrated, productPaymentCycleNew, productPaymentCycleOld}) %>">You will be charged a one-time fee of <%- paymentProrated %> to reflect your subscriptions higher price for the remainder of this <%- productPaymentCycleOld %>. Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>.</span>
<% } %>
</mj-text> </mj-text>
<mj-text css-class="text-body"> <mj-text css-class="text-body">
<span data-l10n-id="subscriptionUpgrade-install" data-l10n-args="<%= JSON.stringify({productName}) %>">If there is new software for you to install in order to use <%- productName %>, you will receive a separate email with download instructions.</span> <span data-l10n-id="subscriptionUpgrade-install" data-l10n-args="<%= JSON.stringify({productName}) %>">If there is new software for you to install in order to use <%- productName %>, you will receive a separate email with download instructions.</span>
</mj-text> </mj-text>

Просмотреть файл

@ -29,20 +29,3 @@ const createStory = subplatStoryWithProps(
); );
export const Default = createStory(); export const Default = createStory();
// remove in FXA-7796
export const SubscriptionUpgradeDifferentBillingCycle = createStory(
{},
'Subscription upgrade - differnt billing cycle'
);
// remove in FXA-7796
export const SubscriptionUpgradeSameBillingCycle = createStory(
{
paymentAmountNew: '£123,121.00',
paymentAmountOld: '¥99,991',
productPaymentCycleNew: 'month',
paymentProrated: '$5,231.00',
},
'Subscription upgrade - same billing cycle'
);

Просмотреть файл

@ -4,7 +4,7 @@ subscriptionUpgrade-title = "Thank you for upgrading!"
subscriptionUpgrade-upgrade-info = "You have successfully upgraded from <%- productNameOld %> to <%- productName %>." subscriptionUpgrade-upgrade-info = "You have successfully upgraded from <%- productNameOld %> to <%- productName %>."
<% if (locals.productPaymentCycleOld === locals.productPaymentCycleNew) { %>subscriptionUpgrade-content-charge-info = "Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>. At that time you will also be charged a one-time fee of <%- paymentProrated %> to reflect the higher charge for the remainder of this <%- productPaymentCycleOld %>."<% } else { %>subscriptionUpgrade-content-charge-info-different-cycle = "You will be charged a one-time fee of <%- paymentProrated %> to reflect your subscriptions higher price for the remainder of this <%- productPaymentCycleOld %>. Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>."<% } %> subscriptionUpgrade-content-charge-info-different-cycle = "You will be charged a one-time fee of <%- paymentProrated %> to reflect your subscriptions higher price for the remainder of this <%- productPaymentCycleOld %>. Starting with your next bill, your charge will change from <%- paymentAmountOld %> per <%- productPaymentCycleOld %> to <%- paymentAmountNew %> per <%- productPaymentCycleNew %>."
subscriptionUpgrade-install = "If there is new software for you to install in order to use <%- productName %>, you will receive a separate email with download instructions." subscriptionUpgrade-install = "If there is new software for you to install in order to use <%- productName %>, you will receive a separate email with download instructions."

Просмотреть файл

@ -129,7 +129,6 @@ const mockConfig = {
subscriptions: { subscriptions: {
cacheTtlSeconds: 10, cacheTtlSeconds: 10,
productConfigsFirestore: { enabled: true }, productConfigsFirestore: { enabled: true },
stripeInvoiceImmediately: { enabled: false },
stripeApiKey: 'sk_test_4eC39HqLyjWDarjtT1zdp7dc', stripeApiKey: 'sk_test_4eC39HqLyjWDarjtT1zdp7dc',
}, },
subhub: { subhub: {
@ -2093,7 +2092,6 @@ describe('#integration - StripeHelper', () => {
}); });
it('retrieves both upcoming invoices with and without proration info', async () => { it('retrieves both upcoming invoices with and without proration info', async () => {
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled = true;
const stripeStub = sandbox const stripeStub = sandbox
.stub(stripeHelper.stripe.invoices, 'retrieveUpcoming') .stub(stripeHelper.stripe.invoices, 'retrieveUpcoming')
.resolves(); .resolves();
@ -2134,8 +2132,6 @@ describe('#integration - StripeHelper', () => {
], ],
expand: ['total_tax_amounts.tax_rate'], expand: ['total_tax_amounts.tax_rate'],
}); });
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled = false;
}); });
}); });
@ -3633,7 +3629,7 @@ describe('#integration - StripeHelper', () => {
plan: 'plan_G93mMKnIFCjZek', plan: 'plan_G93mMKnIFCjZek',
}, },
], ],
proration_behavior: 'create_prorations', proration_behavior: 'always_invoice',
metadata: { metadata: {
key: 'value', key: 'value',
previous_plan_id: subscription1.items.data[0].plan.id, previous_plan_id: subscription1.items.data[0].plan.id,
@ -3659,49 +3655,6 @@ describe('#integration - StripeHelper', () => {
assert.equal(thrown.errno, error.ERRNO.SUBSCRIPTION_ALREADY_CHANGED); assert.equal(thrown.errno, error.ERRNO.SUBSCRIPTION_ALREADY_CHANGED);
sinon.assert.notCalled(stripeHelper.updateSubscriptionAndBackfill); sinon.assert.notCalled(stripeHelper.updateSubscriptionAndBackfill);
}); });
it(`uses 'always_invoice' when the config is enabled`, async () => {
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled = true;
const unixTimestamp = moment().unix();
const subscription = deepCopy(subscription1);
subscription.metadata = {
key: 'value',
previous_plan_id: 'plan_123',
plan_change_date: 12345678,
};
sandbox.stub(moment, 'unix').returns(unixTimestamp);
sandbox
.stub(stripeHelper, 'updateSubscriptionAndBackfill')
.resolves(subscription2);
const actual = await stripeHelper.changeSubscriptionPlan(
subscription,
'plan_G93mMKnIFCjZek'
);
assert.deepEqual(actual, subscription2);
sinon.assert.calledWithExactly(
stripeHelper.updateSubscriptionAndBackfill,
subscription,
{
cancel_at_period_end: false,
items: [
{
id: subscription1.items.data[0].id,
plan: 'plan_G93mMKnIFCjZek',
},
],
proration_behavior: 'always_invoice',
metadata: {
key: 'value',
previous_plan_id: subscription1.items.data[0].plan.id,
plan_change_date: unixTimestamp,
},
}
);
});
}); });
describe('cancelSubscriptionForCustomer', () => { describe('cancelSubscriptionForCustomer', () => {
@ -5963,8 +5916,6 @@ describe('#integration - StripeHelper', () => {
const event = deepCopy(eventCustomerSubscriptionUpdated); const event = deepCopy(eventCustomerSubscriptionUpdated);
const productIdOld = event.data.previous_attributes.plan.product; const productIdOld = event.data.previous_attributes.plan.product;
const productIdNew = event.data.object.plan.product; const productIdNew = event.data.object.plan.product;
const invoiceImmediately =
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled;
const baseDetails = { const baseDetails = {
...expectedBaseUpdateDetails, ...expectedBaseUpdateDetails,
@ -6038,29 +5989,13 @@ describe('#integration - StripeHelper', () => {
event.data.previous_attributes.plan.interval, event.data.previous_attributes.plan.interval,
paymentAmountOldCurrency: paymentAmountOldCurrency:
event.data.previous_attributes.plan.currency, event.data.previous_attributes.plan.currency,
paymentAmountOldInCents: paymentAmountOldInCents: baseDetails.invoiceTotalOldInCents,
upcomingInvoice && upcomingInvoice.total && !invoiceImmediately paymentAmountNewCurrency: upcomingInvoice.currency,
? upcomingInvoice.total paymentAmountNewInCents: upcomingInvoice.total,
: baseDetails.invoiceTotalOldInCents, paymentProratedCurrency: mockInvoice.currency,
paymentAmountNewCurrency: paymentProratedInCents: mockInvoice.amount_due,
upcomingInvoice && upcomingInvoice.currency && !invoiceImmediately
? upcomingInvoice.currency
: mockInvoice.currency,
paymentAmountNewInCents:
upcomingInvoice && upcomingInvoice.total && !invoiceImmediately
? upcomingInvoice.total
: mockInvoice.total,
paymentProratedCurrency:
upcomingInvoice && upcomingInvoice.currency && !invoiceImmediately
? upcomingInvoice.currency
: mockInvoice.currency,
paymentProratedInCents:
upcomingInvoice && !invoiceImmediately
? expectedPaymentProratedInCents
: mockInvoice.amount_due,
invoiceNumber: mockInvoice.number, invoiceNumber: mockInvoice.number,
invoiceId: mockInvoice.id, invoiceId: mockInvoice.id,
invoiceImmediately: invoiceImmediately,
}); });
}; };

Просмотреть файл

@ -2300,7 +2300,7 @@ const TESTS: [string, any, Record<string, any>?][] = [
]), {updateTemplateValues: x => ( ]), {updateTemplateValues: x => (
{...x, discountType: 'repeating', discountDuration: 3})} {...x, discountType: 'repeating', discountDuration: 3})}
], ],
// subscription upgrade email for different billing cycle
['subscriptionUpgradeEmail', new Map<string, Test | any>([ ['subscriptionUpgradeEmail', new Map<string, Test | any>([
['subject', { test: 'equal', expected: `You have upgraded to ${MESSAGE.productNameNew}` }], ['subject', { test: 'equal', expected: `You have upgraded to ${MESSAGE.productNameNew}` }],
['headers', new Map([ ['headers', new Map([
@ -2333,39 +2333,6 @@ const TESTS: [string, any, Record<string, any>?][] = [
]), {updateTemplateValues: x => ( ]), {updateTemplateValues: x => (
{...x, productName: MESSAGE.productNameNew })} {...x, productName: MESSAGE.productNameNew })}
], ],
// subscription upgrade email for same billing cycle
['subscriptionUpgradeEmail', new Map<string, Test | any>([
['subject', { test: 'equal', expected: `You have upgraded to ${MESSAGE.productNameNew}` }],
['headers', new Map([
['X-SES-MESSAGE-TAGS', { test: 'equal', expected: sesMessageTagsHeaderValue('subscriptionUpgrade') }],
['X-Template-Name', { test: 'equal', expected: 'subscriptionUpgrade' }],
['X-Template-Version', { test: 'equal', expected: TEMPLATE_VERSIONS.subscriptionUpgrade }],
])],
['html', [
{ test: 'include', expected: `You have upgraded to ${MESSAGE.productNameNew}` },
{ test: 'include', expected: 'Thank you for upgrading!' },
{ test: 'include', expected: decodeUrl(configHref('subscriptionSettingsUrl', 'subscription-upgrade', 'cancel-subscription', 'plan_id', 'product_id', 'uid', 'email')) },
{ test: 'include', expected: decodeUrl(configHref('subscriptionTermsUrl', 'subscription-upgrade', 'subscription-terms')) },
{ test: 'include', expected: `from ${MESSAGE.productNameOld} to ${MESSAGE.productNameNew}.` },
{ test: 'include', expected: `from ${MESSAGE_FORMATTED.paymentAmountOld} per ${MESSAGE.productPaymentCycleOld} to ${MESSAGE_FORMATTED.paymentAmountNew} per ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'include', expected: `one-time fee of ${MESSAGE_FORMATTED.paymentProrated} to reflect the higher charge for the remainder of this ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'include', expected: `to use ${MESSAGE.productNameNew},` },
{ test: 'notInclude', expected: `one-time fee of ${MESSAGE_FORMATTED.paymentProrated} to reflect your subscriptions higher price for the remainder of this ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'notInclude', expected: 'utm_source=email' },
]],
['text', [
{ test: 'include', expected: `You have upgraded to ${MESSAGE.productNameNew}` },
{ test: 'include', expected: 'Thank you for upgrading!' },
{ test: 'include', expected: `from ${MESSAGE.productNameOld} to ${MESSAGE.productNameNew}.` },
{ test: 'include', expected: `from ${MESSAGE_FORMATTED.paymentAmountOld} per ${MESSAGE.productPaymentCycleOld} to ${MESSAGE_FORMATTED.paymentAmountNew} per ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'include', expected: `one-time fee of ${MESSAGE_FORMATTED.paymentProrated} to reflect the higher charge for the remainder of this ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'include', expected: `to use ${MESSAGE.productNameNew},` },
{ test: 'notInclude', expected: `one-time fee of ${MESSAGE_FORMATTED.paymentProrated} to reflect your subscriptions higher price for the remainder of this ${MESSAGE.productPaymentCycleOld}.` },
{ test: 'notInclude', expected: 'utm_source=email' },
]]
]), {updateTemplateValues: x => (
{...x, productName: MESSAGE.productNameNew, productPaymentCycleNew: MESSAGE.productPaymentCycleOld })}
],
// Template partial specific tests (choose a template containing the partial) // Template partial specific tests (choose a template containing the partial)
['verifyLoginEmail', new Map<string, Test | any>([ ['verifyLoginEmail', new Map<string, Test | any>([

Просмотреть файл

@ -24,12 +24,6 @@ const conf = convict({
env: 'SUBSCRIPTIONS_STRIPE_TAX_ENABLED', env: 'SUBSCRIPTIONS_STRIPE_TAX_ENABLED',
format: Boolean, format: Boolean,
}, },
useStripeInvoiceImmediately: {
default: false,
doc: 'Enables immediate invoicing for stripe in all subscription upgrades',
env: 'SUBSCRIPTIONS_STRIPE_INVOICE_IMMEDIATELY',
format: Boolean,
}
}, },
amplitude: { amplitude: {
enabled: { enabled: {

Просмотреть файл

@ -44,7 +44,6 @@ export const PlanUpgradeDetails = ({
const role = isMobile ? undefined : 'complementary'; const role = isMobile ? undefined : 'complementary';
const showTax = config.featureFlags.useStripeAutomaticTax; const showTax = config.featureFlags.useStripeAutomaticTax;
const invoiceImmediately = config.featureFlags.useStripeInvoiceImmediately;
const exclusiveTaxRates = const exclusiveTaxRates =
invoicePreview.tax?.filter( invoicePreview.tax?.filter(
@ -77,26 +76,20 @@ export const PlanUpgradeDetails = ({
{showTax && !!subTotal && !!exclusiveTaxRates.length && ( {showTax && !!subTotal && !!exclusiveTaxRates.length && (
<> <>
<div className="plan-details-item"> <div className="plan-details-item">
{invoiceImmediately ? ( <Localized
<Localized id={`sub-update-new-plan-${formattedInterval}`}
id={`sub-update-new-plan-${formattedInterval}`} vars={{
vars={{ productName: productDetails.name || product_name,
productName: productDetails.name || product_name, }}
}} >
> <div data-testid={`sub-update-new-plan-${formattedInterval}`}>
<div> {productDetails.name || product_name} (
{productDetails.name || product_name} ( {formattedInterval.replace(/\w/, (firstLetter) =>
{formattedInterval.replace(/\w/, (firstLetter) => firstLetter.toUpperCase()
firstLetter.toUpperCase() )}
)} )
) </div>
</div> </Localized>
</Localized>
) : (
<Localized id="plan-details-list-price">
<div>List Price</div>
</Localized>
)}
<PriceDetails <PriceDetails
total={subTotal} total={subTotal}
@ -150,13 +143,18 @@ export const PlanUpgradeDetails = ({
</div> </div>
)} )}
{invoiceImmediately && oneTimeCharge && ( {oneTimeCharge && (
<> <>
<hr className="m-0 my-5 unit-row-hr" /> <hr className="m-0 my-5 unit-row-hr" />
<div className="plan-details-item font-semibold mt-5"> <div className="plan-details-item font-semibold mt-5">
<Localized id="sub-update-prorated-upgrade"> <Localized id="sub-update-prorated-upgrade">
<div className="total-label">Prorated Upgrade</div> <div
className="total-label"
data-testid="sub-update-prorated-upgrade"
>
Prorated Upgrade
</div>
</Localized> </Localized>
<PriceDetails <PriceDetails

Просмотреть файл

@ -2,10 +2,6 @@
product-plan-change-heading = Review your change product-plan-change-heading = Review your change
sub-change-failed = Plan change failed sub-change-failed = Plan change failed
sub-update-copy =
Your plan will change immediately, and youll be charged an adjusted
amount for the rest of your billing cycle. Starting { $startingDate }
youll be charged the full amount.
sub-update-acknowledgment = sub-update-acknowledgment =
Your plan will change immediately, and youll be charged a prorated Your plan will change immediately, and youll be charged a prorated
amount today for the rest of this billing cycle. Starting { $startingDate } amount today for the rest of this billing cycle. Starting { $startingDate }

Просмотреть файл

@ -148,7 +148,6 @@ export const Default = storyWithContext({
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: { featureFlags: {
useStripeAutomaticTax: true, useStripeAutomaticTax: true,
useStripeInvoiceImmediately: true,
}, },
}, },
}, },
@ -167,7 +166,6 @@ export const DefaultWithInclusiveTax = storyWithContext({
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: { featureFlags: {
useStripeAutomaticTax: true, useStripeAutomaticTax: true,
useStripeInvoiceImmediately: true,
}, },
}, },
}, },
@ -186,7 +184,6 @@ export const DefaultWithExclusiveTax = storyWithContext({
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: { featureFlags: {
useStripeAutomaticTax: true, useStripeAutomaticTax: true,
useStripeInvoiceImmediately: true,
}, },
}, },
}, },
@ -205,7 +202,6 @@ export const MultipleWithExclusiveTax = storyWithContext({
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: { featureFlags: {
useStripeAutomaticTax: true, useStripeAutomaticTax: true,
useStripeInvoiceImmediately: true,
}, },
}, },
}, },
@ -221,9 +217,6 @@ export const LocalizedToPirate = storyWithContext({
navigatorLanguages: ['xx-pirate'], navigatorLanguages: ['xx-pirate'],
config: { config: {
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: {
useStripeInvoiceImmediately: true,
},
}, },
}, },
}); });
@ -241,9 +234,6 @@ export const Submitting = storyWithContext({
...defaultAppContext, ...defaultAppContext,
config: { config: {
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: {
useStripeInvoiceImmediately: true,
},
}, },
}, },
}); });
@ -265,9 +255,6 @@ export const InternalServerError = storyWithContext({
...defaultAppContext, ...defaultAppContext,
config: { config: {
...defaultAppContext.config, ...defaultAppContext.config,
featureFlags: {
useStripeInvoiceImmediately: true,
},
}, },
}, },
}); });

Просмотреть файл

@ -44,7 +44,7 @@ import {
getLocalizedDateString, getLocalizedDateString,
} from '../../../lib/formats'; } from '../../../lib/formats';
import { WebSubscription } from 'fxa-shared/subscriptions/types'; import { WebSubscription } from 'fxa-shared/subscriptions/types';
import { config, updateConfig } from '../../../lib/config'; import { updateConfig } from '../../../lib/config';
import { deepCopy } from '../../../lib/test-utils'; import { deepCopy } from '../../../lib/test-utils';
jest.mock('../../../lib/sentry'); jest.mock('../../../lib/sentry');
@ -100,30 +100,29 @@ async function rendersAsExpected(
expect(queryByTestId('plan-upgrade-subtotal')).not.toBeInTheDocument(); expect(queryByTestId('plan-upgrade-subtotal')).not.toBeInTheDocument();
expect(queryByTestId('plan-upgrade-tax-amount')).not.toBeInTheDocument(); expect(queryByTestId('plan-upgrade-tax-amount')).not.toBeInTheDocument();
if (config.featureFlags.useStripeInvoiceImmediately) { expect(queryByTestId('sub-update-acknowledgment')).toHaveTextContent(
expect(queryByTestId('sub-update-acknowledgment')).toHaveTextContent( expectedInvoiceDate
expectedInvoiceDate );
);
// <Product Name (Interval)> (e.g. Better Upgrade Product (Monthly))
// only appears when there is exclusive tax
if (
invoicePreview?.subtotal_excluding_tax ||
invoicePreview?.total_excluding_tax
) {
expect( expect(
queryByTestId(`sub-update-new-plan-${selectedPlan.interval}`) queryByTestId(`sub-update-new-plan-${selectedPlan.interval}`)
).toBeInTheDocument(); ).toBeInTheDocument();
}
if ( if (!!invoicePreview.one_time_charge && invoicePreview.one_time_charge > 0) {
!!invoicePreview.one_time_charge && expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
invoicePreview.one_time_charge > 0 expect(queryByTestId('prorated-amount')).toBeInTheDocument();
) {
expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
expect(queryByTestId('prorated-amount')).toBeInTheDocument();
} else {
expect(
queryByTestId('sub-update-prorated-upgrade')
).not.toBeInTheDocument();
expect(queryByTestId('prorated-amount')).not.toBeInTheDocument();
}
} else { } else {
expect(queryByTestId('sub-update-copy')).toHaveTextContent( expect(
expectedInvoiceDate queryByTestId('sub-update-prorated-upgrade')
); ).not.toBeInTheDocument();
expect(queryByTestId('prorated-amount')).not.toBeInTheDocument();
} }
} }

Просмотреть файл

@ -1,11 +1,10 @@
import React, { useCallback, useEffect, useState, useContext } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Localized } from '@fluent/react'; import { Localized } from '@fluent/react';
import { Plan, Customer, Profile } from '../../../store/types'; import { Plan, Customer, Profile } from '../../../store/types';
import { SelectorReturns } from '../../../store/selectors'; import { SelectorReturns } from '../../../store/selectors';
import * as Amplitude from '../../../lib/amplitude'; import * as Amplitude from '../../../lib/amplitude';
import AppContext from '../../../lib/AppContext';
import { getLocalizedDate, getLocalizedDateString } from '../../../lib/formats'; import { getLocalizedDate, getLocalizedDateString } from '../../../lib/formats';
import { useCallbackOnce } from '../../../lib/hooks'; import { useCallbackOnce } from '../../../lib/hooks';
@ -61,7 +60,6 @@ export const SubscriptionUpgrade = ({
resetUpdateSubscriptionPlan, resetUpdateSubscriptionPlan,
discount, discount,
}: SubscriptionUpgradeProps) => { }: SubscriptionUpgradeProps) => {
const { config } = useContext(AppContext);
const ariaLabelledBy = 'error-plan-change-failed-header'; const ariaLabelledBy = 'error-plan-change-failed-header';
const ariaDescribedBy = 'error-plan-change-failed-description'; const ariaDescribedBy = 'error-plan-change-failed-description';
const validator = useValidatorState(); const validator = useValidatorState();
@ -201,35 +199,21 @@ export const SubscriptionUpgrade = ({
{...{ validator, onSubmit }} {...{ validator, onSubmit }}
> >
<hr className="my-6" /> <hr className="my-6" />
{config.featureFlags.useStripeInvoiceImmediately ? (
<Localized <Localized
id="sub-update-acknowledgment" id="sub-update-acknowledgment"
vars={{ vars={{
startingDate: getLocalizedDate(nextInvoiceDate), startingDate: getLocalizedDate(nextInvoiceDate),
}} }}
> >
<p data-testid="sub-update-acknowledgment"> <p data-testid="sub-update-acknowledgment">
Your plan will change immediately, and youll be charged a Your plan will change immediately, and youll be charged a
prorated amount today for the rest of this billing cycle. prorated amount today for the rest of this billing cycle.
Starting {getLocalizedDateString(nextInvoiceDate)} youll be Starting {getLocalizedDateString(nextInvoiceDate)} youll be
charged the full amount. charged the full amount.
</p> </p>
</Localized> </Localized>
) : (
<Localized
id="sub-update-copy"
vars={{
startingDate: getLocalizedDate(nextInvoiceDate),
}}
>
<p data-testid="sub-update-copy">
Your plan will change immediately, and youll be charged an
adjusted amount for the rest of your billing cycle. Starting
{getLocalizedDateString(nextInvoiceDate)} youll be charged
the full amount.
</p>
</Localized>
)}
<hr className="my-6" /> <hr className="my-6" />
<PaymentConsentCheckbox <PaymentConsentCheckbox

Просмотреть файл

@ -82,9 +82,6 @@ export type StripeHelperConfig = {
productConfigsFirestore: { productConfigsFirestore: {
enabled: boolean; enabled: boolean;
}; };
stripeInvoiceImmediately: {
enabled: boolean;
};
}; };
authFirestore: { authFirestore: {
prefix: string; prefix: string;