Skip to content

Commit 9d11d75

Browse files
committed
feat(subplat): add free access program to billing-and-subscriptions API
Because: * Relying parties fetch current subscriptions via GET v1/billing-and-subscriptions, but Free Access Program grants were not represented. This commit: * Adds a `free_access` subscription type sourcing product identity from the related offering. * Filters grants by the projection's per-client capabilities. * Gates the code behind a FreeAccessProgramConfig feature flag (default off). Closes #PAY-3790
1 parent a92855d commit 9d11d75

14 files changed

Lines changed: 513 additions & 21 deletions

‎apps/payments/api/.env‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,6 @@ SENTRY_CONFIG__SAMPLE_RATE=1
109109
SENTRY_CONFIG__TRACES_SAMPLE_RATE=0
110110

111111
SWAGGER_UI=false
112+
113+
# Free Access Program
114+
FREE_ACCESS_PROGRAM_CONFIG__ENABLED=false

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { AccountManager } from '@fxa/shared/account/account';
7272
import { CartManager } from '@fxa/payments/cart';
7373
import {
7474
CmsContentValidationManager,
75+
FreeAccessProgramConfigurationManager,
7576
MeteringConfigurationManager,
7677
ProductConfigurationManager,
7778
StrapiClient,
@@ -124,6 +125,7 @@ import { PaymentsMetricsAggregatorService } from '@fxa/payments/metrics-aggregat
124125
BillingAndSubscriptionsService,
125126
CapabilityManager,
126127
ProductConfigurationManager,
128+
FreeAccessProgramConfigurationManager,
127129
CartManager,
128130
SubscriptionEventsService,
129131
PaymentsGleanFactory,

‎apps/payments/api/src/config/index.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AppleIapClientConfig, GoogleIapClientConfig } from '@fxa/payments/iap';
77
import { PaymentsGleanConfig } from '@fxa/payments/metrics';
88
import { PaypalClientConfig } from '@fxa/payments/paypal';
99
import { StripeConfig } from '@fxa/payments/stripe';
10+
import { FreeAccessProgramConfig } from '@fxa/payments/api-server';
1011
import { StrapiClientConfig } from '@fxa/shared/cms';
1112
import { MySQLConfig } from '@fxa/shared/db/mysql/core';
1213
import { FxaWebhookConfig, StripeEventConfig } from '@fxa/payments/webhooks';
@@ -99,4 +100,9 @@ export class RootConfig {
99100
@IsBoolean()
100101
@IsDefined()
101102
public readonly swaggerUi!: boolean;
103+
104+
@Type(() => FreeAccessProgramConfig)
105+
@ValidateNested()
106+
@IsDefined()
107+
public readonly freeAccessProgramConfig!: Partial<FreeAccessProgramConfig>;
102108
}

‎libs/payments/api-server/src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './lib/billing-and-subscriptions.schema';
88
export * from './lib/billing-and-subscriptions.factories';
99
export * from './lib/util/openapi';
1010
export * from './lib/util/response-validation.interceptor';
11+
export * from './lib/free-access-program.config';

‎libs/payments/api-server/src/lib/billing-and-subscriptions.controller.spec.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,19 @@ import {
4141
StripeClient,
4242
} from '@fxa/payments/stripe';
4343
import {
44+
FreeAccessProgramConfigurationManager,
4445
MockStrapiClientConfigProvider,
4546
ProductConfigurationManager,
4647
StrapiClient,
4748
} from '@fxa/shared/cms';
49+
import { AccountManager } from '@fxa/shared/account/account';
4850
import { MockFirestoreProvider } from '@fxa/shared/db/firestore';
4951
import { MockAccountDatabaseNestFactory } from '@fxa/shared/db/mysql/account';
5052
import { MockStatsDProvider } from '@fxa/shared/metrics/statsd';
5153

5254
import { BillingAndSubscriptionsController } from './billing-and-subscriptions.controller';
5355
import { BillingAndSubscriptionsService } from './billing-and-subscriptions.service';
56+
import { MockFreeAccessProgramConfigProvider } from './free-access-program.config';
5457

5558
const FxaOAuthUserFactory = (
5659
override?: Partial<FxaOAuthUser>
@@ -79,6 +82,9 @@ describe('BillingAndSubscriptionsController', () => {
7982
ProductManager,
8083
CapabilityManager,
8184
ProductConfigurationManager,
85+
FreeAccessProgramConfigurationManager,
86+
MockFreeAccessProgramConfigProvider,
87+
AccountManager,
8288
AppleIapPurchaseManager,
8389
AppleIapClient,
8490
MockAppleIapClientConfigProvider,

‎libs/payments/api-server/src/lib/billing-and-subscriptions.schema.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,27 @@ export const appleIapSubscriptionSchema = z.object({
131131
*/
132132
export type AppleIapSubscription = z.infer<typeof appleIapSubscriptionSchema>;
133133

134+
/**
135+
* Zod schema for a Free Access Program grant.
136+
*
137+
* A Free Access Program customer is not subscribed to a Stripe Price, so the
138+
* product identity is sourced from the related offering. There is no price,
139+
* subscription id, or billing interval behind a free grant, so none of those
140+
* fields are reported — only the product the grant provides access to.
141+
*/
142+
export const freeAccessSubscriptionSchema = z.object({
143+
_subscription_type: z.literal('free_access'),
144+
offering_api_identifier: z.string(),
145+
product_id: z.string(),
146+
});
147+
148+
/**
149+
* A Free Access Program grant.
150+
*/
151+
export type FreeAccessSubscription = z.infer<
152+
typeof freeAccessSubscriptionSchema
153+
>;
154+
134155
/**
135156
* Zod schema for a subscription returned by the billing-and-subscriptions
136157
* endpoint, discriminated on `_subscription_type`.
@@ -139,6 +160,7 @@ export const subscriptionSchema = z.discriminatedUnion('_subscription_type', [
139160
webSubscriptionSchema,
140161
googleIapSubscriptionSchema,
141162
appleIapSubscriptionSchema,
163+
freeAccessSubscriptionSchema,
142164
]);
143165

144166
/**

0 commit comments

Comments
 (0)