Files
bsky-social-app/subs-mock/src/util.ts
T
2024-10-29 19:43:19 -05:00

506 lines
16 KiB
TypeScript

/**
* @see https://www.revenuecat.com/docs/api-v2#tag/Product/operation/list-products
*/
export type RevenueCatProduct = {
id: string
object: 'product'
store_identifier: string
type: 'subscription'
app:
| {
type: 'stripe'
id: string
project_id: string
stripe: {
stripe_account_id: string
}
}
| {
type: 'play_store'
id: string
project_id: string
play_store: {
package_name: string
}
}
| {
type: 'app_store'
id: string
project_id: string
app_store: {
bundle_id: string
}
}
}
/**
* @see https://www.revenuecat.com/docs/api-v2#tag/Customer/operation/list-subscriptions
*/
export type RevenueCatSubscription = {
id: string
product_id: 'prodc783cb31d8'
environment: 'sandbox'
status:
| 'trialing'
| 'active'
| 'expired'
| 'in_grace_period'
| 'in_billing_retry'
| 'paused'
| 'unknown'
| 'incomplete'
store: 'stripe' | 'play_store' | 'app_store'
object: 'subscription'
auto_renewal_status:
| 'will_renew'
| 'will_not_renew'
| 'will_change_product'
| 'will_pause'
| 'requires_price_increase_consent'
| 'has_already_renewed'
starts_at: number
current_period_ends_at: number
current_period_starts_at: number
customer_id: string // DID
original_customer_id: string // should be DID, may be anon
entitlements: {
items: {
created_at: number
display_name: string
id: string
lookup_key: string
object: 'entitlement'
project_id: string
}[]
next_page: null
object: 'list'
url: string
}
management_url: unknown
gives_access: boolean
store_subscription_identifier: string
}
export enum SubscriptionId {
Main0MonthlyAuto = 'main:0:monthly:auto',
Main0AnnualAuto = 'main:0:annual:auto',
Main1MonthlyAuto = 'main:1:monthly:auto',
Main1AnnualAuto = 'main:1:annual:auto',
Main2MonthlyAuto = 'main:2:monthly:auto',
Main2AnnualAuto = 'main:2:annual:auto',
}
export type Subscription =
| {
/**
* Using this to discriminate the union
*/
platform: 'android' | 'ios'
/**
* Bsky internal ID. We map RC products to this for use in the app
*/
id: SubscriptionId
/**
* This is the full ID from whatever store this product was configured in
*/
storeId: string
/**
* This is the key needed to fetch the product from the RC SDK. For
* Android, this is the first part of the `storeId` e.g. bsky_tier_0 with
* no `:monthly-annual` suffix
*/
lookupKey: string
/**
* A value needed to check out. This is useless on native, but on web, this is the `price.id`
*/
checkoutId: string
/**
* The internal ID that we can use to fetch the subscription. For
* Androi/iOS, we probably won't need this. On web, this is the Stripe
* "subscription item" ID e.g. `si_xxx`
*/
storeSubscriptionIdentifier: string | null
/**
* This is unused on native, on web we fill this in with fetched Stripe
* product data. The price should be in cents, and we localize and format
* this value on the client.
*/
price?: number
/**
* The rest of this is pretty generic
*/
interval: 'monthly' | 'annual'
active: boolean
autoRenewStatus: RevenueCatSubscription['auto_renewal_status'] | null
provider: RevenueCatSubscription['store']
status: RevenueCatSubscription['status'] | null
entitlements: RevenueCatSubscription['entitlements']['items']
startedAt: RevenueCatSubscription['starts_at'] | null
periodStart: RevenueCatSubscription['current_period_starts_at'] | null
periodEnd: RevenueCatSubscription['current_period_ends_at'] | null
}
| {
platform: 'web'
id: SubscriptionId
storeId: string
lookupKey: string
checkoutId: string
interval: 'monthly' | 'annual'
active: boolean
autoRenewStatus: RevenueCatSubscription['auto_renewal_status'] | null
storeSubscriptionIdentifier: string | null
provider: RevenueCatSubscription['store']
status: RevenueCatSubscription['status'] | null
entitlements: RevenueCatSubscription['entitlements']['items']
startedAt: RevenueCatSubscription['starts_at'] | null
periodStart: RevenueCatSubscription['current_period_starts_at'] | null
periodEnd: RevenueCatSubscription['current_period_ends_at'] | null
price: number
}
export function getMainSubscriptionProducts(
products: RevenueCatProduct[],
subscriptions: RevenueCatSubscription[],
): Subscription[] {
return products
.filter(product => product.type === 'subscription')
.map(product => ({
product,
subscription: subscriptions.find(
subscription =>
subscription.product_id === product.id && subscription.gives_access,
),
}))
.map(normalizeMainSubscriptionProduct)
.filter(Boolean) as Subscription[]
}
export function normalizeMainSubscriptionProduct({
product,
subscription,
}: {
product: RevenueCatProduct
subscription: RevenueCatSubscription | undefined
}): Subscription | undefined {
switch (product.store_identifier) {
/*
* Android
*/
case 'bsky_tier_0:monthly-auto': {
return {
id: SubscriptionId.Main0MonthlyAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_0',
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
platform: 'android',
provider: 'play_store',
autoRenewStatus: subscription?.auto_renewal_status ?? null,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'bsky_tier_0:annual-auto': {
return {
id: SubscriptionId.Main0AnnualAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_0',
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'android',
provider: 'play_store',
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'bsky_tier_1:monthly-auto': {
return {
id: SubscriptionId.Main1MonthlyAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_1',
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'android',
provider: 'play_store',
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'bsky_tier_1:annual-auto': {
return {
id: SubscriptionId.Main1AnnualAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_1',
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'android',
provider: 'play_store',
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'bsky_tier_2:monthly-auto': {
return {
id: SubscriptionId.Main2MonthlyAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_2',
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'android',
provider: 'play_store',
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'bsky_tier_2:annual-auto': {
return {
id: SubscriptionId.Main2AnnualAuto,
storeId: product.store_identifier,
lookupKey: 'bsky_tier_2',
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'android',
provider: 'play_store',
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
/*
* Stripe
*/
case 'prod_R2eNjNa6mB1Jlu': {
return {
id: SubscriptionId.Main0MonthlyAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'prod_R37Zg28EeQ9XAz': {
return {
id: SubscriptionId.Main0AnnualAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'prod_R67eiEMuIf59w7': {
return {
id: SubscriptionId.Main1MonthlyAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'prod_R67fjfexQ7THQ0': {
return {
id: SubscriptionId.Main1AnnualAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'prod_R67ftsSVu2U1D6': {
return {
id: SubscriptionId.Main2MonthlyAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'monthly',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
case 'prod_R67gClEZy1cry2': {
return {
id: SubscriptionId.Main2AnnualAuto,
storeId: product.store_identifier,
lookupKey: product.store_identifier,
checkoutId: product.store_identifier,
interval: 'annual',
active: subscription?.gives_access ?? false,
autoRenewStatus: subscription?.auto_renewal_status ?? null,
platform: 'web',
provider: 'stripe',
price: 0,
storeSubscriptionIdentifier:
subscription?.store_subscription_identifier ?? null,
status: subscription?.status ?? null,
entitlements: subscription?.gives_access
? subscription?.entitlements?.items ?? []
: [],
startedAt: subscription?.starts_at ?? null,
periodStart: subscription?.current_period_starts_at ?? null,
periodEnd: subscription?.current_period_ends_at ?? null,
}
}
/*
* Fallback
*/
default: {
return undefined
}
}
}
export enum EntitlementId {
Main0 = 'main:0',
Main1 = 'main:1',
Main2 = 'main:2',
}
export function normalizeEntitlements(
entitlements: RevenueCatSubscription['entitlements']['items'],
) {
return entitlements.map(entitlement => {
let id = EntitlementId.Main0
switch (entitlement.lookup_key) {
case 'main:0': {
id = EntitlementId.Main0
break
}
case 'main:1': {
id = EntitlementId.Main1
break
}
case 'main:2': {
id = EntitlementId.Main2
break
}
}
return {
id,
}
})
}