Skip to content

Commit 601db2f

Browse files
committed
feat(payments-api): add Stripe webhook handler
Because: - The payments-api service needs to be able to processes Stripe webhooks This commit: - Adds Controller, StripeWebhookService and related modules to payments-api Closes #PAY-3292
1 parent a1fbf07 commit 601db2f

26 files changed

+704
-152
lines changed

apps/payments/api/.env

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1-
TEST_OVERRIDE="default override value"
2-
TEST_DEFAULT="default value"
3-
TEST_NESTED_CONFIG__TEST_NESTED="nested value"
1+
# MySQLConfig
2+
MYSQL_CONFIG__DATABASE=fxa
3+
MYSQL_CONFIG__HOST=::1
4+
MYSQL_CONFIG__PORT=3306
5+
MYSQL_CONFIG__USER=root
6+
MYSQL_CONFIG__PASSWORD=
7+
MYSQL_CONFIG__CONNECTION_LIMIT_MIN=
8+
MYSQL_CONFIG__CONNECTION_LIMIT_MAX=20
9+
MYSQL_CONFIG__ACQUIRE_TIMEOUT_MILLIS=
10+
11+
# Stripe Config
12+
STRIPE_CONFIG__API_KEY=11233
13+
STRIPE_CONFIG__WEBHOOK_SECRET=11233
14+
STRIPE_CONFIG__TAX_IDS={}
15+
16+
# PayPal Config
17+
PAYPAL_CLIENT_CONFIG__SANDBOX=true
18+
PAYPAL_CLIENT_CONFIG__USER=ASDF
19+
PAYPAL_CLIENT_CONFIG__PWD=ASDF
20+
PAYPAL_CLIENT_CONFIG__SIGNATURE=ASDF
21+
PAYPAL_CLIENT_CONFIG__RETRY_OPTIONS__RETRIES=1
22+
PAYPAL_CLIENT_CONFIG__RETRY_OPTIONS__MIN_TIMEOUT=1
23+
PAYPAL_CLIENT_CONFIG__RETRY_OPTIONS__FACTOR=1
24+
25+
# Strapi Config
26+
STRAPI_CLIENT_CONFIG__GRAPHQL_API_URI=https://example.com
27+
STRAPI_CLIENT_CONFIG__API_KEY=PLACEHOLDER
28+
STRAPI_CLIENT_CONFIG__MEM_CACHE_T_T_L=
29+
STRAPI_CLIENT_CONFIG__FIRESTORE_CACHE_COLLECTION_NAME=strapiClientQueryCacheCollection
30+
STRAPI_CLIENT_CONFIG__FIRESTORE_CACHE_T_T_L=
31+
STRAPI_CLIENT_CONFIG__FIRESTORE_OFFLINE_CACHE_T_T_L=
32+
33+
# Firestore Config
34+
FIRESTORE_CONFIG__CREDENTIALS__CLIENT_EMAIL=
35+
FIRESTORE_CONFIG__CREDENTIALS__PRIVATE_KEY=
36+
FIRESTORE_CONFIG__KEY_FILENAME=
37+
FIRESTORE_CONFIG__PROJECT_ID=
38+
39+
# Currency Config
40+
CURRENCY_CONFIG__TAX_IDS={ "EUR": "EU1234", "CHF": "CH1234" }
41+
CURRENCY_CONFIG__CURRENCIES_TO_COUNTRIES={ "USD": ["US", "GB", "NZ", "MY", "SG", "CA", "AS", "GU", "MP", "PR", "VI"], "EUR": ["FR", "DE"] }
42+
43+
# StatsD Config
44+
STATS_D_CONFIG__SAMPLE_RATE=
45+
STATS_D_CONFIG__MAX_BUFFER_SIZE=
46+
STATS_D_CONFIG__HOST=
47+
STATS_D_CONFIG__PORT=
48+
STATS_D_CONFIG__PREFIX=
49+
50+
# Stripe Events Config
51+
STRIPE_EVENTS_CONFIG__FIRESTORE_STRIPE_EVENT_STORE_COLLECTION_NAME=stripeEvents
52+
53+
# Glean Config
54+
GLEAN_CONFIG__ENABLED=false
55+
GLEAN_CONFIG__APPLICATION_ID=
56+
GLEAN_CONFIG__VERSION=0.0.0
57+
GLEAN_CONFIG__CHANNEL='development'
58+
GLEAN_CONFIG__LOGGER_APP_NAME='fxa-payments-next'

