@actuate-media/plugin-commerce 0.0.13 → 0.1.0
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.
- package/CHANGELOG.md +49 -0
- package/dist/__tests__/checkout.test.d.ts +2 -0
- package/dist/__tests__/checkout.test.d.ts.map +1 -0
- package/dist/__tests__/checkout.test.js +68 -0
- package/dist/__tests__/checkout.test.js.map +1 -0
- package/dist/__tests__/webhooks.test.d.ts +2 -0
- package/dist/__tests__/webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/webhooks.test.js +71 -0
- package/dist/__tests__/webhooks.test.js.map +1 -0
- package/dist/index.d.ts +2 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/stripe/checkout.d.ts +10 -0
- package/dist/stripe/checkout.d.ts.map +1 -1
- package/dist/stripe/checkout.js +16 -2
- package/dist/stripe/checkout.js.map +1 -1
- package/dist/stripe/webhooks.d.ts +21 -1
- package/dist/stripe/webhooks.d.ts.map +1 -1
- package/dist/stripe/webhooks.js +56 -5
- package/dist/stripe/webhooks.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @actuate-media/plugin-commerce
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d278add: Verify Stripe webhook signatures and lock in the checkout money-mapping.
|
|
8
|
+
|
|
9
|
+
The webhook handler previously "verified" a payload by only checking that the
|
|
10
|
+
signature and secret strings were non-empty before parsing it — so a forged POST
|
|
11
|
+
with any non-empty `Stripe-Signature` would be processed as a real event (a fake
|
|
12
|
+
`payment_intent.succeeded` is a direct fraud risk). `handleStripeWebhook` now
|
|
13
|
+
performs real HMAC-SHA256 verification via a new exported `verifyStripeSignature`
|
|
14
|
+
(a dependency-free equivalent of `stripe.webhooks.constructEvent`: signed-payload
|
|
15
|
+
HMAC, constant-time compare, multi-`v1` key rotation, and timestamp replay
|
|
16
|
+
tolerance).
|
|
17
|
+
|
|
18
|
+
The money-critical checkout mapping (dollar→integer-cents rounding for line items
|
|
19
|
+
and shipping, discounts, metadata) is extracted into a pure, exported
|
|
20
|
+
`buildCheckoutSessionParams` so it is unit-tested independently of the
|
|
21
|
+
(still-stubbed) Stripe API call. Adds 15 behavioral tests covering signature
|
|
22
|
+
accept/reject/replay/rotation, event routing, and cents/shipping/discount mapping.
|
|
23
|
+
|
|
24
|
+
## 0.0.14
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- c231dd0: Add `definePlugin` and migrate all 10 plugins onto the canonical `PluginDefinition` contract.
|
|
29
|
+
|
|
30
|
+
cms-core already exported `PluginDefinition`, but every plugin hand-rolled a
|
|
31
|
+
local `ActuatePlugin` interface — a lossy subset that dropped `version`/`init`
|
|
32
|
+
and, in blocks/email/ai, widened hook payloads to `unknown[]`/`event: string`.
|
|
33
|
+
That's copy-paste drift with no shared source of truth.
|
|
34
|
+
|
|
35
|
+
`cms-core` now exports `definePlugin(plugin: PluginDefinition)` (an identity
|
|
36
|
+
helper like `defineConfig`), and each plugin imports the canonical type instead
|
|
37
|
+
of its own: the local `ActuatePlugin` interfaces are deleted, factory return
|
|
38
|
+
types are `PluginDefinition`, and the widened `hooks.ts` types are retyped as
|
|
39
|
+
`PluginHook[]`. Type-only change — no runtime behavior, plugin `name`, or config
|
|
40
|
+
options changed. Net −69 lines.
|
|
41
|
+
|
|
42
|
+
- 325694f: Import `definePlugin` from the lightweight `@actuate-media/cms-core/config`
|
|
43
|
+
entry instead of the barrel.
|
|
44
|
+
|
|
45
|
+
The plugin-definition migration imported the `definePlugin` **value** from
|
|
46
|
+
`@actuate-media/cms-core`, which pulls the entire cms-core barrel (graphql,
|
|
47
|
+
sharp, sentry, codegen) into every plugin's runtime — bundle bloat for
|
|
48
|
+
consumers and, under parallel test load, a timeout. The `/config` subpath exists
|
|
49
|
+
precisely to avoid the barrel. Types stay as erased `import type`, so nothing
|
|
50
|
+
heavy is loaded. No API or behavior change.
|
|
51
|
+
|
|
3
52
|
## 0.0.13
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/checkout.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildCheckoutSessionParams } from '../stripe/checkout.js';
|
|
3
|
+
const base = { successUrl: '/success', cancelUrl: '/cancel' };
|
|
4
|
+
const lineItem = (over = {}) => ({
|
|
5
|
+
productId: 'p1',
|
|
6
|
+
name: 'Widget',
|
|
7
|
+
quantity: 1,
|
|
8
|
+
unitPrice: 5,
|
|
9
|
+
...over,
|
|
10
|
+
});
|
|
11
|
+
describe('buildCheckoutSessionParams', () => {
|
|
12
|
+
it('converts dollar unit prices to integer cents (float-safe)', () => {
|
|
13
|
+
const params = buildCheckoutSessionParams({
|
|
14
|
+
...base,
|
|
15
|
+
lineItems: [lineItem({ unitPrice: 19.99, quantity: 2 })],
|
|
16
|
+
});
|
|
17
|
+
const li = params.line_items[0];
|
|
18
|
+
expect(li.price_data.unit_amount).toBe(1999); // 19.99*100 = 1998.999… → round
|
|
19
|
+
expect(li.quantity).toBe(2);
|
|
20
|
+
expect(params.mode).toBe('payment');
|
|
21
|
+
expect(params.success_url).toBe('/success');
|
|
22
|
+
});
|
|
23
|
+
it('rounds a value that floats below the integer (10.1 → 1010c)', () => {
|
|
24
|
+
const params = buildCheckoutSessionParams({
|
|
25
|
+
...base,
|
|
26
|
+
lineItems: [lineItem({ unitPrice: 10.1 })],
|
|
27
|
+
});
|
|
28
|
+
expect(params.line_items[0].price_data.unit_amount).toBe(1010);
|
|
29
|
+
});
|
|
30
|
+
it('includes variant metadata + image only when present', () => {
|
|
31
|
+
const withVariant = buildCheckoutSessionParams({
|
|
32
|
+
...base,
|
|
33
|
+
lineItems: [lineItem({ variantId: 'v1', image: 'https://img/x.png' })],
|
|
34
|
+
});
|
|
35
|
+
const pd = withVariant.line_items[0].price_data.product_data;
|
|
36
|
+
expect(pd.metadata).toEqual({ productId: 'p1', variantId: 'v1' });
|
|
37
|
+
expect(pd.images).toEqual(['https://img/x.png']);
|
|
38
|
+
const bare = buildCheckoutSessionParams({ ...base, lineItems: [lineItem()] });
|
|
39
|
+
const pd2 = bare.line_items[0].price_data.product_data;
|
|
40
|
+
expect(pd2.metadata).toEqual({ productId: 'p1' });
|
|
41
|
+
expect(pd2.images).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
it('maps shipping options to fixed_amount cents', () => {
|
|
44
|
+
const params = buildCheckoutSessionParams({
|
|
45
|
+
...base,
|
|
46
|
+
lineItems: [lineItem()],
|
|
47
|
+
shippingOptions: [{ id: 's1', label: 'Express', amount: 12.5 }],
|
|
48
|
+
});
|
|
49
|
+
const so = params.shipping_options[0];
|
|
50
|
+
expect(so.shipping_rate_data.fixed_amount).toEqual({ amount: 1250, currency: 'usd' });
|
|
51
|
+
expect(so.shipping_rate_data.display_name).toBe('Express');
|
|
52
|
+
});
|
|
53
|
+
it('adds discounts + customer_email only when provided', () => {
|
|
54
|
+
const full = buildCheckoutSessionParams({
|
|
55
|
+
...base,
|
|
56
|
+
lineItems: [lineItem()],
|
|
57
|
+
discountCode: 'SAVE10',
|
|
58
|
+
customerEmail: 'a@b.com',
|
|
59
|
+
});
|
|
60
|
+
expect(full.discounts).toEqual([{ coupon: 'SAVE10' }]);
|
|
61
|
+
expect(full.customer_email).toBe('a@b.com');
|
|
62
|
+
const bare = buildCheckoutSessionParams({ ...base, lineItems: [lineItem()] });
|
|
63
|
+
expect(bare.discounts).toBeUndefined();
|
|
64
|
+
expect(bare.customer_email).toBeUndefined();
|
|
65
|
+
expect(bare.shipping_options).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=checkout.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout.test.js","sourceRoot":"","sources":["../../src/__tests__/checkout.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhooks.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { handleStripeWebhook, verifyStripeSignature } from '../stripe/webhooks.js';
|
|
4
|
+
const SECRET = 'whsec_test';
|
|
5
|
+
const NOW = 1_700_000_000_000; // fixed ms for deterministic replay checks
|
|
6
|
+
const NOW_SEC = Math.floor(NOW / 1000);
|
|
7
|
+
function sign(payload, ts = NOW_SEC, secret = SECRET) {
|
|
8
|
+
const sig = createHmac('sha256', secret).update(`${ts}.${payload}`).digest('hex');
|
|
9
|
+
return `t=${ts},v1=${sig}`;
|
|
10
|
+
}
|
|
11
|
+
describe('verifyStripeSignature', () => {
|
|
12
|
+
it('accepts a valid signature within the tolerance window', () => {
|
|
13
|
+
const payload = JSON.stringify({ type: 'x' });
|
|
14
|
+
expect(() => verifyStripeSignature(payload, sign(payload), SECRET, { now: NOW })).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
it('rejects a tampered payload', () => {
|
|
17
|
+
const header = sign('{"amount":100}');
|
|
18
|
+
expect(() => verifyStripeSignature('{"amount":999}', header, SECRET, { now: NOW })).toThrow(/mismatch/);
|
|
19
|
+
});
|
|
20
|
+
it('rejects a signature made with a different secret', () => {
|
|
21
|
+
const payload = '{}';
|
|
22
|
+
expect(() => verifyStripeSignature(payload, sign(payload, NOW_SEC, 'whsec_other'), SECRET, { now: NOW })).toThrow(/mismatch/);
|
|
23
|
+
});
|
|
24
|
+
it('rejects an out-of-tolerance timestamp (replay protection)', () => {
|
|
25
|
+
const payload = '{}';
|
|
26
|
+
expect(() => verifyStripeSignature(payload, sign(payload, NOW_SEC - 10_000), SECRET, { now: NOW })).toThrow(/tolerance/);
|
|
27
|
+
});
|
|
28
|
+
it('rejects a missing signature or missing secret', () => {
|
|
29
|
+
expect(() => verifyStripeSignature('{}', '', SECRET, { now: NOW })).toThrow(/Missing webhook signature/);
|
|
30
|
+
expect(() => verifyStripeSignature('{}', sign('{}'), '', { now: NOW })).toThrow(/Missing webhook signing secret/);
|
|
31
|
+
});
|
|
32
|
+
it('rejects a malformed signature header', () => {
|
|
33
|
+
expect(() => verifyStripeSignature('{}', 'garbage', SECRET, { now: NOW })).toThrow(/Malformed/);
|
|
34
|
+
});
|
|
35
|
+
it('accepts when any of multiple v1 signatures matches (key rotation)', () => {
|
|
36
|
+
const payload = '{}';
|
|
37
|
+
const good = createHmac('sha256', SECRET).update(`${NOW_SEC}.${payload}`).digest('hex');
|
|
38
|
+
const header = `t=${NOW_SEC},v1=deadbeef,v1=${good}`;
|
|
39
|
+
expect(() => verifyStripeSignature(payload, header, SECRET, { now: NOW })).not.toThrow();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('handleStripeWebhook', () => {
|
|
43
|
+
function evt(type, object = {}) {
|
|
44
|
+
const payload = JSON.stringify({ type, data: { object } });
|
|
45
|
+
return { payload, header: sign(payload) };
|
|
46
|
+
}
|
|
47
|
+
it('routes each known event type to handled=true', async () => {
|
|
48
|
+
for (const type of [
|
|
49
|
+
'checkout.session.completed',
|
|
50
|
+
'payment_intent.succeeded',
|
|
51
|
+
'charge.refunded',
|
|
52
|
+
]) {
|
|
53
|
+
const { payload, header } = evt(type);
|
|
54
|
+
const res = await handleStripeWebhook(payload, header, SECRET, { now: NOW });
|
|
55
|
+
expect(res).toMatchObject({ handled: true, event: type });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
it('returns handled=false for an unknown but authentic event', async () => {
|
|
59
|
+
const { payload, header } = evt('customer.created');
|
|
60
|
+
const res = await handleStripeWebhook(payload, header, SECRET, { now: NOW });
|
|
61
|
+
expect(res).toEqual({ handled: false, event: 'customer.created' });
|
|
62
|
+
});
|
|
63
|
+
it('does NOT process a forged event (invalid signature)', async () => {
|
|
64
|
+
// A fake "payment succeeded" — this must be rejected, not handled.
|
|
65
|
+
const payload = JSON.stringify({ type: 'payment_intent.succeeded', data: { object: {} } });
|
|
66
|
+
const res = await handleStripeWebhook(payload, `t=${NOW_SEC},v1=forged`, SECRET, { now: NOW });
|
|
67
|
+
expect(res.handled).toBe(false);
|
|
68
|
+
expect(res.error).toMatch(/verification failed/i);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=webhooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.test.js","sourceRoot":"","sources":["../../src/__tests__/webhooks.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type PluginDefinition } from '@actuate-media/cms-core/config';
|
|
2
2
|
export interface CommercePluginConfig {
|
|
3
3
|
stripe?: {
|
|
4
4
|
secretKey: string;
|
|
@@ -12,19 +12,8 @@ export interface CommercePluginConfig {
|
|
|
12
12
|
enabled?: boolean;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
interface ActuatePlugin {
|
|
16
|
-
name: string;
|
|
17
|
-
fields?: Record<string, FieldDefinition>;
|
|
18
|
-
collections?: Record<string, CollectionDefinition>;
|
|
19
|
-
adminNavItems?: Array<{
|
|
20
|
-
label: string;
|
|
21
|
-
icon: string;
|
|
22
|
-
path: string;
|
|
23
|
-
}>;
|
|
24
|
-
hooks?: PluginHook[];
|
|
25
|
-
}
|
|
26
15
|
/** Creates the Actuate Commerce plugin with optional Stripe, tax, and shipping configuration. */
|
|
27
|
-
export declare function commercePlugin(config?: CommercePluginConfig):
|
|
16
|
+
export declare function commercePlugin(config?: CommercePluginConfig): PluginDefinition;
|
|
28
17
|
export type { CommercePluginConfig as CommerceConfig };
|
|
29
18
|
export { commerceFields } from './fields.js';
|
|
30
19
|
export { commerceCollections } from './collections.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAKpF,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE;QACP,SAAS,EAAE,MAAM,CAAA;QACjB,aAAa,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,OAAO,CAAA;KACxB,CAAA;IACD,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;CACF;AAED,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,GAAE,oBAAyB,GAAG,gBAAgB,CAiBlF;AAED,YAAY,EAAE,oBAAoB,IAAI,cAAc,EAAE,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { definePlugin } from '@actuate-media/cms-core/config';
|
|
1
2
|
import { commerceFields } from './fields.js';
|
|
2
3
|
import { commerceCollections } from './collections.js';
|
|
3
4
|
import { commerceHooks } from './hooks.js';
|
|
4
5
|
/** Creates the Actuate Commerce plugin with optional Stripe, tax, and shipping configuration. */
|
|
5
6
|
export function commercePlugin(config = {}) {
|
|
6
|
-
return {
|
|
7
|
+
return definePlugin({
|
|
7
8
|
name: 'commerce',
|
|
8
9
|
fields: commerceFields,
|
|
9
10
|
collections: commerceCollections,
|
|
@@ -18,7 +19,7 @@ export function commercePlugin(config = {}) {
|
|
|
18
19
|
},
|
|
19
20
|
],
|
|
20
21
|
hooks: commerceHooks,
|
|
21
|
-
};
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
24
|
export { commerceFields } from './fields.js';
|
|
24
25
|
export { commerceCollections } from './collections.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyB,MAAM,gCAAgC,CAAA;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAgB1C,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,SAA+B,EAAE;IAC9D,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,cAAc;QACtB,WAAW,EAAE,mBAAmB;QAChC,aAAa,EAAE;YACb,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,oBAAoB,EAAE;YAClE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB,EAAE;YACpE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,qBAAqB,EAAE;YACpE;gBACE,KAAK,EAAE,oBAAoB;gBAC3B,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,qBAAqB;aAC5B;SACF;QACD,KAAK,EAAE,aAAa;KACrB,CAAC,CAAA;AACJ,CAAC;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -24,6 +24,16 @@ export interface CheckoutSession {
|
|
|
24
24
|
url: string;
|
|
25
25
|
expiresAt: Date;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the Stripe Checkout Session params from cart line items and options.
|
|
29
|
+
*
|
|
30
|
+
* Pure and money-critical: it converts dollar amounts to integer cents
|
|
31
|
+
* (`Math.round(x * 100)`) for every line item and shipping rate. Extracted from
|
|
32
|
+
* `createCheckoutSession` so this mapping is unit-testable independently of the
|
|
33
|
+
* (still-stubbed) Stripe API call — a cents-rounding or field-shape bug here is
|
|
34
|
+
* a real overcharge/undercharge once the SDK is wired.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildCheckoutSessionParams(options: CheckoutSessionOptions): Record<string, unknown>;
|
|
27
37
|
/** Creates a Stripe Checkout session from cart line items and configuration. */
|
|
28
38
|
export declare function createCheckoutSession(options: CheckoutSessionOptions): Promise<CheckoutSession>;
|
|
29
39
|
//# sourceMappingURL=checkout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/stripe/checkout.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,gBAAgB,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,gFAAgF;AAChF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/stripe/checkout.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,gBAAgB,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,sBAAsB,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwCzB;AAED,gFAAgF;AAChF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CAa1B"}
|
package/dist/stripe/checkout.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Build the Stripe Checkout Session params from cart line items and options.
|
|
3
|
+
*
|
|
4
|
+
* Pure and money-critical: it converts dollar amounts to integer cents
|
|
5
|
+
* (`Math.round(x * 100)`) for every line item and shipping rate. Extracted from
|
|
6
|
+
* `createCheckoutSession` so this mapping is unit-testable independently of the
|
|
7
|
+
* (still-stubbed) Stripe API call — a cents-rounding or field-shape bug here is
|
|
8
|
+
* a real overcharge/undercharge once the SDK is wired.
|
|
9
|
+
*/
|
|
10
|
+
export function buildCheckoutSessionParams(options) {
|
|
3
11
|
const stripeLineItems = options.lineItems.map((item) => ({
|
|
4
12
|
price_data: {
|
|
5
13
|
currency: 'usd',
|
|
@@ -34,7 +42,13 @@ export async function createCheckoutSession(options) {
|
|
|
34
42
|
if (options.discountCode) {
|
|
35
43
|
sessionParams['discounts'] = [{ coupon: options.discountCode }];
|
|
36
44
|
}
|
|
45
|
+
return sessionParams;
|
|
46
|
+
}
|
|
47
|
+
/** Creates a Stripe Checkout session from cart line items and configuration. */
|
|
48
|
+
export async function createCheckoutSession(options) {
|
|
49
|
+
const sessionParams = buildCheckoutSessionParams(options);
|
|
37
50
|
// TODO: Call Stripe SDK — stripe.checkout.sessions.create(sessionParams)
|
|
51
|
+
void sessionParams;
|
|
38
52
|
const expiresAt = new Date();
|
|
39
53
|
expiresAt.setMinutes(expiresAt.getMinutes() + 30);
|
|
40
54
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/stripe/checkout.ts"],"names":[],"mappings":"AA8BA
|
|
1
|
+
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/stripe/checkout.ts"],"names":[],"mappings":"AA8BA;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA+B;IAE/B,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,UAAU,EAAE;YACV,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;gBACtC,QAAQ,EAAE;oBACR,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACzD;aACF;YACD,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;SAC9C;QACD,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC,CAAA;IAEH,MAAM,aAAa,GAA4B;QAC7C,IAAI,EAAE,SAAS;QACf,UAAU,EAAE,eAAe;QAC3B,WAAW,EAAE,OAAO,CAAC,UAAU;QAC/B,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5E,CAAA;IAED,IAAI,OAAO,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;QACpC,aAAa,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACxE,kBAAkB,EAAE;gBAClB,YAAY,EAAE,GAAG,CAAC,KAAK;gBACvB,IAAI,EAAE,cAAc;gBACpB,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE;aACxE;SACF,CAAC,CAAC,CAAA;IACL,CAAC;IAED,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QACzB,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;IACjE,CAAC;IAED,OAAO,aAAa,CAAA;AACtB,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAA+B;IAE/B,MAAM,aAAa,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAA;IAEzD,yEAAyE;IACzE,KAAK,aAAa,CAAA;IAClB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAA;IAC5B,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IAEjD,OAAO;QACL,EAAE,EAAE,WAAW,IAAI,CAAC,GAAG,EAAE,EAAE;QAC3B,GAAG,EAAE,2CAA2C,IAAI,CAAC,GAAG,EAAE,EAAE;QAC5D,SAAS;KACV,CAAA;AACH,CAAC"}
|
|
@@ -3,6 +3,26 @@ export interface WebhookResult {
|
|
|
3
3
|
event: string;
|
|
4
4
|
error?: string;
|
|
5
5
|
}
|
|
6
|
+
export interface VerifyOptions {
|
|
7
|
+
/** Reject events whose timestamp is further than this from now. Default 300s. */
|
|
8
|
+
toleranceSeconds?: number;
|
|
9
|
+
/** Current time in ms — injectable for deterministic tests. Default Date.now(). */
|
|
10
|
+
now?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Verify a Stripe webhook signature (the `Stripe-Signature` header) against the
|
|
14
|
+
* raw request body using the endpoint's signing secret. This is a
|
|
15
|
+
* dependency-free equivalent of `stripe.webhooks.constructEvent`:
|
|
16
|
+
*
|
|
17
|
+
* signed_payload = `${t}.${rawBody}`
|
|
18
|
+
* expected = HMAC-SHA256(signed_payload, secret) // hex
|
|
19
|
+
* valid = any provided v1 == expected (constant-time) AND |now - t| ≤ tolerance
|
|
20
|
+
*
|
|
21
|
+
* Throws on any failure. Multiple `v1` signatures (key rotation) are supported.
|
|
22
|
+
* Without this, a forged POST with any non-empty signature would be processed —
|
|
23
|
+
* a fake "payment succeeded" is a direct money/fraud risk.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyStripeSignature(payload: string, signatureHeader: string, secret: string, options?: VerifyOptions): void;
|
|
6
26
|
/** Verifies and processes a Stripe webhook event payload. */
|
|
7
|
-
export declare function handleStripeWebhook(payload: string, signature: string, secret: string): Promise<WebhookResult>;
|
|
27
|
+
export declare function handleStripeWebhook(payload: string, signature: string, secret: string, options?: VerifyOptions): Promise<WebhookResult>;
|
|
8
28
|
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/stripe/webhooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/stripe/webhooks.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAUD,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,IAAI,CAkCN;AAgCD,6DAA6D;AAC7D,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CA8BxB"}
|
package/dist/stripe/webhooks.js
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/** Default replay-protection window, matching Stripe's SDK (5 minutes). */
|
|
3
|
+
const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
4
|
+
/**
|
|
5
|
+
* Verify a Stripe webhook signature (the `Stripe-Signature` header) against the
|
|
6
|
+
* raw request body using the endpoint's signing secret. This is a
|
|
7
|
+
* dependency-free equivalent of `stripe.webhooks.constructEvent`:
|
|
8
|
+
*
|
|
9
|
+
* signed_payload = `${t}.${rawBody}`
|
|
10
|
+
* expected = HMAC-SHA256(signed_payload, secret) // hex
|
|
11
|
+
* valid = any provided v1 == expected (constant-time) AND |now - t| ≤ tolerance
|
|
12
|
+
*
|
|
13
|
+
* Throws on any failure. Multiple `v1` signatures (key rotation) are supported.
|
|
14
|
+
* Without this, a forged POST with any non-empty signature would be processed —
|
|
15
|
+
* a fake "payment succeeded" is a direct money/fraud risk.
|
|
16
|
+
*/
|
|
17
|
+
export function verifyStripeSignature(payload, signatureHeader, secret, options = {}) {
|
|
18
|
+
if (!secret)
|
|
19
|
+
throw new Error('Missing webhook signing secret');
|
|
20
|
+
if (!signatureHeader)
|
|
21
|
+
throw new Error('Missing webhook signature');
|
|
22
|
+
let timestamp;
|
|
23
|
+
const signatures = [];
|
|
24
|
+
for (const part of signatureHeader.split(',')) {
|
|
25
|
+
const eq = part.indexOf('=');
|
|
26
|
+
if (eq === -1)
|
|
27
|
+
continue;
|
|
28
|
+
const key = part.slice(0, eq).trim();
|
|
29
|
+
const value = part.slice(eq + 1).trim();
|
|
30
|
+
if (key === 't')
|
|
31
|
+
timestamp = value;
|
|
32
|
+
else if (key === 'v1')
|
|
33
|
+
signatures.push(value);
|
|
34
|
+
}
|
|
35
|
+
if (!timestamp || signatures.length === 0) {
|
|
36
|
+
throw new Error('Malformed webhook signature header');
|
|
37
|
+
}
|
|
38
|
+
const timestampSec = Number(timestamp);
|
|
39
|
+
if (!Number.isFinite(timestampSec))
|
|
40
|
+
throw new Error('Invalid webhook timestamp');
|
|
41
|
+
const nowSec = Math.floor((options.now ?? Date.now()) / 1000);
|
|
42
|
+
const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
43
|
+
if (Math.abs(nowSec - timestampSec) > tolerance) {
|
|
44
|
+
throw new Error('Webhook timestamp outside tolerance (possible replay)');
|
|
45
|
+
}
|
|
46
|
+
const expected = createHmac('sha256', secret).update(`${timestamp}.${payload}`).digest('hex');
|
|
47
|
+
const expectedBuf = Buffer.from(expected);
|
|
48
|
+
const matches = signatures.some((sig) => {
|
|
49
|
+
const sigBuf = Buffer.from(sig);
|
|
50
|
+
return sigBuf.length === expectedBuf.length && timingSafeEqual(sigBuf, expectedBuf);
|
|
51
|
+
});
|
|
52
|
+
if (!matches)
|
|
53
|
+
throw new Error('Webhook signature mismatch');
|
|
54
|
+
}
|
|
1
55
|
async function handleCheckoutCompleted(session) {
|
|
2
56
|
const customerEmail = session['customer_email'];
|
|
3
57
|
const paymentIntentId = session['payment_intent'];
|
|
@@ -19,13 +73,10 @@ async function handleChargeRefunded(charge) {
|
|
|
19
73
|
console.info(`[commerce:webhook] Charge refunded — charge=${chargeId} intent=${paymentIntentId} refunded=${amountRefunded}`);
|
|
20
74
|
}
|
|
21
75
|
/** Verifies and processes a Stripe webhook event payload. */
|
|
22
|
-
export async function handleStripeWebhook(payload, signature, secret) {
|
|
76
|
+
export async function handleStripeWebhook(payload, signature, secret, options = {}) {
|
|
23
77
|
let event;
|
|
24
78
|
try {
|
|
25
|
-
|
|
26
|
-
if (!signature || !secret) {
|
|
27
|
-
throw new Error('Missing webhook signature or secret');
|
|
28
|
-
}
|
|
79
|
+
verifyStripeSignature(payload, signature, secret, options);
|
|
29
80
|
event = JSON.parse(payload);
|
|
30
81
|
}
|
|
31
82
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/stripe/webhooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/stripe/webhooks.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/plugin-commerce",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react": "^19.0.0",
|
|
29
29
|
"typescript": "^5.7.0",
|
|
30
30
|
"vitest": "^4.1.0",
|
|
31
|
-
"@actuate-media/cms-core": "0.
|
|
31
|
+
"@actuate-media/cms-core": "0.93.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@actuate-media/cms-core": ">=0.1.0",
|