From f4beacb7440febefef2c8cf0605b9c46fcb0825a Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Sat, 26 Oct 2024 11:05:22 -0500 Subject: [PATCH] Set up other products, web done --- src/state/purchases/constants.ts | 260 ------------------ .../index.ts | 36 +-- .../purchases/subscriptions/index.web.ts | 75 +++++ .../purchases/subscriptions/types/index.ts | 28 ++ .../types/stripe.ts | 0 src/state/purchases/subscriptions/util.ts | 138 ++++++++++ .../purchases/useSubscriptions/index.web.ts | 198 ------------- .../purchases/useSubscriptions/types/index.ts | 19 -- src/state/purchases/useSubscriptions/util.ts | 53 ---- 9 files changed, 261 insertions(+), 546 deletions(-) delete mode 100644 src/state/purchases/constants.ts rename src/state/purchases/{useSubscriptions => subscriptions}/index.ts (57%) create mode 100644 src/state/purchases/subscriptions/index.web.ts create mode 100644 src/state/purchases/subscriptions/types/index.ts rename src/state/purchases/{useSubscriptions => subscriptions}/types/stripe.ts (100%) create mode 100644 src/state/purchases/subscriptions/util.ts delete mode 100644 src/state/purchases/useSubscriptions/index.web.ts delete mode 100644 src/state/purchases/useSubscriptions/types/index.ts delete mode 100644 src/state/purchases/useSubscriptions/util.ts diff --git a/src/state/purchases/constants.ts b/src/state/purchases/constants.ts deleted file mode 100644 index d2937d5e68..0000000000 --- a/src/state/purchases/constants.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * From react-native-localize - * - * MIT License - * Copyright (c) 2017-present, Mathieu Acthernoene - * - * @see https://github.com/zoontek/react-native-localize/blob/master/LICENSE - * @see https://github.com/zoontek/react-native-localize/blob/ee5bf25e0bb8f3b8e4f3fd055f67ad46269c81ea/src/constants.ts - */ -export const countryCodeToCurrency: Record = { - ad: 'eur', - ae: 'aed', - af: 'afn', - ag: 'xcd', - ai: 'xcd', - al: 'all', - am: 'amd', - an: 'ang', - ao: 'aoa', - ar: 'ars', - as: 'usd', - at: 'eur', - au: 'aud', - aw: 'awg', - ax: 'eur', - az: 'azn', - ba: 'bam', - bb: 'bbd', - bd: 'bdt', - be: 'eur', - bf: 'xof', - bg: 'bgn', - bh: 'bhd', - bi: 'bif', - bj: 'xof', - bl: 'eur', - bm: 'bmd', - bn: 'bnd', - bo: 'bob', - bq: 'usd', - br: 'brl', - bs: 'bsd', - bt: 'btn', - bv: 'nok', - bw: 'bwp', - by: 'byn', - bz: 'bzd', - ca: 'cad', - cc: 'aud', - cd: 'cdf', - cf: 'xaf', - cg: 'xaf', - ch: 'chf', - ci: 'xof', - ck: 'nzd', - cl: 'clp', - cm: 'xaf', - cn: 'cny', - co: 'cop', - cr: 'crc', - cu: 'cup', - cv: 'cve', - cw: 'ang', - cx: 'aud', - cy: 'eur', - cz: 'czk', - de: 'eur', - dj: 'djf', - dk: 'dkk', - dm: 'xcd', - do: 'dop', - dz: 'dzd', - ec: 'usd', - ee: 'eur', - eg: 'egp', - eh: 'mad', - er: 'ern', - es: 'eur', - et: 'etb', - fi: 'eur', - fj: 'fjd', - fk: 'fkp', - fm: 'usd', - fo: 'dkk', - fr: 'eur', - ga: 'xaf', - gb: 'gbp', - gd: 'xcd', - ge: 'gel', - gf: 'eur', - gg: 'gbp', - gh: 'ghs', - gi: 'gip', - gl: 'dkk', - gm: 'gmd', - gn: 'gnf', - gp: 'eur', - gq: 'xaf', - gr: 'eur', - gs: 'gbp', - gt: 'gtq', - gu: 'usd', - gw: 'xof', - gy: 'gyd', - hk: 'hkd', - hm: 'aud', - hn: 'hnl', - hr: 'hrk', - ht: 'htg', - hu: 'huf', - id: 'idr', - ie: 'eur', - il: 'ils', - im: 'gbp', - in: 'inr', - io: 'usd', - iq: 'iqd', - ir: 'irr', - is: 'isk', - it: 'eur', - je: 'gbp', - jm: 'jmd', - jo: 'jod', - jp: 'jpy', - ke: 'kes', - kg: 'kgs', - kh: 'khr', - ki: 'aud', - km: 'kmf', - kn: 'xcd', - kp: 'kpw', - kr: 'krw', - kw: 'kwd', - ky: 'kyd', - kz: 'kzt', - la: 'lak', - lb: 'lbp', - lc: 'xcd', - li: 'chf', - lk: 'lkr', - lr: 'lrd', - ls: 'lsl', - lt: 'eur', - lu: 'eur', - lv: 'eur', - ly: 'lyd', - ma: 'mad', - mc: 'eur', - md: 'mdl', - me: 'eur', - mf: 'eur', - mg: 'mga', - mh: 'usd', - mk: 'mkd', - ml: 'xof', - mm: 'mmk', - mn: 'mnt', - mo: 'mop', - mp: 'usd', - mq: 'eur', - mr: 'mro', - ms: 'xcd', - mt: 'eur', - mu: 'mur', - mv: 'mvr', - mw: 'mwk', - mx: 'mxn', - my: 'myr', - mz: 'mzn', - na: 'nad', - nc: 'xpf', - ne: 'xof', - nf: 'aud', - ng: 'ngn', - ni: 'nio', - nl: 'eur', - no: 'nok', - np: 'npr', - nr: 'aud', - nu: 'nzd', - nz: 'nzd', - om: 'omr', - pa: 'pab', - pe: 'pen', - pf: 'xpf', - pg: 'pgk', - ph: 'php', - pk: 'pkr', - pl: 'pln', - pm: 'eur', - pn: 'nzd', - pr: 'usd', - ps: 'ils', - pt: 'eur', - pw: 'usd', - py: 'pyg', - qa: 'qar', - re: 'eur', - ro: 'ron', - rs: 'rsd', - ru: 'rub', - rw: 'rwf', - sa: 'sar', - sb: 'sbd', - sc: 'scr', - sd: 'sdg', - se: 'sek', - sg: 'sgd', - sh: 'shp', - si: 'eur', - sj: 'nok', - sk: 'eur', - sl: 'sll', - sm: 'eur', - sn: 'xof', - so: 'sos', - sr: 'srd', - ss: 'ssp', - st: 'std', - sv: 'svc', - sx: 'ang', - sy: 'syp', - sz: 'szl', - tc: 'usd', - td: 'xaf', - tf: 'eur', - tg: 'xof', - th: 'thb', - tj: 'tjs', - tk: 'nzd', - tl: 'usd', - tm: 'tmt', - tn: 'tnd', - to: 'top', - tr: 'try', - tt: 'ttd', - tv: 'aud', - tw: 'twd', - tz: 'tzs', - ua: 'uah', - ug: 'ugx', - um: 'usd', - us: 'usd', - uy: 'uyu', - uz: 'uzs', - va: 'eur', - vc: 'xcd', - ve: 'vef', - vg: 'usd', - vi: 'usd', - vn: 'vnd', - vu: 'vuv', - wf: 'xpf', - ws: 'wst', - ye: 'yer', - yt: 'eur', - za: 'zar', - zm: 'zmw', - zw: 'zwl', -} diff --git a/src/state/purchases/useSubscriptions/index.ts b/src/state/purchases/subscriptions/index.ts similarity index 57% rename from src/state/purchases/useSubscriptions/index.ts rename to src/state/purchases/subscriptions/index.ts index e90a268bf6..ccdbce8ba0 100644 --- a/src/state/purchases/useSubscriptions/index.ts +++ b/src/state/purchases/subscriptions/index.ts @@ -1,29 +1,32 @@ import Purchases, {PurchasesPackage} from 'react-native-purchases' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useMutation,useQuery} from '@tanstack/react-query' +import {useMutation, useQuery} from '@tanstack/react-query' -import {Subscription} from '#/state/purchases/useSubscriptions/types' -import {identifierToSubscriptionInfo} from '#/state/purchases/useSubscriptions/util' +import { + Subscription, + Subscriptions, +} from '#/state/purchases/subscriptions/types' +import { + identifierToSubscriptionInfo, + organizeSubscriptionsByTier, +} from '#/state/purchases/subscriptions/util' import {useSession} from '#/state/session' export function useAvailableSubscriptions() { - const {_} = useLingui() const {currentAccount} = useSession() const did = currentAccount!.did - return useQuery({ + return useQuery({ queryKey: ['availableSubscriptions', did], async queryFn() { Purchases.logIn(did) const offerings = await Purchases.getOfferings() - if ( - offerings.current !== null && - offerings.current.availablePackages.length !== 0 - ) { - return normalizePackages(offerings.current.availablePackages) - } - throw new Error(_(msg`Error loading subscriptions`)) + const tierOfferings = Object.values(offerings.all).filter(offering => { + return offering.identifier.includes('bsky_tier') + }) + const packages = tierOfferings.flatMap( + offering => offering.availablePackages, + ) + return organizeSubscriptionsByTier(normalizePackages(packages)) }, }) } @@ -41,14 +44,15 @@ function normalizePackages(pkgs: PurchasesPackage[]): Subscription[] { .map(p => { const info = identifierToSubscriptionInfo(p.product.identifier) if (!info) return - return { + const subscription: Subscription = { info, - pricing: { + price: { formatted: p.product.priceString, value: p.product.price, }, raw: p, } + return subscription }) .filter(Boolean) as Subscription[] } diff --git a/src/state/purchases/subscriptions/index.web.ts b/src/state/purchases/subscriptions/index.web.ts new file mode 100644 index 0000000000..b5d6aa1beb --- /dev/null +++ b/src/state/purchases/subscriptions/index.web.ts @@ -0,0 +1,75 @@ +import {useMutation, useQuery} from '@tanstack/react-query' + +import {useCurrencyFormatter} from '#/lib/currency' +import { + Subscription, + Subscriptions, +} from '#/state/purchases/subscriptions/types' +import { + StripePrice, + StripeProduct, +} from '#/state/purchases/subscriptions/types/stripe' +import { + identifierToSubscriptionInfo, + organizeSubscriptionsByTier, +} from '#/state/purchases/subscriptions/util' +import {useSession} from '#/state/session' + +export function useAvailableSubscriptions() { + const {currentAccount} = useSession() + const currencyFormatter = useCurrencyFormatter() + + return useQuery({ + queryKey: ['availableSubscriptions', currentAccount!.did], + async queryFn() { + const res = await fetch(`http://localhost:8787/getWebOffers`).then(res => + res.json(), + ) + const filtered = res.filter((item: any) => + item.product.name.includes('bsky_tier'), + ) + return organizeSubscriptionsByTier( + normalizeProducts(filtered, {currencyFormatter}), + ) + }, + }) +} + +export function usePurchaseSubscription() { + return useMutation({ + async mutationFn(_priceObject: any) {}, + }) +} + +function normalizeProducts( + products: {product: StripeProduct; price: StripePrice}[], + options: { + currencyFormatter: ReturnType + }, +): Subscription[] { + return products + .map(({product, price}) => { + const info = identifierToSubscriptionInfo(product.name) + + 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[] +} diff --git a/src/state/purchases/subscriptions/types/index.ts b/src/state/purchases/subscriptions/types/index.ts new file mode 100644 index 0000000000..8ebbb2cc34 --- /dev/null +++ b/src/state/purchases/subscriptions/types/index.ts @@ -0,0 +1,28 @@ +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[] +} diff --git a/src/state/purchases/useSubscriptions/types/stripe.ts b/src/state/purchases/subscriptions/types/stripe.ts similarity index 100% rename from src/state/purchases/useSubscriptions/types/stripe.ts rename to src/state/purchases/subscriptions/types/stripe.ts diff --git a/src/state/purchases/subscriptions/util.ts b/src/state/purchases/subscriptions/util.ts new file mode 100644 index 0000000000..d552c34363 --- /dev/null +++ b/src/state/purchases/subscriptions/util.ts @@ -0,0 +1,138 @@ +import { + 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 'bsky_tier_0_monthly': { + return { + identifier: SubscriptionIdentifier.Tier0MonthlyAuto, + interval: 'monthly', + autoRenew: true, + } + } + case 'bsky_tier_0_annual': { + return { + identifier: SubscriptionIdentifier.Tier0AnnualAuto, + interval: 'annual', + autoRenew: true, + } + } + case 'bsky_tier_1_monthly': { + return { + identifier: SubscriptionIdentifier.Tier1MonthlyAuto, + interval: 'monthly', + autoRenew: true, + } + } + case 'bsky_tier_1_annual': { + return { + identifier: SubscriptionIdentifier.Tier1AnnualAuto, + interval: 'annual', + autoRenew: true, + } + } + case 'bsky_tier_2_monthly': { + return { + identifier: SubscriptionIdentifier.Tier2MonthlyAuto, + interval: 'monthly', + autoRenew: true, + } + } + case 'bsky_tier_2_annual': { + return { + identifier: SubscriptionIdentifier.Tier2AnnualAuto, + interval: 'annual', + autoRenew: true, + } + } + + /* + * Fallback + */ + default: { + return undefined + } + } +} + +export function organizeSubscriptionsByTier( + subscriptions: Subscription[], +): Subscriptions { + const result: Subscriptions = { + monthly: [], + annual: [], + } + + for (const subscription of subscriptions) { + const {info} = subscription + result[info.interval].push(subscription) + } + + result.monthly = result.monthly.sort((a, b) => { + const _a = parseInt(a.info.identifier.slice(0, 1)) + const _b = parseInt(b.info.identifier.slice(0, 1)) + return _a - _b + }) + result.annual = result.annual.sort((a, b) => { + const _a = parseInt(a.info.identifier.slice(0, 1)) + const _b = parseInt(b.info.identifier.slice(0, 1)) + return _a - _b + }) + + return result +} diff --git a/src/state/purchases/useSubscriptions/index.web.ts b/src/state/purchases/useSubscriptions/index.web.ts deleted file mode 100644 index 2cfef4357a..0000000000 --- a/src/state/purchases/useSubscriptions/index.web.ts +++ /dev/null @@ -1,198 +0,0 @@ -import {useMutation,useQuery} from '@tanstack/react-query' - -import {useCurrencyFormatter} from '#/lib/currency' -import {Subscription} from '#/state/purchases/useSubscriptions/types' -import { - StripePrice, - StripeProduct, -} from '#/state/purchases/useSubscriptions/types/stripe' -import {identifierToSubscriptionInfo} from '#/state/purchases/useSubscriptions/util' -import {useSession} from '#/state/session' - -export function useAvailableSubscriptions() { - const {currentAccount} = useSession() - const currencyFormatter = useCurrencyFormatter() - - return useQuery({ - queryKey: ['availableSubscriptions', currentAccount!.did], - async queryFn() { - return normalizeProducts(RESPONSE, { - currencyFormatter, - }) - }, - }) -} - -export function usePurchaseSubscription() { - return useMutation({ - async mutationFn(_priceObject: any) {}, - }) -} - -function normalizeProducts( - products: {product: StripeProduct; price: StripePrice}[], - options: { - currencyFormatter: ReturnType - }, -): Subscription[] { - return products - .map(({product, price}) => { - const info = identifierToSubscriptionInfo(product.name) - - if (!info) return - - const priceObj = - price.currency_options[options.currencyFormatter.currency] - const value = priceObj.unit_amount - const formatted = options.currencyFormatter.format(value / 100) - - return { - info, - pricing: { - value, - formatted, - }, - raw: { - price_id: price.id, - }, - } - }) - .filter(Boolean) as Subscription[] -} - -const RESPONSE = [ - { - product: { - id: 'prod_R2eNjNa6mB1Jlu', - object: 'product', - active: true, - attributes: [], - created: 1729092225, - default_price: 'price_1QAZ4fAwTlpRxHkARl1gtw5C', - description: null, - features: [], - images: [], - livemode: false, - marketing_features: [], - metadata: {}, - name: 'bsky_tier_0_monthly', - package_dimensions: null, - shippable: null, - statement_descriptor: null, - tax_code: 'txcd_10000000', - type: 'service', - unit_label: null, - updated: 1729869343, - url: null, - }, - price: { - id: 'price_1QAZ4fAwTlpRxHkARl1gtw5C', - object: 'price', - active: true, - billing_scheme: 'per_unit', - created: 1729092225, - currency: 'usd', - currency_options: { - gbp: { - custom_unit_amount: null, - tax_behavior: 'inclusive', - unit_amount: 250, - unit_amount_decimal: '250', - }, - usd: { - custom_unit_amount: null, - tax_behavior: 'inclusive', - unit_amount: 300, - unit_amount_decimal: '300', - }, - }, - custom_unit_amount: null, - livemode: false, - lookup_key: null, - metadata: {}, - nickname: null, - product: 'prod_R2eNjNa6mB1Jlu', - recurring: { - aggregate_usage: null, - interval: 'month', - interval_count: 1, - meter: null, - trial_period_days: null, - usage_type: 'licensed', - }, - tax_behavior: 'inclusive', - tiers_mode: null, - transform_quantity: null, - type: 'recurring', - unit_amount: 300, - unit_amount_decimal: '300', - }, - }, - { - product: { - id: 'prod_R37Zg28EeQ9XAz', - object: 'product', - active: true, - attributes: [], - created: 1729200807, - default_price: 'price_1QB1K0AwTlpRxHkA4Mdfxati', - description: null, - features: [], - images: [], - livemode: false, - marketing_features: [], - metadata: {}, - name: 'bsky_tier_0_annual', - package_dimensions: null, - shippable: null, - statement_descriptor: null, - tax_code: 'txcd_10000000', - type: 'service', - unit_label: null, - updated: 1729869329, - url: null, - }, - price: { - id: 'price_1QB1K0AwTlpRxHkA4Mdfxati', - object: 'price', - active: true, - billing_scheme: 'per_unit', - created: 1729200808, - currency: 'usd', - currency_options: { - gbp: { - custom_unit_amount: null, - tax_behavior: 'inclusive', - unit_amount: 2000, - unit_amount_decimal: '2000', - }, - usd: { - custom_unit_amount: null, - tax_behavior: 'inclusive', - unit_amount: 2400, - unit_amount_decimal: '2400', - }, - }, - custom_unit_amount: null, - livemode: false, - lookup_key: null, - metadata: {}, - nickname: null, - product: 'prod_R37Zg28EeQ9XAz', - recurring: { - aggregate_usage: null, - interval: 'year', - interval_count: 1, - meter: null, - trial_period_days: null, - usage_type: 'licensed', - }, - tax_behavior: 'inclusive', - tiers_mode: null, - transform_quantity: null, - type: 'recurring', - unit_amount: 2400, - unit_amount_decimal: '2400', - }, - }, -] diff --git a/src/state/purchases/useSubscriptions/types/index.ts b/src/state/purchases/useSubscriptions/types/index.ts deleted file mode 100644 index c6025aa449..0000000000 --- a/src/state/purchases/useSubscriptions/types/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum SubscriptionIdentifier { - Tier0MonthlyAuto = 'Tier0MonthlyAuto', - Tier0AnnualAuto = 'Tier0AnnualAuto', - Tier1MonthlyAuto = 'Tier1MonthlyAuto', - Tier1AnnualAuto = 'Tier1AnnualAuto', - Tier2MonthlyAuto = 'Tier2MonthlyAuto', - Tier2AnnualAuto = 'Tier2AnnualAuto', -} - -export type SubscriptionInfo = { - identifier: SubscriptionIdentifier - interval: 'monthly' | 'annual' - autoRenew: boolean -} - -export type Subscription = { - info: SubscriptionInfo - raw: any -} diff --git a/src/state/purchases/useSubscriptions/util.ts b/src/state/purchases/useSubscriptions/util.ts deleted file mode 100644 index 829af96dd0..0000000000 --- a/src/state/purchases/useSubscriptions/util.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - SubscriptionIdentifier, - SubscriptionInfo, -} from '#/state/purchases/useSubscriptions/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, - } - } - - /* - * Stripe - */ - case 'bsky_tier_0_monthly': { - return { - identifier: SubscriptionIdentifier.Tier0MonthlyAuto, - interval: 'monthly', - autoRenew: true, - } - } - case 'bsky_tier_0_annual': { - return { - identifier: SubscriptionIdentifier.Tier0MonthlyAuto, - interval: 'annual', - autoRenew: true, - } - } - - /* - * Fallback - */ - default: { - return undefined - } - } -}