@contractspec/example.openbanking-powens 3.7.6 → 3.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ $ bun run prebuild && bun run build:bundle && bun run build:types
3
3
  $ contractspec-bun-build prebuild
4
4
  $ contractspec-bun-build transpile
5
5
  [contractspec-bun-build] transpile target=bun root=src entries=7 noBundle=false
6
- Bundled 7 modules in 33ms
6
+ Bundled 7 modules in 15ms
7
7
 
8
8
  docs/index.js 1.58 KB (entry point)
9
9
  ./index.js 8.80 KB (entry point)
@@ -14,7 +14,7 @@ Bundled 7 modules in 33ms
14
14
  ./openbanking-powens.feature.js 0.72 KB (entry point)
15
15
 
16
16
  [contractspec-bun-build] transpile target=node root=src entries=7 noBundle=false
17
- Bundled 7 modules in 41ms
17
+ Bundled 7 modules in 27ms
18
18
 
19
19
  docs/index.js 1.56 KB (entry point)
20
20
  ./index.js 8.78 KB (entry point)
package/AGENTS.md CHANGED
@@ -1,30 +1,53 @@
1
- # AI Agent Guide -- `@contractspec/example.openbanking-powens`
1
+ # AI Agent Guide `@contractspec/example.openbanking-powens`
2
2
 
3
3
  Scope: `packages/examples/openbanking-powens/*`
4
4
 
5
- OpenBanking Powens example: OAuth callback + webhook handler patterns for provider integration.
5
+ OpenBanking Powens example: OAuth callback + webhook handler patterns (provider + workflows).
6
6
 
7
7
  ## Quick Context
8
8
 