apps/payments/api/src/app/app.module.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
1-
import { Module } from '@nestjs/common';
1+
import { Logger, Module } from '@nestjs/common';
22
import { TypedConfigModule, dotenvLoader } from 'nest-typed-config';
33
import { AppController } from './app.controller';
44
import { AppService } from './app.service';
55
import { RootConfig } from '../config';
6+
import {
7+
StripeEventManager,
8+
StripeWebhooksController,
9+
StripeWebhookService,
10+
SubscriptionEventsService,
11+
} from '@fxa/payments/webhooks';
12+
import { FirestoreProvider } from '@fxa/shared/db/firestore';
13+
import { StripeClient } from '@fxa/payments/stripe';
14+
import { StatsDProvider } from '@fxa/shared/metrics/statsd';
15+
import {
16+
CustomerManager,
17+
InvoiceManager,
18+
PaymentMethodManager,
19+
PriceManager,
20+
SubscriptionManager,
21+
} from '@fxa/payments/customer';
22+
import {
23+
PaypalBillingAgreementManager,
24+
PayPalClient,
25+
PaypalClientConfig,
26+
PaypalCustomerManager,
27+
} from '@fxa/payments/paypal';
28+
import { CurrencyManager } from '@fxa/payments/currency';
29+
import { AccountDatabaseNestFactory } from '@fxa/shared/db/mysql/account';
30+
import { AccountManager } from '@fxa/shared/account/account';
31+
import { CartManager } from '@fxa/payments/cart';
32+
import { ProductConfigurationManager, StrapiClient } from '@fxa/shared/cms';
33+
import {
34+
MockPaymentsGleanFactory,
35+
PaymentsGleanManager,
36+
} from '@fxa/payments/metrics';
37+
import { PaymentsGleanFactory } from '@fxa/payments/metrics/provider';
38+
import { PaymentsEmitterService } from '@fxa/payments/events';
639

740
@Module({
841
imports: [
@@ -18,7 +51,35 @@ import { RootConfig } from '../config';
1851
}),
1952
}),
2053
],
21-
controllers: [AppController],
22-
providers: [AppService],
54+
controllers: [AppController, StripeWebhooksController],
55+
providers: [
56+
Logger,
57+
AccountDatabaseNestFactory,
58+
AccountManager,
59+
AppService,
60+
ProductConfigurationManager,
61+
CartManager,
62+
SubscriptionEventsService,
63+
PaymentsGleanFactory,
64+
PaymentsGleanManager,
65+
PaymentsEmitterService,
66+
PriceManager,
67+
FirestoreProvider,
68+
StatsDProvider,
69+
StripeClient,
70+
PayPalClient,
71+
PaypalClientConfig,
72+
SubscriptionManager,
73+
CustomerManager,
74+
InvoiceManager,
75+
PaymentMethodManager,
76+
CurrencyManager,
77+
StripeWebhookService,
78+
StripeEventManager,
79+
PaypalBillingAgreementManager,
80+
PaypalCustomerManager,
81+
StrapiClient,
82+
MockPaymentsGleanFactory,
83+
],
2384
})
2485
export class AppModule {}
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
import { Injectable } from '@nestjs/common';
2-
import { RootConfig, TestNestedConfig } from '../config';
2+
import { RootConfig } from '../config';
33

44
@Injectable()
55
export class AppService {
6-
constructor(
7-
private config: RootConfig,
8-
private nestedConfig: TestNestedConfig
9-
) {}
6+
constructor(private config: RootConfig) {}
107

118
getData(): {
129
message: string;
1310
config: RootConfig;
14-
nestedConfigOnly: TestNestedConfig;
1511
} {
1612
console.log('All config', this.config);
17-
console.log('Nested only', this.nestedConfig);
1813
return {
1914
message: 'Hello API',
2015
config: this.config,
21-
nestedConfigOnly: this.nestedConfig,
2216
};
2317
}
2418
}
Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
11
import { Type } from 'class-transformer';
2-
import { IsString, ValidateNested } from 'class-validator';
2+
import { IsDefined, ValidateNested } from 'class-validator';
33

4-
export class TestNestedConfig {
5-
@IsString()
6-
public readonly testNested!: string;
7-
}
4+
import { CurrencyConfig } from '@fxa/payments/currency';
5+
import { PaymentsGleanConfig } from '@fxa/payments/metrics';
6+
import { PaypalClientConfig } from '@fxa/payments/paypal';
7+
import { StripeConfig } from '@fxa/payments/stripe';
8+
import { StrapiClientConfig } from '@fxa/shared/cms';
9+
import { MySQLConfig } from '@fxa/shared/db/mysql/core';
10+
import { StripeEventConfig } from '@fxa/payments/webhooks';
11+
import { StatsDConfig } from '@fxa/shared/metrics/statsd';
12+
import { FirestoreConfig } from 'libs/shared/db/firestore/src/lib/firestore.config';
813

