Skip to content

Commit 6b7b143

Browse files
Merge pull request #20211 from mozilla/PAY-3496-add-payment-method-from-checkout-to-glean-metrics
feat(payments-next): Add payment method from Checkout to Glean metrics
2 parents c8f6f80 + c978167 commit 6b7b143

16 files changed

Lines changed: 174 additions & 39 deletions

File tree

libs/payments/events/src/lib/emitter.service.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
SubscriptionManager,
3232
PaymentMethodManager,
3333
PaymentProvider,
34+
SubPlatPaymentMethodType,
3435
StripePaymentMethodTypeResponseFactory,
3536
} from '@fxa/payments/customer';
3637
import { MockFirestoreProvider } from '@fxa/shared/db/firestore';
@@ -109,6 +110,7 @@ describe('PaymentsEmitterService', () => {
109110
const mockCheckoutPaymentEvents = {
110111
...mockCommonMetricsData,
111112
paymentProvider: PaymentProvider.Stripe,
113+
paymentMethod: SubPlatPaymentMethodType.Card,
112114
};
113115
let retrieveOptOutMock: jest.SpyInstance<any, unknown[], any>;
114116
const mockLogger = {
@@ -383,7 +385,8 @@ describe('PaymentsEmitterService', () => {
383385
experimentationData: { nimbusUserId },
384386
...additionalMetricsData,
385387
},
386-
mockCheckoutPaymentEvents.paymentProvider
388+
mockCheckoutPaymentEvents.paymentProvider,
389+
mockCheckoutPaymentEvents.paymentMethod
387390
);
388391
});
389392

@@ -445,7 +448,8 @@ describe('PaymentsEmitterService', () => {
445448
experimentationData: { nimbusUserId },
446449
...additionalMetricsData,
447450
},
448-
mockPaymentMethod.provider
451+
mockPaymentMethod.provider,
452+
mockPaymentMethod.type
449453
);
450454
});
451455

libs/payments/events/src/lib/emitter.service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
SubscriptionManager,
2828
PaymentMethodManager,
2929
PaymentProvidersType,
30+
type SubPlatPaymentMethodType,
3031
} from '@fxa/payments/customer';
3132
import * as Sentry from '@sentry/nestjs';
3233
import { StatsD, StatsDService } from '@fxa/shared/metrics/statsd';
@@ -205,7 +206,8 @@ export class PaymentsEmitterService {
205206
...additionalData,
206207
experimentationData: { nimbusUserId },
207208
},
208-
eventData.paymentProvider
209+
eventData.paymentProvider,
210+
eventData.paymentMethod
209211
);
210212
}
211213
}
@@ -231,8 +233,9 @@ export class PaymentsEmitterService {
231233
eventData?.searchParams?.['experimentationPreview'] === 'true',
232234
});
233235

234-
// Determine payment provider
236+
// Determine payment provider and method
235237
let paymentProvider: PaymentProvidersType | undefined;
238+
let paymentMethod: SubPlatPaymentMethodType | undefined;
236239
if (additionalData.cartMetricsData.stripeCustomerId) {
237240
const { stripeCustomerId } = additionalData.cartMetricsData;
238241
const customer = await this.customerManager.retrieve(stripeCustomerId);
@@ -246,6 +249,7 @@ export class PaymentsEmitterService {
246249

247250
if (paymentMethodTypeResponse?.type) {
248251
paymentProvider = paymentMethodTypeResponse.provider;
252+
paymentMethod = paymentMethodTypeResponse.type;
249253
}
250254
}
251255

@@ -255,7 +259,8 @@ export class PaymentsEmitterService {
255259
...additionalData,
256260
experimentationData: { nimbusUserId },
257261
},
258-
paymentProvider
262+
paymentProvider,
263+
paymentMethod
259264
);
260265
}
261266
}

libs/payments/events/src/lib/emitter.types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
} from '@fxa/payments/metrics';
1212
import { LocationStatus } from '@fxa/payments/eligibility';
1313
import { TaxChangeAllowedStatus } from '@fxa/payments/cart';
14-
import { PaymentProvidersType } from '@fxa/payments/customer';
14+
import {
15+
PaymentProvidersType,
16+
type SubPlatPaymentMethodType,
17+
} from '@fxa/payments/customer';
1518

