506 lines
16 KiB
TypeScript
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,
|
|
}
|
|
})
|
|
}
|