mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
client, clientReadOnly, server
This commit is contained in:
parent
d27052b79c
commit
20c9691d4f
@ -62,7 +62,9 @@ export async function ensureProductIdOrInlineProduct(
|
||||
freeTrial: value.free_trial,
|
||||
serverOnly: true,
|
||||
}])),
|
||||
metadata: inlineProduct.metadata,
|
||||
clientMetadata: inlineProduct.client_metadata ?? undefined,
|
||||
clientReadOnlyMetadata: inlineProduct.client_read_only_metadata ?? undefined,
|
||||
serverMetadata: inlineProduct.server_metadata ?? undefined,
|
||||
includedItems: typedFromEntries(Object.entries(inlineProduct.included_items).map(([key, value]) => [key, {
|
||||
repeat: value.repeat ?? "never",
|
||||
quantity: value.quantity ?? 0,
|
||||
@ -429,7 +431,9 @@ export function productToInlineProduct(product: ProductWithMetadata): yup.InferT
|
||||
stackable: product.stackable === true,
|
||||
server_only: product.serverOnly === true,
|
||||
included_items: product.includedItems,
|
||||
metadata: product.metadata,
|
||||
client_metadata: product.clientMetadata ?? null,
|
||||
client_read_only_metadata: product.clientReadOnlyMetadata ?? null,
|
||||
server_metadata: product.serverMetadata ?? null,
|
||||
prices: product.prices === "include-by-default" ? {} : typedFromEntries(typedEntries(product.prices).map(([key, value]) => [key, filterUndefined({
|
||||
...typedFromEntries(SUPPORTED_CURRENCIES.map(c => [c.code, getOrUndefined(value, c.code)])),
|
||||
interval: value.interval,
|
||||
|
||||
@ -300,7 +300,7 @@ it("should return inline product metadata when validating purchase code", async
|
||||
},
|
||||
},
|
||||
included_items: {},
|
||||
metadata: {
|
||||
server_metadata: {
|
||||
reference_id: "ref-123",
|
||||
features: ["priority-support", "analytics"],
|
||||
},
|
||||
@ -320,15 +320,43 @@ it("should return inline product metadata when validating purchase code", async
|
||||
full_code: fullCode,
|
||||
},
|
||||
});
|
||||
expect(validateResponse.status).toBe(200);
|
||||
const validateBody = validateResponse.body;
|
||||
expect(validateBody.product.metadata).toMatchInlineSnapshot(`
|
||||
{
|
||||
"features": [
|
||||
"priority-support",
|
||||
"analytics",
|
||||
],
|
||||
"reference_id": "ref-123",
|
||||
expect(validateResponse).toMatchInlineSnapshot(`
|
||||
NiceResponse {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"already_bought_non_stackable": false,
|
||||
"charges_enabled": false,
|
||||
"conflicting_products": [],
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Metadata Inline Product",
|
||||
"included_items": {},
|
||||
"prices": {
|
||||
"monthly-metadata": {
|
||||
"USD": "1500",
|
||||
"interval": [
|
||||
1,
|
||||
"month",
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": {
|
||||
"features": [
|
||||
"priority-support",
|
||||
"analytics",
|
||||
],
|
||||
"reference_id": "ref-123",
|
||||
},
|
||||
"server_only": true,
|
||||
"stackable": false,
|
||||
},
|
||||
"project_id": "<stripped UUID>",
|
||||
"stripe_account_id": <stripped field 'stripe_account_id'>,
|
||||
"test_mode": true,
|
||||
},
|
||||
"headers": Headers { <some fields may have been hidden> },
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@ -108,6 +108,8 @@ it("should grant configured subscription product and expose it via listing", asy
|
||||
{
|
||||
"id": "pro-plan",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Pro Plan",
|
||||
"included_items": {},
|
||||
@ -120,6 +122,7 @@ it("should grant configured subscription product and expose it via listing", asy
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -200,6 +203,8 @@ it("should hide server-only products from clients while exposing them to servers
|
||||
{
|
||||
"id": "server-plan",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Server Plan",
|
||||
"included_items": {},
|
||||
@ -212,6 +217,7 @@ it("should hide server-only products from clients while exposing them to servers
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": null,
|
||||
"server_only": true,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -327,6 +333,8 @@ it("should allow granting stackable product with custom quantity", async ({ expe
|
||||
{
|
||||
"id": "stackable-plan",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Stackable Plan",
|
||||
"included_items": {},
|
||||
@ -339,6 +347,7 @@ it("should allow granting stackable product with custom quantity", async ({ expe
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": true,
|
||||
},
|
||||
@ -372,7 +381,7 @@ it("should grant inline product without needing configuration", async ({ expect
|
||||
},
|
||||
},
|
||||
included_items: {},
|
||||
metadata: {
|
||||
server_metadata: {
|
||||
cohort: "beta",
|
||||
flags: ["inline-grant"],
|
||||
},
|
||||
@ -393,13 +402,11 @@ it("should grant inline product without needing configuration", async ({ expect
|
||||
{
|
||||
"id": null,
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Inline Access",
|
||||
"included_items": {},
|
||||
"metadata": {
|
||||
"cohort": "beta",
|
||||
"flags": ["inline-grant"],
|
||||
},
|
||||
"prices": {
|
||||
"quarterly": {
|
||||
"USD": "2400",
|
||||
@ -409,6 +416,10 @@ it("should grant inline product without needing configuration", async ({ expect
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": {
|
||||
"cohort": "beta",
|
||||
"flags": ["inline-grant"],
|
||||
},
|
||||
"server_only": true,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -693,6 +704,8 @@ it("listing products should list both subscription and one-time products", async
|
||||
{
|
||||
"id": "subscription-plan",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Subscription Plan",
|
||||
"included_items": {},
|
||||
@ -705,6 +718,7 @@ it("listing products should list both subscription and one-time products", async
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -713,10 +727,13 @@ it("listing products should list both subscription and one-time products", async
|
||||
{
|
||||
"id": "lifetime-addon",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Lifetime Add-on",
|
||||
"included_items": {},
|
||||
"prices": { "lifetime": { "USD": "5000" } },
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -821,6 +838,8 @@ it("listing products should support cursor pagination", async ({ expect }) => {
|
||||
{
|
||||
"id": "subscription-plan",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Subscription Plan",
|
||||
"included_items": {},
|
||||
@ -833,6 +852,7 @@ it("listing products should support cursor pagination", async ({ expect }) => {
|
||||
],
|
||||
},
|
||||
},
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -858,10 +878,13 @@ it("listing products should support cursor pagination", async ({ expect }) => {
|
||||
{
|
||||
"id": "lifetime-addon",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Lifetime Add-on",
|
||||
"included_items": {},
|
||||
"prices": { "lifetime": { "USD": "5000" } },
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
@ -870,10 +893,13 @@ it("listing products should support cursor pagination", async ({ expect }) => {
|
||||
{
|
||||
"id": "pro-addon",
|
||||
"product": {
|
||||
"client_metadata": null,
|
||||
"client_read_only_metadata": null,
|
||||
"customer_type": "user",
|
||||
"display_name": "Pro Add-on",
|
||||
"included_items": {},
|
||||
"prices": { "standard": { "USD": "7000" } },
|
||||
"server_metadata": null,
|
||||
"server_only": false,
|
||||
"stackable": false,
|
||||
},
|
||||
|
||||
@ -463,7 +463,7 @@ it("should list inline product metadata after completing test-mode purchase", as
|
||||
},
|
||||
},
|
||||
included_items: {},
|
||||
metadata: {
|
||||
server_metadata: {
|
||||
correlation_id: "inline-test-123",
|
||||
attributes: {
|
||||
seats: 5,
|
||||
@ -495,10 +495,10 @@ it("should list inline product metadata after completing test-mode purchase", as
|
||||
});
|
||||
expect(listResponse.status).toBe(200);
|
||||
const listBody = listResponse.body as {
|
||||
items: Array<{ product: { metadata?: Record<string, unknown> } }>,
|
||||
items: Array<{ product: { server_metadata?: Record<string, unknown> } }>,
|
||||
};
|
||||
expect(listBody.items).toHaveLength(1);
|
||||
expect(listBody.items[0].product.metadata).toMatchInlineSnapshot(`
|
||||
expect(listBody.items[0].product.server_metadata).toMatchInlineSnapshot(`
|
||||
{
|
||||
"attributes": {
|
||||
"seats": 5,
|
||||
|
||||
@ -591,9 +591,17 @@ export const productSchema = yupObject({
|
||||
),
|
||||
});
|
||||
|
||||
const productMetadata = jsonSchema.optional().meta({ openapiField: { description: 'Optional metadata that Stack Auth will store and return with this product. Use this to attach custom data needed by your application.', exampleValue: { featureFlag: true, source: 'marketing-campaign' } } });
|
||||
const productMetadataExample = { featureFlag: true, source: 'marketing-campaign' } as const;
|
||||
|
||||
export const productSchemaWithMetadata = productSchema.concat(yupObject({ metadata: productMetadata }));
|
||||
export const productClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('product'), exampleValue: productMetadataExample } });
|
||||
export const productClientReadOnlyMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientReadOnlyMetaDataDescription('product'), exampleValue: productMetadataExample } });
|
||||
export const productServerMetadataSchema = jsonSchema.meta({ openapiField: { description: _serverMetaDataDescription('product'), exampleValue: productMetadataExample } });
|
||||
|
||||
export const productSchemaWithMetadata = productSchema.concat(yupObject({
|
||||
clientMetadata: productClientMetadataSchema.optional(),
|
||||
clientReadOnlyMetadata: productClientReadOnlyMetadataSchema.optional(),
|
||||
serverMetadata: productServerMetadataSchema.optional(),
|
||||
}));
|
||||
|
||||
export const inlineProductSchema = yupObject({
|
||||
display_name: yupString().defined(),
|
||||
@ -617,7 +625,9 @@ export const inlineProductSchema = yupObject({
|
||||
expires: yupString().oneOf(['never', 'when-purchase-expires', 'when-repeated']).optional(),
|
||||
}),
|
||||
),
|
||||
metadata: productMetadata,
|
||||
client_metadata: productClientMetadataSchema.optional(),
|
||||
client_read_only_metadata: productClientReadOnlyMetadataSchema.optional(),
|
||||
server_metadata: productServerMetadataSchema.optional(),
|
||||
});
|
||||
|
||||
// Users
|
||||
|
||||
Loading…
Reference in New Issue
Block a user