@payloadcms/plugin-ecommerce 3.84.0-canary.0 → 3.84.0-canary.1
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/dist/payments/adapters/stripe/confirmOrder.d.ts.map +1 -1
- package/dist/payments/adapters/stripe/confirmOrder.js +3 -1
- package/dist/payments/adapters/stripe/confirmOrder.js.map +1 -1
- package/dist/payments/adapters/stripe/confirmOrder.spec.js +166 -0
- package/dist/payments/adapters/stripe/confirmOrder.spec.js.map +1 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmOrder.d.ts","sourceRoot":"","sources":["../../../../src/payments/adapters/stripe/confirmOrder.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,KAAK,KAAK,GAAG;IACX,UAAU,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;IACxC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAA;CAC1C,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,WAAW,CAAC,cAAc,CAAC,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"confirmOrder.d.ts","sourceRoot":"","sources":["../../../../src/payments/adapters/stripe/confirmOrder.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,KAAK,KAAK,GAAG;IACX,UAAU,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;IACxC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAA;CAC1C,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,WAAW,CAAC,cAAc,CAAC,CAAC,cAAc,CAsIpF,CAAA"}
|
|
@@ -43,8 +43,10 @@ export const confirmOrder = (props)=>async ({ cartsSlug = 'carts', data, ordersS
|
|
|
43
43
|
if (!transactionsResults.totalDocs || !transaction) {
|
|
44
44
|
throw new Error('No transaction found for the provided PaymentIntent ID');
|
|
45
45
|
}
|
|
46
|
-
// Verify the payment intent exists and retrieve it
|
|
47
46
|
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID);
|
|
47
|
+
if (paymentIntent.status !== 'succeeded') {
|
|
48
|
+
throw new Error(`Payment not completed.`);
|
|
49
|
+
}
|
|
48
50
|
const cartID = paymentIntent.metadata.cartID;
|
|
49
51
|
const cartItemsSnapshot = paymentIntent.metadata.cartItemsSnapshot ? JSON.parse(paymentIntent.metadata.cartItemsSnapshot) : undefined;
|
|
50
52
|
const shippingAddress = paymentIntent.metadata.shippingAddress ? JSON.parse(paymentIntent.metadata.shippingAddress) : undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/payments/adapters/stripe/confirmOrder.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport type { PaymentAdapter } from '../../../types/index.js'\nimport type { StripeAdapterArgs } from './index.js'\n\ntype Props = {\n apiVersion?: Stripe.StripeConfig['apiVersion']\n appInfo?: Stripe.StripeConfig['appInfo']\n secretKey: StripeAdapterArgs['secretKey']\n}\n\nexport const confirmOrder: (props: Props) => NonNullable<PaymentAdapter>['confirmOrder'] =\n (props) =>\n async ({\n cartsSlug = 'carts',\n data,\n ordersSlug = 'orders',\n req,\n transactionsSlug = 'transactions',\n }) => {\n const payload = req.payload\n const { apiVersion, appInfo, secretKey } = props || {}\n\n const customerEmail = data.customerEmail\n\n const paymentIntentID = data.paymentIntentID as string\n\n if (!secretKey) {\n throw new Error('Stripe secret key is required')\n }\n\n if (!paymentIntentID) {\n throw new Error('PaymentIntent ID is required')\n }\n\n const stripe = new Stripe(secretKey, {\n // API version can only be the latest, stripe recommends ts ignoring it\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - ignoring since possible versions are not type safe, only the latest version is recognised\n apiVersion: apiVersion || '2025-03-31.basil',\n appInfo: appInfo || {\n name: 'Stripe Payload Plugin',\n url: 'https://payloadcms.com',\n },\n })\n\n try {\n let customer = (\n await stripe.customers.list({\n email: customerEmail,\n })\n ).data[0]\n\n if (!customer?.id) {\n customer = await stripe.customers.create({\n email: customerEmail,\n })\n }\n\n // Find our existing transaction by the payment intent ID\n const transactionsResults = await payload.find({\n collection: transactionsSlug,\n req,\n where: {\n 'stripe.paymentIntentID': {\n equals: paymentIntentID,\n },\n },\n })\n\n const transaction = transactionsResults.docs[0]\n\n if (!transactionsResults.totalDocs || !transaction) {\n throw new Error('No transaction found for the provided PaymentIntent ID')\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/payments/adapters/stripe/confirmOrder.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport type { PaymentAdapter } from '../../../types/index.js'\nimport type { StripeAdapterArgs } from './index.js'\n\ntype Props = {\n apiVersion?: Stripe.StripeConfig['apiVersion']\n appInfo?: Stripe.StripeConfig['appInfo']\n secretKey: StripeAdapterArgs['secretKey']\n}\n\nexport const confirmOrder: (props: Props) => NonNullable<PaymentAdapter>['confirmOrder'] =\n (props) =>\n async ({\n cartsSlug = 'carts',\n data,\n ordersSlug = 'orders',\n req,\n transactionsSlug = 'transactions',\n }) => {\n const payload = req.payload\n const { apiVersion, appInfo, secretKey } = props || {}\n\n const customerEmail = data.customerEmail\n\n const paymentIntentID = data.paymentIntentID as string\n\n if (!secretKey) {\n throw new Error('Stripe secret key is required')\n }\n\n if (!paymentIntentID) {\n throw new Error('PaymentIntent ID is required')\n }\n\n const stripe = new Stripe(secretKey, {\n // API version can only be the latest, stripe recommends ts ignoring it\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - ignoring since possible versions are not type safe, only the latest version is recognised\n apiVersion: apiVersion || '2025-03-31.basil',\n appInfo: appInfo || {\n name: 'Stripe Payload Plugin',\n url: 'https://payloadcms.com',\n },\n })\n\n try {\n let customer = (\n await stripe.customers.list({\n email: customerEmail,\n })\n ).data[0]\n\n if (!customer?.id) {\n customer = await stripe.customers.create({\n email: customerEmail,\n })\n }\n\n // Find our existing transaction by the payment intent ID\n const transactionsResults = await payload.find({\n collection: transactionsSlug,\n req,\n where: {\n 'stripe.paymentIntentID': {\n equals: paymentIntentID,\n },\n },\n })\n\n const transaction = transactionsResults.docs[0]\n\n if (!transactionsResults.totalDocs || !transaction) {\n throw new Error('No transaction found for the provided PaymentIntent ID')\n }\n\n const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID)\n\n if (paymentIntent.status !== 'succeeded') {\n throw new Error(`Payment not completed.`)\n }\n\n const cartID = paymentIntent.metadata.cartID\n const cartItemsSnapshot = paymentIntent.metadata.cartItemsSnapshot\n ? JSON.parse(paymentIntent.metadata.cartItemsSnapshot)\n : undefined\n\n const shippingAddress = paymentIntent.metadata.shippingAddress\n ? JSON.parse(paymentIntent.metadata.shippingAddress)\n : undefined\n\n if (!cartID) {\n throw new Error('Cart ID not found in the PaymentIntent metadata')\n }\n\n if (!cartItemsSnapshot || !Array.isArray(cartItemsSnapshot)) {\n throw new Error('Cart items snapshot not found or invalid in the PaymentIntent metadata')\n }\n\n const order = await payload.create({\n collection: ordersSlug,\n data: {\n amount: paymentIntent.amount,\n currency: paymentIntent.currency.toUpperCase(),\n ...(req.user ? { customer: req.user.id } : { customerEmail }),\n items: cartItemsSnapshot,\n shippingAddress,\n status: 'processing',\n transactions: [transaction.id],\n },\n req,\n })\n\n const timestamp = new Date().toISOString()\n\n await payload.update({\n id: cartID,\n collection: cartsSlug,\n data: {\n purchasedAt: timestamp,\n },\n req,\n })\n\n await payload.update({\n id: transaction.id,\n collection: transactionsSlug,\n data: {\n order: order.id,\n status: 'succeeded',\n },\n req,\n })\n\n return {\n message: 'Payment initiated successfully',\n orderID: order.id,\n transactionID: transaction.id,\n ...(order.accessToken ? { accessToken: order.accessToken } : {}),\n }\n } catch (error) {\n payload.logger.error({ err: error, msg: 'Error confirming order with Stripe' })\n\n throw new Error(error instanceof Error ? error.message : 'Unknown error initiating payment')\n }\n }\n"],"names":["Stripe","confirmOrder","props","cartsSlug","data","ordersSlug","req","transactionsSlug","payload","apiVersion","appInfo","secretKey","customerEmail","paymentIntentID","Error","stripe","name","url","customer","customers","list","email","id","create","transactionsResults","find","collection","where","equals","transaction","docs","totalDocs","paymentIntent","paymentIntents","retrieve","status","cartID","metadata","cartItemsSnapshot","JSON","parse","undefined","shippingAddress","Array","isArray","order","amount","currency","toUpperCase","user","items","transactions","timestamp","Date","toISOString","update","purchasedAt","message","orderID","transactionID","accessToken","error","logger","err","msg"],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const mockCustomersList = vi.fn();
|
|
3
|
+
const mockCustomersCreate = vi.fn();
|
|
4
|
+
const mockPaymentIntentsRetrieve = vi.fn();
|
|
5
|
+
vi.mock('stripe', ()=>{
|
|
6
|
+
const MockStripe = function() {
|
|
7
|
+
return {
|
|
8
|
+
customers: {
|
|
9
|
+
list: mockCustomersList,
|
|
10
|
+
create: mockCustomersCreate
|
|
11
|
+
},
|
|
12
|
+
paymentIntents: {
|
|
13
|
+
retrieve: mockPaymentIntentsRetrieve
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
default: MockStripe
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
import { confirmOrder } from './confirmOrder';
|
|
22
|
+
const defaultCartItemsSnapshot = JSON.stringify([
|
|
23
|
+
{
|
|
24
|
+
id: 'item-1',
|
|
25
|
+
quantity: 1
|
|
26
|
+
}
|
|
27
|
+
]);
|
|
28
|
+
const createMockPaymentIntent = (status)=>({
|
|
29
|
+
amount: 1000,
|
|
30
|
+
currency: 'usd',
|
|
31
|
+
metadata: {
|
|
32
|
+
cartID: 'cart-123',
|
|
33
|
+
cartItemsSnapshot: defaultCartItemsSnapshot,
|
|
34
|
+
shippingAddress: JSON.stringify({
|
|
35
|
+
city: 'Test City'
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
status
|
|
39
|
+
});
|
|
40
|
+
const createMockPayload = ()=>({
|
|
41
|
+
create: vi.fn().mockResolvedValue({
|
|
42
|
+
id: 'order-123'
|
|
43
|
+
}),
|
|
44
|
+
find: vi.fn().mockResolvedValue({
|
|
45
|
+
docs: [
|
|
46
|
+
{
|
|
47
|
+
id: 'txn-123'
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
totalDocs: 1
|
|
51
|
+
}),
|
|
52
|
+
logger: {
|
|
53
|
+
error: vi.fn()
|
|
54
|
+
},
|
|
55
|
+
update: vi.fn().mockResolvedValue({})
|
|
56
|
+
});
|
|
57
|
+
const createMockReq = (payload)=>({
|
|
58
|
+
payload,
|
|
59
|
+
user: {
|
|
60
|
+
id: 'user-123'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
describe('confirmOrder - payment status check', ()=>{
|
|
64
|
+
const secretKey = 'sk_test_123';
|
|
65
|
+
beforeEach(()=>{
|
|
66
|
+
vi.clearAllMocks();
|
|
67
|
+
mockCustomersList.mockResolvedValue({
|
|
68
|
+
data: [
|
|
69
|
+
{
|
|
70
|
+
id: 'cus-123'
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
mockCustomersCreate.mockResolvedValue({
|
|
75
|
+
id: 'cus-new'
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('should throw when paymentIntent status is requires_payment_method', async ()=>{
|
|
79
|
+
mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('requires_payment_method'));
|
|
80
|
+
const mockPayload = createMockPayload();
|
|
81
|
+
const handler = confirmOrder({
|
|
82
|
+
secretKey
|
|
83
|
+
});
|
|
84
|
+
await expect(handler({
|
|
85
|
+
data: {
|
|
86
|
+
customerEmail: 'test@test.com',
|
|
87
|
+
paymentIntentID: 'pi_123'
|
|
88
|
+
},
|
|
89
|
+
req: createMockReq(mockPayload)
|
|
90
|
+
})).rejects.toThrow('Payment not completed.');
|
|
91
|
+
expect(mockPayload.create).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
it('should throw when paymentIntent status is canceled', async ()=>{
|
|
94
|
+
mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('canceled'));
|
|
95
|
+
const mockPayload = createMockPayload();
|
|
96
|
+
const handler = confirmOrder({
|
|
97
|
+
secretKey
|
|
98
|
+
});
|
|
99
|
+
await expect(handler({
|
|
100
|
+
data: {
|
|
101
|
+
customerEmail: 'test@test.com',
|
|
102
|
+
paymentIntentID: 'pi_123'
|
|
103
|
+
},
|
|
104
|
+
req: createMockReq(mockPayload)
|
|
105
|
+
})).rejects.toThrow('Payment not completed.');
|
|
106
|
+
expect(mockPayload.create).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
it('should throw when paymentIntent status is processing', async ()=>{
|
|
109
|
+
mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('processing'));
|
|
110
|
+
const mockPayload = createMockPayload();
|
|
111
|
+
const handler = confirmOrder({
|
|
112
|
+
secretKey
|
|
113
|
+
});
|
|
114
|
+
await expect(handler({
|
|
115
|
+
data: {
|
|
116
|
+
customerEmail: 'test@test.com',
|
|
117
|
+
paymentIntentID: 'pi_123'
|
|
118
|
+
},
|
|
119
|
+
req: createMockReq(mockPayload)
|
|
120
|
+
})).rejects.toThrow('Payment not completed.');
|
|
121
|
+
expect(mockPayload.create).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
it('should not update cart or transaction when payment has not succeeded', async ()=>{
|
|
124
|
+
mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('requires_payment_method'));
|
|
125
|
+
const mockPayload = createMockPayload();
|
|
126
|
+
const handler = confirmOrder({
|
|
127
|
+
secretKey
|
|
128
|
+
});
|
|
129
|
+
await expect(handler({
|
|
130
|
+
data: {
|
|
131
|
+
customerEmail: 'test@test.com',
|
|
132
|
+
paymentIntentID: 'pi_123'
|
|
133
|
+
},
|
|
134
|
+
req: createMockReq(mockPayload)
|
|
135
|
+
})).rejects.toThrow();
|
|
136
|
+
expect(mockPayload.update).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
it('should create order when paymentIntent status is succeeded', async ()=>{
|
|
139
|
+
mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('succeeded'));
|
|
140
|
+
const mockPayload = createMockPayload();
|
|
141
|
+
const handler = confirmOrder({
|
|
142
|
+
secretKey
|
|
143
|
+
});
|
|
144
|
+
const result = await handler({
|
|
145
|
+
data: {
|
|
146
|
+
customerEmail: 'test@test.com',
|
|
147
|
+
paymentIntentID: 'pi_123'
|
|
148
|
+
},
|
|
149
|
+
req: createMockReq(mockPayload)
|
|
150
|
+
});
|
|
151
|
+
expect(mockPayload.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
152
|
+
collection: 'orders',
|
|
153
|
+
data: expect.objectContaining({
|
|
154
|
+
amount: 1000,
|
|
155
|
+
currency: 'USD',
|
|
156
|
+
status: 'processing'
|
|
157
|
+
})
|
|
158
|
+
}));
|
|
159
|
+
expect(result).toEqual(expect.objectContaining({
|
|
160
|
+
orderID: 'order-123',
|
|
161
|
+
transactionID: 'txn-123'
|
|
162
|
+
}));
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=confirmOrder.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/payments/adapters/stripe/confirmOrder.spec.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from 'vitest'\n\nconst mockCustomersList = vi.fn()\nconst mockCustomersCreate = vi.fn()\nconst mockPaymentIntentsRetrieve = vi.fn()\n\nvi.mock('stripe', () => {\n const MockStripe = function () {\n return {\n customers: {\n list: mockCustomersList,\n create: mockCustomersCreate,\n },\n paymentIntents: {\n retrieve: mockPaymentIntentsRetrieve,\n },\n }\n }\n\n return { default: MockStripe }\n})\n\nimport { confirmOrder } from './confirmOrder'\n\nconst defaultCartItemsSnapshot = JSON.stringify([{ id: 'item-1', quantity: 1 }])\n\nconst createMockPaymentIntent = (status: string) => ({\n amount: 1000,\n currency: 'usd',\n metadata: {\n cartID: 'cart-123',\n cartItemsSnapshot: defaultCartItemsSnapshot,\n shippingAddress: JSON.stringify({ city: 'Test City' }),\n },\n status,\n})\n\nconst createMockPayload = () => ({\n create: vi.fn().mockResolvedValue({ id: 'order-123' }),\n find: vi.fn().mockResolvedValue({\n docs: [{ id: 'txn-123' }],\n totalDocs: 1,\n }),\n logger: { error: vi.fn() },\n update: vi.fn().mockResolvedValue({}),\n})\n\nconst createMockReq = (payload: ReturnType<typeof createMockPayload>) =>\n ({\n payload,\n user: { id: 'user-123' },\n }) as any\n\ndescribe('confirmOrder - payment status check', () => {\n const secretKey = 'sk_test_123'\n\n beforeEach(() => {\n vi.clearAllMocks()\n\n mockCustomersList.mockResolvedValue({ data: [{ id: 'cus-123' }] })\n mockCustomersCreate.mockResolvedValue({ id: 'cus-new' })\n })\n\n it('should throw when paymentIntent status is requires_payment_method', async () => {\n mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('requires_payment_method'))\n\n const mockPayload = createMockPayload()\n const handler = confirmOrder({ secretKey })\n\n await expect(\n handler({\n data: { customerEmail: 'test@test.com', paymentIntentID: 'pi_123' },\n req: createMockReq(mockPayload),\n }),\n ).rejects.toThrow('Payment not completed.')\n\n expect(mockPayload.create).not.toHaveBeenCalled()\n })\n\n it('should throw when paymentIntent status is canceled', async () => {\n mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('canceled'))\n\n const mockPayload = createMockPayload()\n const handler = confirmOrder({ secretKey })\n\n await expect(\n handler({\n data: { customerEmail: 'test@test.com', paymentIntentID: 'pi_123' },\n req: createMockReq(mockPayload),\n }),\n ).rejects.toThrow('Payment not completed.')\n\n expect(mockPayload.create).not.toHaveBeenCalled()\n })\n\n it('should throw when paymentIntent status is processing', async () => {\n mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('processing'))\n\n const mockPayload = createMockPayload()\n const handler = confirmOrder({ secretKey })\n\n await expect(\n handler({\n data: { customerEmail: 'test@test.com', paymentIntentID: 'pi_123' },\n req: createMockReq(mockPayload),\n }),\n ).rejects.toThrow('Payment not completed.')\n\n expect(mockPayload.create).not.toHaveBeenCalled()\n })\n\n it('should not update cart or transaction when payment has not succeeded', async () => {\n mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('requires_payment_method'))\n\n const mockPayload = createMockPayload()\n const handler = confirmOrder({ secretKey })\n\n await expect(\n handler({\n data: { customerEmail: 'test@test.com', paymentIntentID: 'pi_123' },\n req: createMockReq(mockPayload),\n }),\n ).rejects.toThrow()\n\n expect(mockPayload.update).not.toHaveBeenCalled()\n })\n\n it('should create order when paymentIntent status is succeeded', async () => {\n mockPaymentIntentsRetrieve.mockResolvedValue(createMockPaymentIntent('succeeded'))\n\n const mockPayload = createMockPayload()\n const handler = confirmOrder({ secretKey })\n\n const result = await handler({\n data: { customerEmail: 'test@test.com', paymentIntentID: 'pi_123' },\n req: createMockReq(mockPayload),\n })\n\n expect(mockPayload.create).toHaveBeenCalledWith(\n expect.objectContaining({\n collection: 'orders',\n data: expect.objectContaining({\n amount: 1000,\n currency: 'USD',\n status: 'processing',\n }),\n }),\n )\n\n expect(result).toEqual(\n expect.objectContaining({\n orderID: 'order-123',\n transactionID: 'txn-123',\n }),\n )\n })\n})\n"],"names":["beforeEach","describe","expect","it","vi","mockCustomersList","fn","mockCustomersCreate","mockPaymentIntentsRetrieve","mock","MockStripe","customers","list","create","paymentIntents","retrieve","default","confirmOrder","defaultCartItemsSnapshot","JSON","stringify","id","quantity","createMockPaymentIntent","status","amount","currency","metadata","cartID","cartItemsSnapshot","shippingAddress","city","createMockPayload","mockResolvedValue","find","docs","totalDocs","logger","error","update","createMockReq","payload","user","secretKey","clearAllMocks","data","mockPayload","handler","customerEmail","paymentIntentID","req","rejects","toThrow","not","toHaveBeenCalled","result","toHaveBeenCalledWith","objectContaining","collection","toEqual","orderID","transactionID"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/plugin-ecommerce",
|
|
3
|
-
"version": "3.84.0-canary.
|
|
3
|
+
"version": "3.84.0-canary.1",
|
|
4
4
|
"description": "Ecommerce plugin for Payload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
],
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"qs-esm": "8.0.1",
|
|
77
|
-
"@payloadcms/translations": "3.84.0-canary.
|
|
78
|
-
"@payloadcms/ui": "3.84.0-canary.
|
|
77
|
+
"@payloadcms/translations": "3.84.0-canary.1",
|
|
78
|
+
"@payloadcms/ui": "3.84.0-canary.1"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@types/json-schema": "7.0.15",
|
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
"@types/react-dom": "19.2.3",
|
|
84
84
|
"stripe": "18.3.0",
|
|
85
85
|
"@payloadcms/eslint-config": "3.28.0",
|
|
86
|
-
"@payloadcms/next": "3.84.0-canary.
|
|
87
|
-
"
|
|
88
|
-
"
|
|
86
|
+
"@payloadcms/next": "3.84.0-canary.1",
|
|
87
|
+
"payload": "3.84.0-canary.1",
|
|
88
|
+
"@payloadcms/translations": "3.84.0-canary.1"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
91
|
"react": "^19.0.1 || ^19.1.2 || ^19.2.1",
|
|
92
92
|
"react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1",
|
|
93
|
-
"payload": "3.84.0-canary.
|
|
93
|
+
"payload": "3.84.0-canary.1"
|
|
94
94
|
},
|
|
95
95
|
"publishConfig": {
|
|
96
96
|
"registry": "https://registry.npmjs.org/"
|