зеркало из https://github.com/mozilla/fxa.git
Merge pull request #15663 from mozilla/FXA-7796_remove
chore(auth&payments): Remove feature flag
This commit is contained in:
Коммит
d4c6384f2b
|
@ -903,14 +903,6 @@ const convictConf = convict({
|
|||
format: String,
|
||||
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: {
|
||||
default: 1048576,
|
||||
env: 'STRIPE_WEBHOOK_PAYLOAD_LIMIT',
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import * as invoiceDTO from 'fxa-shared/dto/auth/payments/invoice';
|
||||
import { InvoicePreview } from 'fxa-shared/subscriptions/types';
|
||||
import { Stripe } from 'stripe';
|
||||
import { config } from '../../config';
|
||||
|
||||
/**
|
||||
* Formats a Stripe Invoice to the FirstInvoicePreview DTO format.
|
||||
|
@ -51,10 +50,7 @@ export function stripeInvoiceToFirstInvoicePreviewDTO(
|
|||
};
|
||||
}
|
||||
|
||||
if (
|
||||
invoice[1] &&
|
||||
config.getProperties().subscriptions.stripeInvoiceImmediately
|
||||
) {
|
||||
if (invoice[1]) {
|
||||
const proration = invoice[1].lines.data.find(
|
||||
(lineItem) => lineItem.proration
|
||||
);
|
||||
|
|
|
@ -689,11 +689,7 @@ export class StripeHelper extends StripeHelperBase {
|
|||
);
|
||||
|
||||
let proratedInvoice;
|
||||
if (
|
||||
isUpgrade &&
|
||||
requestObject.subscription_items?.length &&
|
||||
this.config.subscriptions.stripeInvoiceImmediately
|
||||
) {
|
||||
if (isUpgrade && requestObject.subscription_items?.length) {
|
||||
try {
|
||||
requestObject.subscription_proration_behavior = 'always_invoice';
|
||||
requestObject.subscription_proration_date = Math.floor(
|
||||
|
@ -1454,7 +1450,10 @@ export class StripeHelper extends StripeHelperBase {
|
|||
expand: ['data.customer', 'data.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;
|
||||
}
|
||||
}
|
||||
|
@ -1974,11 +1973,6 @@ export class StripeHelper extends StripeHelperBase {
|
|||
plan_change_date: moment().unix(),
|
||||
};
|
||||
|
||||
const prorationBehavior = this.config.subscriptions.stripeInvoiceImmediately
|
||||
.enabled
|
||||
? 'always_invoice'
|
||||
: 'create_prorations';
|
||||
|
||||
const updatedSubscription = await this.updateSubscriptionAndBackfill(
|
||||
subscription,
|
||||
{
|
||||
|
@ -1989,7 +1983,7 @@ export class StripeHelper extends StripeHelperBase {
|
|||
plan: newPlanId,
|
||||
},
|
||||
],
|
||||
proration_behavior: prorationBehavior,
|
||||
proration_behavior: 'always_invoice',
|
||||
metadata: updatedMetadata,
|
||||
}
|
||||
);
|
||||
|
@ -3137,7 +3131,6 @@ export class StripeHelper extends StripeHelperBase {
|
|||
id: invoiceId,
|
||||
number: invoiceNumber,
|
||||
currency: paymentProratedCurrency,
|
||||
total: invoiceTotalNewInCents,
|
||||
amount_due: invoiceAmountDue,
|
||||
} = invoice;
|
||||
|
||||
|
@ -3164,87 +3157,8 @@ export class StripeHelper extends StripeHelperBase {
|
|||
subscriptionId: subscription.id,
|
||||
});
|
||||
|
||||
const {
|
||||
total: nextInvoiceTotal,
|
||||
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;
|
||||
// **************** //
|
||||
const { total: nextInvoiceTotal, currency: nextInvoiceCurrency } =
|
||||
nextInvoice || {};
|
||||
|
||||
return {
|
||||
...baseDetails,
|
||||
|
@ -3253,25 +3167,14 @@ export class StripeHelper extends StripeHelperBase {
|
|||
productNameOld,
|
||||
productIconURLOld,
|
||||
productPaymentCycleOld,
|
||||
// remove condition and keep invoiceTotalOldInCents as value in FXA-7796
|
||||
paymentAmountOldInCents: !invoiceImmediately
|
||||
? oldTotalInCents
|
||||
: invoiceTotalOldInCents,
|
||||
paymentAmountOldInCents: baseDetails.invoiceTotalOldInCents,
|
||||
paymentAmountOldCurrency: planOld.currency,
|
||||
// remove condition and keep nextInvoiceTotal as value in FXA-7796
|
||||
paymentAmountNewInCents: !invoiceImmediately
|
||||
? newTotalInCents
|
||||
: nextInvoiceTotal,
|
||||
paymentAmountNewInCents: nextInvoiceTotal,
|
||||
paymentAmountNewCurrency: nextInvoiceCurrency,
|
||||
invoiceNumber,
|
||||
invoiceId,
|
||||
// remove condition and keep invoiceAmountDue as value in FXA-7796
|
||||
paymentProratedInCents: !invoiceImmediately
|
||||
? paymentProratedInCents
|
||||
: invoiceAmountDue,
|
||||
paymentProratedInCents: invoiceAmountDue,
|
||||
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
|
||||
# $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
|
||||
# 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 subscription’s 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:
|
||||
|
|
|
@ -9,22 +9,15 @@
|
|||
<mj-text css-class="text-header">
|
||||
<span data-l10n-id="subscriptionUpgrade-title">Thank you for upgrading!</span>
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<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>
|
||||
</mj-text>
|
||||
|
||||
<mj-text css-class="text-body">
|
||||
<% if (!locals.invoiceImmediately && locals.productPaymentCycleOld === locals.productPaymentCycleNew) { %>
|
||||
<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 subscription’s 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-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 subscription’s 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 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>
|
||||
</mj-text>
|
||||
|
|
|
@ -29,20 +29,3 @@ const createStory = subplatStoryWithProps(
|
|||
);
|
||||
|
||||
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 %>."
|
||||
|
||||
<% 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 subscription’s 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 subscription’s 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."
|
||||
|
||||
|
|
|
@ -129,7 +129,6 @@ const mockConfig = {
|
|||
subscriptions: {
|
||||
cacheTtlSeconds: 10,
|
||||
productConfigsFirestore: { enabled: true },
|
||||
stripeInvoiceImmediately: { enabled: false },
|
||||
stripeApiKey: 'sk_test_4eC39HqLyjWDarjtT1zdp7dc',
|
||||
},
|
||||
subhub: {
|
||||
|
@ -2093,7 +2092,6 @@ describe('#integration - StripeHelper', () => {
|
|||
});
|
||||
|
||||
it('retrieves both upcoming invoices with and without proration info', async () => {
|
||||
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled = true;
|
||||
const stripeStub = sandbox
|
||||
.stub(stripeHelper.stripe.invoices, 'retrieveUpcoming')
|
||||
.resolves();
|
||||
|
@ -2134,8 +2132,6 @@ describe('#integration - StripeHelper', () => {
|
|||
],
|
||||
expand: ['total_tax_amounts.tax_rate'],
|
||||
});
|
||||
|
||||
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -3633,7 +3629,7 @@ describe('#integration - StripeHelper', () => {
|
|||
plan: 'plan_G93mMKnIFCjZek',
|
||||
},
|
||||
],
|
||||
proration_behavior: 'create_prorations',
|
||||
proration_behavior: 'always_invoice',
|
||||
metadata: {
|
||||
key: 'value',
|
||||
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);
|
||||
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', () => {
|
||||
|
@ -5963,8 +5916,6 @@ describe('#integration - StripeHelper', () => {
|
|||
const event = deepCopy(eventCustomerSubscriptionUpdated);
|
||||
const productIdOld = event.data.previous_attributes.plan.product;
|
||||
const productIdNew = event.data.object.plan.product;
|
||||
const invoiceImmediately =
|
||||
stripeHelper.config.subscriptions.stripeInvoiceImmediately.enabled;
|
||||
|
||||
const baseDetails = {
|
||||
...expectedBaseUpdateDetails,
|
||||
|
@ -6038,29 +5989,13 @@ describe('#integration - StripeHelper', () => {
|
|||
event.data.previous_attributes.plan.interval,
|
||||
paymentAmountOldCurrency:
|
||||
event.data.previous_attributes.plan.currency,
|
||||
paymentAmountOldInCents:
|
||||
upcomingInvoice && upcomingInvoice.total && !invoiceImmediately
|
||||
? upcomingInvoice.total
|
||||
: baseDetails.invoiceTotalOldInCents,
|
||||
paymentAmountNewCurrency:
|
||||
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,
|
||||
paymentAmountOldInCents: baseDetails.invoiceTotalOldInCents,
|
||||
paymentAmountNewCurrency: upcomingInvoice.currency,
|
||||
paymentAmountNewInCents: upcomingInvoice.total,
|
||||
paymentProratedCurrency: mockInvoice.currency,
|
||||
paymentProratedInCents: mockInvoice.amount_due,
|
||||
invoiceNumber: mockInvoice.number,
|
||||
invoiceId: mockInvoice.id,
|
||||
invoiceImmediately: invoiceImmediately,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -2300,7 +2300,7 @@ const TESTS: [string, any, Record<string, any>?][] = [
|
|||
]), {updateTemplateValues: x => (
|
||||
{...x, discountType: 'repeating', discountDuration: 3})}
|
||||
],
|
||||
// subscription upgrade email for different billing cycle
|
||||
|
||||
['subscriptionUpgradeEmail', new Map<string, Test | any>([
|
||||
['subject', { test: 'equal', expected: `You have upgraded to ${MESSAGE.productNameNew}` }],
|
||||
['headers', new Map([
|
||||
|
@ -2333,39 +2333,6 @@ const TESTS: [string, any, Record<string, any>?][] = [
|
|||
]), {updateTemplateValues: x => (
|
||||
{...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 subscription’s 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 subscription’s 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)
|
||||
['verifyLoginEmail', new Map<string, Test | any>([
|
||||
|
|
|
@ -24,12 +24,6 @@ const conf = convict({
|
|||
env: 'SUBSCRIPTIONS_STRIPE_TAX_ENABLED',
|
||||
format: Boolean,
|
||||
},
|
||||
useStripeInvoiceImmediately: {
|
||||
default: false,
|
||||
doc: 'Enables immediate invoicing for stripe in all subscription upgrades',
|
||||
env: 'SUBSCRIPTIONS_STRIPE_INVOICE_IMMEDIATELY',
|
||||
format: Boolean,
|
||||
}
|
||||
},
|
||||
amplitude: {
|
||||
enabled: {
|
||||
|
|
|
@ -44,7 +44,6 @@ export const PlanUpgradeDetails = ({
|
|||
const role = isMobile ? undefined : 'complementary';
|
||||
|
||||
const showTax = config.featureFlags.useStripeAutomaticTax;
|
||||
const invoiceImmediately = config.featureFlags.useStripeInvoiceImmediately;
|
||||
|
||||
const exclusiveTaxRates =
|
||||
invoicePreview.tax?.filter(
|
||||
|
@ -77,26 +76,20 @@ export const PlanUpgradeDetails = ({
|
|||
{showTax && !!subTotal && !!exclusiveTaxRates.length && (
|
||||
<>
|
||||
<div className="plan-details-item">
|
||||
{invoiceImmediately ? (
|
||||
<Localized
|
||||
id={`sub-update-new-plan-${formattedInterval}`}
|
||||
vars={{
|
||||
productName: productDetails.name || product_name,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{productDetails.name || product_name} (
|
||||
{formattedInterval.replace(/\w/, (firstLetter) =>
|
||||
firstLetter.toUpperCase()
|
||||
)}
|
||||
)
|
||||
</div>
|
||||
</Localized>
|
||||
) : (
|
||||
<Localized id="plan-details-list-price">
|
||||
<div>List Price</div>
|
||||
</Localized>
|
||||
)}
|
||||
<Localized
|
||||
id={`sub-update-new-plan-${formattedInterval}`}
|
||||
vars={{
|
||||
productName: productDetails.name || product_name,
|
||||
}}
|
||||
>
|
||||
<div data-testid={`sub-update-new-plan-${formattedInterval}`}>
|
||||
{productDetails.name || product_name} (
|
||||
{formattedInterval.replace(/\w/, (firstLetter) =>
|
||||
firstLetter.toUpperCase()
|
||||
)}
|
||||
)
|
||||
</div>
|
||||
</Localized>
|
||||
|
||||
<PriceDetails
|
||||
total={subTotal}
|
||||
|
@ -150,13 +143,18 @@ export const PlanUpgradeDetails = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{invoiceImmediately && oneTimeCharge && (
|
||||
{oneTimeCharge && (
|
||||
<>
|
||||
<hr className="m-0 my-5 unit-row-hr" />
|
||||
|
||||
<div className="plan-details-item font-semibold mt-5">
|
||||
<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>
|
||||
|
||||
<PriceDetails
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
product-plan-change-heading = Review your change
|
||||
sub-change-failed = Plan change failed
|
||||
sub-update-copy =
|
||||
Your plan will change immediately, and you’ll be charged an adjusted
|
||||
amount for the rest of your billing cycle. Starting { $startingDate }
|
||||
you’ll be charged the full amount.
|
||||
sub-update-acknowledgment =
|
||||
Your plan will change immediately, and you’ll be charged a prorated
|
||||
amount today for the rest of this billing cycle. Starting { $startingDate }
|
||||
|
|
|
@ -148,7 +148,6 @@ export const Default = storyWithContext({
|
|||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeAutomaticTax: true,
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -167,7 +166,6 @@ export const DefaultWithInclusiveTax = storyWithContext({
|
|||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeAutomaticTax: true,
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -186,7 +184,6 @@ export const DefaultWithExclusiveTax = storyWithContext({
|
|||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeAutomaticTax: true,
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -205,7 +202,6 @@ export const MultipleWithExclusiveTax = storyWithContext({
|
|||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeAutomaticTax: true,
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -221,9 +217,6 @@ export const LocalizedToPirate = storyWithContext({
|
|||
navigatorLanguages: ['xx-pirate'],
|
||||
config: {
|
||||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -241,9 +234,6 @@ export const Submitting = storyWithContext({
|
|||
...defaultAppContext,
|
||||
config: {
|
||||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -265,9 +255,6 @@ export const InternalServerError = storyWithContext({
|
|||
...defaultAppContext,
|
||||
config: {
|
||||
...defaultAppContext.config,
|
||||
featureFlags: {
|
||||
useStripeInvoiceImmediately: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ import {
|
|||
getLocalizedDateString,
|
||||
} from '../../../lib/formats';
|
||||
import { WebSubscription } from 'fxa-shared/subscriptions/types';
|
||||
import { config, updateConfig } from '../../../lib/config';
|
||||
import { updateConfig } from '../../../lib/config';
|
||||
import { deepCopy } from '../../../lib/test-utils';
|
||||
|
||||
jest.mock('../../../lib/sentry');
|
||||
|
@ -100,30 +100,29 @@ async function rendersAsExpected(
|
|||
expect(queryByTestId('plan-upgrade-subtotal')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('plan-upgrade-tax-amount')).not.toBeInTheDocument();
|
||||
|
||||
if (config.featureFlags.useStripeInvoiceImmediately) {
|
||||
expect(queryByTestId('sub-update-acknowledgment')).toHaveTextContent(
|
||||
expectedInvoiceDate
|
||||
);
|
||||
expect(queryByTestId('sub-update-acknowledgment')).toHaveTextContent(
|
||||
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(
|
||||
queryByTestId(`sub-update-new-plan-${selectedPlan.interval}`)
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
|
||||
if (
|
||||
!!invoicePreview.one_time_charge &&
|
||||
invoicePreview.one_time_charge > 0
|
||||
) {
|
||||
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();
|
||||
}
|
||||
if (!!invoicePreview.one_time_charge && invoicePreview.one_time_charge > 0) {
|
||||
expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
|
||||
expect(queryByTestId('prorated-amount')).toBeInTheDocument();
|
||||
} else {
|
||||
expect(queryByTestId('sub-update-copy')).toHaveTextContent(
|
||||
expectedInvoiceDate
|
||||
);
|
||||
expect(
|
||||
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 { Plan, Customer, Profile } from '../../../store/types';
|
||||
import { SelectorReturns } from '../../../store/selectors';
|
||||
|
||||
import * as Amplitude from '../../../lib/amplitude';
|
||||
import AppContext from '../../../lib/AppContext';
|
||||
|
||||
import { getLocalizedDate, getLocalizedDateString } from '../../../lib/formats';
|
||||
import { useCallbackOnce } from '../../../lib/hooks';
|
||||
|
@ -61,7 +60,6 @@ export const SubscriptionUpgrade = ({
|
|||
resetUpdateSubscriptionPlan,
|
||||
discount,
|
||||
}: SubscriptionUpgradeProps) => {
|
||||
const { config } = useContext(AppContext);
|
||||
const ariaLabelledBy = 'error-plan-change-failed-header';
|
||||
const ariaDescribedBy = 'error-plan-change-failed-description';
|
||||
const validator = useValidatorState();
|
||||
|
@ -201,35 +199,21 @@ export const SubscriptionUpgrade = ({
|
|||
{...{ validator, onSubmit }}
|
||||
>
|
||||
<hr className="my-6" />
|
||||
{config.featureFlags.useStripeInvoiceImmediately ? (
|
||||
<Localized
|
||||
id="sub-update-acknowledgment"
|
||||
vars={{
|
||||
startingDate: getLocalizedDate(nextInvoiceDate),
|
||||
}}
|
||||
>
|
||||
<p data-testid="sub-update-acknowledgment">
|
||||
Your plan will change immediately, and you’ll be charged a
|
||||
prorated amount today for the rest of this billing cycle.
|
||||
Starting {getLocalizedDateString(nextInvoiceDate)} you’ll be
|
||||
charged the full amount.
|
||||
</p>
|
||||
</Localized>
|
||||
) : (
|
||||
<Localized
|
||||
id="sub-update-copy"
|
||||
vars={{
|
||||
startingDate: getLocalizedDate(nextInvoiceDate),
|
||||
}}
|
||||
>
|
||||
<p data-testid="sub-update-copy">
|
||||
Your plan will change immediately, and you’ll be charged an
|
||||
adjusted amount for the rest of your billing cycle. Starting
|
||||
{getLocalizedDateString(nextInvoiceDate)} you’ll be charged
|
||||
the full amount.
|
||||
</p>
|
||||
</Localized>
|
||||
)}
|
||||
|
||||
<Localized
|
||||
id="sub-update-acknowledgment"
|
||||
vars={{
|
||||
startingDate: getLocalizedDate(nextInvoiceDate),
|
||||
}}
|
||||
>
|
||||
<p data-testid="sub-update-acknowledgment">
|
||||
Your plan will change immediately, and you’ll be charged a
|
||||
prorated amount today for the rest of this billing cycle.
|
||||
Starting {getLocalizedDateString(nextInvoiceDate)} you’ll be
|
||||
charged the full amount.
|
||||
</p>
|
||||
</Localized>
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<PaymentConsentCheckbox
|
||||
|
|
|
@ -82,9 +82,6 @@ export type StripeHelperConfig = {
|
|||
productConfigsFirestore: {
|
||||
enabled: boolean;
|
||||
};
|
||||
stripeInvoiceImmediately: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
authFirestore: {
|
||||
prefix: string;
|
||||
|
|
Загрузка…
Ссылка в новой задаче