914
export class RootConfig {
10-
@IsString()
11-
public readonly testOverride!: string;
15+
@Type(() => MySQLConfig)
16+
@ValidateNested()
17+
@IsDefined()
18+
public readonly mysqlConfig!: Partial<MySQLConfig>;
19+
20+
@Type(() => PaymentsGleanConfig)
21+
@ValidateNested()
22+
@IsDefined()
23+
public readonly gleanConfig!: Partial<PaymentsGleanConfig>;
24+
25+
@Type(() => StripeConfig)
26+
@ValidateNested()
27+
@IsDefined()
28+
public readonly stripeConfig!: Partial<StripeConfig>;
29+
30+
@Type(() => PaypalClientConfig)
31+
@ValidateNested()
32+
@IsDefined()
33+
public readonly paypalClientConfig!: Partial<PaypalClientConfig>;
1234

13-
@IsString()
14-
public readonly testDefault!: string;
35+
@Type(() => CurrencyConfig)
36+
@ValidateNested()
37+
@IsDefined()
38+
public readonly currencyConfig!: Partial<CurrencyConfig>;
39+
40+
@Type(() => FirestoreConfig)
41+
@ValidateNested()
42+
@IsDefined()
43+
public readonly firestoreConfig!: Partial<FirestoreConfig>;
44+
45+
@Type(() => StatsDConfig)
46+
@ValidateNested()
47+
@IsDefined()
48+
public readonly statsDConfig!: Partial<StatsDConfig>;
49+
50+
@Type(() => StrapiClientConfig)
51+
@ValidateNested()
52+
@IsDefined()
53+
public readonly strapiClientConfig!: Partial<StrapiClientConfig>;
1554

16-
@Type(() => TestNestedConfig)
55+
@Type(() => StripeEventConfig)
1756
@ValidateNested()
18-
public readonly testNestedConfig!: TestNestedConfig;
57+
@IsDefined()
58+
public readonly stripeEventsConfig!: Partial<StripeEventConfig>;
1959
}

apps/payments/api/src/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { NestFactory } from '@nestjs/core';
88
import { AppModule } from './app/app.module';
99