1619
export enum GleanGenericEventNames {
1720
CancelRouteChurnContent = 'recordCancelRouteChurnContent',
@@ -29,6 +32,7 @@ export enum GleanGenericEventNames {
2932
export type CheckoutEvents = CommonMetrics;
3033
export type CheckoutPaymentEvents = CommonMetrics & {
3134
paymentProvider?: PaymentProvidersType;
35+
paymentMethod?: SubPlatPaymentMethodType;
3236
};
3337

3438
export type SubscriptionEndedEvents = {

libs/payments/metrics/src/lib/glean/glean.manager.spec.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
MockPaymentsGleanConfigProvider,
1818
PaymentsGleanConfig,
1919
} from './glean.config';
20+
import { SubPlatPaymentMethodType } from '@fxa/payments/customer';
2021

2122
const mockCommonMetricsData = {
2223
commonMetricsData: CommonMetricsFactory(),
@@ -27,6 +28,7 @@ const mockCommonMetricsData = {
2728
};
2829
const mockCommonMetrics = { common: 'metrics' };
2930
const mockPaymentProvider = 'stripe';
31+
const mockPaymentMethod = SubPlatPaymentMethodType.Card;
3032

3133
describe('PaymentsGleanManager', () => {
3234
let paymentsGleanManager: PaymentsGleanManager;
@@ -106,19 +108,23 @@ describe('PaymentsGleanManager', () => {
106108
.mockReturnValue(mockCommonMetrics);
107109
});
108110

109-
it('should record fxa pay setup engage', () => {
111+
it('should record fxa pay setup submit', () => {
110112
paymentsGleanManager.recordFxaPaySetupSubmit(
111113
mockCommonMetricsData,
112-
mockPaymentProvider
114+
mockPaymentProvider,
115+
mockPaymentMethod
113116
);
114117
expect(spyPopulateCommonMetrics).toHaveBeenCalledWith(
115-
mockCommonMetricsData
118+
mockCommonMetricsData,
119+
mockPaymentProvider,
120+
mockPaymentMethod
116121
);
117122
expect(
118123
paymentsGleanServerEventsLogger.recordPaySetupSubmit
119124
).toHaveBeenCalledWith({
120125
...mockCommonMetrics,
121126
subscription_payment_provider: mockPaymentProvider,
127+
subscription_payment_method: mockPaymentMethod,
122128
});
123129
});
124130
});
@@ -133,19 +139,23 @@ describe('PaymentsGleanManager', () => {
133139
.mockReturnValue(mockCommonMetrics);
134140
});
135141

136-
it('should record fxa pay setup engage', () => {
142+
it('should record fxa pay setup success', () => {
137143
paymentsGleanManager.recordFxaPaySetupSuccess(
138144
mockCommonMetricsData,
139-
mockPaymentProvider
145+
mockPaymentProvider,
146+
mockPaymentMethod
140147
);
141148
expect(spyPopulateCommonMetrics).toHaveBeenCalledWith(
142-
mockCommonMetricsData
149+
mockCommonMetricsData,
150+
mockPaymentProvider,
151+
mockPaymentMethod
143152
);
144153
expect(
145154
paymentsGleanServerEventsLogger.recordPaySetupSuccess
146155
).toHaveBeenCalledWith({
147156
...mockCommonMetrics,
148157
subscription_payment_provider: mockPaymentProvider,
158+
subscription_payment_method: mockPaymentMethod,
149159
});
150160
});
151161
});
@@ -160,13 +170,13 @@ describe('PaymentsGleanManager', () => {
160170
.mockReturnValue(mockCommonMetrics);
161171
});
162172

163-
it('should record fxa pay setup engage', () => {
173+
it('should record fxa pay setup fail', () => {
164174
paymentsGleanManager.recordFxaPaySetupFail(
165175
mockCommonMetricsData,
166176
mockPaymentProvider
167177
);
168178
expect(spyPopulateCommonMetrics).toHaveBeenCalledWith(
169-
mockCommonMetricsData
179+
mockCommonMetricsData,
170180
);
171181
expect(
172182
paymentsGleanServerEventsLogger.recordPaySetupFail
@@ -196,11 +206,14 @@ describe('PaymentsGleanManager', () => {
196206
},
197207
mockPaymentProvider
198208
);
199-
expect(spyPopulateCommonMetrics).toHaveBeenCalledWith({
200-
cmsMetricsData: mockCommonMetricsData.cmsMetricsData,
201-
subscriptionCancellationData:
202-
mockCommonMetricsData.subscriptionCancellationData,
203-
});
209+
expect(spyPopulateCommonMetrics).toHaveBeenCalledWith(
210+
{
211+
cmsMetricsData: mockCommonMetricsData.cmsMetricsData,
212+
subscriptionCancellationData:
213+
mockCommonMetricsData.subscriptionCancellationData,
214+
},
215+
mockPaymentProvider
216+
);
204217
expect(
205218
paymentsGleanServerEventsLogger.recordSubscriptionEnded
206219
).toHaveBeenCalledWith({

libs/payments/metrics/src/lib/glean/glean.manager.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
type SubPlatCmsMetricsData,
1818
} from './glean.types';
1919
import { Inject, Injectable } from '@nestjs/common';
20-
import { PaymentProvidersType } from '@fxa/payments/customer';
20+
import {
21+
PaymentProvidersType,
22+
type SubPlatPaymentMethodType,
23+
} from '@fxa/payments/customer';
2124
import { type PaymentsGleanServerEventsLogger } from './glean.provider';
2225
import { mapSession } from './utils/mapSession';
2326
import { mapUtm } from './utils/mapUtm';
@@ -86,13 +89,16 @@ export class PaymentsGleanManager {
8689
cmsMetricsData: CmsMetricsData;
8790
experimentationData: ExperimentationData;
8891
},
89-
paymentProvider?: PaymentProvidersType
92+
paymentProvider?: PaymentProvidersType,
93+
paymentMethod?: SubPlatPaymentMethodType
9094
) {
9195
if (this.isEnabled) {
9296
this.paymentsGleanServerEventsLogger.recordPaySetupSubmit({
93-
...this.populateCommonMetrics(metrics),
97+
...this.populateCommonMetrics(metrics, paymentProvider, paymentMethod),
9498
subscription_payment_provider:
9599
normalizeGleanFalsyValues(paymentProvider),
100+
subscription_payment_method:
101+
normalizeGleanFalsyValues(paymentMethod),
96102
});
97103
}
98104
}
@@ -104,15 +110,18 @@ export class PaymentsGleanManager {
104110
cmsMetricsData: CmsMetricsData;
105111
experimentationData: ExperimentationData;
106112
},
107-
paymentProvider?: PaymentProvidersType
113+
paymentProvider?: PaymentProvidersType,
114+
paymentMethod?: SubPlatPaymentMethodType
108115
) {
109-
const commonMetrics = this.populateCommonMetrics(metrics);
116+
const commonMetrics = this.populateCommonMetrics(metrics, paymentProvider, paymentMethod);
110117

111118
if (this.isEnabled) {
112119
this.paymentsGleanServerEventsLogger.recordPaySetupSuccess({
113120
...commonMetrics,
114121
subscription_payment_provider:
115122
normalizeGleanFalsyValues(paymentProvider),
123+
subscription_payment_method:
124+
normalizeGleanFalsyValues(paymentMethod),
116125
});
117126
}
118127
}
@@ -144,7 +153,7 @@ export class PaymentsGleanManager {
144153
},
145154
paymentProvider?: PaymentProvidersType
146155
) {
147-
const commonMetrics = this.populateCommonMetrics(metrics);
156+
const commonMetrics = this.populateCommonMetrics(metrics, paymentProvider);
148157

149158
if (this.isEnabled) {
150159
this.paymentsGleanServerEventsLogger.recordSubscriptionEnded({
@@ -243,6 +252,7 @@ export class PaymentsGleanManager {
243252
subscription_interval: cms.interval ?? '',
244253
subscription_offering_id: cms.offeringId ?? '',
245254
subscription_payment_provider: PLACEHOLDER_FUTURE_USE,
255+
subscription_payment_method: PLACEHOLDER_FUTURE_USE,
246256
subscription_plan_id: stripe.priceId ?? '',
247257
subscription_product_id: stripe.productId ?? '',
248258
subscription_promotion_code: stripe.couponCode ?? '',
@@ -266,13 +276,17 @@ export class PaymentsGleanManager {
266276
};
267277
}
268278

269-
private populateCommonMetrics(metrics: {
270-
commonMetricsData?: CommonMetrics;
271-
cartMetricsData?: CartMetrics;
272-
cmsMetricsData?: CmsMetricsData;
273-
experimentationData?: ExperimentationData;
274-
subscriptionCancellationData?: SubscriptionCancellationData;
275-
}) {
279+
private populateCommonMetrics(
280+
metrics: {
281+
commonMetricsData?: CommonMetrics;
282+
cartMetricsData?: CartMetrics;
283+
cmsMetricsData?: CmsMetricsData;
284+
experimentationData?: ExperimentationData;
285+
subscriptionCancellationData?: SubscriptionCancellationData;
286+
},
287+
paymentProvider?: PaymentProvidersType,
288+
paymentMethod?: SubPlatPaymentMethodType
289+
) {
276290
const emptyCommonMetricsData: CommonMetrics = {
277291
ipAddress: '',
278292
deviceType: '',
@@ -325,6 +339,8 @@ export class PaymentsGleanManager {
325339
cartMetricsData,
326340
cmsMetricsData,
327341
subscriptionCancellationData,
342+
paymentProvider,
343+
paymentMethod,
328344
isFreeTrial: commonMetricsData.isFreeTrial,
329345
}),
330346
...mapUtm(commonMetricsData.searchParams),

libs/payments/metrics/src/lib/glean/utils/mapSubscription.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CheckoutParamsFactory,
1010
} from '../glean.factory';
1111
import { mapSubscription } from './mapSubscription';
12+
import { SubPlatPaymentMethodType } from '@fxa/payments/customer';
1213

1314
describe('mapSubscription', () => {
1415
it('should map all values', () => {
@@ -24,6 +25,8 @@ describe('mapSubscription', () => {
2425
commonMetricsData: mockCommonData,
2526
cartMetricsData: mockCartData,
2627
cmsMetricsData: mockCmsMetricsData,
28+
paymentProvider: 'stripe',
29+
paymentMethod: SubPlatPaymentMethodType.Card,
2730
});
2831
expect(result).toEqual({
2932
subscription_cancellation_reason: '',
@@ -33,7 +36,8 @@ describe('mapSubscription', () => {
3336
subscription_is_free_trial: '',
3437
subscription_interval: mockCommonData.params['interval'],
3538
subscription_offering_id: mockCommonData.params['offeringId'],
36-
subscription_payment_provider: '',
39+
subscription_payment_method: 'card',
40+
subscription_payment_provider: 'stripe',
3741
subscription_plan_id: mockCmsMetricsData.priceId,
3842
subscription_product_id: mockCmsMetricsData.productId,
3943
subscription_promotion_code: mockCartData.couponCode,
@@ -72,6 +76,35 @@ describe('mapSubscription', () => {
7276
expect(result.subscription_is_free_trial).toBe('');
7377
});
7478

79+
it('should map paymentMethod when provided', () => {
80+
const mockCommonData = CommonMetricsFactory({
81+
params: CheckoutParamsFactory(),
82+
});
83+
const mockCartData = CartMetricsFactory();
84+
const mockCmsMetricsData = CmsMetricsDataFactory();
85+
const result = mapSubscription({
86+
commonMetricsData: mockCommonData,
87+
cartMetricsData: mockCartData,
88+
cmsMetricsData: mockCmsMetricsData,
89+
paymentMethod: SubPlatPaymentMethodType.PayPal,
90+
});
91+
expect(result.subscription_payment_method).toBe('external_paypal');
92+
});
93+
94+
it('should default paymentMethod to empty string when not provided', () => {
95+
const mockCommonData = CommonMetricsFactory({
96+
params: CheckoutParamsFactory(),
97+
});
98+
const mockCartData = CartMetricsFactory();
99+
const mockCmsMetricsData = CmsMetricsDataFactory();
100+
const result = mapSubscription({
101+
commonMetricsData: mockCommonData,
102+
cartMetricsData: mockCartData,
103+
cmsMetricsData: mockCmsMetricsData,
104+
});
105+
expect(result.subscription_payment_method).toBe('');
106+
});
107+
75108
it('should return empty strings if values are not present', () => {
76109
const mockCommonData = CommonMetricsFactory({
77110
params: {},
@@ -98,6 +131,7 @@ describe('mapSubscription', () => {
98131
subscription_is_free_trial: '',
99132
subscription_interval: '',
100133
subscription_offering_id: '',
134+
subscription_payment_method: '',
101135
subscription_payment_provider: '',
102136
subscription_plan_id: '',
103137
subscription_product_id: '',

0 commit comments

Comments
 (0)