@@ -85,6 +85,7 @@ import { ProductConfigurationManager } from '@fxa/shared/cms';
8585import { reportSentryError , reportSentryMessage } from '../sentry' ;
8686import { StripeMapperService } from '@fxa/payments/legacy' ;
8787import { VError } from 'verror' ;
88+ import { SubPlatPaymentMethodType } from '@fxa/payments/customer' ;
8889
8990export class SubscriptionManagementPriceInfoError extends VError {
9091 constructor ( message : string , priceId : string , currency : string ) {
@@ -1661,16 +1662,65 @@ export class StripeHelper extends StripeHelperBase {
16611662 ) ;
16621663 }
16631664
1664- getPaymentProvider ( customer : Stripe . Customer ) {
1665+ async getPaymentProvider (
1666+ customer : Stripe . Customer ,
1667+ paymentIntentId ?: string
1668+ ) : Promise < SubPlatPaymentMethodType | 'paypal' | 'not_chosen' > {
16651669 const subscription = customer . subscriptions ?. data . find ( ( sub ) =>
16661670 ACTIVE_SUBSCRIPTION_STATUSES . includes ( sub . status )
16671671 ) ;
1668- if ( subscription ) {
1669- return subscription . collection_method === 'send_invoice'
1670- ? 'paypal'
1671- : 'stripe' ;
1672+
1673+ if ( ! subscription ) return 'not_chosen' ;
1674+
1675+ if ( subscription . collection_method === 'send_invoice' ) {
1676+ return 'paypal' ;
1677+ }
1678+
1679+ let paymentMethod : Stripe . PaymentMethod | null = null ;
1680+
1681+ if ( paymentIntentId ) {
1682+ const paymentIntent =
1683+ await this . stripe . paymentIntents . retrieve ( paymentIntentId ) ;
1684+ paymentMethod = await this . getPaymentMethod (
1685+ paymentIntent . payment_method as string
1686+ ) ;
1687+ } else if ( typeof subscription . latest_invoice === 'string' ) {
1688+ const invoice = await this . stripe . invoices . retrieve (
1689+ subscription . latest_invoice
1690+ ) ;
1691+
1692+ if (
1693+ invoice . payment_intent &&
1694+ typeof invoice . payment_intent === 'string'
1695+ ) {
1696+ const paymentIntent = await this . stripe . paymentIntents . retrieve (
1697+ invoice . payment_intent
1698+ ) ;
1699+ if ( paymentIntent . payment_method ) {
1700+ paymentMethod = await this . getPaymentMethod (
1701+ paymentIntent . payment_method as string
1702+ ) ;
1703+ }
1704+ }
1705+ }
1706+
1707+ if ( paymentMethod ) {
1708+ const walletType = paymentMethod . card ?. wallet ?. type ;
1709+
1710+ if ( walletType === 'apple_pay' ) {
1711+ return SubPlatPaymentMethodType . ApplePay ;
1712+ } else if ( walletType === 'google_pay' ) {
1713+ return SubPlatPaymentMethodType . GooglePay ;
1714+ } else if ( paymentMethod . type === 'link' ) {
1715+ return SubPlatPaymentMethodType . Link ;
1716+ } else if ( paymentMethod . type === 'card' ) {
1717+ return SubPlatPaymentMethodType . Card ;
1718+ } else {
1719+ return SubPlatPaymentMethodType . Stripe ;
1720+ }
16721721 }
1673- return 'not_chosen' ;
1722+
1723+ return SubPlatPaymentMethodType . Stripe ;
16741724 }
16751725
16761726 /**
@@ -2433,7 +2483,7 @@ export class StripeHelper extends StripeHelperBase {
24332483 */
24342484 async extractBillingDetails ( customer : Stripe . Customer ) {
24352485 const defaultPayment = customer . invoice_settings . default_payment_method ;
2436- const paymentProvider = this . getPaymentProvider ( customer ) ;
2486+ const paymentProvider = await this . getPaymentProvider ( customer ) ;
24372487
24382488 if ( defaultPayment ) {
24392489 if ( typeof defaultPayment === 'string' ) {
@@ -2714,7 +2764,7 @@ export class StripeHelper extends StripeHelperBase {
27142764 }
27152765
27162766 // Dig up & expand objects in the invoice that usually come as just IDs
2717- const { plan } = lineItem ;
2767+ const { amount : offeringAmountInCents , plan } = lineItem ;
27182768 if ( ! plan ) {
27192769 // No plan is present if this is not a subscription or proration, which
27202770 // should never happen as we only have subscriptions.
@@ -2775,6 +2825,35 @@ export class StripeHelper extends StripeHelperBase {
27752825 ) ;
27762826 }
27772827
2828+ let remainingAmountTotal : number | undefined ;
2829+ let unusedAmountTotal = 0 ;
2830+
2831+ if ( invoice . lines . data ) {
2832+ const totals = invoice . lines . data . reduce (
2833+ ( totals , line ) => {
2834+ if ( line . proration === true ) {
2835+ const amount = line . amount || 0 ;
2836+ const description = line . description || '' ;
2837+
2838+ if ( amount < 0 && / ^ U n u s e d / i. test ( description ) ) {
2839+ totals . unusedAmountTotal += amount ;
2840+ } else if ( amount > 0 && / ^ R e m a i n i n g / i. test ( description ) ) {
2841+ totals . remainingAmountTotal =
2842+ ( totals . remainingAmountTotal ?? 0 ) + amount ;
2843+ }
2844+ }
2845+ return totals ;
2846+ } ,
2847+ {
2848+ remainingAmountTotal : undefined as number | undefined ,
2849+ unusedAmountTotal : 0 ,
2850+ }
2851+ ) ;
2852+
2853+ remainingAmountTotal = totals . remainingAmountTotal ;
2854+ unusedAmountTotal = totals . unusedAmountTotal ;
2855+ }
2856+
27782857 const {
27792858 email,
27802859 metadata : { userid : uid } ,
@@ -2788,10 +2867,17 @@ export class StripeHelper extends StripeHelperBase {
27882867 subtotal : invoiceSubtotalInCents ,
27892868 hosted_invoice_url : invoiceLink ,
27902869 tax : invoiceTaxAmountInCents ,
2870+ total_tax_amounts : invoiceTotalTaxAmounts ,
27912871 status : invoiceStatus ,
27922872 amount_due : invoiceAmountDueInCents ,
2873+ ending_balance : invoiceEndingBalance ,
2874+ starting_balance : invoiceStartingBalance ,
27932875 } = invoice ;
27942876
2877+ const hasExclusiveTax = invoiceTotalTaxAmounts . some (
2878+ ( tax ) => ! tax . inclusive
2879+ ) ;
2880+
27952881 const nextInvoiceDate = lineItem . period . end ;
27962882
27972883 const invoiceDiscountAmountInCents =
@@ -2800,10 +2886,6 @@ export class StripeHelper extends StripeHelperBase {
28002886 invoice . total_discount_amounts [ 0 ] . amount ) ||
28012887 null ;
28022888
2803- // Only show the Subtotal when there is a Discount
2804- const showSubtotal =
2805- invoiceDiscountAmountInCents || discountType || discountDuration ;
2806-
28072889 const { id : planId , nickname : planName } = plan ;
28082890 const abbrevPlan = await this . findAbbrevPlanById ( planId ) ;
28092891 const productMetadata = this . mergeMetadata (
@@ -2831,25 +2913,37 @@ export class StripeHelper extends StripeHelperBase {
28312913 charge,
28322914 } ) ;
28332915
2834- const payment_provider = this . getPaymentProvider ( customer ) ;
2916+ const paymentIntent = invoice . payment_intent as string ;
2917+ const payment_provider = await this . getPaymentProvider (
2918+ customer ,
2919+ paymentIntent
2920+ ) ;
28352921
28362922 return {
28372923 uid,
28382924 email,
28392925 cardType,
28402926 lastFour,
28412927 payment_provider,
2928+ creditAppliedInCents : invoiceEndingBalance
2929+ ? invoiceStartingBalance - invoiceEndingBalance
2930+ : invoiceStartingBalance ,
28422931 invoiceAmountDueInCents,
28432932 invoiceLink,
28442933 invoiceNumber,
28452934 invoiceStatus,
2935+ invoiceStartingBalance,
28462936 invoiceTotalInCents,
28472937 invoiceTotalCurrency,
2848- invoiceSubtotalInCents : showSubtotal ? invoiceSubtotalInCents : null ,
2849- invoiceDiscountAmountInCents,
2938+ invoiceSubtotalInCents,
2939+ invoiceDiscountAmountInCents :
2940+ invoiceDiscountAmountInCents && - 1 & invoiceDiscountAmountInCents ,
28502941 invoiceTaxAmountInCents,
28512942 invoiceDate : new Date ( invoiceDate * 1000 ) ,
28522943 nextInvoiceDate : new Date ( nextInvoiceDate * 1000 ) ,
2944+ offeringPriceInCents : hasExclusiveTax
2945+ ? abbrevPlan . amount
2946+ : offeringAmountInCents ,
28532947 productId,
28542948 productName,
28552949 planId,
@@ -2858,8 +2952,9 @@ export class StripeHelper extends StripeHelperBase {
28582952 planSuccessActionButtonURL,
28592953 planConfig,
28602954 productMetadata,
2861- showPaymentMethod : ! ! invoiceTotalInCents ,
2862- showTaxAmount : false , // Currently we do not want to show tax amounts in emails
2955+ remainingAmountTotalInCents : remainingAmountTotal ,
2956+ showTaxAmount : hasExclusiveTax ,
2957+ unusedAmountTotalInCents : unusedAmountTotal ,
28632958 discountType,
28642959 discountDuration,
28652960 } ;
0 commit comments