1010
async function bootstrap() {
11-
const app = await NestFactory.create(AppModule);
11+
const app = await NestFactory.create(AppModule, {
12+
rawBody: true,
13+
});
1214
const globalPrefix = 'api';
1315
app.setGlobalPrefix(globalPrefix);
1416
const port = process.env.PORT || 3000;

apps/payments/next/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ CONTENT_SERVER_CLIENT_CONFIG__URL=http://localhost:3030
139139
# GoogleClient Config
140140
GOOGLE_CLIENT_CONFIG__GOOGLE_MAPS_API_KEY=
141141

142+
# Stripe Events Config
143+
STRIPE_EVENTS_CONFIG__FIRESTORE_STRIPE_EVENT_STORE_COLLECTION_NAME=stripeEvents
144+
142145
# Feature flags
143146
FEATURE_FLAG_SUB_MANAGE=true
144147

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
import { faker } from '@faker-js/faker';
55
import { Provider } from '@nestjs/common';
6+
import { Type } from 'class-transformer';
67
import { IsBoolean, IsEnum, IsString } from 'class-validator';
78

89
enum GleanChannel {
@@ -12,6 +13,7 @@ enum GleanChannel {
1213
}
1314

1415
export class PaymentsGleanConfig {
16+
@Type(() => Boolean)
1517
@IsBoolean()
1618
enabled!: boolean;
1719

libs/payments/paypal/src/lib/paypal.client.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@ import { Type } from 'class-transformer';
66
import { IsBoolean, IsNumber, IsString, ValidateNested } from 'class-validator';
77

88
export class PaypalRetryConfig {
9+
@Type(() => Number)
910
@IsNumber()
1011
public readonly retries!: number;
1112

13+
@Type(() => Number)
1214
@IsNumber()
1315
public readonly minTimeout!: number;
1416

17+
@Type(() => Number)
1518
@IsNumber()
1619
public readonly factor!: number;
1720
}
1821

1922
export class PaypalClientConfig {
23+
@Type(() => Boolean)
2024
@IsBoolean()
2125
public readonly sandbox!: boolean;
2226

libs/payments/stripe/src/lib/factories/event.factory.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,43 @@ import { StripeSubscription } from '../stripe.client.types';
99

1010
// TODO - Create generic factory
1111
export const StripeEventCustomerSubscriptionCreatedFactory = (
12+
override?: Partial<Stripe.Event>,
1213
dataObjectOverride?: Partial<StripeSubscription>
1314
): Stripe.Event => ({
1415
id: 'evt_123',
1516
object: 'event',
1617
api_version: '2019-02-19',
1718
created: faker.date.past().getTime(),
19+
livemode: false,
20+
request: null,
21+
pending_webhooks: 0,
22+
...override,
23+
type: 'customer.subscription.created',
1824
data: {
1925
object: {
2026
...StripeSubscriptionFactory(),
2127
...dataObjectOverride,
2228
},
2329
},
24-
livemode: false,
25-
request: null,
26-
pending_webhooks: 0,
27-
type: 'customer.subscription.created',
2830
});
2931

3032
export const StripeEventCustomerSubscriptionDeletedFactory = (
33+
override?: Partial<Stripe.Event>,
3134
dataObjectOverride?: Partial<StripeSubscription>
3235
): Stripe.Event => ({
3336
id: 'evt_123',
3437
object: 'event',
3538
api_version: '2019-02-19',
3639
created: faker.date.past().getTime(),
40+
livemode: false,
41+
request: null,
42+
pending_webhooks: 0,
43+
...override,
44+
type: 'customer.subscription.deleted',
3745
data: {
3846
object: {
3947
...StripeSubscriptionFactory(),
4048
...dataObjectOverride,
4149
},
4250
},
43-
livemode: false,
44-
request: null,
45-
pending_webhooks: 0,
46-
type: 'customer.subscription.deleted',
4751
});

libs/payments/ui/src/lib/nestapp/config.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ import { GoogleClientConfig } from '@fxa/google';
99
import { MySQLConfig } from '@fxa/shared/db/mysql/core';
1010
import { GeoDBConfig, GeoDBManagerConfig } from '@fxa/shared/geodb';
1111
import { LocationConfig } from '@fxa/payments/eligibility';
12-
import { PaypalClientConfig } from 'libs/payments/paypal/src/lib/paypal.client.config';
12+
import { PaypalClientConfig } from '@fxa/payments/paypal';
1313
import { StripeConfig } from '@fxa/payments/stripe';
1414
import { StrapiClientConfig } from '@fxa/shared/cms';
15-
import { FirestoreConfig } from 'libs/shared/db/firestore/src/lib/firestore.config';
16-
import { StatsDConfig } from 'libs/shared/metrics/statsd/src/lib/statsd.config';
15+
import { StatsDConfig } from '@fxa/shared/metrics/statsd';
1716
import { PaymentsGleanConfig } from '@fxa/payments/metrics';
18-
import { CurrencyConfig } from 'libs/payments/currency/src/lib/currency.config';
17+
import { CurrencyConfig } from '@fxa/payments/currency';
1918
import { ProfileClientConfig } from '@fxa/profile/client';
2019
import { ContentServerClientConfig } from '@fxa/payments/content-server';
2120
import { NotifierSnsConfig } from '@fxa/shared/notifier';
2221
import { AppleIapClientConfig, GoogleIapClientConfig } from '@fxa/payments/iap';
2322
import { TracingConfig } from './tracing.config';
23+
import { StripeEventConfig } from '@fxa/payments/webhooks';
24+
import { FirestoreConfig } from 'libs/shared/db/firestore/src/lib/firestore.config';
2425

2526
export class RootConfig {
2627
@Type(() => MySQLConfig)
@@ -41,7 +42,7 @@ export class RootConfig {
4142
@ValidateNested()
4243
@IsDefined()
4344
public readonly stripeConfig!: Partial<StripeConfig>;
44-
45+
4546
@Type(() => TracingConfig)
4647
@ValidateNested()
4748
@IsDefined()
@@ -107,6 +108,11 @@ export class RootConfig {
107108
@IsDefined()
108109
public readonly googleClientConfig!: Partial<GoogleClientConfig>;
109110

111+
@Type(() => StripeEventConfig)
112+
@ValidateNested()
113+
@IsDefined()
114+
public readonly stripeEventsConfig!: Partial<StripeEventConfig>;
115+
110116
@Type(() => LocationConfig)
111117
@ValidateNested()
112118
@IsDefined()

0 commit comments

Comments
 (0)