/**
* @module UserMemberships
*/
import './types.js'
import { HttpClient } from '../../infrastructure/http/HttpClient'
import { globalConfig } from '../config'
const baseUrl = `/api/user-memberships`
/**
* Represents a user membership object
*/
export interface Membership {
id: number
user_id: number
membership_type: string
start_date: string
expiration_date: string | null
status: string
created_at: string
updated_at: string
[key: string]: any
}
/**
* Represents tokens for Shopify and Recharge integration
*/
export interface RechargeTokens {
shopify_customer_access_token: string
store_identifier: string
recharge_storefront_access_token: string
storefront_access_token: string
}
/**
* Represents the response from subscription upgrade
*/
export interface UpgradeSubscriptionResponse {
action: 'instant_upgrade' | 'shopify'
message?: string
url?: string
}
/**
* Represents the response when user should create an account (no entitlements or user not found)
*/
export interface RestorePurchasesCreateAccountResponse {
shouldCreateAccount: true
originalAppUserId?: string
}
/**
* Represents the response when user should login
*/
export interface RestorePurchasesShouldLoginResponse {
shouldLogin: true
email: string
}
/**
* Represents the response when user is authenticated successfully
*/
export interface RestorePurchasesSuccessResponse {
success: boolean
token: string
tokenType: string
userId: number
}
/**
* Represents the response when user should setup an account (entitlements found but account requires setup)
*/
export interface RestorePurchasesSetupAccountResponse {
shouldSetupAccount: true
email: string
originalAppUserId: string
}
/**
* Represents response for latest subscription platform as best we can determine.
*/
export interface SubscriptionPlatform {
last_platform: 'ios' | 'android' | 'web' | null
has_active_platform_subscription: boolean
}
/**
* Represents all possible responses from RevenueCat purchase restoration
*/
export type RestorePurchasesResponse =
| RestorePurchasesCreateAccountResponse
| RestorePurchasesShouldLoginResponse
| RestorePurchasesSuccessResponse
| RestorePurchasesSetupAccountResponse
/**
* Fetches the authenticated user's memberships from the API.
*
* @returns {Promise<Array<Membership>>} - A promise that resolves to an array of membership objects.
*
* @throws {Error} - Throws an error if the request fails.
*
* @example
* fetchMemberships()
* .then(memberships => console.log(memberships))
* .catch(error => console.error(error));
*/
export async function fetchMemberships(): Promise<Membership[]> {
const httpClient = new HttpClient(globalConfig.baseUrl)
return httpClient.get<Membership[]>(`${baseUrl}/v1`)
}
/**
* Fetches tokens required for interacting with Shopify and Recharge.
*
* @returns {Promise<RechargeTokens>} - A promise that resolves to an object containing:
* - {string} shopify_customer_access_token - Shopify customer access token.
* - {string} store_identifier - The store domain identifier.
* - {string} recharge_storefront_access_token - Recharge storefront access token.
* - {string} storefront_access_token - Shopify storefront access token.
*
* @throws {Error} - Throws an error if the request fails.
*
* @example
* fetchRechargeTokens()
* .then(tokens => console.log(tokens))
* .catch(error => console.error(error));
*/
export async function fetchRechargeTokens(): Promise<RechargeTokens> {
const httpClient = new HttpClient(globalConfig.baseUrl)
return httpClient.get<RechargeTokens>(`${baseUrl}/v1/subscriptions-tokens`)
}
/**
* Upgrades the user's subscription or provides a prefilled add-to-cart URL.
*
* @returns {Promise<UpgradeSubscriptionResponse>} A promise that resolves to an object containing either:
* - {string} action - The action performed (e.g., 'instant_upgrade').
* - {string} message - Success message if the subscription was upgraded immediately.
* OR
* - {string} action - The action performed (e.g., 'shopify').
* - {string} url - URL to the ecommerce store with prefilled add-to-cart parameters.
*
* @throws {Error} Throws an error if the request fails.
*
* @example
* upgradeSubscription()
* .then(response => console.log(response))
* .catch(error => console.error(error));
*/
export async function upgradeSubscription(): Promise<UpgradeSubscriptionResponse> {
const httpClient = new HttpClient(globalConfig.baseUrl)
return httpClient.get<UpgradeSubscriptionResponse>(`${baseUrl}/v1/update-subscription`)
}
/**
* Restores purchases by verifying subscriber status with RevenueCat.
*
* This function verifies the subscriber's status with RevenueCat and attempts to sync their
* subscription data with the platform. The backend will search for a user by email (if provided)
* or by the RevenueCat original app user ID.
*
* @param {string} originalAppUserId - The original app user ID from RevenueCat.
* @param {string} [email] - (Optional) The user's email address. If provided and a user with this
* email exists, their subscription will be synced. If omitted, the backend
* will only search by the RevenueCat original app user ID.
*
* @returns {Promise<RestorePurchasesResponse>} - A promise that resolves to one of three possible responses:
*
* **Case 1: Should Create Account** (No active entitlements OR user not found with active entitlements)
* - {boolean} shouldCreateAccount - Always true
* - {string} [originalAppUserId] - RevenueCat ID to link when creating account (only if user has entitlements)
*
* **Case 2: Should Login** (User exists but not currently authenticated)
* - {boolean} shouldLogin - Always true
* - {string} email - The email address of the found user
*
* **Case 3: Success** (User authenticated and synced successfully)
* - {boolean} success - Always true
* - {string} token - Authentication token
* - {string} tokenType - Token type 'bearer'
* - {number} userId - The user's ID
*
* @throws {Error} - Throws an error if the request fails or if required parameters are missing.
*
* @example
* // With email
* restorePurchases('rc_user_123', 'user@example.com')
* .then(response => {
* if ('shouldCreateAccount' in response) {
* // Handle account creation
* } else if ('shouldLogin' in response) {
* // Handle login with response.email
* } else if ('success' in response) {
* // Handle successful authentication with response.token
* }
* })
* .catch(error => console.error(error));
*
* @example
* // Without email (search by original app user ID only)
* restorePurchases('rc_user_123')
* .then(response => console.log(response))
* .catch(error => console.error(error));
*/
export async function restorePurchases(
originalAppUserId: string,
email?: string|null
): Promise<RestorePurchasesResponse> {
if (!originalAppUserId) {
throw new Error('originalAppUserId is a required parameter')
}
const requestBody: { original_app_user_id: string; email?: string } = {
original_app_user_id: originalAppUserId
}
// Only include email if it has a valid value
if (email) {
requestBody.email = email
}
const httpClient = new HttpClient(globalConfig.baseUrl)
return httpClient.post<RestorePurchasesResponse>(
`${baseUrl}/v1/revenuecat/restore`,
requestBody
)
}
/**
* Get the upgrade price from Basic to Plus membership.
* Returns the price based on the user's subscription interval.
*
* For monthly subscribers: Returns the monthly upgrade cost (difference between Plus and Base monthly prices, ~$5/month)
* For yearly subscribers: Returns the monthly equivalent upgrade cost ($3.33/month from $40/year)
* For lifetime subscribers: Returns the annual upgrade cost for songs add-on ($40/year)
* If interval cannot be determined: Defaults to monthly price
*
* @returns {Promise<{price: number, currency: string, period: string|null}>} - The upgrade price information
* @property {number} price - The upgrade cost in USD (monthly for month/year, annual for lifetime)
* @property {string} currency - The currency
* @property {string|null} period - The billing period for the price ('month' or 'year'). Note: lifetime subscribers return 'year' period with annual price
*
* @example
* getUpgradePrice()
* .then(info => {
* console.log(`Upgrade price: $${info.price} per ${info.period}`)
* // Example outputs:
* // Monthly: "Upgrade price: $5 per month"
* // Yearly: "Upgrade price: $3.33 per month"
* // Lifetime: "Upgrade price: $40 per year"
* })
* .catch(error => {
* console.error('Failed to fetch upgrade price:', error)
* })
*/
export async function getUpgradePrice() {
const httpClient = new HttpClient(globalConfig.baseUrl)
return httpClient.get(`${baseUrl}/v1/upgrade-price`)
}
/**
* @returns {Promise<'ios' | 'android' | 'web' | null>} The platform of the user's last known subscription
*/
export async function fetchLastSubscriptionPlatform(): Promise<'ios' | 'android' | 'web' | null> {
const httpClient = new HttpClient(globalConfig.baseUrl)
const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
return response.last_platform
}
/**
* @returns {Promise<boolean>} Whether the user has any subscription from a known platform (web, ios, android)
*/
export async function fetchHasActivePlatformSubscription(): Promise<boolean> {
const httpClient = new HttpClient(globalConfig.baseUrl)
const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
return response.has_active_platform_subscription
}