9
- - **Layer**: example
10
- - **Related Packages**: `integration.providers-impls`, `lib.contracts-spec`
9
+ - Layer: `example`.
10
+ - Package visibility: published package.
11
+ - Primary consumers are example explorers, template authors, and documentation readers.
12
+ - Related packages: `@contractspec/integration.providers-impls`, `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
11
13
 
12
- ## What This Demonstrates
14
+ ## Architecture
13
15
 
14
- - OAuth callback handler pattern for open banking
15
- - Webhook handler for asynchronous bank event ingestion
16
- - Provider integration via contracts-integrations
16
+ - `src/docs/` contains docblocks and documentation-facing exports.
17
+ - `src/example.ts` is the runnable example entrypoint.
18
+ - `src/handlers/` contains handlers or demo adapters wired to contract surfaces.
19
+ - `src/index.ts` is the root public barrel and package entrypoint.
20
+ - `src/openbanking-powens.feature.ts` defines a feature entrypoint.
17
21
 
18
- ## Public Exports
22
+ ## Public Surface
19
23
 
20
- - `.` -- root barrel
21
- - `./handlers/oauth-callback` -- OAuth flow handler
22
- - `./handlers/webhook-handler` -- webhook ingestion
23
- - `./docs`, `./example`
24
+ - Export `.` resolves through `./src/index.ts`.
25
+ - Export `./docs` resolves through `./src/docs/index.ts`.
26
+ - Export `./docs/openbanking-powens.docblock` resolves through `./src/docs/openbanking-powens.docblock.ts`.
27
+ - Export `./example` resolves through `./src/example.ts`.
28
+ - Export `./handlers/oauth-callback` resolves through `./src/handlers/oauth-callback.ts`.
29
+ - Export `./handlers/webhook-handler` resolves through `./src/handlers/webhook-handler.ts`.
30
+ - Export `./openbanking-powens.feature` resolves through `./src/openbanking-powens.feature.ts`.
31
+
32
+ ## Guardrails
33
+
34
+ - Keep the example package demonstrative, buildable, and aligned with the exported feature surface.
35
+ - Do not add hidden production assumptions that are not actually implemented in the example.
36
+ - Changes here can affect downstream packages such as `@contractspec/integration.providers-impls`, `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
37
+ - Changes here can affect downstream packages such as `@contractspec/integration.providers-impls`, `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
24
38
 
25
39
  ## Local Commands
26
40
 
27
- - Build: `bun run build`
28
- - Dev: `bun run dev`
29
- - Test: `bun test --pass-with-no-tests`
30
- - Typecheck: `bun run typecheck`
41
+ - `bun run dev` — contractspec-bun-build dev
42
+ - `bun run build`bun run prebuild && bun run build:bundle && bun run build:types
43
+ - `bun run test`bun test --pass-with-no-tests
44
+ - `bun run lint` — bun lint:fix
45
+ - `bun run lint:check` — biome check .
46
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
47
+ - `bun run typecheck` — tsc --noEmit
48
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
49
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
50
+ - `bun run clean` — rimraf dist .turbo
51
+ - `bun run build:bundle` — contractspec-bun-build transpile
52
+ - `bun run build:types` — contractspec-bun-build types
53
+ - `bun run prebuild` — contractspec-bun-build prebuild
package/README.md CHANGED
@@ -1,32 +1,69 @@
1
1
  # @contractspec/example.openbanking-powens
2
2
 
3
- Website: https://contractspec.io/
3
+ Website: https://contractspec.io
4
4
 
5
- **OAuth callback + webhook handler patterns**
6
-
7
- OpenBanking Powens example: OAuth callback + webhook handler patterns. Framework-neutral handlers operate on standard `Request` for use in Next.js, Elysia, or any fetch-compatible runtime.
5
+ **OpenBanking Powens example: OAuth callback + webhook handler patterns (provider + workflows).**
8
6
 
9
7
  ## What This Demonstrates
10
8
 
11
- - OAuth callback handler for Powens open banking
12
- - Webhook handler for Powens events
13
- - Example spec with provider and workflow orchestration
9
+ - OAuth callback handler pattern for open banking.
10
+ - Webhook handler for asynchronous bank event ingestion.
11
+ - Provider integration via contracts-integrations.
12
+ - `src/docs/` contains docblocks and documentation-facing exports.
13
+ - `src/handlers/` contains handlers or demo adapters wired to contract surfaces.
14
+ - `src/docs/` contains docblocks and documentation-facing exports.
14
15
 
15
- ## Exports
16
+ ## Running Locally
16
17
 
17
- - `.` — main entry (registers docs, exports example and handlers)
18
- - `./docs` doc blocks
19
- - `./example` example metadata
20
- - `./handlers/oauth-callback` OAuth callback handler
21
- - `./handlers/webhook-handler` webhook handler
18
+ From `packages/examples/openbanking-powens`:
19
+ - `bun run dev`
20
+ - `bun run build`
21
+ - `bun run test`
22
+ - `bun run typecheck`
22
23
 
23
24
  ## Usage
24
25
 
25
- ```ts
26
- import { powensOAuthCallbackHandler } from "@contractspec/example.openbanking-powens/handlers/oauth-callback";
27
- import { powensWebhookHandler } from "@contractspec/example.openbanking-powens/handlers/webhook-handler";
26
+ Use `@contractspec/example.openbanking-powens` as a reference implementation, or import its exported surfaces into a workspace that composes ContractSpec examples and bundles.
27
+
28
+ ## Architecture
29
+
30
+ - `src/docs/` contains docblocks and documentation-facing exports.
31
+ - `src/example.ts` is the runnable example entrypoint.
32
+ - `src/handlers/` contains handlers or demo adapters wired to contract surfaces.
33
+ - `src/index.ts` is the root public barrel and package entrypoint.
34
+ - `src/openbanking-powens.feature.ts` defines a feature entrypoint.
35
+
36
+ ## Public Entry Points
37
+
38
+ - Export `.` resolves through `./src/index.ts`.
39
+ - Export `./docs` resolves through `./src/docs/index.ts`.
40
+ - Export `./docs/openbanking-powens.docblock` resolves through `./src/docs/openbanking-powens.docblock.ts`.
41
+ - Export `./example` resolves through `./src/example.ts`.
42
+ - Export `./handlers/oauth-callback` resolves through `./src/handlers/oauth-callback.ts`.
43
+ - Export `./handlers/webhook-handler` resolves through `./src/handlers/webhook-handler.ts`.
44
+ - Export `./openbanking-powens.feature` resolves through `./src/openbanking-powens.feature.ts`.
45
+
46
+ ## Local Commands
47
+
48
+ - `bun run dev` — contractspec-bun-build dev
49
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
50
+ - `bun run test` — bun test --pass-with-no-tests
51
+ - `bun run lint` — bun lint:fix
52
+ - `bun run lint:check` — biome check .
53
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
54
+ - `bun run typecheck` — tsc --noEmit
55
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
56
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
57
+ - `bun run clean` — rimraf dist .turbo
58
+ - `bun run build:bundle` — contractspec-bun-build transpile
59
+ - `bun run build:types` — contractspec-bun-build types
60
+ - `bun run prebuild` — contractspec-bun-build prebuild
61
+
62
+ ## Recent Updates
63
+
64
+ - Replace eslint+prettier by biomejs to optimize speed.
65
+ - Missing contract layers.
66
+
67
+ ## Notes
28
68
 
29
- // Wire into your framework (Next.js, Elysia, etc.)
30
- // GET /api/oauth/callback → powensOAuthCallbackHandler
31
- // POST /api/webhooks/powens → powensWebhookHandler
32
- ```
69
+ - Works alongside `@contractspec/integration.providers-impls`, `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
@@ -1 +1,7 @@
1
+ /**
2
+ * Example OAuth callback handler for Powens (open banking).
3
+ *
4
+ * This example stays framework-neutral: it operates on the standard `Request`
5
+ * type so it can be used in Next.js, Elysia, or any fetch-compatible runtime.
6
+ */
1
7
  export declare function powensOAuthCallbackHandler(req: Request): Promise<Response>;
