diff --git a/changelog.md b/changelog.md index b4bc26a..f0ff6b0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-07 - 1.10.0 - feat(billingplan) +Add Paddle v2 checkout support and backend config endpoint; add CSP headers and bump typedserver + +- Add getPaddleConfig typedrequest handler in BillingPlanManager to expose PADDLE_TOKEN and PADDLE_PRICE_ID from environment. +- Introduce IReq_GetPaddleConfig typedrequest interface. +- Update frontend paddlesetup to use Paddle v2: load v2 script, call Paddle.Initialize with token, open Checkout using items.priceId and customer.email, and handle checkout.completed events (store transaction_id). +- Attempt to obtain user email from account state or via idpClient.whoIs before starting checkout; show error if email unavailable. +- Add Content Security Policy securityHeaders to website server configuration to allow Paddle, ProfitWell, Sentry and related assets/connections. +- Bump dependency @api.global/typedserver from ^7.8.17 to ^7.10.2. + ## 2025-12-01 - 1.9.0 - feat(account) Refactor account UI: migrate modals to promise-based show() API and improve navigation URL tracking diff --git a/package.json b/package.json index 053daee..380c35a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dependencies": { "@api.global/typedrequest": "^3.2.5", "@api.global/typedrequest-interfaces": "^3.0.19", - "@api.global/typedserver": "^7.8.17", + "@api.global/typedserver": "^7.10.2", "@api.global/typedsocket": "^4.1.0", "@consent.software/catalog": "^2.0.1", "@design.estate/dees-catalog": "^2.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa30fc3..600c435 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,11 +15,11 @@ importers: specifier: ^3.0.19 version: 3.0.19 '@api.global/typedserver': - specifier: ^7.8.17 - version: 7.8.17(@tiptap/pm@2.27.1) + specifier: ^7.10.2 + version: 7.10.2(@tiptap/pm@2.27.1) '@api.global/typedsocket': specifier: ^4.1.0 - version: 4.1.0(@push.rocks/smartserve@1.1.2) + version: 4.1.0(@push.rocks/smartserve@1.3.0) '@consent.software/catalog': specifier: ^2.0.1 version: 2.0.1 @@ -94,7 +94,7 @@ importers: version: 2.0.20 '@serve.zone/platformclient': specifier: ^1.1.2 - version: 1.1.2(@push.rocks/smartserve@1.1.2) + version: 1.1.2(@push.rocks/smartserve@1.3.0) '@tsclass/tsclass': specifier: ^9.3.0 version: 9.3.0 @@ -113,7 +113,7 @@ importers: version: 2.0.0 '@git.zone/tswatch': specifier: ^2.2.3 - version: 2.2.3(@push.rocks/smartserve@1.1.2) + version: 2.2.3(@push.rocks/smartserve@1.3.0) '@push.rocks/projectinfo': specifier: ^5.0.1 version: 5.0.2 @@ -135,8 +135,8 @@ packages: '@api.global/typedserver@3.0.80': resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==} - '@api.global/typedserver@7.8.17': - resolution: {integrity: sha512-I4aA8l2f43HP70dZTEqOyWiDcolFsZm6/0d3B7lYq/DwzorV4IJZ0ZO18jHyGHZuaaJri4m5j3Wt7dn6yHDY3A==} + '@api.global/typedserver@7.10.2': + resolution: {integrity: sha512-b/LzAbcJB6aPTd044s23yptZEukD7Hvsi8mXfPfE68OI85I2vfalFO8/h0E9LizoeDFGaTguLXhZYnGl8HkVZQ==} '@api.global/typedsocket@3.1.1': resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==} @@ -911,8 +911,8 @@ packages: '@push.rocks/smartrx@3.0.10': resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} - '@push.rocks/smartserve@1.1.2': - resolution: {integrity: sha512-NkJNgdDt/rfsd9AMheCxtFd5X+ubzffvxOxjb0Aw1A5JR3xmiWeRifqEV1oN7mMTGL9jyQVvIME6Yrdxr244dA==} + '@push.rocks/smartserve@1.3.0': + resolution: {integrity: sha512-4ZR9uKVWXVAPzU5wtCQ1mA9jNmOlUl3oGr50EceLT6803UwbNcst7Ek/BhzSaZ0qb2pz0jO5T/V+icgvZ1/5ww==} '@push.rocks/smartshell@3.3.0': resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} @@ -3678,11 +3678,11 @@ snapshots: '@push.rocks/webrequest': 3.0.37 '@push.rocks/webstream': 1.0.10 - '@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.1.2)': + '@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.3.0)': dependencies: '@api.global/typedrequest': 3.2.5 '@api.global/typedrequest-interfaces': 3.0.19 - '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.1.2) + '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.3.0) '@cloudflare/workers-types': 4.20251202.0 '@design.estate/dees-comms': 1.0.27 '@push.rocks/lik': 6.2.2 @@ -3726,11 +3726,11 @@ snapshots: - utf-8-validate - vue - '@api.global/typedserver@7.8.17(@tiptap/pm@2.27.1)': + '@api.global/typedserver@7.10.2(@tiptap/pm@2.27.1)': dependencies: '@api.global/typedrequest': 3.2.5 '@api.global/typedrequest-interfaces': 3.0.19 - '@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.1.2) + '@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.3.0) '@cloudflare/workers-types': 4.20251205.0 '@design.estate/dees-catalog': 2.0.3(@tiptap/pm@2.27.1) '@design.estate/dees-comms': 1.0.30 @@ -3753,7 +3753,7 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 5.0.1 '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smartserve': 1.1.2 + '@push.rocks/smartserve': 1.3.0 '@push.rocks/smartsitemap': 2.0.4 '@push.rocks/smartstream': 3.2.5 '@push.rocks/smarttime': 4.1.1 @@ -3772,18 +3772,18 @@ snapshots: - utf-8-validate - vue - '@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.1.2)': + '@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.3.0)': dependencies: '@api.global/typedrequest': 3.2.5 '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/isohash': 2.0.1 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smartsocket': 2.1.0(@push.rocks/smartserve@1.1.2) + '@push.rocks/smartsocket': 2.1.0(@push.rocks/smartserve@1.3.0) '@push.rocks/smartstring': 4.1.0 '@push.rocks/smarturl': 3.1.0 optionalDependencies: - '@push.rocks/smartserve': 1.1.2 + '@push.rocks/smartserve': 1.3.0 transitivePeerDependencies: - '@nuxt/kit' - bufferutil @@ -3792,7 +3792,7 @@ snapshots: - utf-8-validate - vue - '@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.1.2)': + '@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.3.0)': dependencies: '@api.global/typedrequest': 3.2.5 '@api.global/typedrequest-interfaces': 3.0.19 @@ -3801,7 +3801,7 @@ snapshots: '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 - '@push.rocks/smartserve': 1.1.2 + '@push.rocks/smartserve': 1.3.0 '@push.rocks/smartstring': 4.1.0 '@push.rocks/smarturl': 3.1.0 @@ -4598,9 +4598,9 @@ snapshots: '@push.rocks/smartshell': 3.3.0 tsx: 4.21.0 - '@git.zone/tswatch@2.2.3(@push.rocks/smartserve@1.1.2)': + '@git.zone/tswatch@2.2.3(@push.rocks/smartserve@1.3.0)': dependencies: - '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.1.2) + '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0) '@git.zone/tsbundle': 2.6.3 '@git.zone/tsrun': 2.0.0 '@push.rocks/early': 4.0.4 @@ -5380,7 +5380,7 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 rxjs: 7.8.2 - '@push.rocks/smartserve@1.1.2': + '@push.rocks/smartserve@1.3.0': dependencies: '@api.global/typedrequest': 3.2.5 '@push.rocks/lik': 6.2.2 @@ -5410,10 +5410,10 @@ snapshots: '@push.rocks/webrequest': 4.0.1 '@tsclass/tsclass': 9.3.0 - '@push.rocks/smartsocket@2.1.0(@push.rocks/smartserve@1.1.2)': + '@push.rocks/smartsocket@2.1.0(@push.rocks/smartserve@1.3.0)': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 - '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.1.2) + '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0) '@push.rocks/isohash': 2.0.1 '@push.rocks/isounique': 1.0.5 '@push.rocks/lik': 6.2.2 @@ -5727,11 +5727,11 @@ snapshots: '@push.rocks/smartlog-interfaces': 3.0.2 '@tsclass/tsclass': 4.4.4 - '@serve.zone/platformclient@1.1.2(@push.rocks/smartserve@1.1.2)': + '@serve.zone/platformclient@1.1.2(@push.rocks/smartserve@1.3.0)': dependencies: '@api.global/typedrequest': 3.2.5 - '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.1.2) - '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.1.2) + '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0) + '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.3.0) '@push.rocks/qenv': 6.1.3 '@push.rocks/smartlog': 3.1.10 '@push.rocks/smartntml': 2.0.8 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 14a6a44..b6b687b 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@idp.global/idp.global', - version: '1.9.0', + version: '1.10.0', description: 'An identity provider software managing user authentications, registrations, and sessions.' } diff --git a/ts/index.ts b/ts/index.ts index 38db723..2613577 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -8,6 +8,17 @@ export const runCli = async () => { feedMetadata: null, domain: 'idp.global', serveDir: paths.distWebDir, + securityHeaders: { + csp: { + defaultSrc: "'self'", + scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://cdn.paddle.com", "https://public.profitwell.com"], + styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.paddle.com", "https://assetbroker.lossless.one"], + imgSrc: ["'self'", "data:", "https:"], + fontSrc: ["'self'", "data:"], + connectSrc: ["'self'", "https://*.paddle.com", "https://buy.paddle.com", "https://checkout.paddle.com", "https://checkout-service.paddle.com", "https://cdn.paddle.com", "https://*.sentry.io", "https://public.profitwell.com", "wss:"], + frameSrc: ["https://buy.paddle.com", "https://checkout.paddle.com", "https://*.paddle.com"], + }, + }, addCustomRoutes: async (typedserver) => { // Enable SPA fallback - serves index.html for non-file routes (e.g., /login, /dashboard) typedserver.options.spaFallback = true; diff --git a/ts/reception/classes.billingplanmanager.ts b/ts/reception/classes.billingplanmanager.ts index 094869f..56fd95b 100644 --- a/ts/reception/classes.billingplanmanager.ts +++ b/ts/reception/classes.billingplanmanager.ts @@ -59,6 +59,17 @@ export class BillingPlanManager { } } } - })) + })); + + // Paddle configuration endpoint + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + 'getPaddleConfig', + async () => ({ + paddleToken: await this.receptionRef.serviceQenv.getEnvVarOnDemand('PADDLE_TOKEN'), + paddlePriceId: await this.receptionRef.serviceQenv.getEnvVarOnDemand('PADDLE_PRICE_ID'), + }) + ) + ); } } diff --git a/ts_interfaces/request/loint-reception.billingplan.ts b/ts_interfaces/request/loint-reception.billingplan.ts index bf8d55d..45d42d5 100644 --- a/ts_interfaces/request/loint-reception.billingplan.ts +++ b/ts_interfaces/request/loint-reception.billingplan.ts @@ -37,3 +37,19 @@ export interface IReq_GetBillingPlan billingPlan: data.IBillingPlan; }; } + +/** + * Returns Paddle configuration from environment variables + */ +export interface IReq_GetPaddleConfig + extends plugins.typedRequestInterfaces.implementsTR< + plugins.typedRequestInterfaces.ITypedRequest, + IReq_GetPaddleConfig + > { + method: 'getPaddleConfig'; + request: {}; + response: { + paddleToken: string; + paddlePriceId: string; + }; +} diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 14a6a44..b6b687b 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@idp.global/idp.global', - version: '1.9.0', + version: '1.10.0', description: 'An identity provider software managing user authentications, registrations, and sessions.' } diff --git a/ts_web/elements/account/views/paddlesetup.ts b/ts_web/elements/account/views/paddlesetup.ts index db5c511..cadf0e3 100644 --- a/ts_web/elements/account/views/paddlesetup.ts +++ b/ts_web/elements/account/views/paddlesetup.ts @@ -11,6 +11,7 @@ import { import * as plugins from '../../../plugins.js'; import sharedStyles from '../sharedstyles.js'; import * as state from '../../../states/accountstate.js'; +import { IdpState } from '../../../states/idp.state.js'; declare global { interface HTMLElementTagNameMap { @@ -61,28 +62,50 @@ export class PaddleSetupView extends DeesElement { public async openPaddle() { await this.domtoolsPromise; const paddleButton = this.shadowRoot.querySelector('dees-button'); - await this.domtools.setExternalScript('https://cdn.paddle.com/paddle/paddle.js'); - globalThis.Paddle.Setup({ - vendor: 30954, + const idpState = await IdpState.getSingletonInstance(); + + // Get user email - first try from state, then fetch directly + let userEmail = state.accountState.getState().user?.data?.email; + + if (!userEmail) { + // State not loaded, fetch user directly + const whoIsResponse = await idpState.idpClient.whoIs().catch(() => null); + userEmail = whoIsResponse?.user?.data?.email; + } + + if (!userEmail) { + console.error('Unable to get user email for Paddle checkout'); + paddleButton.status = 'error'; + paddleButton.text = 'Error: Not logged in'; + return; + } + + // Fetch Paddle config from backend + const configRequest = idpState.idpClient.typedsocket + .createTypedRequest('getPaddleConfig'); + const { paddleToken, paddlePriceId } = await configRequest.fire({}); + + await this.domtools.setExternalScript('https://cdn.paddle.com/paddle/v2/paddle.js'); + globalThis.Paddle.Initialize({ + token: paddleToken, eventCallback: async (dataArg: any) => { - // The data.event will specify the event type - if (dataArg.event === 'Checkout.Complete') { - const data: plugins.idpInterfaces.data.IPaddleCheckoutData = dataArg.eventData; + // Paddle Billing v2 event handling + if (dataArg.name === 'checkout.completed') { const paddleIframe = document.body.querySelector('iframe'); if (paddleIframe) { document.body.removeChild(paddleIframe); } paddleButton.status = 'pending'; paddleButton.text = 'Processing...'; - await state.accountState.dispatchAction(state.updatePaddleCheckoutId, data.checkout.id); + await state.accountState.dispatchAction(state.updatePaddleCheckoutId, dataArg.data.transaction_id); paddleButton.status = 'success'; paddleButton.text = 'Paddle connected!' } }, }); globalThis.Paddle.Checkout.open({ - product: 561076, - email: 'phil@kunz.io', + items: [{ priceId: paddlePriceId, quantity: 1 }], + customer: { email: userEmail }, }); } }