fix(store): use updated cloud sdk pricing contract

This commit is contained in:
saltbo
2026-06-02 13:52:55 -04:00
parent dd1d237945
commit c3864c1d2c
4 changed files with 81 additions and 86 deletions
+2 -2
View File
@@ -64,8 +64,8 @@
"@tanstack/react-query": "^5.100.9",
"@tanstack/react-router": "^1.93.0",
"@tanstack/react-table": "^8.21.3",
"@uiw/react-md-editor": "^4.1.0",
"@uiw/react-markdown-preview": "5.2.0",
"@uiw/react-md-editor": "^4.1.0",
"@vidstack/react": "^1.12.13",
"better-auth": "^1.6.2",
"better-sqlite3": "^11.0.0",
@@ -97,7 +97,7 @@
"tailwind-merge": "^3.5.0",
"yet-another-react-lightbox": "^3.30.1",
"zod": "^4.4.3",
"zpan-cloud-sdk": "^1.0.2"
"zpan-cloud-sdk": "^1.0.3"
},
"devDependencies": {
"@biomejs/biome": "^2.4.10",
+5 -5
View File
@@ -194,8 +194,8 @@ importers:
specifier: ^4.4.3
version: 4.4.3
zpan-cloud-sdk:
specifier: ^1.0.2
version: 1.0.2(hono@4.12.18)(zod@4.4.3)
specifier: ^1.0.3
version: 1.0.3(hono@4.12.18)(zod@4.4.3)
devDependencies:
'@biomejs/biome':
specifier: ^2.4.10
@@ -5580,8 +5580,8 @@ packages:
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
zpan-cloud-sdk@1.0.2:
resolution: {integrity: sha512-PjMck/Y5n4usubIc33VphjhjERmtkLyEqC+qm6HsHO5akXwieLrs69kkjEAi/P7jl/cV+vVK2K4YLKoieacuKg==}
zpan-cloud-sdk@1.0.3:
resolution: {integrity: sha512-x5GCerFEXFLaRx+VZWmfDA1iikjQlMau8CC3fStoQZrEvtsijV+PbxnRxSCIVbmTFst2rx+Jx7y7RJVrGuXWpg==}
peerDependencies:
hono: ^4.7.11
zod: ^4.3.6
@@ -10979,7 +10979,7 @@ snapshots:
zod@4.4.3: {}
zpan-cloud-sdk@1.0.2(hono@4.12.18)(zod@4.4.3):
zpan-cloud-sdk@1.0.3(hono@4.12.18)(zod@4.4.3):
dependencies:
hono: 4.12.18
zod: 4.4.3
+73 -77
View File
@@ -42,8 +42,8 @@ function cloudProduct(overrides: Record<string, unknown> = {}) {
type: 'store_item',
name: 'Small',
description: 'starter',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
prices: [{ id: 'price-usd', currency: 'usd', amount: 500 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
prices: [{ id: 'price-usd', currency: 'usd', amount: 500, recurring: { interval: 'month', intervalCount: 1 } }],
active: true,
sortOrder: 1,
createdAt: '2026-05-06T00:00:00.000Z',
@@ -57,8 +57,8 @@ function cloudProductRequest(overrides: Record<string, unknown> = {}) {
type: 'store_item',
name: 'Small',
description: '',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
prices: [{ currency: 'usd', amount: 500 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
prices: [{ currency: 'usd', amount: 500, recurring: { interval: 'month', intervalCount: 1 } }],
active: true,
sortOrder: 0,
...overrides,
@@ -249,8 +249,8 @@ beforeEach(() => {
id: 'cloud-pkg-inactive',
name: 'Retired',
description: 'hidden from users',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192 } },
prices: [{ currency: 'usd', amount: 900 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 8192, includedCredits: 0 } },
prices: [{ currency: 'usd', amount: 900, recurring: { interval: 'month', intervalCount: 1 } }],
active: false,
sortOrder: 2,
}),
@@ -279,7 +279,9 @@ beforeEach(() => {
? new URL(String(url)).pathname.split('/').at(-1)
: 'cloud-pkg-1',
...body,
metadata: body.metadata ?? { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
metadata: body.metadata ?? {
deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 },
},
}),
} as Response
}
@@ -376,8 +378,8 @@ describe('Quota Store API', () => {
type: 'store_item',
name: 'SDK Package',
description: null,
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 8192 } },
prices: [{ currency: 'usd', amount: 999 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
prices: [{ currency: 'usd', amount: 999, recurring: { interval: 'month', intervalCount: 1 } }],
active: true,
sortOrder: 2,
createdAt: '2026-05-06T00:00:00.000Z',
@@ -389,8 +391,8 @@ describe('Quota Store API', () => {
type: 'store_item',
name: 'SDK Package',
description: null,
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 8192 } },
prices: [{ currency: 'usd', amount: 999 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
prices: [{ currency: 'usd', amount: 999, recurring: { interval: 'month', intervalCount: 1 } }],
active: true,
sortOrder: 2,
createdAt: '2026-05-06T00:00:00.000Z',
@@ -489,7 +491,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Bad',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 0, includedCredits: 0 } },
prices: [],
}),
),
@@ -581,9 +583,9 @@ describe('Quota Store API', () => {
expect(JSON.parse(body)).toMatchObject({
name: 'Small',
description: 'starter',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
type: 'store_item',
prices: [{ currency: 'usd', amount: 500 }],
prices: [{ currency: 'usd', amount: 500, recurring: { interval: 'month', intervalCount: 1 } }],
})
expect(JSON.parse(body)).not.toHaveProperty('callbackUrl')
expect(listed.status).toBe(200)
@@ -592,12 +594,12 @@ describe('Quota Store API', () => {
items: [
{
id: 'cloud-pkg-1',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
active: true,
},
{
id: 'cloud-pkg-inactive',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 8192, includedCredits: 0 } },
active: false,
},
],
@@ -618,8 +620,8 @@ describe('Quota Store API', () => {
id: 'cloud-pkg-object',
name: 'Object Shape',
description: null,
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 4096 } },
prices: [{ currency: 'usd', amount: 500 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [{ currency: 'usd', amount: 500, recurring: { interval: 'month', intervalCount: 1 } }],
sortOrder: 3,
}),
],
@@ -636,8 +638,8 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Updated',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192 } },
prices: [{ currency: 'usd', amount: 900 }],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 8192, includedCredits: 200 } },
prices: [{ currency: 'usd', amount: 900, recurring: { interval: 'year', intervalCount: 1 } }],
}),
),
})
@@ -649,7 +651,7 @@ describe('Quota Store API', () => {
{
id: 'cloud-pkg-object',
description: null,
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 4096 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
sortOrder: 3,
},
],
@@ -664,9 +666,9 @@ describe('Quota Store API', () => {
description: '',
type: 'store_item',
metadata: {
deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192 },
deliverable: { type: 'zpan.plan', storageBytes: 8192, includedCredits: 200 },
},
prices: [{ currency: 'usd', amount: 900 }],
prices: [{ currency: 'usd', amount: 900, recurring: { interval: 'year', intervalCount: 1 } }],
active: true,
sortOrder: 0,
})
@@ -708,7 +710,7 @@ describe('Quota Store API', () => {
expect(JSON.parse(init.body as string)).not.toHaveProperty('callbackUrl')
})
it('proxies recurring plans and fixed-duration quota packages to Cloud deliverables', async () => {
it('proxies recurring plans and credit packages to Cloud deliverables', async () => {
const { app, db } = await createTestApp()
await seedProLicense(db)
const headers = await adminHeaders(app)
@@ -721,55 +723,65 @@ describe('Quota Store API', () => {
cloudProductRequest({
name: 'Team Monthly',
metadata: {
deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 8192, trafficOveragePriceCents: 2 },
deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 1200 },
},
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{
currency: 'usd',
amount: 2,
recurring: { interval: 'month', intervalCount: 1, usageType: 'metered' },
metadata: { usageResource: 'traffic_egress' },
amount: 1900,
recurring: { interval: 'month', intervalCount: 1 },
metadata: { creditGrantType: 'subscription_grant', creditAmount: '1200' },
},
{
currency: 'usd',
amount: 19000,
recurring: { interval: 'year', intervalCount: 1 },
metadata: { creditGrantType: 'subscription_grant', creditAmount: '1200' },
},
],
}),
),
})
const fixedPackage = await app.request('/api/admin/store/packages', {
const creditPackage = await app.request('/api/admin/store/packages', {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify(
cloudProductRequest({
name: 'Traffic Pack',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192, validityDays: 30 } },
prices: [{ currency: 'usd', amount: 900 }],
name: 'Credits Pack',
metadata: { deliverable: { type: 'zpan.credits', includedCredits: 5000 } },
prices: [{ currency: 'usd', amount: 900, metadata: { creditGrantType: 'top_up', creditAmount: '5000' } }],
}),
),
})
expect(recurringPlan.status).toBe(201)
expect(fixedPackage.status).toBe(201)
expect(creditPackage.status).toBe(201)
const recurringBody = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string)
const fixedBody = JSON.parse((vi.mocked(fetch).mock.calls[1][1] as RequestInit).body as string)
const creditBody = JSON.parse((vi.mocked(fetch).mock.calls[1][1] as RequestInit).body as string)
expect(recurringBody).toMatchObject({
type: 'store_item',
metadata: {
deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 8192, trafficOveragePriceCents: 2 },
deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 1200 },
},
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{
currency: 'usd',
amount: 2,
recurring: { interval: 'month', intervalCount: 1, usageType: 'metered' },
metadata: { usageResource: 'traffic_egress' },
amount: 1900,
recurring: { interval: 'month', intervalCount: 1 },
metadata: { creditGrantType: 'subscription_grant', creditAmount: '1200' },
},
{
currency: 'usd',
amount: 19000,
recurring: { interval: 'year', intervalCount: 1 },
metadata: { creditGrantType: 'subscription_grant', creditAmount: '1200' },
},
],
})
expect(fixedBody).toMatchObject({
expect(creditBody).toMatchObject({
type: 'store_item',
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 0, trafficBytes: 8192, validityDays: 30 } },
prices: [{ currency: 'usd', amount: 900 }],
metadata: { deliverable: { type: 'zpan.credits', includedCredits: 5000 } },
prices: [{ currency: 'usd', amount: 900, metadata: { creditGrantType: 'top_up', creditAmount: '5000' } }],
})
})
@@ -785,7 +797,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Mixed Billing',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{ currency: 'usd', amount: 9000 },
@@ -837,7 +849,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Malformed Metered',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{ currency: 'usd', amount: 2, recurring: { interval: 'month', intervalCount: 1, usageType: 'metered' } },
@@ -851,7 +863,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Malformed Resource',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{
@@ -882,7 +894,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Duplicate Fixed',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{ currency: 'usd', amount: 2900, recurring: { interval: 'month', intervalCount: 1 } },
@@ -902,7 +914,7 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Duplicate Metered',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 1 } },
{
@@ -927,7 +939,7 @@ describe('Quota Store API', () => {
expect(vi.mocked(fetch)).not.toHaveBeenCalled()
})
it('rejects non-monthly subscription prices before proxying to Cloud', async () => {
it('accepts yearly subscription prices and rejects multi-month intervals before proxying to Cloud', async () => {
const { app, db } = await createTestApp()
await seedProLicense(db)
const headers = await adminHeaders(app)
@@ -939,16 +951,8 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Yearly Plan',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'year', intervalCount: 1 } },
{
currency: 'usd',
amount: 2,
recurring: { interval: 'year', intervalCount: 1, usageType: 'metered' },
metadata: { usageResource: 'traffic_egress' },
},
],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [{ currency: 'usd', amount: 1900, recurring: { interval: 'year', intervalCount: 1 } }],
}),
),
})
@@ -958,26 +962,18 @@ describe('Quota Store API', () => {
body: JSON.stringify(
cloudProductRequest({
name: 'Quarterly Plan',
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, trafficBytes: 0 } },
prices: [
{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 3 } },
{
currency: 'usd',
amount: 2,
recurring: { interval: 'month', intervalCount: 3, usageType: 'metered' },
metadata: { usageResource: 'traffic_egress' },
},
],
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 0 } },
prices: [{ currency: 'usd', amount: 1900, recurring: { interval: 'month', intervalCount: 3 } }],
}),
),
})
expect(yearlyInterval.status).toBe(400)
expect(yearlyInterval.status).toBe(201)
expect(multiMonthInterval.status).toBe(400)
expect(vi.mocked(fetch)).not.toHaveBeenCalled()
expect(vi.mocked(fetch)).toHaveBeenCalledOnce()
})
it('updates package quota deliverables directly through Cloud', async () => {
it('updates package plan deliverables directly through Cloud', async () => {
const { app, db } = await createTestApp()
await seedProLicense(db)
const headers = await adminHeaders(app)
@@ -987,14 +983,14 @@ describe('Quota Store API', () => {
method: 'PATCH',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
}),
})
expect(updated.status).toBe(200)
const [, init] = vi.mocked(fetch).mock.calls[0] as [URL, RequestInit]
expect(JSON.parse(String(init.body))).toEqual({
metadata: { deliverable: { type: 'zpan.extra', storageBytes: 4096, trafficBytes: 0 } },
metadata: { deliverable: { type: 'zpan.plan', storageBytes: 4096, includedCredits: 100 } },
})
})
@@ -1802,7 +1798,7 @@ describe('Quota Store API', () => {
expect(calls.some(([url, init]) => init.method === 'POST' && String(url).endsWith('/orders'))).toBe(false)
})
it('rejects non-USD checkout price ids instead of falling back to USD', async () => {
it('rejects malformed Cloud checkout products with non-USD prices', async () => {
const { app, db } = await createTestApp()
await seedProLicense(db)
const headers = await authedHeaders(app, 'buyer@example.com')
@@ -1827,8 +1823,8 @@ describe('Quota Store API', () => {
body: JSON.stringify({ packageId, priceId: 'price-cny' }),
})
expect(checkout.status).toBe(400)
await expect(checkout.json()).resolves.toEqual({ error: 'package_price_missing' })
expect(checkout.status).toBe(502)
await expect(checkout.json()).resolves.toEqual({ error: 'invalid_cloud_response' })
const calls = vi.mocked(fetch).mock.calls as Array<[URL, RequestInit]>
expect(calls.some(([url, init]) => init.method === 'POST' && String(url).endsWith('/orders'))).toBe(false)
})
@@ -477,7 +477,7 @@ describe('AdminCloudStorePage', () => {
)
})
it('edits legacy multi-currency packages with USD-only form controls and payloads', async () => {
it('edits plan packages with USD-only form controls and payloads', async () => {
vi.mocked(getCloudStoreSettings).mockResolvedValue(settings())
vi.mocked(listAdminCloudProducts).mockResolvedValue({
items: [
@@ -495,7 +495,6 @@ describe('AdminCloudStorePage', () => {
recurring: { interval: 'year', intervalCount: 1 },
metadata: { creditGrantType: 'subscription_grant', creditAmount: '1500' },
},
{ currency: 'cny', amount: 9800, recurring: { interval: 'month', intervalCount: 1 } },
],
metadata: {
deliverable: { type: 'zpan.plan', storageBytes: 107374182400, includedCredits: 1500 },