@@ -1 +1,7 @@
1
+ /**
2
+ * Example Powens webhook handler (fetch-compatible).
3
+ *
4
+ * Verifies signature, then enqueues the canonical workflows to keep the ledger
5
+ * in sync. Unknown events are ignored (or can be recorded by the app layer).
6
+ */
1
7
  export declare function powensWebhookHandler(req: Request): Promise<Response>;
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/handlers/webhook-handler.ts
3
- import { createHmac, timingSafeEqual } from "crypto";
4
3
  import { PowensOpenBankingProvider } from "@contractspec/integration.providers-impls/impls/powens-openbanking";
4
+ import { createHmac, timingSafeEqual } from "crypto";
5
5
  async function powensWebhookHandler(req) {
6
6
  const signature = req.headers.get("x-powens-signature");
7
7
  const stateHeader = req.headers.get("x-powens-state");
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ export { default as example } from './example';
1
2
  export * from './handlers/oauth-callback';
2
3
  export * from './handlers/webhook-handler';
3
4
  export * from './openbanking-powens.feature';
4
- export { default as example } from './example';
5
5
  import './docs';
package/dist/index.js CHANGED
@@ -131,8 +131,8 @@ var fakeWorkflowQueue = {
131
131
  };
132
132
 
133
133
  // src/handlers/webhook-handler.ts
134
- import { createHmac, timingSafeEqual } from "crypto";
135
134
  import { PowensOpenBankingProvider as PowensOpenBankingProvider2 } from "@contractspec/integration.providers-impls/impls/powens-openbanking";
135
+ import { createHmac, timingSafeEqual } from "crypto";
136
136
  async function powensWebhookHandler(req) {
137
137
  const signature = req.headers.get("x-powens-signature");
138
138
  const stateHeader = req.headers.get("x-powens-state");
@@ -1,6 +1,6 @@
1
1
  // src/handlers/webhook-handler.ts
2
- import { createHmac, timingSafeEqual } from "crypto";
3
2
  import { PowensOpenBankingProvider } from "@contractspec/integration.providers-impls/impls/powens-openbanking";
3
+ import { createHmac, timingSafeEqual } from "crypto";
4
4
  async function powensWebhookHandler(req) {
5
5
  const signature = req.headers.get("x-powens-signature");
6
6
  const stateHeader = req.headers.get("x-powens-state");
@@ -130,8 +130,8 @@ var fakeWorkflowQueue = {
130
130
  };
131
131
 
132
132
  // src/handlers/webhook-handler.ts
133
- import { createHmac, timingSafeEqual } from "crypto";
134
133
  import { PowensOpenBankingProvider as PowensOpenBankingProvider2 } from "@contractspec/integration.providers-impls/impls/powens-openbanking";
134
+ import { createHmac, timingSafeEqual } from "crypto";
135
135
  async function powensWebhookHandler(req) {
136
136
  const signature = req.headers.get("x-powens-signature");
137
137
  const stateHeader = req.headers.get("x-powens-state");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/example.openbanking-powens",
3
- "version": "3.7.6",
3
+ "version": "3.7.7",
4
4
  "description": "OpenBanking Powens example: OAuth callback + webhook handler patterns (provider + workflows).",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -57,15 +57,15 @@
57
57
  "dev": "contractspec-bun-build dev",
58
58
  "clean": "rimraf dist .turbo",
59
59
  "lint": "bun lint:fix",
60
- "lint:fix": "eslint src --fix",
61
- "lint:check": "eslint src",
60
+ "lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
61
+ "lint:check": "biome check .",
62
62
  "test": "bun test --pass-with-no-tests",
63
63
  "prebuild": "contractspec-bun-build prebuild",
64
64
  "typecheck": "tsc --noEmit"
65
65
  },
66
66
  "dependencies": {
67
- "@contractspec/integration.providers-impls": "3.7.6",
68
- "@contractspec/lib.contracts-spec": "3.7.6"
67
+ "@contractspec/integration.providers-impls": "3.7.7",
68
+ "@contractspec/lib.contracts-spec": "4.0.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@contractspec/tool.typescript": "3.7.6",
@@ -2,27 +2,27 @@ import type { DocBlock } from '@contractspec/lib.contracts-spec/docs';
2
2
  import { registerDocBlocks } from '@contractspec/lib.contracts-spec/docs';
3
3
 
4
4
  const blocks: DocBlock[] = [
5
- {
6
- id: 'docs.examples.openbanking-powens',
7
- title: 'Open Banking — Powens (example)',
8
- summary:
9
- 'Framework-neutral OAuth callback + webhook handler patterns for Powens, orchestrating canonical sync workflows.',
10
- kind: 'reference',
11
- visibility: 'public',
12
- route: '/docs/examples/openbanking-powens',
13
- tags: ['openbanking', 'powens', 'integration', 'example'],
14
- body: `## What this example shows\n- OAuth callback handler: exchange auth code, map powens user, enqueue sync workflow.\n- Webhook handler: verify signature, route event → workflow, optionally refresh balances.\n\n## Guardrails\n- Secrets via secret providers/env only.\n- Verify webhook signatures.\n- Keep side effects explicit: enqueue workflows instead of mutating canonical stores inline.`,
15
- },
16
- {
17
- id: 'docs.examples.openbanking-powens.usage',
18
- title: 'Open Banking — Powens — Usage',
19
- summary: 'How to integrate the handlers in a fetch-compatible runtime.',
20
- kind: 'usage',
21
- visibility: 'public',
22
- route: '/docs/examples/openbanking-powens/usage',
23
- tags: ['openbanking', 'usage'],
24
- body: `## Usage\n- Wire \`powensOAuthCallbackHandler(req)\` at your OAuth redirect route.\n- Wire \`powensWebhookHandler(req)\` at your webhook route.\n\n## Notes\n- Replace the fake stores with your app-layer persistence.\n- Enqueue ContractSpec workflows for canonical upserts and telemetry.`,
25
- },
5
+ {
6
+ id: 'docs.examples.openbanking-powens',
7
+ title: 'Open Banking — Powens (example)',
8
+ summary:
9
+ 'Framework-neutral OAuth callback + webhook handler patterns for Powens, orchestrating canonical sync workflows.',
10
+ kind: 'reference',
11
+ visibility: 'public',
12
+ route: '/docs/examples/openbanking-powens',
13
+ tags: ['openbanking', 'powens', 'integration', 'example'],
14
+ body: `## What this example shows\n- OAuth callback handler: exchange auth code, map powens user, enqueue sync workflow.\n- Webhook handler: verify signature, route event → workflow, optionally refresh balances.\n\n## Guardrails\n- Secrets via secret providers/env only.\n- Verify webhook signatures.\n- Keep side effects explicit: enqueue workflows instead of mutating canonical stores inline.`,
15
+ },
16
+ {
17
+ id: 'docs.examples.openbanking-powens.usage',
18
+ title: 'Open Banking — Powens — Usage',
19
+ summary: 'How to integrate the handlers in a fetch-compatible runtime.',
20
+ kind: 'usage',
21
+ visibility: 'public',
22
+ route: '/docs/examples/openbanking-powens/usage',
23
+ tags: ['openbanking', 'usage'],
24
+ body: `## Usage\n- Wire \`powensOAuthCallbackHandler(req)\` at your OAuth redirect route.\n- Wire \`powensWebhookHandler(req)\` at your webhook route.\n\n## Notes\n- Replace the fake stores with your app-layer persistence.\n- Enqueue ContractSpec workflows for canonical upserts and telemetry.`,
25
+ },
26
26
  ];
27
27
 
28
28
  registerDocBlocks(blocks);
package/src/example.ts CHANGED
@@ -1,32 +1,32 @@
1
1
  import { defineExample } from '@contractspec/lib.contracts-spec';
2
2
 
3
3
  const example = defineExample({
4
- meta: {
5
- key: 'openbanking-powens',
6
- version: '1.0.0',
7
- title: 'Open Banking — Powens',
8
- description:
9
- 'OAuth callback + webhook handler patterns for Powens open banking integration (provider + workflow orchestration).',
10
- kind: 'integration',
11
- visibility: 'public',
12
- stability: 'experimental',
13
- owners: ['@platform.core'],
14
- tags: ['openbanking', 'powens', 'oauth', 'webhooks', 'integrations'],
15
- },
16
- docs: {
17
- rootDocId: 'docs.examples.openbanking-powens',
18
- usageDocId: 'docs.examples.openbanking-powens.usage',
19
- },
20
- entrypoints: {
21
- packageName: '@contractspec/example.openbanking-powens',
22
- docs: './docs',
23
- },
24
- surfaces: {
25
- templates: true,
26
- sandbox: { enabled: true, modes: ['markdown', 'specs'] },
27
- studio: { enabled: true, installable: true },
28
- mcp: { enabled: true },
29
- },
4
+ meta: {
5
+ key: 'openbanking-powens',
6
+ version: '1.0.0',
7
+ title: 'Open Banking — Powens',
8
+ description:
9
+ 'OAuth callback + webhook handler patterns for Powens open banking integration (provider + workflow orchestration).',
10
+ kind: 'integration',
11
+ visibility: 'public',
12
+ stability: 'experimental',
13
+ owners: ['@platform.core'],
14
+ tags: ['openbanking', 'powens', 'oauth', 'webhooks', 'integrations'],
15
+ },
16
+ docs: {
17
+ rootDocId: 'docs.examples.openbanking-powens',
18
+ usageDocId: 'docs.examples.openbanking-powens.usage',
19
+ },
20
+ entrypoints: {
21
+ packageName: '@contractspec/example.openbanking-powens',
22
+ docs: './docs',
23
+ },
24
+ surfaces: {
25
+ templates: true,
26
+ sandbox: { enabled: true, modes: ['markdown', 'specs'] },
27
+ studio: { enabled: true, installable: true },
28
+ mcp: { enabled: true },
29
+ },
30
30
  });
31
31
 
32
32
  export default example;
@@ -4,110 +4,111 @@
4
4
  * This example stays framework-neutral: it operates on the standard `Request`
5
5
  * type so it can be used in Next.js, Elysia, or any fetch-compatible runtime.
6
6
  */
7
- import { PowensOpenBankingProvider } from '@contractspec/integration.providers-impls/impls/powens-openbanking';
7
+
8
8
  import type { PowensEnvironment } from '@contractspec/integration.providers-impls/impls/powens-client';
9
+ import { PowensOpenBankingProvider } from '@contractspec/integration.providers-impls/impls/powens-openbanking';
9
10
 
10
11
  export async function powensOAuthCallbackHandler(req: Request) {
11
- const url = new URL(req.url);
12
- const code = url.searchParams.get('code');
13
- const state = url.searchParams.get('state');
14
- const userUuid = url.searchParams.get('user_uuid');
15
-
16
- if (!code || !state || !userUuid) {
17
- return new Response('Missing Powens OAuth params', { status: 400 });
18
- }
19
-
20
- const connection = await getConnectionByState(state);
21
- if (!connection) {
22
- return new Response('Unknown Powens OAuth state', { status: 404 });
23
- }
24
-
25
- const secrets = await getPowensSecretsForConnection(connection.meta.id);
26
-
27
- const provider = new PowensOpenBankingProvider({
28
- clientId: secrets.clientId,
29
- clientSecret: secrets.clientSecret,
30
- apiKey: secrets.apiKey,
31
- environment: connection.config.environment as PowensEnvironment,
32
- baseUrl: connection.config.baseUrl as string | undefined,
33
- });
34
-
35
- const preview = await provider.listAccounts({
36
- tenantId: connection.meta.tenantId,
37
- connectionId: connection.meta.id,
38
- userId: userUuid,
39
- });
40
-
41
- await connection.storePowensUser({
42
- tenantUserId: connection.meta.tenantUserId,
43
- powensUserUuid: userUuid,
44
- authCode: code,
45
- });
46
-
47
- await enqueueWorkflow('pfo.workflow.sync-openbanking-accounts', {
48
- tenantId: connection.meta.tenantId,
49
- userUuid,
50
- connectionId: connection.meta.id,
51
- previewAccounts: preview.accounts,
52
- });
53
-
54
- const redirectBase = process.env.APP_DASHBOARD_URL ?? '';
55
- return Response.redirect(
56
- `${redirectBase}/banking/linked?tenant=${connection.meta.tenantId}`,
57
- 302
58
- );
12
+ const url = new URL(req.url);
13
+ const code = url.searchParams.get('code');
14
+ const state = url.searchParams.get('state');
15
+ const userUuid = url.searchParams.get('user_uuid');
16
+
17
+ if (!code || !state || !userUuid) {
18
+ return new Response('Missing Powens OAuth params', { status: 400 });
19
+ }
20
+
21
+ const connection = await getConnectionByState(state);
22
+ if (!connection) {
23
+ return new Response('Unknown Powens OAuth state', { status: 404 });
24
+ }
25
+
26
+ const secrets = await getPowensSecretsForConnection(connection.meta.id);
27
+
28
+ const provider = new PowensOpenBankingProvider({
29
+ clientId: secrets.clientId,
30
+ clientSecret: secrets.clientSecret,
31
+ apiKey: secrets.apiKey,
32
+ environment: connection.config.environment as PowensEnvironment,
33
+ baseUrl: connection.config.baseUrl as string | undefined,
34
+ });
35
+
36
+ const preview = await provider.listAccounts({
37
+ tenantId: connection.meta.tenantId,
38
+ connectionId: connection.meta.id,
39
+ userId: userUuid,
40
+ });
41
+
42
+ await connection.storePowensUser({
43
+ tenantUserId: connection.meta.tenantUserId,
44
+ powensUserUuid: userUuid,
45
+ authCode: code,
46
+ });
47
+
48
+ await enqueueWorkflow('pfo.workflow.sync-openbanking-accounts', {
49
+ tenantId: connection.meta.tenantId,
50
+ userUuid,
51
+ connectionId: connection.meta.id,
52
+ previewAccounts: preview.accounts,
53
+ });
54
+
55
+ const redirectBase = process.env.APP_DASHBOARD_URL ?? '';
56
+ return Response.redirect(
57
+ `${redirectBase}/banking/linked?tenant=${connection.meta.tenantId}`,
58
+ 302
59
+ );
59
60
  }
60
61
 
61
62
  interface ExamplePowensSecrets {
62
- clientId: string;
63
- clientSecret: string;
64
- apiKey?: string;
63
+ clientId: string;
64
+ clientSecret: string;
65
+ apiKey?: string;
65
66
  }
66
67
 
67
68
  interface ExampleIntegrationConnection {
68
- meta: {
69
- id: string;
70
- tenantId: string;
71
- tenantUserId: string;
72
- };
73
- config: {
74
- environment: PowensEnvironment;
75
- baseUrl?: string;
76
- };
77
- storePowensUser(input: {
78
- tenantUserId: string;
79
- powensUserUuid: string;
80
- authCode: string;
81
- }): Promise<void>;
69
+ meta: {
70
+ id: string;
71
+ tenantId: string;
72
+ tenantUserId: string;
73
+ };
74
+ config: {
75
+ environment: PowensEnvironment;
76
+ baseUrl?: string;
77
+ };
78
+ storePowensUser(input: {
79
+ tenantUserId: string;
80
+ powensUserUuid: string;
81
+ authCode: string;
82
+ }): Promise<void>;
82
83
  }
83
84
 
84
85
  async function getConnectionByState(
85
- state: string
86
+ state: string
86
87
  ): Promise<ExampleIntegrationConnection | null> {
87
- const record = fakeDatabase.connections.find((conn) => conn.state === state);
88
- return record ?? null;
88
+ const record = fakeDatabase.connections.find((conn) => conn.state === state);
89
+ return record ?? null;
89
90
  }
90
91
 
91
92
  async function getPowensSecretsForConnection(
92
- connectionId: string
93
+ connectionId: string
93
94
  ): Promise<ExamplePowensSecrets> {
94
- const secret = fakeSecretStore[connectionId];
95
- if (!secret) throw new Error(`Missing Powens secrets for ${connectionId}`);
96
- return secret;
95
+ const secret = fakeSecretStore[connectionId];
96
+ if (!secret) throw new Error(`Missing Powens secrets for ${connectionId}`);
97
+ return secret;
97
98
  }
98
99
 
99
100
  async function enqueueWorkflow(name: string, input: Record<string, unknown>) {
100
- await fakeWorkflowQueue.enqueue({ name, input });
101
+ await fakeWorkflowQueue.enqueue({ name, input });
101
102
  }
102
103
 
103
104
  const fakeDatabase = {
104
- connections: [] as (ExampleIntegrationConnection & { state: string })[],
105
+ connections: [] as (ExampleIntegrationConnection & { state: string })[],
105
106
  };
106
107
 
107
108
  const fakeSecretStore: Record<string, ExamplePowensSecrets> = {};
108
109
 
109
110
  const fakeWorkflowQueue = {
110
- enqueue: async (_payload: Record<string, unknown>) => {
111
- /* no-op */
112
- },
111
+ enqueue: async (_payload: Record<string, unknown>) => {
112
+ /* no-op */
113
+ },
113
114
  };
@@ -4,144 +4,145 @@
4
4
  * Verifies signature, then enqueues the canonical workflows to keep the ledger
5
5
  * in sync. Unknown events are ignored (or can be recorded by the app layer).
6
6
  */
7
- import { createHmac, timingSafeEqual } from 'crypto';
8
- import { PowensOpenBankingProvider } from '@contractspec/integration.providers-impls/impls/powens-openbanking';
7
+
9
8
  import type { PowensEnvironment } from '@contractspec/integration.providers-impls/impls/powens-client';
9
+ import { PowensOpenBankingProvider } from '@contractspec/integration.providers-impls/impls/powens-openbanking';
10
+ import { createHmac, timingSafeEqual } from 'crypto';
10
11
 
11
12
  export async function powensWebhookHandler(req: Request) {
12
- const signature = req.headers.get('x-powens-signature');
13
- const stateHeader = req.headers.get('x-powens-state');
14
- const payload = await req.text();
15
-
16
- if (!signature || !stateHeader) {
17
- return new Response('Missing Powens signature headers', { status: 400 });
18
- }
19
-
20
- const connection = await getConnectionByState(stateHeader);
21
- if (!connection) {
22
- return new Response('Unknown Powens state header', { status: 404 });
23
- }
24
-
25
- const secrets = await getPowensSecretsForConnection(connection.meta.id);
26
- if (!verifySignature(payload, signature, secrets.webhookSecret)) {
27
- return new Response('Invalid Powens webhook signature', { status: 401 });
28
- }
29
-
30
- const event = JSON.parse(payload) as PowensWebhookEvent;
31
- const provider = new PowensOpenBankingProvider({
32
- clientId: secrets.clientId,
33
- clientSecret: secrets.clientSecret,
34
- apiKey: secrets.apiKey,
35
- environment: connection.config.environment as PowensEnvironment,
36
- baseUrl: connection.config.baseUrl as string | undefined,
37
- });
38
-
39
- switch (event.type) {
40
- case 'connection.updated':
41
- case 'user.sync.completed': {
42
- await enqueueWorkflow('pfo.workflow.sync-openbanking-accounts', {
43
- tenantId: connection.meta.tenantId,
44
- connectionId: connection.meta.id,
45
- userUuid: event.user_uuid,
46
- });
47
- break;
48
- }
49
- case 'transactions.created':
50
- case 'transactions.updated': {
51
- await enqueueWorkflow('pfo.workflow.sync-openbanking-transactions', {
52
- tenantId: connection.meta.tenantId,
53
- connectionId: connection.meta.id,
54
- userUuid: event.user_uuid,
55
- accountId: event.account_uuid,
56
- });
57
- break;
58
- }
59
- default:
60
- await logUnmappedEvent(event);
61
- }
62
-
63
- if (event.account_uuid) {
64
- await provider.getBalances({
65
- tenantId: connection.meta.tenantId,
66
- connectionId: connection.meta.id,
67
- accountId: event.account_uuid,
68
- });
69
- }
70
-
71
- return new Response('OK', { status: 200 });
13
+ const signature = req.headers.get('x-powens-signature');
14
+ const stateHeader = req.headers.get('x-powens-state');
15
+ const payload = await req.text();
16
+
17
+ if (!signature || !stateHeader) {
18
+ return new Response('Missing Powens signature headers', { status: 400 });
19
+ }
20
+
21
+ const connection = await getConnectionByState(stateHeader);
22
+ if (!connection) {
23
+ return new Response('Unknown Powens state header', { status: 404 });
24
+ }
25
+
26
+ const secrets = await getPowensSecretsForConnection(connection.meta.id);
27
+ if (!verifySignature(payload, signature, secrets.webhookSecret)) {
28
+ return new Response('Invalid Powens webhook signature', { status: 401 });
29
+ }
30
+
31
+ const event = JSON.parse(payload) as PowensWebhookEvent;
32
+ const provider = new PowensOpenBankingProvider({
33
+ clientId: secrets.clientId,
34
+ clientSecret: secrets.clientSecret,
35
+ apiKey: secrets.apiKey,
36
+ environment: connection.config.environment as PowensEnvironment,
37
+ baseUrl: connection.config.baseUrl as string | undefined,
38
+ });
39
+
40
+ switch (event.type) {
41
+ case 'connection.updated':
42
+ case 'user.sync.completed': {
43
+ await enqueueWorkflow('pfo.workflow.sync-openbanking-accounts', {
44
+ tenantId: connection.meta.tenantId,
45
+ connectionId: connection.meta.id,
46
+ userUuid: event.user_uuid,
47
+ });
48
+ break;
49
+ }
50
+ case 'transactions.created':
51
+ case 'transactions.updated': {
52
+ await enqueueWorkflow('pfo.workflow.sync-openbanking-transactions', {
53
+ tenantId: connection.meta.tenantId,
54
+ connectionId: connection.meta.id,
55
+ userUuid: event.user_uuid,
56
+ accountId: event.account_uuid,
57
+ });
58
+ break;
59
+ }
60
+ default:
61
+ await logUnmappedEvent(event);
62
+ }
63
+
64
+ if (event.account_uuid) {
65
+ await provider.getBalances({
66
+ tenantId: connection.meta.tenantId,
67
+ connectionId: connection.meta.id,
68
+ accountId: event.account_uuid,
69
+ });
70
+ }
71
+
72
+ return new Response('OK', { status: 200 });
72
73
  }
73
74
 
74
75
  interface PowensWebhookEvent {
75
- type: string;
76
- user_uuid: string;
77
- connection_uuid: string;
78
- account_uuid?: string;
76
+ type: string;
77
+ user_uuid: string;
78
+ connection_uuid: string;
79
+ account_uuid?: string;
79
80
  }
80
81
 
81
82
  interface ExamplePowensSecrets {
82
- clientId: string;
83
- clientSecret: string;
84
- apiKey?: string;
85
- webhookSecret: string;
83
+ clientId: string;
84
+ clientSecret: string;
85
+ apiKey?: string;
86
+ webhookSecret: string;
86
87
  }
87
88
 
88
89
  interface ExampleIntegrationConnection {
89
- meta: {
90
- id: string;
91
- tenantId: string;
92
- };
93
- config: {
94
- environment: PowensEnvironment;
95
- baseUrl?: string;
96
- };
90
+ meta: {
91
+ id: string;
92
+ tenantId: string;
93
+ };
94
+ config: {
95
+ environment: PowensEnvironment;
96
+ baseUrl?: string;
97
+ };
97
98
  }
98
99
 
99
100
  function verifySignature(payload: string, signature: string, secret: string) {
100
- const digest = createHmac('sha256', secret).update(payload).digest('hex');
101
- const a = Buffer.from(digest, 'hex');
102
- const b = Buffer.from(signature, 'hex');
103
- return a.length === b.length && timingSafeEqual(a, b);
101
+ const digest = createHmac('sha256', secret).update(payload).digest('hex');
102
+ const a = Buffer.from(digest, 'hex');
103
+ const b = Buffer.from(signature, 'hex');
104
+ return a.length === b.length && timingSafeEqual(a, b);
104
105
  }
105
106
 
106
107
  async function getConnectionByState(
107
- state: string
108
+ state: string
108
109
  ): Promise<ExampleIntegrationConnection | null> {
109
- return fakeDatabase.connections.find((conn) => conn.state === state) ?? null;
110
+ return fakeDatabase.connections.find((conn) => conn.state === state) ?? null;
110
111
  }
111
112
 
112
113
  async function getPowensSecretsForConnection(
113
- connectionId: string
114
+ connectionId: string
114
115
  ): Promise<ExamplePowensSecrets> {
115
- const secret = fakeSecretStore[connectionId];
116
- if (!secret) throw new Error(`Missing Powens secrets for ${connectionId}`);
117
- return secret;
116
+ const secret = fakeSecretStore[connectionId];
117
+ if (!secret) throw new Error(`Missing Powens secrets for ${connectionId}`);
118
+ return secret;
118
119
  }
119
120
 
120
121
  async function enqueueWorkflow(name: string, input: Record<string, unknown>) {
121
- await fakeWorkflowQueue.enqueue({ name, input });
122
+ await fakeWorkflowQueue.enqueue({ name, input });
122
123
  }
123
124
 
124
125
  async function logUnmappedEvent(_event: PowensWebhookEvent) {
125
- await fakeTelemetryLogger.record({
126
- event: 'openbanking.webhook.unmapped',
127
- payload: 'redacted',
128
- });
126
+ await fakeTelemetryLogger.record({
127
+ event: 'openbanking.webhook.unmapped',
128
+ payload: 'redacted',
129
+ });
129
130
  }
130
131
 
131
132
  const fakeDatabase = {
132
- connections: [] as (ExampleIntegrationConnection & { state: string })[],
133
+ connections: [] as (ExampleIntegrationConnection & { state: string })[],
133
134
  };
134
135
 
135
136
  const fakeSecretStore: Record<string, ExamplePowensSecrets> = {};
136
137
 
137
138
  const fakeWorkflowQueue = {
138
- enqueue: async (_payload: Record<string, unknown>) => {
139
- /* no-op */
140
- },
139
+ enqueue: async (_payload: Record<string, unknown>) => {
140
+ /* no-op */
141
+ },
141
142
  };
142
143
 
143
144
  const fakeTelemetryLogger = {
144
- record: async (_payload: Record<string, unknown>) => {
145
- /* no-op */
146
- },
145
+ record: async (_payload: Record<string, unknown>) => {
146
+ /* no-op */
147
+ },
147
148
  };
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
+ export { default as example } from './example';
1
2
  export * from './handlers/oauth-callback';
2
3
  export * from './handlers/webhook-handler';
3
4
  export * from './openbanking-powens.feature';
4
- export { default as example } from './example';
5
5
  import './docs';
@@ -1,24 +1,24 @@
1
1
  import { defineFeature } from '@contractspec/lib.contracts-spec';
2
2
 
3
3
  export const OpenbankingPowensFeature = defineFeature({
4
- meta: {
5
- key: 'openbanking-powens',
6
- version: '1.0.0',
7
- title: 'Open Banking - Powens',
8
- description:
9
- 'Powens open banking OAuth callback and webhook handler patterns',
10
- domain: 'integration',
11
- owners: ['@examples'],
12
- tags: ['openbanking', 'powens', 'oauth', 'webhooks'],
13
- stability: 'experimental',
14
- },
4
+ meta: {
5
+ key: 'openbanking-powens',
6
+ version: '1.0.0',
7
+ title: 'Open Banking - Powens',
8
+ description:
9
+ 'Powens open banking OAuth callback and webhook handler patterns',
10
+ domain: 'integration',
11
+ owners: ['@examples'],
12
+ tags: ['openbanking', 'powens', 'oauth', 'webhooks'],
13
+ stability: 'experimental',
14
+ },
15
15
 
16
- integrations: [
17
- { key: 'openbanking-powens.integration.powens', version: '1.0.0' },
18
- ],
16
+ integrations: [
17
+ { key: 'openbanking-powens.integration.powens', version: '1.0.0' },
18
+ ],
19
19
 
20
- docs: [
21
- 'docs.examples.openbanking-powens',
22
- 'docs.examples.openbanking-powens.usage',
23
- ],
20
+ docs: [
21
+ 'docs.examples.openbanking-powens',
22
+ 'docs.examples.openbanking-powens.usage',
23
+ ],
24
24
  });
package/tsconfig.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
- "extends": "@contractspec/tool.typescript/react-library.json",
3
- "include": ["src"],
4
- "exclude": ["node_modules", "dist"],
5
- "compilerOptions": {
6
- "rootDir": "src",
7
- "outDir": "dist"
8
- }
2
+ "extends": "@contractspec/tool.typescript/react-library.json",
3
+ "include": ["src"],
4
+ "exclude": ["node_modules", "dist"],
5
+ "compilerOptions": {
6
+ "rootDir": "src",
7
+ "outDir": "dist"
8
+ }
9
9
  }
10
-
11
-
package/tsdown.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineConfig, moduleLibrary } from '@contractspec/tool.bun';
2
2
 
3
3
  export default defineConfig(() => ({
4
- ...moduleLibrary,
4
+ ...moduleLibrary,
5
5
  }));