@payloadcms/plugin-stripe 3.89.0-internal.95c9cba → 3.90.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.
Files changed (43) hide show
  1. package/dist/admin.d.ts.map +1 -1
  2. package/dist/admin.js +4 -2
  3. package/dist/admin.js.map +1 -1
  4. package/dist/exports/types.d.ts +1 -1
  5. package/dist/exports/types.d.ts.map +1 -1
  6. package/dist/exports/types.js.map +1 -1
  7. package/dist/fields/getFields.d.ts.map +1 -1
  8. package/dist/fields/getFields.js +8 -0
  9. package/dist/fields/getFields.js.map +1 -1
  10. package/dist/hooks/createNewInStripe.d.ts.map +1 -1
  11. package/dist/hooks/createNewInStripe.js +6 -7
  12. package/dist/hooks/createNewInStripe.js.map +1 -1
  13. package/dist/hooks/syncExistingWithStripe.d.ts.map +1 -1
  14. package/dist/hooks/syncExistingWithStripe.js +6 -7
  15. package/dist/hooks/syncExistingWithStripe.js.map +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.spec.js +115 -0
  20. package/dist/index.spec.js.map +1 -0
  21. package/dist/routes/rest.d.ts +3 -3
  22. package/dist/routes/rest.d.ts.map +1 -1
  23. package/dist/routes/rest.js +110 -27
  24. package/dist/routes/rest.js.map +1 -1
  25. package/dist/routes/rest.spec.js +282 -0
  26. package/dist/routes/rest.spec.js.map +1 -0
  27. package/dist/types.d.ts +10 -3
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/utilities/deepen.d.ts +6 -0
  31. package/dist/utilities/deepen.d.ts.map +1 -1
  32. package/dist/utilities/deepen.js +33 -3
  33. package/dist/utilities/deepen.js.map +1 -1
  34. package/dist/utilities/deepen.spec.js +110 -0
  35. package/dist/utilities/deepen.spec.js.map +1 -0
  36. package/dist/utilities/sanitizeStripeRESTConfig.d.ts +5 -0
  37. package/dist/utilities/sanitizeStripeRESTConfig.d.ts.map +1 -0
  38. package/dist/utilities/sanitizeStripeRESTConfig.js +25 -0
  39. package/dist/utilities/sanitizeStripeRESTConfig.js.map +1 -0
  40. package/dist/webhooks/handleCreatedOrUpdated.d.ts.map +1 -1
  41. package/dist/webhooks/handleCreatedOrUpdated.js +6 -6
  42. package/dist/webhooks/handleCreatedOrUpdated.js.map +1 -1
  43. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/rest.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { addDataAndFileToRequest, Forbidden } from 'payload'\n\nimport type { StripePluginConfig } from '../types.js'\n\nimport { stripeProxy } from '../utilities/stripeProxy.js'\n\nexport const stripeREST = async (args: {\n pluginConfig: StripePluginConfig\n req: PayloadRequest\n}): Promise<any> => {\n let responseStatus = 200\n let responseJSON\n\n const { pluginConfig, req } = args\n\n await addDataAndFileToRequest(req)\n\n const requestWithData = req\n const { data, payload, user } = requestWithData\n\n const { stripeSecretKey } = pluginConfig\n\n try {\n if (!user) {\n // TODO: make this customizable from the config\n throw new Forbidden(req.t)\n }\n\n responseJSON = await stripeProxy({\n stripeArgs: data?.stripeArgs, // example: ['cus_MGgt3Tuj3D66f2'] or [{ limit: 100 }, { stripeAccount: 'acct_1J9Z4pKZ4Z4Z4Z4Z' }]\n stripeMethod: data?.stripeMethod, // example: 'subscriptions.list',\n stripeSecretKey,\n })\n\n const { status } = responseJSON\n responseStatus = status\n } catch (error: unknown) {\n const message = `An error has occurred in the Stripe plugin REST handler: '${JSON.stringify(\n error,\n )}'`\n payload.logger.error(message)\n responseStatus = 500\n responseJSON = {\n message,\n }\n }\n\n return Response.json(responseJSON, {\n status: responseStatus,\n })\n}\n"],"names":["addDataAndFileToRequest","Forbidden","stripeProxy","stripeREST","args","responseStatus","responseJSON","pluginConfig","req","requestWithData","data","payload","user","stripeSecretKey","t","stripeArgs","stripeMethod","status","error","message","JSON","stringify","logger","Response","json"],"mappings":"AAEA,SAASA,uBAAuB,EAAEC,SAAS,QAAQ,UAAS;AAI5D,SAASC,WAAW,QAAQ,8BAA6B;AAEzD,OAAO,MAAMC,aAAa,OAAOC;IAI/B,IAAIC,iBAAiB;IACrB,IAAIC;IAEJ,MAAM,EAAEC,YAAY,EAAEC,GAAG,EAAE,GAAGJ;IAE9B,MAAMJ,wBAAwBQ;IAE9B,MAAMC,kBAAkBD;IACxB,MAAM,EAAEE,IAAI,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAAGH;IAEhC,MAAM,EAAEI,eAAe,EAAE,GAAGN;IAE5B,IAAI;QACF,IAAI,CAACK,MAAM;YACT,+CAA+C;YAC/C,MAAM,IAAIX,UAAUO,IAAIM,CAAC;QAC3B;QAEAR,eAAe,MAAMJ,YAAY;YAC/Ba,YAAYL,MAAMK;YAClBC,cAAcN,MAAMM;YACpBH;QACF;QAEA,MAAM,EAAEI,MAAM,EAAE,GAAGX;QACnBD,iBAAiBY;IACnB,EAAE,OAAOC,OAAgB;QACvB,MAAMC,UAAU,CAAC,0DAA0D,EAAEC,KAAKC,SAAS,CACzFH,OACA,CAAC,CAAC;QACJP,QAAQW,MAAM,CAACJ,KAAK,CAACC;QACrBd,iBAAiB;QACjBC,eAAe;YACba;QACF;IACF;IAEA,OAAOI,SAASC,IAAI,CAAClB,cAAc;QACjCW,QAAQZ;IACV;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/routes/rest.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { addDataAndFileToRequest, canAccessAdmin, UnauthorizedError } from 'payload'\n\nimport type { SanitizedStripePluginConfig } from '../types.js'\n\nimport { stripeProxy } from '../utilities/stripeProxy.js'\n\nexport const stripeREST = async (args: {\n pluginConfig: SanitizedStripePluginConfig\n req: PayloadRequest\n}): Promise<Response> => {\n const { pluginConfig, req } = args\n\n try {\n await addDataAndFileToRequest(req)\n } catch (err) {\n return unexpectedErrorResponse({ err, req })\n }\n\n if (!req.user) {\n return errorResponse({ message: 'Unauthorized', status: 401 })\n }\n\n if (!pluginConfig.rest) {\n return unexpectedErrorResponse({\n err: new Error('Stripe REST handler called while disabled'),\n req,\n })\n }\n\n let hasAccess: boolean\n\n if (pluginConfig.rest.access) {\n try {\n hasAccess = await pluginConfig.rest.access({ req })\n } catch (err) {\n return unexpectedErrorResponse({ err, req })\n }\n } else {\n try {\n hasAccess = await hasAdminAccess({ req })\n } catch (err) {\n return unexpectedErrorResponse({ err, req })\n }\n }\n\n if (!hasAccess) {\n return errorResponse({ message: 'Forbidden', status: 403 })\n }\n\n const { stripeArgs, stripeMethod } = req.data ?? {}\n\n if (typeof stripeMethod !== 'string' || stripeMethod.trim().length === 0) {\n return errorResponse({ message: 'Invalid request', status: 400 })\n }\n\n if (!pluginConfig.rest.allowedMethods.includes(stripeMethod)) {\n return errorResponse({ message: 'Invalid request', status: 400 })\n }\n\n if (!Array.isArray(stripeArgs)) {\n return errorResponse({ message: 'Invalid request', status: 400 })\n }\n\n try {\n const responseJSON = await stripeProxy({\n stripeArgs,\n stripeMethod,\n stripeSecretKey: pluginConfig.stripeSecretKey,\n })\n\n return Response.json(responseJSON, { status: responseJSON.status })\n } catch (err) {\n return unexpectedErrorResponse({ err, req })\n }\n}\n\nconst hasAdminAccess = async ({ req }: { req: PayloadRequest }): Promise<boolean> => {\n try {\n await canAccessAdmin({ req })\n return true\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return false\n }\n\n throw err\n }\n}\n\nconst errorResponse = ({ message, status }: { message: string; status: number }): Response =>\n Response.json({ message }, { status })\n\nconst unexpectedErrorResponse = ({ err, req }: { err: unknown; req: PayloadRequest }): Response => {\n req.payload.logger.error({\n err,\n msg: 'An unexpected error occurred in the Stripe plugin REST handler.',\n })\n\n return errorResponse({ message: 'Internal server error', status: 500 })\n}\n"],"names":["addDataAndFileToRequest","canAccessAdmin","UnauthorizedError","stripeProxy","stripeREST","args","pluginConfig","req","err","unexpectedErrorResponse","user","errorResponse","message","status","rest","Error","hasAccess","access","hasAdminAccess","stripeArgs","stripeMethod","data","trim","length","allowedMethods","includes","Array","isArray","responseJSON","stripeSecretKey","Response","json","payload","logger","error","msg"],"mappings":"AAEA,SAASA,uBAAuB,EAAEC,cAAc,EAAEC,iBAAiB,QAAQ,UAAS;AAIpF,SAASC,WAAW,QAAQ,8BAA6B;AAEzD,OAAO,MAAMC,aAAa,OAAOC;IAI/B,MAAM,EAAEC,YAAY,EAAEC,GAAG,EAAE,GAAGF;IAE9B,IAAI;QACF,MAAML,wBAAwBO;IAChC,EAAE,OAAOC,KAAK;QACZ,OAAOC,wBAAwB;YAAED;YAAKD;QAAI;IAC5C;IAEA,IAAI,CAACA,IAAIG,IAAI,EAAE;QACb,OAAOC,cAAc;YAAEC,SAAS;YAAgBC,QAAQ;QAAI;IAC9D;IAEA,IAAI,CAACP,aAAaQ,IAAI,EAAE;QACtB,OAAOL,wBAAwB;YAC7BD,KAAK,IAAIO,MAAM;YACfR;QACF;IACF;IAEA,IAAIS;IAEJ,IAAIV,aAAaQ,IAAI,CAACG,MAAM,EAAE;QAC5B,IAAI;YACFD,YAAY,MAAMV,aAAaQ,IAAI,CAACG,MAAM,CAAC;gBAAEV;YAAI;QACnD,EAAE,OAAOC,KAAK;YACZ,OAAOC,wBAAwB;gBAAED;gBAAKD;YAAI;QAC5C;IACF,OAAO;QACL,IAAI;YACFS,YAAY,MAAME,eAAe;gBAAEX;YAAI;QACzC,EAAE,OAAOC,KAAK;YACZ,OAAOC,wBAAwB;gBAAED;gBAAKD;YAAI;QAC5C;IACF;IAEA,IAAI,CAACS,WAAW;QACd,OAAOL,cAAc;YAAEC,SAAS;YAAaC,QAAQ;QAAI;IAC3D;IAEA,MAAM,EAAEM,UAAU,EAAEC,YAAY,EAAE,GAAGb,IAAIc,IAAI,IAAI,CAAC;IAElD,IAAI,OAAOD,iBAAiB,YAAYA,aAAaE,IAAI,GAAGC,MAAM,KAAK,GAAG;QACxE,OAAOZ,cAAc;YAAEC,SAAS;YAAmBC,QAAQ;QAAI;IACjE;IAEA,IAAI,CAACP,aAAaQ,IAAI,CAACU,cAAc,CAACC,QAAQ,CAACL,eAAe;QAC5D,OAAOT,cAAc;YAAEC,SAAS;YAAmBC,QAAQ;QAAI;IACjE;IAEA,IAAI,CAACa,MAAMC,OAAO,CAACR,aAAa;QAC9B,OAAOR,cAAc;YAAEC,SAAS;YAAmBC,QAAQ;QAAI;IACjE;IAEA,IAAI;QACF,MAAMe,eAAe,MAAMzB,YAAY;YACrCgB;YACAC;YACAS,iBAAiBvB,aAAauB,eAAe;QAC/C;QAEA,OAAOC,SAASC,IAAI,CAACH,cAAc;YAAEf,QAAQe,aAAaf,MAAM;QAAC;IACnE,EAAE,OAAOL,KAAK;QACZ,OAAOC,wBAAwB;YAAED;YAAKD;QAAI;IAC5C;AACF,EAAC;AAED,MAAMW,iBAAiB,OAAO,EAAEX,GAAG,EAA2B;IAC5D,IAAI;QACF,MAAMN,eAAe;YAAEM;QAAI;QAC3B,OAAO;IACT,EAAE,OAAOC,KAAK;QACZ,IAAIA,eAAeN,mBAAmB;YACpC,OAAO;QACT;QAEA,MAAMM;IACR;AACF;AAEA,MAAMG,gBAAgB,CAAC,EAAEC,OAAO,EAAEC,MAAM,EAAuC,GAC7EiB,SAASC,IAAI,CAAC;QAAEnB;IAAQ,GAAG;QAAEC;IAAO;AAEtC,MAAMJ,0BAA0B,CAAC,EAAED,GAAG,EAAED,GAAG,EAAyC;IAClFA,IAAIyB,OAAO,CAACC,MAAM,CAACC,KAAK,CAAC;QACvB1B;QACA2B,KAAK;IACP;IAEA,OAAOxB,cAAc;QAAEC,SAAS;QAAyBC,QAAQ;IAAI;AACvE"}
@@ -0,0 +1,282 @@
1
+ import { UnauthorizedError } from 'payload';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ const mocks = vi.hoisted(()=>({
4
+ addDataAndFileToRequest: vi.fn(),
5
+ canAccessAdmin: vi.fn(),
6
+ stripeProxy: vi.fn()
7
+ }));
8
+ vi.mock('payload', async (importOriginal)=>{
9
+ const payload = await importOriginal();
10
+ return {
11
+ ...payload,
12
+ addDataAndFileToRequest: mocks.addDataAndFileToRequest,
13
+ canAccessAdmin: mocks.canAccessAdmin
14
+ };
15
+ });
16
+ vi.mock('../utilities/stripeProxy.js', ()=>({
17
+ stripeProxy: mocks.stripeProxy
18
+ }));
19
+ import { stripeREST } from './rest.js';
20
+ const loggerError = vi.fn();
21
+ const createPluginConfig = ({ access } = {})=>({
22
+ rest: access === undefined ? {
23
+ allowedMethods: [
24
+ 'subscriptions.list'
25
+ ]
26
+ } : {
27
+ access,
28
+ allowedMethods: [
29
+ 'subscriptions.list'
30
+ ]
31
+ },
32
+ stripeSecretKey: 'sk_test_example',
33
+ sync: []
34
+ });
35
+ const createRequest = ({ stripeArgs = [], stripeMethod = 'subscriptions.list', user = {
36
+ collection: 'users',
37
+ id: 'user-1'
38
+ } } = {})=>({
39
+ data: {
40
+ stripeArgs,
41
+ stripeMethod
42
+ },
43
+ payload: {
44
+ logger: {
45
+ error: loggerError
46
+ }
47
+ },
48
+ user
49
+ });
50
+ describe('stripeREST', ()=>{
51
+ beforeEach(()=>{
52
+ vi.clearAllMocks();
53
+ mocks.addDataAndFileToRequest.mockResolvedValue(undefined);
54
+ mocks.canAccessAdmin.mockResolvedValue(undefined);
55
+ mocks.stripeProxy.mockResolvedValue({
56
+ data: {
57
+ id: 'sub_123'
58
+ },
59
+ status: 200
60
+ });
61
+ });
62
+ // Mutation caught: replacing the anonymous guard with a generic failure or forwarding anonymous requests.
63
+ it('should return 401 for an anonymous request without invoking Stripe', async ()=>{
64
+ const req = createRequest({
65
+ user: null
66
+ });
67
+ const response = await stripeREST({
68
+ pluginConfig: createPluginConfig(),
69
+ req
70
+ });
71
+ expect(response.status).toBe(401);
72
+ expect(mocks.canAccessAdmin).not.toHaveBeenCalled();
73
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
74
+ });
75
+ // Mutation caught: omitting the default admin access check for an authenticated request.
76
+ it('should return 403 when default admin access denies the user', async ()=>{
77
+ mocks.canAccessAdmin.mockRejectedValue(new UnauthorizedError());
78
+ const req = createRequest();
79
+ const response = await stripeREST({
80
+ pluginConfig: createPluginConfig(),
81
+ req
82
+ });
83
+ expect(response.status).toBe(403);
84
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
85
+ });
86
+ // Mutation caught: rejecting every configured method or bypassing the default admin authorization on success.
87
+ it('should invoke an exactly listed method for an authenticated admin user', async ()=>{
88
+ const req = createRequest({
89
+ stripeArgs: [
90
+ {
91
+ limit: 10
92
+ }
93
+ ]
94
+ });
95
+ const response = await stripeREST({
96
+ pluginConfig: createPluginConfig(),
97
+ req
98
+ });
99
+ expect(response.status).toBe(200);
100
+ expect(mocks.canAccessAdmin).toHaveBeenCalledWith({
101
+ req
102
+ });
103
+ expect(mocks.stripeProxy).toHaveBeenCalledWith({
104
+ stripeArgs: [
105
+ {
106
+ limit: 10
107
+ }
108
+ ],
109
+ stripeMethod: 'subscriptions.list',
110
+ stripeSecretKey: 'sk_test_example'
111
+ });
112
+ });
113
+ // Mutation caught: always running the default admin check instead of a configured custom access callback.
114
+ it('should use a custom access callback instead of the default admin check', async ()=>{
115
+ const access = vi.fn(async ()=>true);
116
+ const req = createRequest();
117
+ const response = await stripeREST({
118
+ pluginConfig: createPluginConfig({
119
+ access
120
+ }),
121
+ req
122
+ });
123
+ expect(response.status).toBe(200);
124
+ expect(access).toHaveBeenCalledWith({
125
+ req
126
+ });
127
+ expect(mocks.canAccessAdmin).not.toHaveBeenCalled();
128
+ expect(mocks.stripeProxy).toHaveBeenCalledTimes(1);
129
+ });
130
+ // Mutation caught: ignoring a false result from a custom access callback.
131
+ it('should return 403 when custom access denies the authenticated user', async ()=>{
132
+ const access = vi.fn(async ()=>false);
133
+ const req = createRequest();
134
+ const response = await stripeREST({
135
+ pluginConfig: createPluginConfig({
136
+ access
137
+ }),
138
+ req
139
+ });
140
+ expect(response.status).toBe(403);
141
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
142
+ });
143
+ // Mutation caught: forwarding an unlisted Stripe method to the proxy instead of rejecting it locally.
144
+ it('should return a generic invalid-request response for an unlisted method without invoking Stripe', async ()=>{
145
+ const req = createRequest({
146
+ stripeMethod: 'customers.list'
147
+ });
148
+ const response = await stripeREST({
149
+ pluginConfig: createPluginConfig(),
150
+ req
151
+ });
152
+ expect(response.status).toBe(400);
153
+ expect(await response.json()).toEqual({
154
+ message: 'Invalid request'
155
+ });
156
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
157
+ });
158
+ // Mutation caught: treating an empty allowlist as a wildcard.
159
+ it('should return a generic invalid-request response for an empty allowlist', async ()=>{
160
+ const req = createRequest();
161
+ const pluginConfig = {
162
+ ...createPluginConfig(),
163
+ rest: {
164
+ allowedMethods: []
165
+ }
166
+ };
167
+ const response = await stripeREST({
168
+ pluginConfig,
169
+ req
170
+ });
171
+ expect(response.status).toBe(400);
172
+ expect(await response.json()).toEqual({
173
+ message: 'Invalid request'
174
+ });
175
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
176
+ });
177
+ it.each([
178
+ [
179
+ ''
180
+ ],
181
+ [
182
+ ' '
183
+ ],
184
+ [
185
+ 123
186
+ ],
187
+ [
188
+ null
189
+ ]
190
+ ])(// Mutation caught: passing malformed method input through to Stripe.
191
+ 'should return 400 for malformed method %s without invoking Stripe', async (stripeMethod)=>{
192
+ const req = createRequest({
193
+ stripeMethod
194
+ });
195
+ const response = await stripeREST({
196
+ pluginConfig: createPluginConfig(),
197
+ req
198
+ });
199
+ expect(response.status).toBe(400);
200
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
201
+ });
202
+ // Mutation caught: forwarding non-array Stripe arguments to the Stripe proxy.
203
+ it('should return 400 for non-array Stripe arguments without invoking Stripe', async ()=>{
204
+ const req = createRequest({
205
+ stripeArgs: {
206
+ limit: 10
207
+ }
208
+ });
209
+ const response = await stripeREST({
210
+ pluginConfig: createPluginConfig(),
211
+ req
212
+ });
213
+ expect(response.status).toBe(400);
214
+ expect(await response.json()).toEqual({
215
+ message: 'Invalid request'
216
+ });
217
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
218
+ });
219
+ // Mutation caught: leaking request parser errors through the HTTP response.
220
+ it('should return a generic structured 500 when request parsing fails', async ()=>{
221
+ const parserError = new Error('sensitive parser detail');
222
+ const req = createRequest();
223
+ mocks.addDataAndFileToRequest.mockRejectedValue(parserError);
224
+ const response = await stripeREST({
225
+ pluginConfig: createPluginConfig(),
226
+ req
227
+ });
228
+ expect(response.status).toBe(500);
229
+ expect(await response.json()).toEqual({
230
+ message: 'Internal server error'
231
+ });
232
+ expect(loggerError).toHaveBeenCalledWith({
233
+ err: parserError,
234
+ msg: 'An unexpected error occurred in the Stripe plugin REST handler.'
235
+ });
236
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
237
+ });
238
+ // Mutation caught: leaking a custom access callback exception or continuing to Stripe.
239
+ it('should return a generic structured 500 when custom access throws', async ()=>{
240
+ const accessError = new Error('sensitive custom access detail');
241
+ const access = vi.fn(async ()=>{
242
+ throw accessError;
243
+ });
244
+ const req = createRequest();
245
+ const response = await stripeREST({
246
+ pluginConfig: createPluginConfig({
247
+ access
248
+ }),
249
+ req
250
+ });
251
+ expect(response.status).toBe(500);
252
+ expect(await response.json()).toEqual({
253
+ message: 'Internal server error'
254
+ });
255
+ expect(loggerError).toHaveBeenCalledWith({
256
+ err: accessError,
257
+ msg: 'An unexpected error occurred in the Stripe plugin REST handler.'
258
+ });
259
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
260
+ });
261
+ // Mutation caught: treating an unexpected default admin access exception as a 403 or leaking it.
262
+ it('should return a generic structured 500 when default admin access throws unexpectedly', async ()=>{
263
+ const accessError = new Error('sensitive admin access detail');
264
+ const req = createRequest();
265
+ mocks.canAccessAdmin.mockRejectedValue(accessError);
266
+ const response = await stripeREST({
267
+ pluginConfig: createPluginConfig(),
268
+ req
269
+ });
270
+ expect(response.status).toBe(500);
271
+ expect(await response.json()).toEqual({
272
+ message: 'Internal server error'
273
+ });
274
+ expect(loggerError).toHaveBeenCalledWith({
275
+ err: accessError,
276
+ msg: 'An unexpected error occurred in the Stripe plugin REST handler.'
277
+ });
278
+ expect(mocks.stripeProxy).not.toHaveBeenCalled();
279
+ });
280
+ });
281
+
282
+ //# sourceMappingURL=rest.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/rest.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { UnauthorizedError } from 'payload'\nimport { beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport type { SanitizedStripePluginConfig } from '../types.js'\n\nconst mocks = vi.hoisted(() => ({\n addDataAndFileToRequest: vi.fn(),\n canAccessAdmin: vi.fn(),\n stripeProxy: vi.fn(),\n}))\n\nvi.mock('payload', async (importOriginal) => {\n const payload = await importOriginal<typeof import('payload')>()\n\n return {\n ...payload,\n addDataAndFileToRequest: mocks.addDataAndFileToRequest,\n canAccessAdmin: mocks.canAccessAdmin,\n }\n})\n\nvi.mock('../utilities/stripeProxy.js', () => ({\n stripeProxy: mocks.stripeProxy,\n}))\n\nimport { stripeREST } from './rest.js'\n\nconst loggerError = vi.fn()\n\nconst createPluginConfig = ({\n access,\n}: {\n access?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n} = {}): SanitizedStripePluginConfig => ({\n rest:\n access === undefined\n ? { allowedMethods: ['subscriptions.list'] }\n : { access, allowedMethods: ['subscriptions.list'] },\n stripeSecretKey: 'sk_test_example',\n sync: [],\n})\n\nconst createRequest = ({\n stripeArgs = [],\n stripeMethod = 'subscriptions.list',\n user = { collection: 'users', id: 'user-1' },\n}: {\n stripeArgs?: unknown\n stripeMethod?: unknown\n user?: null | { collection: string; id: string }\n} = {}): PayloadRequest =>\n ({\n data: { stripeArgs, stripeMethod },\n payload: {\n logger: { error: loggerError },\n },\n user,\n }) as unknown as PayloadRequest\n\ndescribe('stripeREST', () => {\n beforeEach(() => {\n vi.clearAllMocks()\n mocks.addDataAndFileToRequest.mockResolvedValue(undefined)\n mocks.canAccessAdmin.mockResolvedValue(undefined)\n mocks.stripeProxy.mockResolvedValue({ data: { id: 'sub_123' }, status: 200 })\n })\n\n // Mutation caught: replacing the anonymous guard with a generic failure or forwarding anonymous requests.\n it('should return 401 for an anonymous request without invoking Stripe', async () => {\n const req = createRequest({ user: null })\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(401)\n expect(mocks.canAccessAdmin).not.toHaveBeenCalled()\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: omitting the default admin access check for an authenticated request.\n it('should return 403 when default admin access denies the user', async () => {\n mocks.canAccessAdmin.mockRejectedValue(new UnauthorizedError())\n const req = createRequest()\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(403)\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: rejecting every configured method or bypassing the default admin authorization on success.\n it('should invoke an exactly listed method for an authenticated admin user', async () => {\n const req = createRequest({ stripeArgs: [{ limit: 10 }] })\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(200)\n expect(mocks.canAccessAdmin).toHaveBeenCalledWith({ req })\n expect(mocks.stripeProxy).toHaveBeenCalledWith({\n stripeArgs: [{ limit: 10 }],\n stripeMethod: 'subscriptions.list',\n stripeSecretKey: 'sk_test_example',\n })\n })\n\n // Mutation caught: always running the default admin check instead of a configured custom access callback.\n it('should use a custom access callback instead of the default admin check', async () => {\n const access = vi.fn(async () => true)\n const req = createRequest()\n\n const response = await stripeREST({ pluginConfig: createPluginConfig({ access }), req })\n\n expect(response.status).toBe(200)\n expect(access).toHaveBeenCalledWith({ req })\n expect(mocks.canAccessAdmin).not.toHaveBeenCalled()\n expect(mocks.stripeProxy).toHaveBeenCalledTimes(1)\n })\n\n // Mutation caught: ignoring a false result from a custom access callback.\n it('should return 403 when custom access denies the authenticated user', async () => {\n const access = vi.fn(async () => false)\n const req = createRequest()\n\n const response = await stripeREST({ pluginConfig: createPluginConfig({ access }), req })\n\n expect(response.status).toBe(403)\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: forwarding an unlisted Stripe method to the proxy instead of rejecting it locally.\n it('should return a generic invalid-request response for an unlisted method without invoking Stripe', async () => {\n const req = createRequest({ stripeMethod: 'customers.list' })\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(400)\n expect(await response.json()).toEqual({ message: 'Invalid request' })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: treating an empty allowlist as a wildcard.\n it('should return a generic invalid-request response for an empty allowlist', async () => {\n const req = createRequest()\n const pluginConfig = {\n ...createPluginConfig(),\n rest: { allowedMethods: [] },\n }\n\n const response = await stripeREST({ pluginConfig, req })\n\n expect(response.status).toBe(400)\n expect(await response.json()).toEqual({ message: 'Invalid request' })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n it.each([[''], [' '], [123], [null]])(\n // Mutation caught: passing malformed method input through to Stripe.\n 'should return 400 for malformed method %s without invoking Stripe',\n async (stripeMethod) => {\n const req = createRequest({ stripeMethod })\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(400)\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n },\n )\n\n // Mutation caught: forwarding non-array Stripe arguments to the Stripe proxy.\n it('should return 400 for non-array Stripe arguments without invoking Stripe', async () => {\n const req = createRequest({ stripeArgs: { limit: 10 } })\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(400)\n expect(await response.json()).toEqual({ message: 'Invalid request' })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: leaking request parser errors through the HTTP response.\n it('should return a generic structured 500 when request parsing fails', async () => {\n const parserError = new Error('sensitive parser detail')\n const req = createRequest()\n\n mocks.addDataAndFileToRequest.mockRejectedValue(parserError)\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(500)\n expect(await response.json()).toEqual({ message: 'Internal server error' })\n expect(loggerError).toHaveBeenCalledWith({\n err: parserError,\n msg: 'An unexpected error occurred in the Stripe plugin REST handler.',\n })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: leaking a custom access callback exception or continuing to Stripe.\n it('should return a generic structured 500 when custom access throws', async () => {\n const accessError = new Error('sensitive custom access detail')\n const access = vi.fn(async () => {\n throw accessError\n })\n const req = createRequest()\n\n const response = await stripeREST({ pluginConfig: createPluginConfig({ access }), req })\n\n expect(response.status).toBe(500)\n expect(await response.json()).toEqual({ message: 'Internal server error' })\n expect(loggerError).toHaveBeenCalledWith({\n err: accessError,\n msg: 'An unexpected error occurred in the Stripe plugin REST handler.',\n })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n\n // Mutation caught: treating an unexpected default admin access exception as a 403 or leaking it.\n it('should return a generic structured 500 when default admin access throws unexpectedly', async () => {\n const accessError = new Error('sensitive admin access detail')\n const req = createRequest()\n\n mocks.canAccessAdmin.mockRejectedValue(accessError)\n\n const response = await stripeREST({ pluginConfig: createPluginConfig(), req })\n\n expect(response.status).toBe(500)\n expect(await response.json()).toEqual({ message: 'Internal server error' })\n expect(loggerError).toHaveBeenCalledWith({\n err: accessError,\n msg: 'An unexpected error occurred in the Stripe plugin REST handler.',\n })\n expect(mocks.stripeProxy).not.toHaveBeenCalled()\n })\n})\n"],"names":["UnauthorizedError","beforeEach","describe","expect","it","vi","mocks","hoisted","addDataAndFileToRequest","fn","canAccessAdmin","stripeProxy","mock","importOriginal","payload","stripeREST","loggerError","createPluginConfig","access","rest","undefined","allowedMethods","stripeSecretKey","sync","createRequest","stripeArgs","stripeMethod","user","collection","id","data","logger","error","clearAllMocks","mockResolvedValue","status","req","response","pluginConfig","toBe","not","toHaveBeenCalled","mockRejectedValue","limit","toHaveBeenCalledWith","toHaveBeenCalledTimes","json","toEqual","message","each","parserError","Error","err","msg","accessError"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -21,19 +21,26 @@ export type SyncConfig = {
21
21
  stripeResourceType: 'customers' | 'products';
22
22
  stripeResourceTypeSingular: 'customer' | 'product';
23
23
  };
24
+ export type StripeRESTAccess = (args: {
25
+ req: PayloadRequest;
26
+ }) => boolean | Promise<boolean>;
27
+ export type StripeRESTConfig = {
28
+ access?: StripeRESTAccess;
29
+ allowedMethods: readonly string[];
30
+ };
24
31
  export type StripePluginConfig = {
25
32
  isTestKey?: boolean;
26
33
  logs?: boolean;
27
- /** @default false */
28
- rest?: boolean;
34
+ rest?: StripeRESTConfig;
29
35
  stripeSecretKey: string;
30
36
  stripeWebhooksEndpointSecret?: string;
31
37
  sync?: SyncConfig[];
32
38
  webhooks?: StripeWebhookHandler | StripeWebhookHandlers;
33
39
  };
34
40
  export type SanitizedStripePluginConfig = {
41
+ rest?: StripeRESTConfig;
35
42
  sync: SyncConfig[];
36
- } & StripePluginConfig;
43
+ } & Omit<StripePluginConfig, 'rest' | 'sync'>;
37
44
  export type StripeProxy = (args: {
38
45
  stripeArgs: any[];
39
46
  stripeMethod: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAEhC,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE;IACjD,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAE1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,cAAc,CAAA;IAC1B,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,kBAAkB,EAAE,WAAW,GAAG,UAAU,CAAA;IAC5C,0BAA0B,EAAE,UAAU,GAAG,SAAS,CAAA;CACnD,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,qBAAqB;IACrB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;IACvB,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAA;IACnB,QAAQ,CAAC,EAAE,oBAAoB,GAAG,qBAAqB,CAAA;CACxD,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,UAAU,EAAE,CAAA;CACnB,GAAG,kBAAkB,CAAA;AAEtB,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE;IAC/B,UAAU,EAAE,GAAG,EAAE,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;CACxB,KAAK,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAEhC,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE;IACjD,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAE1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,cAAc,CAAA;IAC1B,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,kBAAkB,EAAE,WAAW,GAAG,UAAU,CAAA;IAC5C,0BAA0B,EAAE,UAAU,GAAG,SAAS,CAAA;CACnD,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,cAAc,CAAA;CAAE,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAE5F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAA;IACnB,QAAQ,CAAC,EAAE,oBAAoB,GAAG,qBAAqB,CAAA;CACxD,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,IAAI,EAAE,UAAU,EAAE,CAAA;CACnB,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAE7C,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE;IAC/B,UAAU,EAAE,GAAG,EAAE,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;CACxB,KAAK,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAC,CAAA"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug, Payload, Config as PayloadConfig, PayloadRequest } from 'payload'\nimport type Stripe from 'stripe'\n\nexport type StripeWebhookHandler<T = any> = (args: {\n config: PayloadConfig\n event: T\n payload: Payload\n pluginConfig?: StripePluginConfig\n req: PayloadRequest\n stripe: Stripe\n}) => Promise<void> | void\n\nexport type StripeWebhookHandlers = {\n [webhookName: string]: StripeWebhookHandler\n}\n\nexport type FieldSyncConfig = {\n fieldPath: string\n stripeProperty: string\n}\n\nexport type SyncConfig = {\n collection: CollectionSlug\n fields: FieldSyncConfig[]\n stripeResourceType: 'customers' | 'products' // TODO: get this from Stripe types\n stripeResourceTypeSingular: 'customer' | 'product' // TODO: there must be a better way to do this\n}\n\nexport type StripePluginConfig = {\n isTestKey?: boolean\n logs?: boolean\n /** @default false */\n rest?: boolean\n stripeSecretKey: string\n stripeWebhooksEndpointSecret?: string\n sync?: SyncConfig[]\n webhooks?: StripeWebhookHandler | StripeWebhookHandlers\n}\n\nexport type SanitizedStripePluginConfig = {\n sync: SyncConfig[] // convert to required\n} & StripePluginConfig\n\nexport type StripeProxy = (args: {\n stripeArgs: any[]\n stripeMethod: string\n stripeSecretKey: string\n}) => Promise<{\n data?: any\n message?: string\n status: number\n}>\n"],"names":[],"mappings":"AA2CA,WAQE"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug, Payload, Config as PayloadConfig, PayloadRequest } from 'payload'\nimport type Stripe from 'stripe'\n\nexport type StripeWebhookHandler<T = any> = (args: {\n config: PayloadConfig\n event: T\n payload: Payload\n pluginConfig?: StripePluginConfig\n req: PayloadRequest\n stripe: Stripe\n}) => Promise<void> | void\n\nexport type StripeWebhookHandlers = {\n [webhookName: string]: StripeWebhookHandler\n}\n\nexport type FieldSyncConfig = {\n fieldPath: string\n stripeProperty: string\n}\n\nexport type SyncConfig = {\n collection: CollectionSlug\n fields: FieldSyncConfig[]\n stripeResourceType: 'customers' | 'products' // TODO: get this from Stripe types\n stripeResourceTypeSingular: 'customer' | 'product' // TODO: there must be a better way to do this\n}\n\nexport type StripeRESTAccess = (args: { req: PayloadRequest }) => boolean | Promise<boolean>\n\nexport type StripeRESTConfig = {\n access?: StripeRESTAccess\n allowedMethods: readonly string[]\n}\n\nexport type StripePluginConfig = {\n isTestKey?: boolean\n logs?: boolean\n rest?: StripeRESTConfig\n stripeSecretKey: string\n stripeWebhooksEndpointSecret?: string\n sync?: SyncConfig[]\n webhooks?: StripeWebhookHandler | StripeWebhookHandlers\n}\n\nexport type SanitizedStripePluginConfig = {\n rest?: StripeRESTConfig\n sync: SyncConfig[]\n} & Omit<StripePluginConfig, 'rest' | 'sync'>\n\nexport type StripeProxy = (args: {\n stripeArgs: any[]\n stripeMethod: string\n stripeSecretKey: string\n}) => Promise<{\n data?: any\n message?: string\n status: number\n}>\n"],"names":[],"mappings":"AAkDA,WAQE"}
@@ -1,2 +1,8 @@
1
+ import type { FieldSyncConfig } from '../types.js';
1
2
  export declare const deepen: (obj: Record<string, any>) => Record<string, any>;
3
+ export declare const getSyncedFields: ({ data, fields, source, }: {
4
+ data: object;
5
+ fields: FieldSyncConfig[];
6
+ source: "payload" | "stripe";
7
+ }) => Record<string, unknown>;
2
8
  //# sourceMappingURL=deepen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deepen.d.ts","sourceRoot":"","sources":["../../src/utilities/deepen.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM,QAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAiBnE,CAAA"}
1
+ {"version":3,"file":"deepen.d.ts","sourceRoot":"","sources":["../../src/utilities/deepen.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,eAAO,MAAM,MAAM,QAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAwBnE,CAAA;AAED,eAAO,MAAM,eAAe,8BAIzB;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAA;CAC7B,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAczB,CAAA"}
@@ -1,21 +1,51 @@
1
1
  // converts an object of dot notation keys to a nested object
2
2
  // i.e. { 'price.stripePriceID': '123' } to { price: { stripePriceID: '123' } }
3
+ import { hasUnsupportedFieldPathSegment, setOwnProperty } from 'payload/shared';
3
4
  export const deepen = (obj)=>{
4
5
  const result = {};
5
- for(const key in obj){
6
+ for (const key of Object.keys(obj)){
6
7
  const value = obj[key];
7
8
  const keys = key.split('.');
9
+ if (hasUnsupportedFieldPathSegment({
10
+ segments: keys
11
+ })) {
12
+ throw new Error('Invalid field path.');
13
+ }
8
14
  let current = result;
9
15
  keys.forEach((k, index)=>{
10
16
  if (index === keys.length - 1) {
11
- current[k] = value;
17
+ setOwnProperty({
18
+ key: k,
19
+ target: current,
20
+ value
21
+ });
12
22
  } else {
13
- current[k] = current[k] || {};
23
+ if (!Object.hasOwn(current, k) || current[k] === null || typeof current[k] !== 'object') {
24
+ setOwnProperty({
25
+ key: k,
26
+ target: current,
27
+ value: {}
28
+ });
29
+ }
14
30
  current = current[k];
15
31
  }
16
32
  });
17
33
  }
18
34
  return result;
19
35
  };
36
+ export const getSyncedFields = ({ data, fields, source })=>{
37
+ const flattened = Object.create(null);
38
+ for (const { fieldPath, stripeProperty } of fields){
39
+ const sourceProperty = source === 'payload' ? fieldPath : stripeProperty;
40
+ const targetProperty = source === 'payload' ? stripeProperty : fieldPath;
41
+ const value = Object.hasOwn(data, sourceProperty) ? data[sourceProperty] : undefined;
42
+ setOwnProperty({
43
+ key: targetProperty,
44
+ target: flattened,
45
+ value
46
+ });
47
+ }
48
+ return deepen(flattened);
49
+ };
20
50
 
21
51
  //# sourceMappingURL=deepen.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/deepen.ts"],"sourcesContent":["// converts an object of dot notation keys to a nested object\n// i.e. { 'price.stripePriceID': '123' } to { price: { stripePriceID: '123' } }\n\nexport const deepen = (obj: Record<string, any>): Record<string, any> => {\n const result: Record<string, any> = {}\n\n for (const key in obj) {\n const value = obj[key]\n const keys = key.split('.')\n let current = result\n keys.forEach((k, index) => {\n if (index === keys.length - 1) {\n current[k] = value\n } else {\n current[k] = current[k] || {}\n current = current[k]\n }\n })\n }\n return result\n}\n"],"names":["deepen","obj","result","key","value","keys","split","current","forEach","k","index","length"],"mappings":"AAAA,6DAA6D;AAC7D,+EAA+E;AAE/E,OAAO,MAAMA,SAAS,CAACC;IACrB,MAAMC,SAA8B,CAAC;IAErC,IAAK,MAAMC,OAAOF,IAAK;QACrB,MAAMG,QAAQH,GAAG,CAACE,IAAI;QACtB,MAAME,OAAOF,IAAIG,KAAK,CAAC;QACvB,IAAIC,UAAUL;QACdG,KAAKG,OAAO,CAAC,CAACC,GAAGC;YACf,IAAIA,UAAUL,KAAKM,MAAM,GAAG,GAAG;gBAC7BJ,OAAO,CAACE,EAAE,GAAGL;YACf,OAAO;gBACLG,OAAO,CAACE,EAAE,GAAGF,OAAO,CAACE,EAAE,IAAI,CAAC;gBAC5BF,UAAUA,OAAO,CAACE,EAAE;YACtB;QACF;IACF;IACA,OAAOP;AACT,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/deepen.ts"],"sourcesContent":["// converts an object of dot notation keys to a nested object\n// i.e. { 'price.stripePriceID': '123' } to { price: { stripePriceID: '123' } }\n\nimport { hasUnsupportedFieldPathSegment, setOwnProperty } from 'payload/shared'\n\nimport type { FieldSyncConfig } from '../types.js'\n\nexport const deepen = (obj: Record<string, any>): Record<string, any> => {\n const result: Record<string, any> = {}\n\n for (const key of Object.keys(obj)) {\n const value = obj[key]\n const keys = key.split('.')\n\n if (hasUnsupportedFieldPathSegment({ segments: keys })) {\n throw new Error('Invalid field path.')\n }\n\n let current = result\n keys.forEach((k, index) => {\n if (index === keys.length - 1) {\n setOwnProperty({ key: k, target: current, value })\n } else {\n if (!Object.hasOwn(current, k) || current[k] === null || typeof current[k] !== 'object') {\n setOwnProperty({ key: k, target: current, value: {} })\n }\n current = current[k]\n }\n })\n }\n return result\n}\n\nexport const getSyncedFields = ({\n data,\n fields,\n source,\n}: {\n data: object\n fields: FieldSyncConfig[]\n source: 'payload' | 'stripe'\n}): Record<string, unknown> => {\n const flattened = Object.create(null) as Record<string, unknown>\n\n for (const { fieldPath, stripeProperty } of fields) {\n const sourceProperty = source === 'payload' ? fieldPath : stripeProperty\n const targetProperty = source === 'payload' ? stripeProperty : fieldPath\n const value = Object.hasOwn(data, sourceProperty)\n ? (data as Record<string, unknown>)[sourceProperty]\n : undefined\n\n setOwnProperty({ key: targetProperty, target: flattened, value })\n }\n\n return deepen(flattened)\n}\n"],"names":["hasUnsupportedFieldPathSegment","setOwnProperty","deepen","obj","result","key","Object","keys","value","split","segments","Error","current","forEach","k","index","length","target","hasOwn","getSyncedFields","data","fields","source","flattened","create","fieldPath","stripeProperty","sourceProperty","targetProperty","undefined"],"mappings":"AAAA,6DAA6D;AAC7D,+EAA+E;AAE/E,SAASA,8BAA8B,EAAEC,cAAc,QAAQ,iBAAgB;AAI/E,OAAO,MAAMC,SAAS,CAACC;IACrB,MAAMC,SAA8B,CAAC;IAErC,KAAK,MAAMC,OAAOC,OAAOC,IAAI,CAACJ,KAAM;QAClC,MAAMK,QAAQL,GAAG,CAACE,IAAI;QACtB,MAAME,OAAOF,IAAII,KAAK,CAAC;QAEvB,IAAIT,+BAA+B;YAAEU,UAAUH;QAAK,IAAI;YACtD,MAAM,IAAII,MAAM;QAClB;QAEA,IAAIC,UAAUR;QACdG,KAAKM,OAAO,CAAC,CAACC,GAAGC;YACf,IAAIA,UAAUR,KAAKS,MAAM,GAAG,GAAG;gBAC7Bf,eAAe;oBAAEI,KAAKS;oBAAGG,QAAQL;oBAASJ;gBAAM;YAClD,OAAO;gBACL,IAAI,CAACF,OAAOY,MAAM,CAACN,SAASE,MAAMF,OAAO,CAACE,EAAE,KAAK,QAAQ,OAAOF,OAAO,CAACE,EAAE,KAAK,UAAU;oBACvFb,eAAe;wBAAEI,KAAKS;wBAAGG,QAAQL;wBAASJ,OAAO,CAAC;oBAAE;gBACtD;gBACAI,UAAUA,OAAO,CAACE,EAAE;YACtB;QACF;IACF;IACA,OAAOV;AACT,EAAC;AAED,OAAO,MAAMe,kBAAkB,CAAC,EAC9BC,IAAI,EACJC,MAAM,EACNC,MAAM,EAKP;IACC,MAAMC,YAAYjB,OAAOkB,MAAM,CAAC;IAEhC,KAAK,MAAM,EAAEC,SAAS,EAAEC,cAAc,EAAE,IAAIL,OAAQ;QAClD,MAAMM,iBAAiBL,WAAW,YAAYG,YAAYC;QAC1D,MAAME,iBAAiBN,WAAW,YAAYI,iBAAiBD;QAC/D,MAAMjB,QAAQF,OAAOY,MAAM,CAACE,MAAMO,kBAC9B,AAACP,IAAgC,CAACO,eAAe,GACjDE;QAEJ5B,eAAe;YAAEI,KAAKuB;YAAgBX,QAAQM;YAAWf;QAAM;IACjE;IAEA,OAAON,OAAOqB;AAChB,EAAC"}
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { deepen, getSyncedFields } from './deepen.js';
3
+ describe('Stripe field mappings', ()=>{
4
+ it('should build constrained mappings from owned values', ()=>{
5
+ const marker = 'constrainedStripeMapping';
6
+ const inheritedSource = 'constrainedStripeSource';
7
+ const originalDescriptor = Object.getOwnPropertyDescriptor(Object.prototype, marker);
8
+ let inheritedGetterCalls = 0;
9
+ let processStateChanged = false;
10
+ let nestedMapping;
11
+ let syncedMapping;
12
+ let webhookMapping;
13
+ try {
14
+ expect(()=>deepen({
15
+ [`__proto__.${marker}`]: 'value'
16
+ })).toThrow('Invalid field path.');
17
+ nestedMapping = deepen(Object.assign(Object.create({
18
+ 'ignored.value': 'inherited'
19
+ }), {
20
+ [`constructor.prototype.${marker}`]: 'local',
21
+ 'profile.displayName': 'Ada'
22
+ }));
23
+ const data = Object.create({
24
+ get [inheritedSource] () {
25
+ inheritedGetterCalls += 1;
26
+ return 'inherited';
27
+ }
28
+ });
29
+ data.displayName = 'Ada';
30
+ syncedMapping = getSyncedFields({
31
+ data,
32
+ fields: [
33
+ {
34
+ fieldPath: inheritedSource,
35
+ stripeProperty: 'metadata.inherited'
36
+ },
37
+ {
38
+ fieldPath: 'constructor',
39
+ stripeProperty: 'constructor'
40
+ },
41
+ {
42
+ fieldPath: 'displayName',
43
+ stripeProperty: `constructor.prototype.${marker}`
44
+ }
45
+ ],
46
+ source: 'payload'
47
+ });
48
+ const stripeData = Object.create({
49
+ get [inheritedSource] () {
50
+ inheritedGetterCalls += 1;
51
+ return 'inherited';
52
+ }
53
+ });
54
+ stripeData.name = 'Ada';
55
+ webhookMapping = getSyncedFields({
56
+ data: stripeData,
57
+ fields: [
58
+ {
59
+ fieldPath: 'profile.inherited',
60
+ stripeProperty: inheritedSource
61
+ },
62
+ {
63
+ fieldPath: 'profile.displayName',
64
+ stripeProperty: 'name'
65
+ }
66
+ ],
67
+ source: 'stripe'
68
+ });
69
+ processStateChanged = Object.hasOwn(Object.prototype, marker);
70
+ } finally{
71
+ if (originalDescriptor) {
72
+ Object.defineProperty(Object.prototype, marker, originalDescriptor);
73
+ } else {
74
+ delete Object.prototype[marker];
75
+ }
76
+ }
77
+ expect(processStateChanged).toBe(false);
78
+ expect(inheritedGetterCalls).toBe(0);
79
+ expect(Object.getOwnPropertyDescriptor(Object.prototype, marker)).toEqual(originalDescriptor);
80
+ expect(nestedMapping).toEqual({
81
+ constructor: {
82
+ prototype: {
83
+ [marker]: 'local'
84
+ }
85
+ },
86
+ profile: {
87
+ displayName: 'Ada'
88
+ }
89
+ });
90
+ expect(Object.hasOwn(nestedMapping, 'ignored')).toBe(false);
91
+ expect(syncedMapping).toEqual({
92
+ constructor: {
93
+ prototype: {
94
+ [marker]: 'Ada'
95
+ }
96
+ },
97
+ metadata: {
98
+ inherited: undefined
99
+ }
100
+ });
101
+ expect(webhookMapping).toEqual({
102
+ profile: {
103
+ displayName: 'Ada',
104
+ inherited: undefined
105
+ }
106
+ });
107
+ });
108
+ });
109
+
110
+ //# sourceMappingURL=deepen.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/deepen.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { deepen, getSyncedFields } from './deepen.js'\n\ndescribe('Stripe field mappings', () => {\n it('should build constrained mappings from owned values', () => {\n const marker = 'constrainedStripeMapping'\n const inheritedSource = 'constrainedStripeSource'\n const originalDescriptor = Object.getOwnPropertyDescriptor(Object.prototype, marker)\n let inheritedGetterCalls = 0\n let processStateChanged = false\n let nestedMapping: Record<string, unknown> | undefined\n let syncedMapping: Record<string, unknown> | undefined\n let webhookMapping: Record<string, unknown> | undefined\n\n try {\n expect(() => deepen({ [`__proto__.${marker}`]: 'value' })).toThrow('Invalid field path.')\n\n nestedMapping = deepen(\n Object.assign(Object.create({ 'ignored.value': 'inherited' }), {\n [`constructor.prototype.${marker}`]: 'local',\n 'profile.displayName': 'Ada',\n }),\n )\n\n const data = Object.create({\n get [inheritedSource]() {\n inheritedGetterCalls += 1\n return 'inherited'\n },\n }) as Record<string, unknown>\n data.displayName = 'Ada'\n\n syncedMapping = getSyncedFields({\n data,\n fields: [\n { fieldPath: inheritedSource, stripeProperty: 'metadata.inherited' },\n { fieldPath: 'constructor', stripeProperty: 'constructor' },\n {\n fieldPath: 'displayName',\n stripeProperty: `constructor.prototype.${marker}`,\n },\n ],\n source: 'payload',\n })\n\n const stripeData = Object.create({\n get [inheritedSource]() {\n inheritedGetterCalls += 1\n return 'inherited'\n },\n }) as Record<string, unknown>\n stripeData.name = 'Ada'\n\n webhookMapping = getSyncedFields({\n data: stripeData,\n fields: [\n { fieldPath: 'profile.inherited', stripeProperty: inheritedSource },\n { fieldPath: 'profile.displayName', stripeProperty: 'name' },\n ],\n source: 'stripe',\n })\n processStateChanged = Object.hasOwn(Object.prototype, marker)\n } finally {\n if (originalDescriptor) {\n Object.defineProperty(Object.prototype, marker, originalDescriptor)\n } else {\n delete (Object.prototype as Record<string, unknown>)[marker]\n }\n }\n\n expect(processStateChanged).toBe(false)\n expect(inheritedGetterCalls).toBe(0)\n expect(Object.getOwnPropertyDescriptor(Object.prototype, marker)).toEqual(originalDescriptor)\n expect(nestedMapping).toEqual({\n constructor: {\n prototype: {\n [marker]: 'local',\n },\n },\n profile: {\n displayName: 'Ada',\n },\n })\n expect(Object.hasOwn(nestedMapping as object, 'ignored')).toBe(false)\n expect(syncedMapping).toEqual({\n constructor: {\n prototype: {\n [marker]: 'Ada',\n },\n },\n metadata: {\n inherited: undefined,\n },\n })\n expect(webhookMapping).toEqual({\n profile: {\n displayName: 'Ada',\n inherited: undefined,\n },\n })\n })\n})\n"],"names":["describe","expect","it","deepen","getSyncedFields","marker","inheritedSource","originalDescriptor","Object","getOwnPropertyDescriptor","prototype","inheritedGetterCalls","processStateChanged","nestedMapping","syncedMapping","webhookMapping","toThrow","assign","create","data","displayName","fields","fieldPath","stripeProperty","source","stripeData","name","hasOwn","defineProperty","toBe","toEqual","profile","metadata","inherited","undefined"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,MAAM,EAAEC,eAAe,QAAQ,cAAa;AAErDJ,SAAS,yBAAyB;IAChCE,GAAG,uDAAuD;QACxD,MAAMG,SAAS;QACf,MAAMC,kBAAkB;QACxB,MAAMC,qBAAqBC,OAAOC,wBAAwB,CAACD,OAAOE,SAAS,EAAEL;QAC7E,IAAIM,uBAAuB;QAC3B,IAAIC,sBAAsB;QAC1B,IAAIC;QACJ,IAAIC;QACJ,IAAIC;QAEJ,IAAI;YACFd,OAAO,IAAME,OAAO;oBAAE,CAAC,CAAC,UAAU,EAAEE,QAAQ,CAAC,EAAE;gBAAQ,IAAIW,OAAO,CAAC;YAEnEH,gBAAgBV,OACdK,OAAOS,MAAM,CAACT,OAAOU,MAAM,CAAC;gBAAE,iBAAiB;YAAY,IAAI;gBAC7D,CAAC,CAAC,sBAAsB,EAAEb,QAAQ,CAAC,EAAE;gBACrC,uBAAuB;YACzB;YAGF,MAAMc,OAAOX,OAAOU,MAAM,CAAC;gBACzB,IAAI,CAACZ,gBAAgB,IAAG;oBACtBK,wBAAwB;oBACxB,OAAO;gBACT;YACF;YACAQ,KAAKC,WAAW,GAAG;YAEnBN,gBAAgBV,gBAAgB;gBAC9Be;gBACAE,QAAQ;oBACN;wBAAEC,WAAWhB;wBAAiBiB,gBAAgB;oBAAqB;oBACnE;wBAAED,WAAW;wBAAeC,gBAAgB;oBAAc;oBAC1D;wBACED,WAAW;wBACXC,gBAAgB,CAAC,sBAAsB,EAAElB,QAAQ;oBACnD;iBACD;gBACDmB,QAAQ;YACV;YAEA,MAAMC,aAAajB,OAAOU,MAAM,CAAC;gBAC/B,IAAI,CAACZ,gBAAgB,IAAG;oBACtBK,wBAAwB;oBACxB,OAAO;gBACT;YACF;YACAc,WAAWC,IAAI,GAAG;YAElBX,iBAAiBX,gBAAgB;gBAC/Be,MAAMM;gBACNJ,QAAQ;oBACN;wBAAEC,WAAW;wBAAqBC,gBAAgBjB;oBAAgB;oBAClE;wBAAEgB,WAAW;wBAAuBC,gBAAgB;oBAAO;iBAC5D;gBACDC,QAAQ;YACV;YACAZ,sBAAsBJ,OAAOmB,MAAM,CAACnB,OAAOE,SAAS,EAAEL;QACxD,SAAU;YACR,IAAIE,oBAAoB;gBACtBC,OAAOoB,cAAc,CAACpB,OAAOE,SAAS,EAAEL,QAAQE;YAClD,OAAO;gBACL,OAAO,AAACC,OAAOE,SAAS,AAA4B,CAACL,OAAO;YAC9D;QACF;QAEAJ,OAAOW,qBAAqBiB,IAAI,CAAC;QACjC5B,OAAOU,sBAAsBkB,IAAI,CAAC;QAClC5B,OAAOO,OAAOC,wBAAwB,CAACD,OAAOE,SAAS,EAAEL,SAASyB,OAAO,CAACvB;QAC1EN,OAAOY,eAAeiB,OAAO,CAAC;YAC5B,aAAa;gBACXpB,WAAW;oBACT,CAACL,OAAO,EAAE;gBACZ;YACF;YACA0B,SAAS;gBACPX,aAAa;YACf;QACF;QACAnB,OAAOO,OAAOmB,MAAM,CAACd,eAAyB,YAAYgB,IAAI,CAAC;QAC/D5B,OAAOa,eAAegB,OAAO,CAAC;YAC5B,aAAa;gBACXpB,WAAW;oBACT,CAACL,OAAO,EAAE;gBACZ;YACF;YACA2B,UAAU;gBACRC,WAAWC;YACb;QACF;QACAjC,OAAOc,gBAAgBe,OAAO,CAAC;YAC7BC,SAAS;gBACPX,aAAa;gBACba,WAAWC;YACb;QACF;IACF;AACF"}
@@ -0,0 +1,5 @@
1
+ import type { StripeRESTConfig } from '../types.js';
2
+ export declare const sanitizeStripeRESTConfig: ({ rest, }: {
3
+ rest: unknown;
4
+ }) => StripeRESTConfig | undefined;
5
+ //# sourceMappingURL=sanitizeStripeRESTConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeStripeRESTConfig.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizeStripeRESTConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAKrE,eAAO,MAAM,wBAAwB,cAElC;IACD,IAAI,EAAE,OAAO,CAAA;CACd,KAAG,gBAAgB,GAAG,SA8BtB,CAAA"}
@@ -0,0 +1,25 @@
1
+ const migrationError = 'The Stripe REST endpoint now requires an object with a non-empty allowedMethods array of exact method names.';
2
+ export const sanitizeStripeRESTConfig = ({ rest })=>{
3
+ if (rest === undefined || rest === false) {
4
+ return undefined;
5
+ }
6
+ if (typeof rest !== 'object' || rest === null || Array.isArray(rest) || !('allowedMethods' in rest) || !Array.isArray(rest.allowedMethods)) {
7
+ throw new Error(migrationError);
8
+ }
9
+ const access = 'access' in rest ? rest.access : undefined;
10
+ if (rest.allowedMethods.length === 0 || rest.allowedMethods.some((method)=>typeof method !== 'string' || method.trim().length === 0 || method.includes('*')) || access !== undefined && !isStripeRESTAccess(access)) {
11
+ throw new Error(migrationError);
12
+ }
13
+ const allowedMethods = Object.freeze([
14
+ ...new Set(rest.allowedMethods)
15
+ ]);
16
+ return access === undefined ? {
17
+ allowedMethods
18
+ } : {
19
+ access,
20
+ allowedMethods
21
+ };
22
+ };
23
+ const isStripeRESTAccess = (access)=>typeof access === 'function';
24
+
25
+ //# sourceMappingURL=sanitizeStripeRESTConfig.js.map