UNPKG

@tribute-tg/better-auth

Version:

Tribute integration for better-auth

1 lines 19.5 kB
{"version":3,"file":"index.cjs","names":["z","tribute","cachedSubscriptions: CheckoutSubscription[]","z","APIError","subscriptions","subscription","tribute","e: unknown","sessionMiddleware"],"sources":["../src/schema.ts","../src/util.ts","../src/webhooks.ts","../src/subscription.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import { mergeSchema } from 'better-auth/db';\nimport type { BetterAuthPluginDBSchema } from 'better-auth/db';\nimport type { TributeOptions } from './types';\n\nexport const subscriptions = {\n subscription: {\n fields: {\n tributeSubscriptionId: {\n type: 'number',\n required: true,\n },\n tributeSubscriptionName: {\n type: 'string',\n required: true,\n },\n tributeUserId: {\n type: 'number',\n required: true,\n },\n telegramUserId: {\n type: 'number',\n required: true,\n },\n channelId: {\n type: 'number',\n required: true,\n },\n period: {\n type: 'string',\n required: true,\n },\n price: {\n type: 'number',\n required: true,\n },\n amount: {\n type: 'number',\n required: true,\n },\n currency: {\n type: 'string',\n required: true,\n },\n expiresAt: {\n type: 'string',\n required: true,\n },\n status: {\n type: 'string',\n defaultValue: 'active',\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const user = {\n user: {\n fields: {\n tributeUserId: {\n type: 'number',\n required: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = (options: TributeOptions) => {\n return mergeSchema(\n {\n ...subscriptions,\n ...user,\n },\n options.schema\n );\n};\n","export const verifyHmac = async (key: string, data: string, expectedHmac: string) => {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n const dataToVerify = encoder.encode(data);\n const cryptoKey = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, dataToVerify);\n const computedHmac = Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n return computedHmac === expectedHmac;\n};\n","import { z } from 'zod';\nimport { createAuthEndpoint } from 'better-auth/api';\nimport type { Account } from 'better-auth';\nimport type { Tribute } from '@tribute-tg/sdk';\nimport type { Subscription, TributeOptions } from './types';\nimport { verifyHmac } from './util';\n\nexport const webhooks =\n (webhooksOptions: Pick<TributeOptions, 'onNewSubscription' | 'onCancelledSubscription' | 'onEvent'>) =>\n (tribute: Tribute) => {\n return {\n tributeWebhooks: createAuthEndpoint(\n '/tribute/webhooks',\n {\n method: 'POST',\n body: z.object({\n name: z.string(),\n created_at: z.string(),\n sent_at: z.string(),\n payload: z.any(),\n }),\n },\n async (ctx) => {\n const signature = ctx.headers?.get('Trbt-Signature');\n if (!signature) return ctx.error(401);\n const isVerified = await verifyHmac(tribute.apiKey, JSON.stringify(ctx.body), signature);\n if (!isVerified) return ctx.error(401);\n\n const { onNewSubscription, onCancelledSubscription, onEvent } = webhooksOptions;\n\n const event = ctx.body;\n const eventName = event.name;\n const payload = event.payload;\n\n if (eventName === 'new_subscription') {\n await onNewSubscription?.(event.payload);\n\n const telegramUserId = payload.telegram_user_id;\n const account = await ctx.context.adapter.findOne<Account>({\n model: 'account',\n where: [\n { field: 'accountId', value: String(telegramUserId) },\n { field: 'providerId', value: 'telegram' },\n ],\n });\n\n if (account) {\n const userId = account.userId;\n const tributeUserId = payload.user_id;\n\n await ctx.context.adapter.update({\n model: 'user',\n update: { tributeUserId },\n where: [{ field: 'id', value: userId }],\n });\n\n await ctx.context.adapter.create<Subscription>({\n model: 'subscription',\n data: {\n userId,\n telegramUserId,\n tributeUserId: payload.user_id,\n tributeSubscriptionId: payload.subscription_id,\n tributeSubscriptionName: payload.subscription_name,\n channelId: payload.channel_id,\n period: payload.period,\n price: payload.price / 100,\n amount: payload.amount / 100,\n currency: payload.currency,\n expiresAt: payload.expires_at,\n status: 'active',\n },\n });\n } else {\n console.log('Account not found');\n }\n } else if (eventName === 'cancelled_subscription') {\n await onCancelledSubscription?.(payload);\n\n await ctx.context.adapter.update({\n model: 'subscription',\n update: { status: 'cancelled' },\n where: [\n { field: 'tributeSubscriptionId', value: payload.subscription_id },\n { field: 'tributeUserId', value: payload.user_id },\n ],\n });\n }\n await onEvent?.(event);\n }\n ),\n };\n };\n","import { APIError } from 'better-auth/api';\nimport { sessionMiddleware } from 'better-auth/api';\nimport { createAuthEndpoint } from 'better-auth/plugins';\nimport { z } from 'zod';\nimport type { Tribute } from '@tribute-tg/sdk';\nimport type { CheckoutSubscription, Subscription, TributeOptions } from './types';\n\nconst cachedSubscriptions: CheckoutSubscription[] = [];\n\nexport const subscription = (options: Pick<TributeOptions, 'subscriptions'>) => (tribute: Tribute) => {\n return {\n upgradeSubscription: createAuthEndpoint(\n '/subscription/upgrade',\n {\n method: 'POST',\n body: z.object({\n subscriptionId: z.number().optional(),\n period: z.string().optional(),\n currency: z.string().optional(),\n slug: z.string().optional(),\n }),\n },\n async (ctx) => {\n // const session = await getSessionFromCtx(ctx);\n\n if (!ctx.body.slug && !ctx.body.subscriptionId) {\n throw new APIError('BAD_REQUEST', {\n message: 'Either slug or subscriptionId is required',\n });\n }\n\n const findFrom = (subscriptions: CheckoutSubscription[]) =>\n subscriptions.find(\n (subscription) =>\n (!ctx.body.subscriptionId || subscription.subscriptionId === ctx.body.subscriptionId) &&\n (!ctx.body.slug || subscription.slug === ctx.body.slug) &&\n (!ctx.body.period || subscription.period === ctx.body.period) &&\n (!ctx.body.currency || subscription.currency === ctx.body.currency)\n );\n\n let subscription = findFrom(cachedSubscriptions);\n\n if (!subscription) {\n const resolvedSubscriptions = await (typeof options.subscriptions === 'function'\n ? options.subscriptions()\n : options.subscriptions);\n const resolvedSubscription = findFrom(resolvedSubscriptions ?? []);\n if (resolvedSubscription) subscription = resolvedSubscription;\n }\n\n if (!subscription) {\n throw new APIError('BAD_REQUEST', {\n message: 'Subscription not found',\n });\n }\n\n try {\n const cachedLink = subscription.redirectUrl?.trim();\n if (cachedLink) return ctx.json({ url: cachedLink, redirect: true });\n ctx.context.logger.info(`Fetching subscription link for ${subscription.subscriptionId}`);\n const subscriptionResponse = await tribute.getSubscription(subscription.subscriptionId);\n const url = subscriptionResponse.subscription.webLink;\n // Cache link (and other subscription info) in memory to reduce Tribute API calls\n const index = cachedSubscriptions.findIndex((s) => s.subscriptionId === subscription.subscriptionId);\n if (index === -1) {\n cachedSubscriptions.push({ ...subscription, redirectUrl: url });\n } else {\n cachedSubscriptions[index] = { ...subscription, redirectUrl: url };\n }\n return ctx.json({ url, redirect: true });\n } catch (e: unknown) {\n if (e instanceof Error) {\n ctx.context.logger.error(`Tribute checkout creation failed. Error: ${e.message}`);\n }\n\n throw new APIError('INTERNAL_SERVER_ERROR', {\n message: 'Checkout creation failed',\n });\n }\n }\n ),\n\n listActiveSubscriptions: createAuthEndpoint(\n '/subscription/list',\n {\n method: 'GET',\n use: [sessionMiddleware],\n },\n async (ctx) => {\n if (!ctx.context.session.user.id || !ctx.context.session.user['tributeUserId']) {\n throw new APIError('BAD_REQUEST', {\n message: 'User not found',\n });\n }\n\n try {\n const userSubscriptions = await ctx.context.adapter.findMany<Subscription>({\n model: 'subscription',\n where: [{ field: 'tributeUserId', value: ctx.context.session.user['tributeUserId'] }],\n });\n\n const activeSubscriptions = userSubscriptions.filter((s) => {\n const expiresAt = new Date(s.expiresAt);\n return expiresAt > new Date() || s.status === 'active';\n });\n\n return ctx.json(activeSubscriptions);\n } catch (e: unknown) {\n if (e instanceof Error) {\n ctx.context.logger.error(`Tribute subscriptions list failed. Error: ${e.message}`);\n }\n\n throw new APIError('INTERNAL_SERVER_ERROR', {\n message: 'Subscriptions list failed',\n });\n }\n }\n ),\n\n portal: createAuthEndpoint(\n '/customer/portal',\n {\n method: 'GET',\n use: [sessionMiddleware],\n },\n async (ctx) => {\n if (!ctx.context.session?.user.id) {\n throw new APIError('BAD_REQUEST', {\n message: 'User not found',\n });\n }\n\n try {\n return ctx.json({\n url: 'https://t.me/tribute/app?startapp=',\n redirect: true,\n });\n } catch (e: unknown) {\n if (e instanceof Error) {\n ctx.context.logger.error(`Tribute customer portal creation failed. Error: ${e.message}`);\n }\n\n throw new APIError('INTERNAL_SERVER_ERROR', {\n message: 'Customer portal creation failed',\n });\n }\n }\n ),\n state: createAuthEndpoint(\n '/customer/state',\n {\n method: 'GET',\n use: [sessionMiddleware],\n },\n async (ctx) => {\n if (!ctx.context.session.user.id || !ctx.context.session.user['tributeUserId']) {\n throw new APIError('BAD_REQUEST', {\n message: 'User not found',\n });\n }\n\n try {\n const userSubscriptions = await ctx.context.adapter.findMany<Subscription>({\n model: 'subscription',\n where: [{ field: 'tributeUserId', value: ctx.context.session.user['tributeUserId'] }],\n });\n\n const activeSubscriptions = userSubscriptions.filter((s) => {\n const expiresAt = new Date(s.expiresAt);\n return expiresAt > new Date() || s.status === 'active';\n });\n\n const state = {\n ...ctx.context.session.user,\n activeSubscriptions,\n };\n\n return ctx.json(state);\n } catch (e: unknown) {\n if (e instanceof Error) {\n ctx.context.logger.error(`Tribute customer state failed. Error: ${e.message}`);\n }\n\n throw new APIError('INTERNAL_SERVER_ERROR', {\n message: 'Customer state failed',\n });\n }\n }\n ),\n };\n};\n","import type { BetterAuthClientPlugin } from 'better-auth';\nimport type { tribute } from './index';\n\nexport const tributeClient = () => {\n return {\n id: 'tribute-client',\n $InferServerPlugin: {} as ReturnType<typeof tribute>,\n } satisfies BetterAuthClientPlugin;\n};\n","import type { BetterAuthPlugin } from 'better-auth';\n\nimport type { TributeEndpoints, TributeOptions } from './types';\nimport { getSchema } from './schema';\nimport { webhooks } from './webhooks';\nimport { subscription } from './subscription';\n\nexport { tributeClient } from './client';\n\nexport * from './subscription';\nexport * from './webhooks';\nexport * from './types';\n\nexport const tribute = (options: TributeOptions) => {\n const plugins = [webhooks(options), subscription(options)]\n .map((use) => use(options.tributeClient))\n .reduce((acc, plugin) => {\n Object.assign(acc, plugin);\n return acc;\n }, {} as TributeEndpoints);\n\n return {\n id: 'tribute',\n endpoints: { ...plugins },\n schema: getSchema(options),\n } satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}