Clean up subs hooks and state
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
import {RawSubscriptionObject} from '#/state/purchases/subscriptions/api/types'
|
||||||
|
|
||||||
|
export async function getSubscriptions({
|
||||||
|
did,
|
||||||
|
platform,
|
||||||
|
}: {
|
||||||
|
did: string
|
||||||
|
platform: 'web' | 'ios' | 'android'
|
||||||
|
}) {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://bsky-purchases.ngrok.io/subscriptions?user=${did}&platform=${platform}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('error fetching subscriptions', res.status, await res.text())
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const {subscriptions} = await res.json()
|
||||||
|
|
||||||
|
return subscriptions as RawSubscriptionObject[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {SubscriptionId} from '#/state/purchases/subscriptions/types'
|
||||||
|
|
||||||
|
export type RawSubscriptionObjectBase<T> = T & {
|
||||||
|
id: SubscriptionId
|
||||||
|
storeId: string
|
||||||
|
lookupKey: string
|
||||||
|
checkoutId: string
|
||||||
|
interval: 'monthly' | 'annual'
|
||||||
|
autoRenew: boolean
|
||||||
|
status:
|
||||||
|
| 'trialing'
|
||||||
|
| 'active'
|
||||||
|
| 'expired'
|
||||||
|
| 'in_grace_period'
|
||||||
|
| 'in_billing_retry'
|
||||||
|
| 'paused'
|
||||||
|
| 'unknown'
|
||||||
|
| 'incomplete'
|
||||||
|
| null
|
||||||
|
entitlements: {
|
||||||
|
created_at: number
|
||||||
|
display_name: string
|
||||||
|
id: string
|
||||||
|
lookup_key: string
|
||||||
|
object: 'entitlement'
|
||||||
|
project_id: string
|
||||||
|
}[]
|
||||||
|
startedAt: number | null
|
||||||
|
periodStart: number | null
|
||||||
|
periodEnd: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RawSubscriptionObject =
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'android'
|
||||||
|
provider: 'play_store'
|
||||||
|
}>
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'ios'
|
||||||
|
provider: 'app_store'
|
||||||
|
}>
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'web'
|
||||||
|
provider: 'stripe'
|
||||||
|
price: number
|
||||||
|
}>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import Purchases, {PurchasesPackage} from 'react-native-purchases'
|
import Purchases from 'react-native-purchases'
|
||||||
import {useMutation, useQuery} from '@tanstack/react-query'
|
import {useMutation, useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {isAndroid} from '#/platform/detection'
|
||||||
|
import {getSubscriptions} from '#/state/purchases/subscriptions/api'
|
||||||
import {
|
import {
|
||||||
Subscription,
|
Subscription,
|
||||||
Subscriptions,
|
Subscriptions,
|
||||||
} from '#/state/purchases/subscriptions/types'
|
} from '#/state/purchases/subscriptions/types'
|
||||||
import {
|
import {organizeSubscriptionsByTier} from '#/state/purchases/subscriptions/util'
|
||||||
identifierToSubscriptionInfo,
|
|
||||||
organizeSubscriptionsByTier,
|
|
||||||
} from '#/state/purchases/subscriptions/util'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
|
||||||
export function useAvailableSubscriptions() {
|
export function useAvailableSubscriptions() {
|
||||||
@@ -19,40 +18,51 @@ export function useAvailableSubscriptions() {
|
|||||||
queryKey: ['availableSubscriptions', did],
|
queryKey: ['availableSubscriptions', did],
|
||||||
async queryFn() {
|
async queryFn() {
|
||||||
Purchases.logIn(did)
|
Purchases.logIn(did)
|
||||||
const offerings = await Purchases.getOfferings()
|
Purchases.setEmail(currentAccount!.email!)
|
||||||
const tierOfferings = Object.values(offerings.all).filter(offering => {
|
|
||||||
return offering.identifier.includes('bsky_tier')
|
const platform = isAndroid ? 'android' : ('ios' as const)
|
||||||
|
const rawSubscriptions = await getSubscriptions({
|
||||||
|
did: currentAccount!.did,
|
||||||
|
platform,
|
||||||
})
|
})
|
||||||
const packages = tierOfferings.flatMap(
|
const platformSubscriptions = rawSubscriptions
|
||||||
offering => offering.availablePackages,
|
.filter(s => s.platform !== 'web')
|
||||||
|
.filter(s => s.platform === platform)
|
||||||
|
const lookupKeys = Array.from(
|
||||||
|
new Set(platformSubscriptions.map(s => s.lookupKey)),
|
||||||
)
|
)
|
||||||
return organizeSubscriptionsByTier(normalizePackages(packages))
|
const products = await Purchases.getProducts(lookupKeys)
|
||||||
|
const subscriptions: Subscription[] = platformSubscriptions
|
||||||
|
.map(sub => {
|
||||||
|
const productData = products.find(p => p.identifier === sub.storeId)
|
||||||
|
if (!productData) return undefined
|
||||||
|
const subscription = {
|
||||||
|
...sub,
|
||||||
|
price: {
|
||||||
|
value: productData.price * 100, // convert to cents
|
||||||
|
formatted: productData.priceString,
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
platform,
|
||||||
|
data: productData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return subscription
|
||||||
|
})
|
||||||
|
.filter(Boolean) as Subscription[]
|
||||||
|
|
||||||
|
return organizeSubscriptionsByTier(subscriptions)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePurchaseSubscription() {
|
export function usePurchaseSubscription() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
async mutationFn(pkg: PurchasesPackage) {
|
async mutationFn(product: Subscription['product']) {
|
||||||
return Purchases.purchasePackage(pkg)
|
if (product.platform === 'web') {
|
||||||
|
throw new Error('Cannot purchase web subscription on native')
|
||||||
|
}
|
||||||
|
return Purchases.purchaseStoreProduct(product.data)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePackages(pkgs: PurchasesPackage[]): Subscription[] {
|
|
||||||
return pkgs
|
|
||||||
.map(p => {
|
|
||||||
const info = identifierToSubscriptionInfo(p.product.identifier)
|
|
||||||
if (!info) return
|
|
||||||
const subscription: Subscription = {
|
|
||||||
info,
|
|
||||||
price: {
|
|
||||||
formatted: p.product.priceString,
|
|
||||||
value: p.product.price,
|
|
||||||
},
|
|
||||||
raw: p,
|
|
||||||
}
|
|
||||||
return subscription
|
|
||||||
})
|
|
||||||
.filter(Boolean) as Subscription[]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,18 +2,12 @@ import {Linking} from 'react-native'
|
|||||||
import {useMutation, useQuery} from '@tanstack/react-query'
|
import {useMutation, useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useCurrencyFormatter} from '#/lib/currency'
|
import {useCurrencyFormatter} from '#/lib/currency'
|
||||||
|
import {getSubscriptions} from '#/state/purchases/subscriptions/api'
|
||||||
import {
|
import {
|
||||||
Subscription,
|
Subscription,
|
||||||
Subscriptions,
|
Subscriptions,
|
||||||
} from '#/state/purchases/subscriptions/types'
|
} from '#/state/purchases/subscriptions/types'
|
||||||
import {
|
import {organizeSubscriptionsByTier} from '#/state/purchases/subscriptions/util'
|
||||||
StripePrice,
|
|
||||||
StripeProduct,
|
|
||||||
} from '#/state/purchases/subscriptions/types/stripe'
|
|
||||||
import {
|
|
||||||
identifierToSubscriptionInfo,
|
|
||||||
organizeSubscriptionsByTier,
|
|
||||||
} from '#/state/purchases/subscriptions/util'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {BSKY_PURCHASES_API} from '#/env'
|
import {BSKY_PURCHASES_API} from '#/env'
|
||||||
|
|
||||||
@@ -24,55 +18,60 @@ export function useAvailableSubscriptions() {
|
|||||||
return useQuery<Subscriptions>({
|
return useQuery<Subscriptions>({
|
||||||
queryKey: ['availableSubscriptions', currentAccount!.did],
|
queryKey: ['availableSubscriptions', currentAccount!.did],
|
||||||
async queryFn() {
|
async queryFn() {
|
||||||
const res = await fetch(`${BSKY_PURCHASES_API}/getWebOffers`).then(res =>
|
const rawSubscriptions = await getSubscriptions({
|
||||||
res.json(),
|
did: currentAccount!.did,
|
||||||
)
|
platform: 'web',
|
||||||
return organizeSubscriptionsByTier(
|
})
|
||||||
normalizeProducts(res, {currencyFormatter}),
|
const platformSubscriptions = rawSubscriptions.filter(
|
||||||
|
s => s.platform === 'web',
|
||||||
)
|
)
|
||||||
|
const subscriptions = platformSubscriptions.map(sub => {
|
||||||
|
const subscription: Subscription = {
|
||||||
|
...sub,
|
||||||
|
price: {
|
||||||
|
value: sub.price,
|
||||||
|
formatted: currencyFormatter.format(sub.price / 100),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
platform: 'web',
|
||||||
|
data: sub.checkoutId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return subscription
|
||||||
|
})
|
||||||
|
|
||||||
|
return organizeSubscriptionsByTier(subscriptions)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePurchaseSubscription() {
|
export function usePurchaseSubscription() {
|
||||||
|
const {currentAccount} = useSession()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
async mutationFn(priceObject: any) {
|
async mutationFn(product: Subscription['product']) {
|
||||||
Linking.openURL(
|
if (product.platform !== 'web') {
|
||||||
`${BSKY_PURCHASES_API}/initCheckout/${priceObject.price_id}`,
|
throw new Error('Cannot purchase native subscription on web')
|
||||||
)
|
}
|
||||||
|
if (!currentAccount || !currentAccount.email) {
|
||||||
|
throw new Error('No account or email')
|
||||||
|
}
|
||||||
|
|
||||||
|
const {checkoutUrl} = await fetch(
|
||||||
|
`${BSKY_PURCHASES_API}/createCheckout`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
price: product.data,
|
||||||
|
// TODO should NOT use query, use auth and our db state
|
||||||
|
user: currentAccount.did,
|
||||||
|
email: currentAccount.email,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).then(res => res.json())
|
||||||
|
Linking.openURL(checkoutUrl)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeProducts(
|
|
||||||
products: {product: StripeProduct; price: StripePrice}[],
|
|
||||||
options: {
|
|
||||||
currencyFormatter: ReturnType<typeof useCurrencyFormatter>
|
|
||||||
},
|
|
||||||
): Subscription[] {
|
|
||||||
return products
|
|
||||||
.map(({product, price}) => {
|
|
||||||
const info = identifierToSubscriptionInfo(product.id)
|
|
||||||
|
|
||||||
if (!info) return
|
|
||||||
|
|
||||||
const priceObj =
|
|
||||||
price.currency_options[options.currencyFormatter.currency]
|
|
||||||
const value = priceObj.unit_amount
|
|
||||||
const formatted = options.currencyFormatter.format(value / 100)
|
|
||||||
|
|
||||||
const subscription: Subscription = {
|
|
||||||
info,
|
|
||||||
price: {
|
|
||||||
value,
|
|
||||||
formatted,
|
|
||||||
},
|
|
||||||
raw: {
|
|
||||||
price_id: price.id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscription
|
|
||||||
})
|
|
||||||
.filter(Boolean) as Subscription[]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import {PurchasesStoreProduct} from 'react-native-purchases'
|
||||||
|
|
||||||
|
import {RawSubscriptionObjectBase} from '#/state/purchases/subscriptions/api/types'
|
||||||
|
|
||||||
|
export enum SubscriptionId {
|
||||||
|
Tier0MonthlyAuto = '0:monthly:auto',
|
||||||
|
Tier0AnnualAuto = '0:annual:auto',
|
||||||
|
Tier1MonthlyAuto = '1:monthly:auto',
|
||||||
|
Tier1AnnualAuto = '1:annual:auto',
|
||||||
|
Tier2MonthlyAuto = '2:monthly:auto',
|
||||||
|
Tier2AnnualAuto = '2:annual:auto',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Subscription =
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'android'
|
||||||
|
provider: 'play_store'
|
||||||
|
price: {
|
||||||
|
value: number
|
||||||
|
formatted: string
|
||||||
|
}
|
||||||
|
product: {
|
||||||
|
platform: 'android'
|
||||||
|
data: PurchasesStoreProduct
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'ios'
|
||||||
|
provider: 'app_store'
|
||||||
|
price: {
|
||||||
|
value: number
|
||||||
|
formatted: string
|
||||||
|
}
|
||||||
|
product: {
|
||||||
|
platform: 'ios'
|
||||||
|
data: PurchasesStoreProduct
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
| RawSubscriptionObjectBase<{
|
||||||
|
platform: 'web'
|
||||||
|
provider: 'stripe'
|
||||||
|
price: {
|
||||||
|
value: number
|
||||||
|
formatted: string
|
||||||
|
}
|
||||||
|
product: {
|
||||||
|
platform: 'web'
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
|
||||||
|
export type Subscriptions = {
|
||||||
|
monthly: Subscription[]
|
||||||
|
annual: Subscription[]
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
export enum SubscriptionIdentifier {
|
|
||||||
Tier0MonthlyAuto = '0:monthly:auto',
|
|
||||||
Tier0AnnualAuto = '0:annual:auto',
|
|
||||||
Tier1MonthlyAuto = '1:monthly:auto',
|
|
||||||
Tier1AnnualAuto = '1:annual:auto',
|
|
||||||
Tier2MonthlyAuto = '2:monthly:auto',
|
|
||||||
Tier2AnnualAuto = '2:annual:auto',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SubscriptionInfo = {
|
|
||||||
identifier: SubscriptionIdentifier
|
|
||||||
interval: 'monthly' | 'annual'
|
|
||||||
autoRenew: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Subscription = {
|
|
||||||
info: SubscriptionInfo
|
|
||||||
price: {
|
|
||||||
formatted: string
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
raw: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Subscriptions = {
|
|
||||||
monthly: Subscription[]
|
|
||||||
annual: Subscription[]
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
export type StripeProduct = {
|
|
||||||
// main stuff
|
|
||||||
id: string
|
|
||||||
default_price: string // ID of Price obj
|
|
||||||
name: string
|
|
||||||
active: boolean
|
|
||||||
// other stuff
|
|
||||||
object: string
|
|
||||||
attributes: unknown[]
|
|
||||||
created: number
|
|
||||||
description: string | null
|
|
||||||
features: unknown[]
|
|
||||||
images: unknown[]
|
|
||||||
livemode: boolean
|
|
||||||
marketing_features: unknown[]
|
|
||||||
metadata: unknown
|
|
||||||
package_dimensions: unknown | null
|
|
||||||
shippable: unknown | null
|
|
||||||
statement_descriptor: unknown | null
|
|
||||||
tax_code: string
|
|
||||||
type: 'service' | string
|
|
||||||
unit_label: unknown | null
|
|
||||||
updated: number
|
|
||||||
url: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StripePrice = {
|
|
||||||
// main stuff
|
|
||||||
id: string
|
|
||||||
product: string // Product obj associated
|
|
||||||
currency_options: {
|
|
||||||
[key: string]: {
|
|
||||||
custom_unit_amount: number | null
|
|
||||||
tax_behavior: 'inclusive' | string
|
|
||||||
unit_amount: number
|
|
||||||
unit_amount_decimal: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
active: boolean
|
|
||||||
recurring: {
|
|
||||||
interval: 'month' | 'year' | string
|
|
||||||
interval_count: number
|
|
||||||
usage_type: 'licensed' | string
|
|
||||||
}
|
|
||||||
object: 'price' | string
|
|
||||||
type: 'recurring' | string
|
|
||||||
// other stuff
|
|
||||||
metadata: unknown
|
|
||||||
unit_amount: number // default Price in cents
|
|
||||||
currency: string // default currency
|
|
||||||
tax_behavior: 'inclusive' | string // default tax behavior
|
|
||||||
billing_scheme: 'per_unit' | string
|
|
||||||
created: number
|
|
||||||
custom_unit_amount: number | null
|
|
||||||
livemode: boolean
|
|
||||||
lookup_key: string | null
|
|
||||||
nickname: string | null
|
|
||||||
tiers_mode: string | null
|
|
||||||
transform_quantity: string | null
|
|
||||||
}
|
|
||||||
@@ -1,114 +1,4 @@
|
|||||||
import {
|
import {Subscription, Subscriptions} from './types'
|
||||||
Subscription,
|
|
||||||
SubscriptionIdentifier,
|
|
||||||
SubscriptionInfo,
|
|
||||||
Subscriptions,
|
|
||||||
} from '#/state/purchases/subscriptions/types'
|
|
||||||
|
|
||||||
export function identifierToSubscriptionInfo(
|
|
||||||
identifier: string,
|
|
||||||
): SubscriptionInfo | undefined {
|
|
||||||
switch (identifier) {
|
|
||||||
/*
|
|
||||||
* Android
|
|
||||||
*/
|
|
||||||
case 'bsky_tier_0:monthly-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier0MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'bsky_tier_0:annual-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier0AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'bsky_tier_1:monthly-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier1MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'bsky_tier_1:annual-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier1AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'bsky_tier_2:monthly-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier2MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'bsky_tier_2:annual-auto': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier2AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Stripe
|
|
||||||
*/
|
|
||||||
case 'prod_R2eNjNa6mB1Jlu': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier0MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'prod_R37Zg28EeQ9XAz': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier0AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'prod_R67eiEMuIf59w7': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier1MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'prod_R67fjfexQ7THQ0': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier1AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'prod_R67ftsSVu2U1D6': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier2MonthlyAuto,
|
|
||||||
interval: 'monthly',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'prod_R67gClEZy1cry2': {
|
|
||||||
return {
|
|
||||||
identifier: SubscriptionIdentifier.Tier2AnnualAuto,
|
|
||||||
interval: 'annual',
|
|
||||||
autoRenew: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fallback
|
|
||||||
*/
|
|
||||||
default: {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function organizeSubscriptionsByTier(
|
export function organizeSubscriptionsByTier(
|
||||||
subscriptions: Subscription[],
|
subscriptions: Subscription[],
|
||||||
@@ -119,18 +9,18 @@ export function organizeSubscriptionsByTier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const subscription of subscriptions) {
|
for (const subscription of subscriptions) {
|
||||||
const {info} = subscription
|
const {interval} = subscription
|
||||||
result[info.interval].push(subscription)
|
result[interval].push(subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.monthly = result.monthly.sort((a, b) => {
|
result.monthly = result.monthly.sort((a, b) => {
|
||||||
const _a = parseInt(a.info.identifier.slice(0, 1))
|
const _a = parseInt(a.id.slice(0, 1))
|
||||||
const _b = parseInt(b.info.identifier.slice(0, 1))
|
const _b = parseInt(b.id.slice(0, 1))
|
||||||
return _a - _b
|
return _a - _b
|
||||||
})
|
})
|
||||||
result.annual = result.annual.sort((a, b) => {
|
result.annual = result.annual.sort((a, b) => {
|
||||||
const _a = parseInt(a.info.identifier.slice(0, 1))
|
const _a = parseInt(a.id.slice(0, 1))
|
||||||
const _b = parseInt(b.info.identifier.slice(0, 1))
|
const _b = parseInt(b.id.slice(0, 1))
|
||||||
return _a - _b
|
return _a - _b
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user