@casual-simulation/aux-records
Advanced tools
Comparing version 3.1.25-alpha.4329401863 to 3.1.25-alpha.4388028584
@@ -15,3 +15,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { cleanupObject, isActiveSubscription, } from './Utils'; | ||
import { formatV1SessionKey, parseSessionKey, randomCode } from './AuthUtils'; | ||
import { formatV1OpenAiKey, formatV1SessionKey, parseSessionKey, randomCode, } from './AuthUtils'; | ||
/** | ||
@@ -777,2 +777,5 @@ * The number of miliseconds that a login request should be valid for before expiration. | ||
}); | ||
if (cleaned.openAiKey) { | ||
cleaned.openAiKey = formatV1OpenAiKey(cleaned.openAiKey); | ||
} | ||
yield this._store.saveUser(Object.assign(Object.assign({}, user), cleaned)); | ||
@@ -779,0 +782,0 @@ return { |
@@ -39,2 +39,17 @@ /** | ||
]; | ||
/** | ||
* Formats the given OpenAI Key into a string that is detectable as an OpenAI Key. | ||
* @param apiKey The API Key that should be formatted. | ||
*/ | ||
export declare function formatV1OpenAiKey(apiKey: string): string; | ||
/** | ||
* Determines if the given string represents an OpenAI Key. | ||
* @param apiKey The API Key. | ||
*/ | ||
export declare function isOpenAiKey(apiKey: string): boolean; | ||
/** | ||
* Parses the given OpenAI Key. | ||
* @param key The key that should be parsed. | ||
*/ | ||
export declare function parseOpenAiKey(key: string): [key: string]; | ||
//# sourceMappingURL=AuthUtils.d.ts.map |
@@ -86,2 +86,30 @@ import { padStart } from 'lodash'; | ||
} | ||
/** | ||
* Formats the given OpenAI Key into a string that is detectable as an OpenAI Key. | ||
* @param apiKey The API Key that should be formatted. | ||
*/ | ||
export function formatV1OpenAiKey(apiKey) { | ||
return `vAI1.${toBase64String(apiKey)}`; | ||
} | ||
/** | ||
* Determines if the given string represents an OpenAI Key. | ||
* @param apiKey The API Key. | ||
*/ | ||
export function isOpenAiKey(apiKey) { | ||
return typeof apiKey === 'string' && apiKey.startsWith(`vAI1.`); | ||
} | ||
/** | ||
* Parses the given OpenAI Key. | ||
* @param key The key that should be parsed. | ||
*/ | ||
export function parseOpenAiKey(key) { | ||
if (!key || typeof key !== 'string') { | ||
return null; | ||
} | ||
if (!key.startsWith('vAI1.')) { | ||
return null; | ||
} | ||
const withoutVersion = key.slice('vAI1.'.length); | ||
return [fromBase64String(withoutVersion)]; | ||
} | ||
//# sourceMappingURL=AuthUtils.js.map |
{ | ||
"name": "@casual-simulation/aux-records", | ||
"version": "3.1.25-alpha.4329401863", | ||
"version": "3.1.25-alpha.4388028584", | ||
"description": "Helpers and managers used by the CasualOS records system.", | ||
@@ -40,7 +40,7 @@ "keywords": [], | ||
"dependencies": { | ||
"@casual-simulation/crypto": "^3.1.25-alpha.4329401863", | ||
"@casual-simulation/crypto": "^3.1.25-alpha.4388028584", | ||
"livekit-server-sdk": "1.0.2", | ||
"tweetnacl": "1.0.3" | ||
}, | ||
"gitHead": "08ad7876adf8e599fc0c818c1d292c3331f0c5bc" | ||
"gitHead": "cfe9114706fd53faaca7247bb1883b7eeec7babd" | ||
} |
@@ -795,2 +795,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
})), | ||
purchasableSubscriptions: result.purchasableSubscriptions.map((s) => ({ | ||
id: s.id, | ||
name: s.name, | ||
description: s.description, | ||
featureList: s.featureList, | ||
prices: s.prices, | ||
})), | ||
}); | ||
@@ -812,5 +819,22 @@ }); | ||
} | ||
let subscriptionId; | ||
let expectedPrice; | ||
if (typeof request.body === 'string' && request.body) { | ||
let body = tryParseJson(request.body); | ||
if (body.success) { | ||
if (typeof body.value.subscriptionId === 'string') { | ||
subscriptionId = body.value.subscriptionId; | ||
} | ||
if (typeof body.value.expectedPrice === 'object') { | ||
expectedPrice = body.value.expectedPrice; | ||
} | ||
} | ||
} | ||
console.log('sub id', subscriptionId); | ||
console.log('expected price', expectedPrice); | ||
const result = yield this._subscriptions.createManageSubscriptionLink({ | ||
sessionKey, | ||
userId, | ||
subscriptionId, | ||
expectedPrice, | ||
}); | ||
@@ -817,0 +841,0 @@ if (!result.success) { |
@@ -15,2 +15,7 @@ /** | ||
/** | ||
* Gets the information about the given product. | ||
* @param product The product. | ||
*/ | ||
getProductAndPriceInfo(product: string): Promise<StripeProduct>; | ||
/** | ||
* Creates a new checkout session for a user to use. | ||
@@ -48,2 +53,23 @@ * @param request The checkout session request. | ||
id: string; | ||
/** | ||
* The information about how this price has recurring payments. | ||
*/ | ||
recurring: { | ||
/** | ||
* The type of recurring interval that is used for this item's price. | ||
*/ | ||
interval: 'month' | 'year' | 'week' | 'day'; | ||
/** | ||
* The number of intervals that have to happen in order for the subscription to be renewed. | ||
*/ | ||
interval_count: number; | ||
}; | ||
/** | ||
* The currency that the scription is renewed in. | ||
*/ | ||
currency: string; | ||
/** | ||
* The amount of units that are charged for each renewal. | ||
*/ | ||
unit_amount: number; | ||
} | ||
@@ -327,2 +353,20 @@ /** | ||
} | ||
export interface StripeProduct { | ||
/** | ||
* The ID of the product. | ||
*/ | ||
id: string; | ||
/** | ||
* The name of the product. | ||
*/ | ||
name: string; | ||
/** | ||
* The description of the product. | ||
*/ | ||
description: string; | ||
/** | ||
* The default price for the product. | ||
*/ | ||
default_price: StripePrice; | ||
} | ||
//# sourceMappingURL=StripeInterface.d.ts.map |
@@ -7,13 +7,26 @@ import { AuthController, ValidateSessionKeyFailure } from './AuthController'; | ||
/** | ||
* The line items that should be included in the checkout request. | ||
* The information that should be used for subscriptions. | ||
*/ | ||
lineItems: { | ||
subscriptions: { | ||
/** | ||
* The ID of the price for the line item. | ||
* The ID of the subscription. | ||
* Only used for the API. | ||
*/ | ||
price: string; | ||
id: string; | ||
/** | ||
* The quantity to purchase. | ||
* The ID of the product that needs to be purchased for the subscription. | ||
*/ | ||
quantity?: number; | ||
product: string; | ||
/** | ||
* The list of features that should be shown for this subscription tier. | ||
*/ | ||
featureList: string[]; | ||
/** | ||
* The list of products that are eligible for this subscription tier. | ||
*/ | ||
eligibleProducts: string[]; | ||
/** | ||
* Whether this subscription should be the default. | ||
*/ | ||
defaultSubscription?: boolean; | ||
}[]; | ||
@@ -23,3 +36,2 @@ /** | ||
*/ | ||
products: string[]; | ||
/** | ||
@@ -56,2 +68,3 @@ * The webhook secret that should be used for validating webhooks. | ||
getSubscriptionStatus(request: GetSubscriptionStatusRequest): Promise<GetSubscriptionStatusResult>; | ||
private _getPurchasableSubscriptions; | ||
/** | ||
@@ -62,2 +75,3 @@ * Creates a link that the user can be redirected to in order to manage their subscription. | ||
createManageSubscriptionLink(request: CreateManageSubscriptionRequest): Promise<CreateManageSubscriptionResult>; | ||
private _createCheckoutSession; | ||
/** | ||
@@ -85,2 +99,15 @@ * Handles the webhook from Stripe for updating the internal database. | ||
userId: string; | ||
/** | ||
* The subscription that was selected for purcahse by the user. | ||
*/ | ||
subscriptionId?: string; | ||
/** | ||
* The price that the user expects to pay. | ||
*/ | ||
expectedPrice?: { | ||
currency: string; | ||
cost: number; | ||
interval: 'month' | 'year' | 'week' | 'day'; | ||
intervalLength: number; | ||
}; | ||
} | ||
@@ -100,3 +127,3 @@ export declare type CreateManageSubscriptionResult = CreateManageSubscriptionSuccess | CreateManageSubscriptionFailure; | ||
*/ | ||
errorCode: ServerError | ValidateSessionKeyFailure['errorCode'] | 'unacceptable_user_id' | 'not_supported'; | ||
errorCode: ServerError | ValidateSessionKeyFailure['errorCode'] | 'unacceptable_user_id' | 'unacceptable_request' | 'price_does_not_match' | 'not_supported'; | ||
/** | ||
@@ -132,2 +159,6 @@ * The error message. | ||
subscriptions: SubscriptionStatus[]; | ||
/** | ||
* The list of subscriptions that the user can purchase. | ||
*/ | ||
purchasableSubscriptions: PurchasableSubscription[]; | ||
} | ||
@@ -197,2 +228,45 @@ export interface SubscriptionStatus { | ||
} | ||
export interface PurchasableSubscription { | ||
/** | ||
* The ID of the subscription tier. | ||
*/ | ||
id: string; | ||
/** | ||
* The name of the product. | ||
*/ | ||
name: string; | ||
/** | ||
* The description of the product. | ||
*/ | ||
description: string; | ||
/** | ||
* The list of features included in the product. | ||
*/ | ||
featureList: string[]; | ||
/** | ||
* The list of prices that the subscription can be purchased at. | ||
*/ | ||
prices: { | ||
/** | ||
* The ID of the price. | ||
*/ | ||
id: string; | ||
/** | ||
* How frequently the subscription will renew when this price is purchased. | ||
*/ | ||
interval: 'month' | 'year' | 'week' | 'day'; | ||
/** | ||
* The number of months/years/weeks/days that the interval lasts for. | ||
*/ | ||
intervalLength: number; | ||
/** | ||
* The currency that this price is listed in. | ||
*/ | ||
currency: string; | ||
/** | ||
* The cost of this price. | ||
*/ | ||
cost: number; | ||
}[]; | ||
} | ||
export interface GetSubscriptionStatusFailure { | ||
@@ -199,0 +273,0 @@ success: false; |
@@ -63,2 +63,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
subscriptions: [], | ||
purchasableSubscriptions: yield this._getPurchasableSubscriptions(), | ||
}; | ||
@@ -85,2 +86,5 @@ } | ||
}); | ||
const purchasableSubscriptions = subscriptions.length > 0 | ||
? [] | ||
: yield this._getPurchasableSubscriptions(); | ||
return { | ||
@@ -91,2 +95,3 @@ success: true, | ||
subscriptions, | ||
purchasableSubscriptions, | ||
}; | ||
@@ -104,2 +109,30 @@ } | ||
} | ||
_getPurchasableSubscriptions() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const promises = this._config.subscriptions.map((s) => __awaiter(this, void 0, void 0, function* () { | ||
return ({ | ||
sub: s, | ||
info: yield this._stripe.getProductAndPriceInfo(s.product), | ||
}); | ||
})); | ||
const productInfo = yield Promise.all(promises); | ||
return productInfo | ||
.filter((i) => !!i.info) | ||
.map((i) => ({ | ||
id: i.sub.id, | ||
name: i.info.name, | ||
description: i.info.description, | ||
featureList: i.sub.featureList, | ||
prices: [ | ||
{ | ||
id: 'default', | ||
currency: i.info.default_price.currency, | ||
cost: i.info.default_price.unit_amount, | ||
interval: i.info.default_price.recurring.interval, | ||
intervalLength: i.info.default_price.recurring.interval_count, | ||
}, | ||
], | ||
})); | ||
}); | ||
} | ||
/** | ||
@@ -126,2 +159,18 @@ * Creates a link that the user can be redirected to in order to manage their subscription. | ||
} | ||
if (!!request.subscriptionId && | ||
typeof request.subscriptionId !== 'string') { | ||
return { | ||
success: false, | ||
errorCode: 'unacceptable_request', | ||
errorMessage: 'The given Subscription ID is invalid. If provided, it must be a correctly formatted string.', | ||
}; | ||
} | ||
if (!!request.expectedPrice && | ||
typeof request.expectedPrice !== 'object') { | ||
return { | ||
success: false, | ||
errorCode: 'unacceptable_request', | ||
errorMessage: 'The given Price ID is invalid. If provided, it must be an object.', | ||
}; | ||
} | ||
const keyResult = yield this._auth.validateSessionKey(request.sessionKey); | ||
@@ -139,2 +188,9 @@ if (keyResult.success === false) { | ||
} | ||
// if (this._config.subscriptions.length <= 0) { | ||
// return { | ||
// success: false, | ||
// errorCode: 'not_supported', | ||
// errorMessage: 'This method is not supported.', | ||
// }; | ||
// } | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Creating a checkout/management session for User (${keyResult.userId}).`); | ||
@@ -144,2 +200,9 @@ const user = yield this._authStore.findUser(keyResult.userId); | ||
if (!customerId) { | ||
if (this._config.subscriptions.length <= 0) { | ||
return { | ||
success: false, | ||
errorCode: 'not_supported', | ||
errorMessage: 'New subscriptions are not supported.', | ||
}; | ||
} | ||
console.log('[SubscriptionController] [createManageSubscriptionLink] No Stripe Customer ID. Creating New Customer and Checkout Session in Stripe.'); | ||
@@ -154,18 +217,3 @@ const result = yield this._stripe.createCustomer({ | ||
yield this._authStore.saveUser(Object.assign({}, user)); | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Creating Checkout Session.`); | ||
const session = yield this._stripe.createCheckoutSession({ | ||
customer: customerId, | ||
success_url: this._config.successUrl, | ||
cancel_url: this._config.cancelUrl, | ||
line_items: this._config.lineItems, | ||
mode: 'subscription', | ||
metadata: { | ||
userId: user.id, | ||
}, | ||
}); | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Checkout Session Success!`); | ||
return { | ||
success: true, | ||
url: session.url, | ||
}; | ||
return yield this._createCheckoutSession(request, user, customerId); | ||
} | ||
@@ -184,3 +232,3 @@ console.log(`[SubscriptionController] [createManageSubscriptionLink] User (${user.id}) Has Stripe Customer ID (${user.stripeCustomerId}). Checking active subscriptions for customer.`); | ||
} | ||
const hasManagableProduct = this._config.products.some((p) => s.items.some((i) => i.price.product.id === p)); | ||
const hasManagableProduct = this._config.subscriptions.some((sub) => sub.eligibleProducts.some((p) => s.items.some((i) => i.price.product.id === p))); | ||
return hasManagableProduct; | ||
@@ -201,17 +249,3 @@ }); | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Customer does not have a managable subscription. Creating a checkout session.`); | ||
const session = yield this._stripe.createCheckoutSession({ | ||
customer: customerId, | ||
success_url: this._config.successUrl, | ||
cancel_url: this._config.cancelUrl, | ||
line_items: this._config.lineItems, | ||
mode: 'subscription', | ||
metadata: { | ||
userId: user.id, | ||
}, | ||
}); | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Checkout Session Success!`); | ||
return { | ||
success: true, | ||
url: session.url, | ||
}; | ||
return yield this._createCheckoutSession(request, user, customerId); | ||
} | ||
@@ -228,2 +262,62 @@ catch (err) { | ||
} | ||
_createCheckoutSession(request, user, customerId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let sub; | ||
if (request.subscriptionId) { | ||
sub = this._config.subscriptions.find((s) => s.id === request.subscriptionId); | ||
if (sub) { | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Using specified subscription (${request.subscriptionId}).`); | ||
} | ||
} | ||
if (!sub) { | ||
sub = this._config.subscriptions.find((s) => s.defaultSubscription); | ||
if (sub) { | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Using default subscription.`); | ||
} | ||
} | ||
if (!sub) { | ||
sub = this._config.subscriptions[0]; | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Using first subscription.`); | ||
} | ||
const productInfo = yield this._stripe.getProductAndPriceInfo(sub.product); | ||
if (request.expectedPrice) { | ||
if (request.expectedPrice.currency !== | ||
productInfo.default_price.currency || | ||
request.expectedPrice.cost !== | ||
productInfo.default_price.unit_amount || | ||
request.expectedPrice.interval !== | ||
productInfo.default_price.recurring.interval || | ||
request.expectedPrice.intervalLength !== | ||
productInfo.default_price.recurring.interval_count) { | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Expected price does not match actual price.`); | ||
return { | ||
success: false, | ||
errorCode: 'price_does_not_match', | ||
errorMessage: 'The expected price does not match the actual price.', | ||
}; | ||
} | ||
} | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Creating Checkout Session.`); | ||
const session = yield this._stripe.createCheckoutSession({ | ||
customer: customerId, | ||
success_url: this._config.successUrl, | ||
cancel_url: this._config.cancelUrl, | ||
line_items: [ | ||
{ | ||
price: productInfo.default_price.id, | ||
quantity: 1, | ||
}, | ||
], | ||
mode: 'subscription', | ||
metadata: { | ||
userId: user.id, | ||
}, | ||
}); | ||
console.log(`[SubscriptionController] [createManageSubscriptionLink] Checkout Session Success!`); | ||
return { | ||
success: true, | ||
url: session.url, | ||
}; | ||
}); | ||
} | ||
/** | ||
@@ -277,2 +371,10 @@ * Handles the webhook from Stripe for updating the internal database. | ||
const subscription = event.data.object; | ||
const items = subscription.items.data; | ||
const matches = items.some((i) => this._config.subscriptions.some((s) => s.eligibleProducts.some((p) => p === i.price.product))); | ||
if (!matches) { | ||
console.log(`[SubscriptionController] [handleStripeWebhook] No item in the subscription matches an eligible product in the config.`); | ||
return { | ||
success: true, | ||
}; | ||
} | ||
const status = subscription.status; | ||
@@ -299,3 +401,3 @@ const active = isActiveSubscription(status); | ||
catch (err) { | ||
console.log('[SubscriptionController] An error occurred while handling a stripe webhook:', err); | ||
console.error('[SubscriptionController] An error occurred while handling a stripe webhook:', err); | ||
return { | ||
@@ -323,8 +425,5 @@ success: false, | ||
typeof subscriptionConfig.successUrl !== 'string' || | ||
typeof subscriptionConfig.lineItems !== 'object' || | ||
typeof subscriptionConfig.products !== 'object' || | ||
!Array.isArray(subscriptionConfig.lineItems) || | ||
!Array.isArray(subscriptionConfig.products) || | ||
subscriptionConfig.lineItems.some((li) => typeof li !== 'object') || | ||
subscriptionConfig.products.some((p) => typeof p !== 'string')) { | ||
typeof subscriptionConfig.subscriptions !== 'object' || | ||
!Array.isArray(subscriptionConfig.subscriptions) || | ||
subscriptionConfig.subscriptions.some((s) => !isValidSubscription(s))) { | ||
subscriptionConfig = null; | ||
@@ -335,2 +434,10 @@ } | ||
} | ||
function isValidSubscription(sub) { | ||
return (sub && | ||
typeof sub.id === 'string' && | ||
Array.isArray(sub.featureList) && | ||
Array.isArray(sub.eligibleProducts) && | ||
typeof sub.product === 'string' && | ||
typeof sub.defaultSubscription === 'boolean'); | ||
} | ||
//# sourceMappingURL=SubscriptionController.js.map |
@@ -78,3 +78,3 @@ /** | ||
success: true; | ||
}): 401 | 501 | 404 | 400 | 200 | 403 | 500; | ||
}): 401 | 501 | 404 | 400 | 200 | 403 | 500 | 412; | ||
/** | ||
@@ -81,0 +81,0 @@ * Clones the given object into a new object that only has non-null and not-undefined properties. |
@@ -297,2 +297,5 @@ import { fromByteArray, toByteArray } from 'base64-js'; | ||
} | ||
else if (response.errorCode === 'price_does_not_match') { | ||
return 412; | ||
} | ||
else { | ||
@@ -299,0 +302,0 @@ return 400; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
393859
7036