payload-cod-adapter 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 blaze IT s.r.o.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # payload-cod-adapter
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+
5
+ Cash on Delivery (COD / Pouzeće) payment adapter for [PayloadCMS](https://payloadcms.com/) ecommerce plugin.
6
+
7
+ Enables customers to pay upon delivery — no external payment gateway redirect needed. Orders are created directly with a configurable surcharge.
8
+
9
+ ## Features
10
+
11
+ - Full integration with `@payloadcms/plugin-ecommerce`
12
+ - No external API calls — orders created instantly
13
+ - Optional COD surcharge (flat fee)
14
+ - Complete pricing breakdown (subtotal, discount, shipping, surcharge, grandTotal)
15
+ - Address passthrough (billing + shipping) to orders
16
+ - TypeScript support with full type safety
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install payload-cod-adapter
22
+ # or
23
+ pnpm add payload-cod-adapter
24
+ # or
25
+ yarn add payload-cod-adapter
26
+ ```
27
+
28
+ ## Requirements
29
+
30
+ - PayloadCMS 3.x
31
+ - `@payloadcms/plugin-ecommerce` ^3.60.0
32
+
33
+ ## Quick Start
34
+
35
+ ### Server Configuration
36
+
37
+ ```typescript
38
+ // payload.config.ts
39
+ import { buildConfig } from 'payload'
40
+ import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
41
+ import { codAdapter } from 'payload-cod-adapter'
42
+
43
+ export default buildConfig({
44
+ plugins: [
45
+ ecommercePlugin({
46
+ payments: {
47
+ paymentMethods: [
48
+ codAdapter({
49
+ label: 'Cash on Delivery',
50
+ surcharge: 200, // Optional: 200 cents = 2.00 surcharge
51
+ }),
52
+ ],
53
+ },
54
+ }),
55
+ ],
56
+ })
57
+ ```
58
+
59
+ ### Client Configuration
60
+
61
+ COD does not require a client adapter — no payment form is needed. The payment method appears as a selectable option in checkout automatically.
62
+
63
+ ## Configuration Options
64
+
65
+ | Option | Type | Default | Description |
66
+ | ----------- | -------- | -------------------- | ----------------------------------------- |
67
+ | `label` | `string` | `'Cash on Delivery'` | Display label for the payment method |
68
+ | `surcharge` | `number` | `0` | COD surcharge in cents (e.g., 200 = 2.00) |
69
+
70
+ ## Payment Flow
71
+
72
+ 1. **Customer selects COD** at checkout
73
+ 2. **`initiatePayment()` called** — creates transaction with status `succeeded`
74
+ 3. **Order created immediately** (no redirect, no webhook)
75
+ 4. **Cart marked as purchased**
76
+ 5. **Customer receives order confirmation**
77
+
78
+ Unlike card payment adapters, COD skips the external gateway entirely. The order is confirmed instantly.
79
+
80
+ ## Transaction Fields
81
+
82
+ The adapter adds a `cod` group field to transactions with:
83
+
84
+ - `surcharge` - COD surcharge amount applied
85
+ - `note` - Payment collection note
86
+
87
+ ## TypeScript
88
+
89
+ ```typescript
90
+ import { codAdapter } from 'payload-cod-adapter'
91
+ import type { CodAdapterArgs } from 'payload-cod-adapter'
92
+ ```
93
+
94
+ ## Changelog
95
+
96
+ ### 0.1.0
97
+
98
+ - Initial release
99
+ - Full `@payloadcms/plugin-ecommerce` PaymentAdapter implementation
100
+ - COD surcharge support
101
+ - Pricing breakdown passthrough
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ // src/codAdapterClient.ts
4
+ var codAdapterClient = (config) => {
5
+ const { label = "Pouze\u0107e" } = config || {};
6
+ return {
7
+ name: "cod",
8
+ label,
9
+ initiatePayment: true,
10
+ confirmOrder: true
11
+ };
12
+ };
13
+
14
+ exports.codAdapterClient = codAdapterClient;
15
+ //# sourceMappingURL=client.cjs.map
16
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codAdapterClient.ts"],"names":[],"mappings":";;;AAqBO,IAAM,gBAAA,GAAmB,CAC9B,MAAA,KACyB;AACzB,EAAA,MAAM,EAAE,KAAA,GAAQ,cAAA,EAAU,GAAI,UAAU,EAAC;AAEzC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAA;AAAA,IACN,KAAA;AAAA,IACA,eAAA,EAAiB,IAAA;AAAA,IACjB,YAAA,EAAc;AAAA,GAChB;AACF","file":"client.cjs","sourcesContent":["import type { PaymentAdapterClient } from '@payloadcms/plugin-ecommerce/types'\n\nexport interface CodAdapterClientArgs {\n /** Label shown in payment method selection */\n label?: string\n}\n\n/**\n * Client-side COD adapter for PayloadCMS ecommerce plugin\n *\n * @example\n * ```typescript\n * import { codAdapterClient } from 'payload-cod-adapter/client'\n *\n * <EcommerceProvider\n * paymentMethods={[\n * codAdapterClient({ label: 'Pouzeće' }),\n * ]}\n * >\n * ```\n */\nexport const codAdapterClient = (\n config?: CodAdapterClientArgs,\n): PaymentAdapterClient => {\n const { label = 'Pouzeće' } = config || {}\n\n return {\n name: 'cod',\n label,\n initiatePayment: true,\n confirmOrder: true,\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { PaymentAdapterClient } from '@payloadcms/plugin-ecommerce/types';
2
+
3
+ interface CodAdapterClientArgs {
4
+ /** Label shown in payment method selection */
5
+ label?: string;
6
+ }
7
+ /**
8
+ * Client-side COD adapter for PayloadCMS ecommerce plugin
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { codAdapterClient } from 'payload-cod-adapter/client'
13
+ *
14
+ * <EcommerceProvider
15
+ * paymentMethods={[
16
+ * codAdapterClient({ label: 'Pouzeće' }),
17
+ * ]}
18
+ * >
19
+ * ```
20
+ */
21
+ declare const codAdapterClient: (config?: CodAdapterClientArgs) => PaymentAdapterClient;
22
+
23
+ export { type CodAdapterClientArgs, codAdapterClient };
@@ -0,0 +1,23 @@
1
+ import { PaymentAdapterClient } from '@payloadcms/plugin-ecommerce/types';
2
+
3
+ interface CodAdapterClientArgs {
4
+ /** Label shown in payment method selection */
5
+ label?: string;
6
+ }
7
+ /**
8
+ * Client-side COD adapter for PayloadCMS ecommerce plugin
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { codAdapterClient } from 'payload-cod-adapter/client'
13
+ *
14
+ * <EcommerceProvider
15
+ * paymentMethods={[
16
+ * codAdapterClient({ label: 'Pouzeće' }),
17
+ * ]}
18
+ * >
19
+ * ```
20
+ */
21
+ declare const codAdapterClient: (config?: CodAdapterClientArgs) => PaymentAdapterClient;
22
+
23
+ export { type CodAdapterClientArgs, codAdapterClient };
package/dist/client.js ADDED
@@ -0,0 +1,14 @@
1
+ // src/codAdapterClient.ts
2
+ var codAdapterClient = (config) => {
3
+ const { label = "Pouze\u0107e" } = config || {};
4
+ return {
5
+ name: "cod",
6
+ label,
7
+ initiatePayment: true,
8
+ confirmOrder: true
9
+ };
10
+ };
11
+
12
+ export { codAdapterClient };
13
+ //# sourceMappingURL=client.js.map
14
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codAdapterClient.ts"],"names":[],"mappings":";AAqBO,IAAM,gBAAA,GAAmB,CAC9B,MAAA,KACyB;AACzB,EAAA,MAAM,EAAE,KAAA,GAAQ,cAAA,EAAU,GAAI,UAAU,EAAC;AAEzC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAA;AAAA,IACN,KAAA;AAAA,IACA,eAAA,EAAiB,IAAA;AAAA,IACjB,YAAA,EAAc;AAAA,GAChB;AACF","file":"client.js","sourcesContent":["import type { PaymentAdapterClient } from '@payloadcms/plugin-ecommerce/types'\n\nexport interface CodAdapterClientArgs {\n /** Label shown in payment method selection */\n label?: string\n}\n\n/**\n * Client-side COD adapter for PayloadCMS ecommerce plugin\n *\n * @example\n * ```typescript\n * import { codAdapterClient } from 'payload-cod-adapter/client'\n *\n * <EcommerceProvider\n * paymentMethods={[\n * codAdapterClient({ label: 'Pouzeće' }),\n * ]}\n * >\n * ```\n */\nexport const codAdapterClient = (\n config?: CodAdapterClientArgs,\n): PaymentAdapterClient => {\n const { label = 'Pouzeće' } = config || {}\n\n return {\n name: 'cod',\n label,\n initiatePayment: true,\n confirmOrder: true,\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ // src/codAdapter.ts
4
+ var codAdapter = (config = {}) => {
5
+ const {
6
+ surchargeAmount = 200,
7
+ label = "Pouze\u0107e",
8
+ locale: _locale = "rs",
9
+ serverUrl: _serverUrl,
10
+ groupOverrides
11
+ } = config;
12
+ const initiatePayment = async ({
13
+ data,
14
+ req,
15
+ transactionsSlug
16
+ }) => {
17
+ const payload = req.payload;
18
+ const currency = data.currency;
19
+ if (!currency) {
20
+ throw new Error("Currency is required.");
21
+ }
22
+ const cart = data.cart;
23
+ const customerEmail = data.customerEmail || (req.user?.collection === "users" ? req.user.email : void 0);
24
+ if (!customerEmail) {
25
+ throw new Error("Customer email is required.");
26
+ }
27
+ if (!cart || typeof cart.subtotal !== "number") {
28
+ throw new Error("Valid cart with subtotal is required.");
29
+ }
30
+ const additionalData = data;
31
+ const discount = additionalData.discount;
32
+ const shippingMethod = additionalData.shippingMethod;
33
+ const shippingAddress = additionalData.shippingAddress;
34
+ const billingAddress = additionalData.billingAddress;
35
+ const pricing = additionalData.pricing;
36
+ const subtotalCents = cart.subtotal;
37
+ const discountCents = discount?.calculatedAmount || 0;
38
+ const shippingCents = Math.round((shippingMethod?.cost || 0) * 100);
39
+ const surchargeCents = surchargeAmount * 100;
40
+ const cartTotalCents = Math.max(0, subtotalCents - discountCents) + shippingCents + surchargeCents;
41
+ const cartTotal = cartTotalCents / 100;
42
+ try {
43
+ const transaction = await payload.create({
44
+ collection: transactionsSlug,
45
+ data: {
46
+ paymentMethod: "cod",
47
+ status: "pending",
48
+ amount: cartTotal,
49
+ currency,
50
+ cart: typeof cart.id === "number" ? cart.id : void 0,
51
+ customerEmail,
52
+ paymentSurcharge: surchargeAmount,
53
+ // Store additional data for order creation
54
+ ...additionalData.discount ? { discount: additionalData.discount } : {},
55
+ ...additionalData.shippingMethod ? { shippingMethod: additionalData.shippingMethod } : {},
56
+ ...shippingAddress ? { shippingAddress } : {},
57
+ ...billingAddress ? { billingAddress } : {},
58
+ // Pricing breakdown (flat fields matching Transaction schema)
59
+ ...pricing ? {
60
+ ...pricing.subtotal != null ? { subtotal: pricing.subtotal } : {},
61
+ ...pricing.discountAmount != null ? { discountAmount: pricing.discountAmount } : {},
62
+ ...pricing.shippingCost != null ? { shippingCost: pricing.shippingCost } : {},
63
+ ...pricing.grandTotal != null ? { grandTotal: pricing.grandTotal } : {},
64
+ ...pricing.freeShipping != null ? { freeShipping: pricing.freeShipping } : {}
65
+ } : {},
66
+ ...additionalData.invoiceType ? { invoiceType: additionalData.invoiceType } : {},
67
+ ...additionalData.taxNumber ? { taxNumber: additionalData.taxNumber } : {},
68
+ ...discountCents > 0 ? { subtotalBeforeDiscount: subtotalCents / 100 } : {}
69
+ }
70
+ });
71
+ payload.logger.info(
72
+ `[COD] Transaction ${transaction.id} created for ${cartTotal} ${currency}`
73
+ );
74
+ return {
75
+ message: "COD payment initiated",
76
+ transactionID: transaction.id
77
+ // No `redirect` field — this signals the client to handle order confirmation inline
78
+ };
79
+ } catch (error) {
80
+ payload.logger.error(error, "Error initiating COD payment");
81
+ throw new Error(
82
+ error instanceof Error ? error.message : "Unknown error initiating COD payment"
83
+ );
84
+ }
85
+ };
86
+ const confirmOrder = async ({
87
+ data,
88
+ ordersSlug = "orders",
89
+ req,
90
+ transactionsSlug = "transactions",
91
+ cartsSlug = "carts"
92
+ }) => {
93
+ const payload = req.payload;
94
+ const transactionId = data.transactionId;
95
+ if (!transactionId) {
96
+ throw new Error("Transaction ID is required for COD confirmation");
97
+ }
98
+ try {
99
+ const transactionsResults = await payload.find({
100
+ collection: transactionsSlug,
101
+ where: { id: { equals: transactionId } },
102
+ depth: 2
103
+ });
104
+ if (transactionsResults.docs.length === 0) {
105
+ throw new Error("Transaction not found");
106
+ }
107
+ const transaction = transactionsResults.docs[0];
108
+ if (transaction.order) {
109
+ const existingOrderId = typeof transaction.order === "object" && transaction.order !== null ? transaction.order.id : transaction.order;
110
+ return { message: "Order already confirmed", orderID: existingOrderId, transactionID: transaction.id, customerEmail: transaction.customerEmail };
111
+ }
112
+ const priceField = `priceIn${transaction.currency || "EUR"}`;
113
+ const salePriceField = `salePriceIn${transaction.currency || "EUR"}`;
114
+ const orderItems = transaction.cart?.items?.map((item) => {
115
+ const product = typeof item.product === "object" && item.product !== null ? item.product : null;
116
+ const variant = item.variant && typeof item.variant === "object" ? item.variant : null;
117
+ const source = variant || product;
118
+ const regularPrice = source?.[priceField] || 0;
119
+ const salePrice = source?.saleEnabled ? source?.[salePriceField] : null;
120
+ const price = salePrice && salePrice < regularPrice ? salePrice : regularPrice;
121
+ return {
122
+ product: product ? product.id : item.product,
123
+ variant: variant ? variant.id : item.variant,
124
+ quantity: item.quantity,
125
+ price,
126
+ name: product?.title || void 0
127
+ };
128
+ }) || [];
129
+ const orderData = {
130
+ customer: req.user?.id || void 0,
131
+ customerEmail: transaction.customerEmail,
132
+ items: orderItems,
133
+ amount: transaction.amount,
134
+ currency: transaction.currency,
135
+ status: "processing",
136
+ paymentMethod: "cod",
137
+ paymentSurcharge: transaction.paymentSurcharge || surchargeAmount,
138
+ // Copy additional data from transaction
139
+ ...transaction.discount ? { discount: transaction.discount } : {},
140
+ ...transaction.shippingMethod ? { shippingMethod: transaction.shippingMethod } : {},
141
+ ...transaction.subtotalBeforeDiscount ? { subtotalBeforeDiscount: transaction.subtotalBeforeDiscount } : {},
142
+ ...transaction.shippingAddress ? { shippingAddress: transaction.shippingAddress } : {},
143
+ ...transaction.billingAddress ? { billingAddress: transaction.billingAddress } : {},
144
+ // Pricing fields (flat from transaction)
145
+ ...transaction.subtotal != null ? { subtotal: transaction.subtotal } : {},
146
+ ...transaction.discountAmount != null ? { discountAmount: transaction.discountAmount } : {},
147
+ ...transaction.shippingCost != null ? { shippingCost: transaction.shippingCost } : {},
148
+ ...transaction.grandTotal != null ? { grandTotal: transaction.grandTotal } : {},
149
+ ...transaction.freeShipping != null ? { freeShipping: transaction.freeShipping } : {},
150
+ // Invoice
151
+ ...transaction.invoiceType ? { invoiceType: transaction.invoiceType } : {},
152
+ ...transaction.taxNumber ? { taxNumber: transaction.taxNumber } : {}
153
+ };
154
+ const order = await payload.create({
155
+ collection: ordersSlug,
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ data: orderData
158
+ });
159
+ payload.logger.info(`[COD] Order ${order.id} created from transaction ${transaction.id}`);
160
+ if (transaction.cart?.id) {
161
+ await payload.update({
162
+ id: transaction.cart.id,
163
+ collection: cartsSlug,
164
+ data: { purchasedAt: (/* @__PURE__ */ new Date()).toISOString() }
165
+ });
166
+ }
167
+ await payload.update({
168
+ id: transaction.id,
169
+ collection: transactionsSlug,
170
+ data: {
171
+ order: order.id
172
+ // Status stays 'pending' — will be updated when courier collects payment
173
+ }
174
+ });
175
+ return {
176
+ message: "COD order confirmed successfully",
177
+ orderID: order.id,
178
+ transactionID: transaction.id,
179
+ customerEmail: transaction.customerEmail
180
+ };
181
+ } catch (error) {
182
+ payload.logger.error(error, "Error confirming COD order");
183
+ throw new Error(error instanceof Error ? error.message : "Unknown error confirming COD order");
184
+ }
185
+ };
186
+ const baseFields = [
187
+ {
188
+ name: "surchargeAmount",
189
+ type: "number",
190
+ label: "COD Surcharge",
191
+ admin: { readOnly: true }
192
+ }
193
+ ];
194
+ let finalFields = baseFields;
195
+ if (groupOverrides?.fields) {
196
+ if (typeof groupOverrides.fields === "function") {
197
+ finalFields = groupOverrides.fields({ defaultFields: baseFields });
198
+ } else {
199
+ finalFields = groupOverrides.fields;
200
+ }
201
+ }
202
+ const groupField = {
203
+ name: "cod",
204
+ type: "group",
205
+ admin: {
206
+ condition: (data) => data?.paymentMethod === "cod",
207
+ ...groupOverrides?.admin || {}
208
+ },
209
+ fields: finalFields
210
+ };
211
+ return {
212
+ name: "cod",
213
+ label,
214
+ initiatePayment,
215
+ confirmOrder,
216
+ group: groupField
217
+ };
218
+ };
219
+
220
+ exports.codAdapter = codAdapter;
221
+ //# sourceMappingURL=index.cjs.map
222
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codAdapter.ts"],"names":[],"mappings":";;;AAyBO,IAAM,UAAA,GAAa,CAAC,MAAA,GAAyB,EAAC,KAAsB;AACzE,EAAA,MAAM;AAAA,IACJ,eAAA,GAAkB,GAAA;AAAA,IAClB,KAAA,GAAQ,cAAA;AAAA,IACR,QAAQ,OAAA,GAAU,IAAA;AAAA,IAClB,SAAA,EAAW,UAAA;AAAA,IACX;AAAA,GACF,GAAI,MAAA;AAKJ,EAAA,MAAM,kBAAqD,OAAO;AAAA,IAChE,IAAA;AAAA,IACA,GAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,IAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AAEtB,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAAA,IACzC;AAEA,IAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAWlB,IAAA,MAAM,aAAA,GACJ,KAAK,aAAA,KAAkB,GAAA,CAAI,MAAM,UAAA,KAAe,OAAA,GAAU,GAAA,CAAI,IAAA,CAAK,KAAA,GAAQ,MAAA,CAAA;AAE7E,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAAA,IAC/C;AAEA,IAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,CAAK,aAAa,QAAA,EAAU;AAC9C,MAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAAA,IACzD;AAGA,IAAA,MAAM,cAAA,GAAiB,IAAA;AACvB,IAAA,MAAM,WAAW,cAAA,CAAe,QAAA;AAChC,IAAA,MAAM,iBAAiB,cAAA,CAAe,cAAA;AACtC,IAAA,MAAM,kBAAkB,cAAA,CAAe,eAAA;AACvC,IAAA,MAAM,iBAAiB,cAAA,CAAe,cAAA;AACtC,IAAA,MAAM,UAAU,cAAA,CAAe,OAAA;AAE/B,IAAA,MAAM,gBAAgB,IAAA,CAAK,QAAA;AAC3B,IAAA,MAAM,aAAA,GAAgB,UAAU,gBAAA,IAAoB,CAAA;AACpD,IAAA,MAAM,gBAAgB,IAAA,CAAK,KAAA,CAAA,CAAO,cAAA,EAAgB,IAAA,IAAQ,KAAK,GAAG,CAAA;AAClE,IAAA,MAAM,iBAAiB,eAAA,GAAkB,GAAA;AAEzC,IAAA,MAAM,iBACJ,IAAA,CAAK,GAAA,CAAI,GAAG,aAAA,GAAgB,aAAa,IAAI,aAAA,GAAgB,cAAA;AAC/D,IAAA,MAAM,YAAY,cAAA,GAAiB,GAAA;AAEnC,IAAA,IAAI;AAEF,MAAA,MAAM,WAAA,GAAc,MAAM,OAAA,CAAQ,MAAA,CAAO;AAAA,QACvC,UAAA,EAAY,gBAAA;AAAA,QACZ,IAAA,EAAM;AAAA,UACJ,aAAA,EAAe,KAAA;AAAA,UACf,MAAA,EAAQ,SAAA;AAAA,UACR,MAAA,EAAQ,SAAA;AAAA,UACR,QAAA;AAAA,UACA,MAAM,OAAO,IAAA,CAAK,EAAA,KAAO,QAAA,GAAW,KAAK,EAAA,GAAK,KAAA,CAAA;AAAA,UAC9C,aAAA;AAAA,UACA,gBAAA,EAAkB,eAAA;AAAA;AAAA,UAElB,GAAI,eAAe,QAAA,GAAW,EAAE,UAAU,cAAA,CAAe,QAAA,KAAa,EAAC;AAAA,UACvE,GAAI,eAAe,cAAA,GACf,EAAE,gBAAgB,cAAA,CAAe,cAAA,KACjC,EAAC;AAAA,UACL,GAAI,eAAA,GAAkB,EAAE,eAAA,KAAoB,EAAC;AAAA,UAC7C,GAAI,cAAA,GAAiB,EAAE,cAAA,KAAmB,EAAC;AAAA;AAAA,UAE3C,GAAI,OAAA,GACA;AAAA,YACE,GAAI,QAAQ,QAAA,IAAY,IAAA,GAAO,EAAE,QAAA,EAAU,OAAA,CAAQ,QAAA,EAAS,GAAI,EAAC;AAAA,YACjE,GAAI,QAAQ,cAAA,IAAkB,IAAA,GAC1B,EAAE,cAAA,EAAgB,OAAA,CAAQ,cAAA,EAAe,GACzC,EAAC;AAAA,YACL,GAAI,QAAQ,YAAA,IAAgB,IAAA,GAAO,EAAE,YAAA,EAAc,OAAA,CAAQ,YAAA,EAAa,GAAI,EAAC;AAAA,YAC7E,GAAI,QAAQ,UAAA,IAAc,IAAA,GAAO,EAAE,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAW,GAAI,EAAC;AAAA,YACvE,GAAI,QAAQ,YAAA,IAAgB,IAAA,GAAO,EAAE,YAAA,EAAc,OAAA,CAAQ,YAAA,EAAa,GAAI;AAAC,cAE/E,EAAC;AAAA,UACL,GAAI,eAAe,WAAA,GAAc,EAAE,aAAa,cAAA,CAAe,WAAA,KAAgB,EAAC;AAAA,UAChF,GAAI,eAAe,SAAA,GAAY,EAAE,WAAW,cAAA,CAAe,SAAA,KAAc,EAAC;AAAA,UAC1E,GAAI,gBAAgB,CAAA,GAAI,EAAE,wBAAwB,aAAA,GAAgB,GAAA,KAAQ;AAAC;AAC7E,OACD,CAAA;AAED,MAAA,OAAA,CAAQ,MAAA,CAAO,IAAA;AAAA,QACb,qBAAqB,WAAA,CAAY,EAAE,CAAA,aAAA,EAAgB,SAAS,IAAI,QAAQ,CAAA;AAAA,OAC1E;AAGA,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,uBAAA;AAAA,QACT,eAAe,WAAA,CAAY;AAAA;AAAA,OAE7B;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,KAAA,EAAO,8BAA8B,CAAA;AAC1D,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,OAC3C;AAAA,IACF;AAAA,EACF,CAAA;AAKA,EAAA,MAAM,eAA+C,OAAO;AAAA,IAC1D,IAAA;AAAA,IACA,UAAA,GAAa,QAAA;AAAA,IACb,GAAA;AAAA,IACA,gBAAA,GAAmB,cAAA;AAAA,IACnB,SAAA,GAAY;AAAA,GACd,KAAM;AACJ,IAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,IAAA,MAAM,gBAAgB,IAAA,CAAK,aAAA;AAE3B,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,IACnE;AAEA,IAAA,IAAI;AAEF,MAAA,MAAM,mBAAA,GAAsB,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,QAC7C,UAAA,EAAY,gBAAA;AAAA,QACZ,OAAO,EAAE,EAAA,EAAI,EAAE,MAAA,EAAQ,eAAc,EAAE;AAAA,QACvC,KAAA,EAAO;AAAA,OACR,CAAA;AAED,MAAA,IAAI,mBAAA,CAAoB,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG;AACzC,QAAA,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAAA,MACzC;AAuBA,MAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,IAAA,CAAK,CAAC,CAAA;AAG9C,MAAA,IAAI,YAAY,KAAA,EAAO;AACrB,QAAA,MAAM,eAAA,GAAkB,OAAO,WAAA,CAAY,KAAA,KAAU,QAAA,IAAY,WAAA,CAAY,KAAA,KAAU,IAAA,GAClF,WAAA,CAAY,KAAA,CAAyB,EAAA,GACrC,WAAA,CAAY,KAAA;AACjB,QAAA,OAAO,EAAE,OAAA,EAAS,yBAAA,EAA2B,OAAA,EAAS,eAAA,EAAiB,eAAe,WAAA,CAAY,EAAA,EAAI,aAAA,EAAe,WAAA,CAAY,aAAA,EAAc;AAAA,MACjJ;AAGA,MAAA,MAAM,UAAA,GAAa,CAAA,OAAA,EAAU,WAAA,CAAY,QAAA,IAAY,KAAK,CAAA,CAAA;AAC1D,MAAA,MAAM,cAAA,GAAiB,CAAA,WAAA,EAAc,WAAA,CAAY,QAAA,IAAY,KAAK,CAAA,CAAA;AAElE,MAAA,MAAM,aACJ,WAAA,CAAY,IAAA,EAAM,KAAA,EAAO,GAAA,CAAI,CAAC,IAAA,KAAS;AACrC,QAAA,MAAM,OAAA,GACJ,OAAO,IAAA,CAAK,OAAA,KAAY,YAAY,IAAA,CAAK,OAAA,KAAY,IAAA,GAChD,IAAA,CAAK,OAAA,GACN,IAAA;AACN,QAAA,MAAM,OAAA,GACJ,KAAK,OAAA,IAAW,OAAO,KAAK,OAAA,KAAY,QAAA,GACnC,KAAK,OAAA,GACN,IAAA;AACN,QAAA,MAAM,SAAS,OAAA,IAAW,OAAA;AAC1B,QAAA,MAAM,YAAA,GAAgB,MAAA,GAAS,UAAU,CAAA,IAAgB,CAAA;AACzD,QAAA,MAAM,SAAA,GAAY,MAAA,EAAQ,WAAA,GAAe,MAAA,GAAS,cAAc,CAAA,GAAe,IAAA;AAC/E,QAAA,MAAM,KAAA,GAAQ,SAAA,IAAa,SAAA,GAAY,YAAA,GAAe,SAAA,GAAY,YAAA;AAElE,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,OAAA,GAAW,OAAA,CAAQ,EAAA,GAAgB,IAAA,CAAK,OAAA;AAAA,UACjD,OAAA,EAAS,OAAA,GAAW,OAAA,CAAQ,EAAA,GAAgB,IAAA,CAAK,OAAA;AAAA,UACjD,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,KAAA;AAAA,UACA,IAAA,EAAO,SAAS,KAAA,IAAoB,KAAA;AAAA,SACtC;AAAA,MACF,CAAC,KAAK,EAAC;AAGT,MAAA,MAAM,SAAA,GAAY;AAAA,QAChB,QAAA,EAAU,GAAA,CAAI,IAAA,EAAM,EAAA,IAAM,KAAA,CAAA;AAAA,QAC1B,eAAe,WAAA,CAAY,aAAA;AAAA,QAC3B,KAAA,EAAO,UAAA;AAAA,QACP,QAAQ,WAAA,CAAY,MAAA;AAAA,QACpB,UAAU,WAAA,CAAY,QAAA;AAAA,QACtB,MAAA,EAAQ,YAAA;AAAA,QACR,aAAA,EAAe,KAAA;AAAA,QACf,gBAAA,EAAkB,YAAY,gBAAA,IAAoB,eAAA;AAAA;AAAA,QAElD,GAAI,YAAY,QAAA,GAAW,EAAE,UAAU,WAAA,CAAY,QAAA,KAAa,EAAC;AAAA,QACjE,GAAI,YAAY,cAAA,GAAiB,EAAE,gBAAgB,WAAA,CAAY,cAAA,KAAmB,EAAC;AAAA,QACnF,GAAI,YAAY,sBAAA,GACZ,EAAE,wBAAwB,WAAA,CAAY,sBAAA,KACtC,EAAC;AAAA,QACL,GAAI,YAAY,eAAA,GAAkB,EAAE,iBAAiB,WAAA,CAAY,eAAA,KAAoB,EAAC;AAAA,QACtF,GAAI,YAAY,cAAA,GAAiB,EAAE,gBAAgB,WAAA,CAAY,cAAA,KAAmB,EAAC;AAAA;AAAA,QAEnF,GAAI,YAAY,QAAA,IAAY,IAAA,GAAO,EAAE,QAAA,EAAU,WAAA,CAAY,QAAA,EAAS,GAAI,EAAC;AAAA,QACzE,GAAI,YAAY,cAAA,IAAkB,IAAA,GAC9B,EAAE,cAAA,EAAgB,WAAA,CAAY,cAAA,EAAe,GAC7C,EAAC;AAAA,QACL,GAAI,YAAY,YAAA,IAAgB,IAAA,GAAO,EAAE,YAAA,EAAc,WAAA,CAAY,YAAA,EAAa,GAAI,EAAC;AAAA,QACrF,GAAI,YAAY,UAAA,IAAc,IAAA,GAAO,EAAE,UAAA,EAAY,WAAA,CAAY,UAAA,EAAW,GAAI,EAAC;AAAA,QAC/E,GAAI,YAAY,YAAA,IAAgB,IAAA,GAAO,EAAE,YAAA,EAAc,WAAA,CAAY,YAAA,EAAa,GAAI,EAAC;AAAA;AAAA,QAErF,GAAI,YAAY,WAAA,GAAc,EAAE,aAAa,WAAA,CAAY,WAAA,KAAgB,EAAC;AAAA,QAC1E,GAAI,YAAY,SAAA,GAAY,EAAE,WAAW,WAAA,CAAY,SAAA,KAAc;AAAC,OACtE;AAEA,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,MAAA,CAAO;AAAA,QACjC,UAAA,EAAY,UAAA;AAAA;AAAA,QAEZ,IAAA,EAAM;AAAA,OACP,CAAA;AAED,MAAA,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA,YAAA,EAAe,KAAA,CAAM,EAAE,CAAA,0BAAA,EAA6B,WAAA,CAAY,EAAE,CAAA,CAAE,CAAA;AAGxF,MAAA,IAAI,WAAA,CAAY,MAAM,EAAA,EAAI;AACxB,QAAA,MAAM,QAAQ,MAAA,CAAO;AAAA,UACnB,EAAA,EAAI,YAAY,IAAA,CAAK,EAAA;AAAA,UACrB,UAAA,EAAY,SAAA;AAAA,UACZ,MAAM,EAAE,WAAA,EAAA,qBAAiB,IAAA,EAAK,EAAE,aAAY;AAAE,SAC/C,CAAA;AAAA,MACH;AAGA,MAAA,MAAM,QAAQ,MAAA,CAAO;AAAA,QACnB,IAAI,WAAA,CAAY,EAAA;AAAA,QAChB,UAAA,EAAY,gBAAA;AAAA,QACZ,IAAA,EAAM;AAAA,UACJ,OAAO,KAAA,CAAM;AAAA;AAAA;AAEf,OACD,CAAA;AAED,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,kCAAA;AAAA,QACT,SAAS,KAAA,CAAM,EAAA;AAAA,QACf,eAAe,WAAA,CAAY,EAAA;AAAA,QAC3B,eAAe,WAAA,CAAY;AAAA,OAC7B;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,KAAA,EAAO,4BAA4B,CAAA;AACxD,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,UAAU,oCAAoC,CAAA;AAAA,IAC/F;AAAA,EACF,CAAA;AAGA,EAAA,MAAM,UAAA,GAAsB;AAAA,IAC1B;AAAA,MACE,IAAA,EAAM,iBAAA;AAAA,MACN,IAAA,EAAM,QAAA;AAAA,MACN,KAAA,EAAO,eAAA;AAAA,MACP,KAAA,EAAO,EAAE,QAAA,EAAU,IAAA;AAAK;AAC1B,GACF;AAEA,EAAA,IAAI,WAAA,GAAuB,UAAA;AAE3B,EAAA,IAAI,gBAAgB,MAAA,EAAQ;AAC1B,IAAA,IAAI,OAAO,cAAA,CAAe,MAAA,KAAW,UAAA,EAAY;AAC/C,MAAA,WAAA,GAAc,cAAA,CAAe,MAAA,CAAO,EAAE,aAAA,EAAe,YAAY,CAAA;AAAA,IACnE,CAAA,MAAO;AACL,MAAA,WAAA,GAAc,cAAA,CAAe,MAAA;AAAA,IAC/B;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAyB;AAAA,IAC7B,IAAA,EAAM,KAAA;AAAA,IACN,IAAA,EAAM,OAAA;AAAA,IACN,KAAA,EAAO;AAAA,MACL,SAAA,EAAW,CAAC,IAAA,KAAS,IAAA,EAAM,aAAA,KAAkB,KAAA;AAAA,MAC7C,GAAI,cAAA,EAAgB,KAAA,IAAS;AAAC,KAChC;AAAA,IACA,MAAA,EAAQ;AAAA,GACV;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAA;AAAA,IACN,KAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,KAAA,EAAO;AAAA,GACT;AACF","file":"index.cjs","sourcesContent":["import type { PaymentAdapter } from '@payloadcms/plugin-ecommerce/types'\nimport type { Field, GroupField } from 'payload'\n\nexport interface CodAdapterArgs {\n /** COD surcharge amount in full currency units (e.g. 200 for 200 RSD) */\n surchargeAmount?: number\n /** Label shown in admin UI */\n label?: string\n /** Locale for redirect URLs */\n locale?: string\n /** Server URL override */\n serverUrl?: string\n /** Override group fields */\n groupOverrides?: {\n fields?: ((args: { defaultFields: Field[] }) => Field[]) | Field[]\n admin?: Record<string, unknown>\n }\n}\n\n/**\n * Cash on Delivery (Pouzeće) payment adapter for PayloadCMS ecommerce plugin\n *\n * Creates orders directly without redirecting to an external payment gateway.\n * Optionally adds a surcharge to the order total.\n */\nexport const codAdapter = (config: CodAdapterArgs = {}): PaymentAdapter => {\n const {\n surchargeAmount = 200,\n label = 'Pouzeće',\n locale: _locale = 'rs',\n serverUrl: _serverUrl,\n groupOverrides,\n } = config\n\n /**\n * Initiate a COD payment — creates a transaction record but does NOT redirect\n */\n const initiatePayment: PaymentAdapter['initiatePayment'] = async ({\n data,\n req,\n transactionsSlug,\n }) => {\n const payload = req.payload\n const currency = data.currency\n\n if (!currency) {\n throw new Error('Currency is required.')\n }\n\n const cart = data.cart as\n | {\n id: string | number\n subtotal: number\n items: Array<{\n product: { title?: string; name?: string } | string | number\n quantity: number\n }>\n }\n | undefined\n\n const customerEmail =\n data.customerEmail || (req.user?.collection === 'users' ? req.user.email : undefined)\n\n if (!customerEmail) {\n throw new Error('Customer email is required.')\n }\n\n if (!cart || typeof cart.subtotal !== 'number') {\n throw new Error('Valid cart with subtotal is required.')\n }\n\n // Calculate total (same pattern as CorvusPay adapter)\n const additionalData = data as Record<string, unknown>\n const discount = additionalData.discount as { calculatedAmount?: number } | undefined\n const shippingMethod = additionalData.shippingMethod as { cost?: number } | undefined\n const shippingAddress = additionalData.shippingAddress as Record<string, unknown> | undefined\n const billingAddress = additionalData.billingAddress as Record<string, unknown> | undefined\n const pricing = additionalData.pricing as Record<string, unknown> | undefined\n\n const subtotalCents = cart.subtotal\n const discountCents = discount?.calculatedAmount || 0\n const shippingCents = Math.round((shippingMethod?.cost || 0) * 100)\n const surchargeCents = surchargeAmount * 100\n\n const cartTotalCents =\n Math.max(0, subtotalCents - discountCents) + shippingCents + surchargeCents\n const cartTotal = cartTotalCents / 100\n\n try {\n // Create transaction record\n const transaction = await payload.create({\n collection: transactionsSlug as 'transactions',\n data: {\n paymentMethod: 'cod' as const,\n status: 'pending' as const,\n amount: cartTotal,\n currency,\n cart: typeof cart.id === 'number' ? cart.id : undefined,\n customerEmail,\n paymentSurcharge: surchargeAmount,\n // Store additional data for order creation\n ...(additionalData.discount ? { discount: additionalData.discount } : {}),\n ...(additionalData.shippingMethod\n ? { shippingMethod: additionalData.shippingMethod }\n : {}),\n ...(shippingAddress ? { shippingAddress } : {}),\n ...(billingAddress ? { billingAddress } : {}),\n // Pricing breakdown (flat fields matching Transaction schema)\n ...(pricing\n ? {\n ...(pricing.subtotal != null ? { subtotal: pricing.subtotal } : {}),\n ...(pricing.discountAmount != null\n ? { discountAmount: pricing.discountAmount }\n : {}),\n ...(pricing.shippingCost != null ? { shippingCost: pricing.shippingCost } : {}),\n ...(pricing.grandTotal != null ? { grandTotal: pricing.grandTotal } : {}),\n ...(pricing.freeShipping != null ? { freeShipping: pricing.freeShipping } : {}),\n }\n : {}),\n ...(additionalData.invoiceType ? { invoiceType: additionalData.invoiceType } : {}),\n ...(additionalData.taxNumber ? { taxNumber: additionalData.taxNumber } : {}),\n ...(discountCents > 0 ? { subtotalBeforeDiscount: subtotalCents / 100 } : {}),\n },\n })\n\n payload.logger.info(\n `[COD] Transaction ${transaction.id} created for ${cartTotal} ${currency}`,\n )\n\n // No redirect — COD flow continues on the client\n return {\n message: 'COD payment initiated',\n transactionID: transaction.id,\n // No `redirect` field — this signals the client to handle order confirmation inline\n }\n } catch (error) {\n payload.logger.error(error, 'Error initiating COD payment')\n throw new Error(\n error instanceof Error ? error.message : 'Unknown error initiating COD payment',\n )\n }\n }\n\n /**\n * Confirm a COD order — creates the order from transaction data\n */\n const confirmOrder: PaymentAdapter['confirmOrder'] = async ({\n data,\n ordersSlug = 'orders',\n req,\n transactionsSlug = 'transactions',\n cartsSlug = 'carts',\n }) => {\n const payload = req.payload\n const transactionId = data.transactionId as string | number\n\n if (!transactionId) {\n throw new Error('Transaction ID is required for COD confirmation')\n }\n\n try {\n // Find the transaction with populated cart\n const transactionsResults = await payload.find({\n collection: transactionsSlug as 'transactions',\n where: { id: { equals: transactionId } },\n depth: 2,\n })\n\n if (transactionsResults.docs.length === 0) {\n throw new Error('Transaction not found')\n }\n\n interface CodTransaction {\n id: string | number\n amount: number\n currency?: string\n customerEmail?: string\n paymentSurcharge?: number\n order?: { id: number } | number | null\n cart?: {\n id: string | number\n items: Array<{ product: unknown; variant?: unknown; quantity: number }>\n }\n subtotal?: number\n discountAmount?: number\n shippingCost?: number\n grandTotal?: number\n freeShipping?: boolean\n billingAddress?: Record<string, unknown>\n invoiceType?: string\n taxNumber?: string\n [key: string]: unknown\n }\n const transaction = transactionsResults.docs[0] as unknown as CodTransaction\n\n // Idempotency guard — if order already created, return it\n if (transaction.order) {\n const existingOrderId = typeof transaction.order === 'object' && transaction.order !== null\n ? (transaction.order as { id: number }).id\n : (transaction.order as number)\n return { message: 'Order already confirmed', orderID: existingOrderId, transactionID: transaction.id, customerEmail: transaction.customerEmail }\n }\n\n // Extract order items from cart with currency-specific prices\n const priceField = `priceIn${transaction.currency || 'EUR'}`\n const salePriceField = `salePriceIn${transaction.currency || 'EUR'}`\n\n const orderItems =\n transaction.cart?.items?.map((item) => {\n const product =\n typeof item.product === 'object' && item.product !== null\n ? (item.product as Record<string, unknown>)\n : null\n const variant =\n item.variant && typeof item.variant === 'object'\n ? (item.variant as Record<string, unknown>)\n : null\n const source = variant || product\n const regularPrice = (source?.[priceField] as number) || 0\n const salePrice = source?.saleEnabled ? (source?.[salePriceField] as number) : null\n const price = salePrice && salePrice < regularPrice ? salePrice : regularPrice\n\n return {\n product: product ? (product.id as number) : item.product,\n variant: variant ? (variant.id as number) : item.variant,\n quantity: item.quantity,\n price,\n name: (product?.title as string) || undefined,\n }\n }) || []\n\n // Create order\n const orderData = {\n customer: req.user?.id || undefined,\n customerEmail: transaction.customerEmail,\n items: orderItems,\n amount: transaction.amount,\n currency: transaction.currency,\n status: 'processing',\n paymentMethod: 'cod',\n paymentSurcharge: transaction.paymentSurcharge || surchargeAmount,\n // Copy additional data from transaction\n ...(transaction.discount ? { discount: transaction.discount } : {}),\n ...(transaction.shippingMethod ? { shippingMethod: transaction.shippingMethod } : {}),\n ...(transaction.subtotalBeforeDiscount\n ? { subtotalBeforeDiscount: transaction.subtotalBeforeDiscount }\n : {}),\n ...(transaction.shippingAddress ? { shippingAddress: transaction.shippingAddress } : {}),\n ...(transaction.billingAddress ? { billingAddress: transaction.billingAddress } : {}),\n // Pricing fields (flat from transaction)\n ...(transaction.subtotal != null ? { subtotal: transaction.subtotal } : {}),\n ...(transaction.discountAmount != null\n ? { discountAmount: transaction.discountAmount }\n : {}),\n ...(transaction.shippingCost != null ? { shippingCost: transaction.shippingCost } : {}),\n ...(transaction.grandTotal != null ? { grandTotal: transaction.grandTotal } : {}),\n ...(transaction.freeShipping != null ? { freeShipping: transaction.freeShipping } : {}),\n // Invoice\n ...(transaction.invoiceType ? { invoiceType: transaction.invoiceType } : {}),\n ...(transaction.taxNumber ? { taxNumber: transaction.taxNumber } : {}),\n }\n\n const order = await payload.create({\n collection: ordersSlug as 'orders',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: orderData as any,\n })\n\n payload.logger.info(`[COD] Order ${order.id} created from transaction ${transaction.id}`)\n\n // Mark cart as purchased\n if (transaction.cart?.id) {\n await payload.update({\n id: transaction.cart.id,\n collection: cartsSlug as 'carts',\n data: { purchasedAt: new Date().toISOString() },\n })\n }\n\n // Update transaction — link to order, keep status 'pending' (payment not yet collected)\n await payload.update({\n id: transaction.id,\n collection: transactionsSlug as 'transactions',\n data: {\n order: order.id,\n // Status stays 'pending' — will be updated when courier collects payment\n },\n })\n\n return {\n message: 'COD order confirmed successfully',\n orderID: order.id as number,\n transactionID: transaction.id as number,\n customerEmail: transaction.customerEmail,\n }\n } catch (error) {\n payload.logger.error(error, 'Error confirming COD order')\n throw new Error(error instanceof Error ? error.message : 'Unknown error confirming COD order')\n }\n }\n\n // Define COD group field for transaction data\n const baseFields: Field[] = [\n {\n name: 'surchargeAmount',\n type: 'number',\n label: 'COD Surcharge',\n admin: { readOnly: true },\n },\n ]\n\n let finalFields: Field[] = baseFields\n\n if (groupOverrides?.fields) {\n if (typeof groupOverrides.fields === 'function') {\n finalFields = groupOverrides.fields({ defaultFields: baseFields })\n } else {\n finalFields = groupOverrides.fields\n }\n }\n\n const groupField: GroupField = {\n name: 'cod',\n type: 'group',\n admin: {\n condition: (data) => data?.paymentMethod === 'cod',\n ...(groupOverrides?.admin || {}),\n },\n fields: finalFields,\n }\n\n return {\n name: 'cod',\n label,\n initiatePayment,\n confirmOrder,\n group: groupField,\n }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { PaymentAdapter } from '@payloadcms/plugin-ecommerce/types';
2
+ import { Field } from 'payload';
3
+
4
+ interface CodAdapterArgs {
5
+ /** COD surcharge amount in full currency units (e.g. 200 for 200 RSD) */
6
+ surchargeAmount?: number;
7
+ /** Label shown in admin UI */
8
+ label?: string;
9
+ /** Locale for redirect URLs */
10
+ locale?: string;
11
+ /** Server URL override */
12
+ serverUrl?: string;
13
+ /** Override group fields */
14
+ groupOverrides?: {
15
+ fields?: ((args: {
16
+ defaultFields: Field[];
17
+ }) => Field[]) | Field[];
18
+ admin?: Record<string, unknown>;
19
+ };
20
+ }
21
+ /**
22
+ * Cash on Delivery (Pouzeće) payment adapter for PayloadCMS ecommerce plugin
23
+ *
24
+ * Creates orders directly without redirecting to an external payment gateway.
25
+ * Optionally adds a surcharge to the order total.
26
+ */
27
+ declare const codAdapter: (config?: CodAdapterArgs) => PaymentAdapter;
28
+
29
+ export { type CodAdapterArgs, codAdapter };
@@ -0,0 +1,29 @@
1
+ import { PaymentAdapter } from '@payloadcms/plugin-ecommerce/types';
2
+ import { Field } from 'payload';
3
+
4
+ interface CodAdapterArgs {
5
+ /** COD surcharge amount in full currency units (e.g. 200 for 200 RSD) */
6
+ surchargeAmount?: number;
7
+ /** Label shown in admin UI */
8
+ label?: string;
9
+ /** Locale for redirect URLs */
10
+ locale?: string;
11
+ /** Server URL override */
12
+ serverUrl?: string;
13
+ /** Override group fields */
14
+ groupOverrides?: {
15
+ fields?: ((args: {
16
+ defaultFields: Field[];
17
+ }) => Field[]) | Field[];
18
+ admin?: Record<string, unknown>;
19
+ };
20
+ }
21
+ /**
22
+ * Cash on Delivery (Pouzeće) payment adapter for PayloadCMS ecommerce plugin
23
+ *
24
+ * Creates orders directly without redirecting to an external payment gateway.
25
+ * Optionally adds a surcharge to the order total.
26
+ */
27
+ declare const codAdapter: (config?: CodAdapterArgs) => PaymentAdapter;
28
+
29
+ export { type CodAdapterArgs, codAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ // src/codAdapter.ts
2
+ var codAdapter = (config = {}) => {
3
+ const {
4
+ surchargeAmount = 200,
5
+ label = "Pouze\u0107e",
6
+ locale: _locale = "rs",
7
+ serverUrl: _serverUrl,
8
+ groupOverrides
9
+ } = config;
10
+ const initiatePayment = async ({
11
+ data,
12
+ req,
13
+ transactionsSlug
14
+ }) => {
15
+ const payload = req.payload;
16
+ const currency = data.currency;
17
+ if (!currency) {
18
+ throw new Error("Currency is required.");
19
+ }
20
+ const cart = data.cart;
21
+ const customerEmail = data.customerEmail || (req.user?.collection === "users" ? req.user.email : void 0);
22
+ if (!customerEmail) {
23
+ throw new Error("Customer email is required.");
24
+ }
25
+ if (!cart || typeof cart.subtotal !== "number") {
26
+ throw new Error("Valid cart with subtotal is required.");
27
+ }
28
+ const additionalData = data;
29
+ const discount = additionalData.discount;
30
+ const shippingMethod = additionalData.shippingMethod;
31
+ const shippingAddress = additionalData.shippingAddress;
32
+ const billingAddress = additionalData.billingAddress;
33
+ const pricing = additionalData.pricing;
34
+ const subtotalCents = cart.subtotal;
35
+ const discountCents = discount?.calculatedAmount || 0;
36
+ const shippingCents = Math.round((shippingMethod?.cost || 0) * 100);
37
+ const surchargeCents = surchargeAmount * 100;
38
+ const cartTotalCents = Math.max(0, subtotalCents - discountCents) + shippingCents + surchargeCents;
39
+ const cartTotal = cartTotalCents / 100;
40
+ try {
41
+ const transaction = await payload.create({
42
+ collection: transactionsSlug,
43
+ data: {
44
+ paymentMethod: "cod",
45
+ status: "pending",
46
+ amount: cartTotal,
47
+ currency,
48
+ cart: typeof cart.id === "number" ? cart.id : void 0,
49
+ customerEmail,
50
+ paymentSurcharge: surchargeAmount,
51
+ // Store additional data for order creation
52
+ ...additionalData.discount ? { discount: additionalData.discount } : {},
53
+ ...additionalData.shippingMethod ? { shippingMethod: additionalData.shippingMethod } : {},
54
+ ...shippingAddress ? { shippingAddress } : {},
55
+ ...billingAddress ? { billingAddress } : {},
56
+ // Pricing breakdown (flat fields matching Transaction schema)
57
+ ...pricing ? {
58
+ ...pricing.subtotal != null ? { subtotal: pricing.subtotal } : {},
59
+ ...pricing.discountAmount != null ? { discountAmount: pricing.discountAmount } : {},
60
+ ...pricing.shippingCost != null ? { shippingCost: pricing.shippingCost } : {},
61
+ ...pricing.grandTotal != null ? { grandTotal: pricing.grandTotal } : {},
62
+ ...pricing.freeShipping != null ? { freeShipping: pricing.freeShipping } : {}
63
+ } : {},
64
+ ...additionalData.invoiceType ? { invoiceType: additionalData.invoiceType } : {},
65
+ ...additionalData.taxNumber ? { taxNumber: additionalData.taxNumber } : {},
66
+ ...discountCents > 0 ? { subtotalBeforeDiscount: subtotalCents / 100 } : {}
67
+ }
68
+ });
69
+ payload.logger.info(
70
+ `[COD] Transaction ${transaction.id} created for ${cartTotal} ${currency}`
71
+ );
72
+ return {
73
+ message: "COD payment initiated",
74
+ transactionID: transaction.id
75
+ // No `redirect` field — this signals the client to handle order confirmation inline
76
+ };
77
+ } catch (error) {
78
+ payload.logger.error(error, "Error initiating COD payment");
79
+ throw new Error(
80
+ error instanceof Error ? error.message : "Unknown error initiating COD payment"
81
+ );
82
+ }
83
+ };
84
+ const confirmOrder = async ({
85
+ data,
86
+ ordersSlug = "orders",
87
+ req,
88
+ transactionsSlug = "transactions",
89
+ cartsSlug = "carts"
90
+ }) => {
91
+ const payload = req.payload;
92
+ const transactionId = data.transactionId;
93
+ if (!transactionId) {
94
+ throw new Error("Transaction ID is required for COD confirmation");
95
+ }
96
+ try {
97
+ const transactionsResults = await payload.find({
98
+ collection: transactionsSlug,
99
+ where: { id: { equals: transactionId } },
100
+ depth: 2
101
+ });
102
+ if (transactionsResults.docs.length === 0) {
103
+ throw new Error("Transaction not found");
104
+ }
105
+ const transaction = transactionsResults.docs[0];
106
+ if (transaction.order) {
107
+ const existingOrderId = typeof transaction.order === "object" && transaction.order !== null ? transaction.order.id : transaction.order;
108
+ return { message: "Order already confirmed", orderID: existingOrderId, transactionID: transaction.id, customerEmail: transaction.customerEmail };
109
+ }
110
+ const priceField = `priceIn${transaction.currency || "EUR"}`;
111
+ const salePriceField = `salePriceIn${transaction.currency || "EUR"}`;
112
+ const orderItems = transaction.cart?.items?.map((item) => {
113
+ const product = typeof item.product === "object" && item.product !== null ? item.product : null;
114
+ const variant = item.variant && typeof item.variant === "object" ? item.variant : null;
115
+ const source = variant || product;
116
+ const regularPrice = source?.[priceField] || 0;
117
+ const salePrice = source?.saleEnabled ? source?.[salePriceField] : null;
118
+ const price = salePrice && salePrice < regularPrice ? salePrice : regularPrice;
119
+ return {
120
+ product: product ? product.id : item.product,
121
+ variant: variant ? variant.id : item.variant,
122
+ quantity: item.quantity,
123
+ price,
124
+ name: product?.title || void 0
125
+ };
126
+ }) || [];
127
+ const orderData = {
128
+ customer: req.user?.id || void 0,
129
+ customerEmail: transaction.customerEmail,
130
+ items: orderItems,
131
+ amount: transaction.amount,
132
+ currency: transaction.currency,
133
+ status: "processing",
134
+ paymentMethod: "cod",
135
+ paymentSurcharge: transaction.paymentSurcharge || surchargeAmount,
136
+ // Copy additional data from transaction
137
+ ...transaction.discount ? { discount: transaction.discount } : {},
138
+ ...transaction.shippingMethod ? { shippingMethod: transaction.shippingMethod } : {},
139
+ ...transaction.subtotalBeforeDiscount ? { subtotalBeforeDiscount: transaction.subtotalBeforeDiscount } : {},
140
+ ...transaction.shippingAddress ? { shippingAddress: transaction.shippingAddress } : {},
141
+ ...transaction.billingAddress ? { billingAddress: transaction.billingAddress } : {},
142
+ // Pricing fields (flat from transaction)
143
+ ...transaction.subtotal != null ? { subtotal: transaction.subtotal } : {},
144
+ ...transaction.discountAmount != null ? { discountAmount: transaction.discountAmount } : {},
145
+ ...transaction.shippingCost != null ? { shippingCost: transaction.shippingCost } : {},
146
+ ...transaction.grandTotal != null ? { grandTotal: transaction.grandTotal } : {},
147
+ ...transaction.freeShipping != null ? { freeShipping: transaction.freeShipping } : {},
148
+ // Invoice
149
+ ...transaction.invoiceType ? { invoiceType: transaction.invoiceType } : {},
150
+ ...transaction.taxNumber ? { taxNumber: transaction.taxNumber } : {}
151
+ };
152
+ const order = await payload.create({
153
+ collection: ordersSlug,
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ data: orderData
156
+ });
157
+ payload.logger.info(`[COD] Order ${order.id} created from transaction ${transaction.id}`);
158
+ if (transaction.cart?.id) {
159
+ await payload.update({
160
+ id: transaction.cart.id,
161
+ collection: cartsSlug,
162
+ data: { purchasedAt: (/* @__PURE__ */ new Date()).toISOString() }
163
+ });
164
+ }
165
+ await payload.update({
166
+ id: transaction.id,
167
+ collection: transactionsSlug,
168
+ data: {
169
+ order: order.id
170
+ // Status stays 'pending' — will be updated when courier collects payment
171
+ }
172
+ });
173
+ return {
174
+ message: "COD order confirmed successfully",
175
+ orderID: order.id,
176
+ transactionID: transaction.id,
177
+ customerEmail: transaction.customerEmail
178
+ };
179
+ } catch (error) {
180
+ payload.logger.error(error, "Error confirming COD order");
181
+ throw new Error(error instanceof Error ? error.message : "Unknown error confirming COD order");
182
+ }
183
+ };
184
+ const baseFields = [
185
+ {
186
+ name: "surchargeAmount",
187
+ type: "number",
188
+ label: "COD Surcharge",
189
+ admin: { readOnly: true }
190
+ }
191
+ ];
192
+ let finalFields = baseFields;
193
+ if (groupOverrides?.fields) {
194
+ if (typeof groupOverrides.fields === "function") {
195
+ finalFields = groupOverrides.fields({ defaultFields: baseFields });
196
+ } else {
197
+ finalFields = groupOverrides.fields;
198
+ }
199
+ }
200
+ const groupField = {
201
+ name: "cod",
202
+ type: "group",
203
+ admin: {
204
+ condition: (data) => data?.paymentMethod === "cod",
205
+ ...groupOverrides?.admin || {}
206
+ },
207
+ fields: finalFields
208
+ };
209
+ return {
210
+ name: "cod",
211
+ label,
212
+ initiatePayment,
213
+ confirmOrder,
214
+ group: groupField
215
+ };
216
+ };
217
+
218
+ export { codAdapter };
219
+ //# sourceMappingURL=index.js.map
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codAdapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PaymentAdapter } from '@payloadcms/plugin-ecommerce/types'\nimport type { Field, GroupField } from 'payload'\n\nexport interface CodAdapterArgs {\n /** COD surcharge amount in full currency units (e.g. 200 for 200 RSD) */\n surchargeAmount?: number\n /** Label shown in admin UI */\n label?: string\n /** Locale for redirect URLs */\n locale?: string\n /** Server URL override */\n serverUrl?: string\n /** Override group fields */\n groupOverrides?: {\n fields?: ((args: { defaultFields: Field[] }) => Field[]) | Field[]\n admin?: Record<string, unknown>\n }\n}\n\n/**\n * Cash on Delivery (Pouzeće) payment adapter for PayloadCMS ecommerce plugin\n *\n * Creates orders directly without redirecting to an external payment gateway.\n * Optionally adds a surcharge to the order total.\n */\nexport const codAdapter = (config: CodAdapterArgs = {}): PaymentAdapter => {\n const {\n surchargeAmount = 200,\n label = 'Pouzeće',\n locale: _locale = 'rs',\n serverUrl: _serverUrl,\n groupOverrides,\n } = config\n\n /**\n * Initiate a COD payment — creates a transaction record but does NOT redirect\n */\n const initiatePayment: PaymentAdapter['initiatePayment'] = async ({\n data,\n req,\n transactionsSlug,\n }) => {\n const payload = req.payload\n const currency = data.currency\n\n if (!currency) {\n throw new Error('Currency is required.')\n }\n\n const cart = data.cart as\n | {\n id: string | number\n subtotal: number\n items: Array<{\n product: { title?: string; name?: string } | string | number\n quantity: number\n }>\n }\n | undefined\n\n const customerEmail =\n data.customerEmail || (req.user?.collection === 'users' ? req.user.email : undefined)\n\n if (!customerEmail) {\n throw new Error('Customer email is required.')\n }\n\n if (!cart || typeof cart.subtotal !== 'number') {\n throw new Error('Valid cart with subtotal is required.')\n }\n\n // Calculate total (same pattern as CorvusPay adapter)\n const additionalData = data as Record<string, unknown>\n const discount = additionalData.discount as { calculatedAmount?: number } | undefined\n const shippingMethod = additionalData.shippingMethod as { cost?: number } | undefined\n const shippingAddress = additionalData.shippingAddress as Record<string, unknown> | undefined\n const billingAddress = additionalData.billingAddress as Record<string, unknown> | undefined\n const pricing = additionalData.pricing as Record<string, unknown> | undefined\n\n const subtotalCents = cart.subtotal\n const discountCents = discount?.calculatedAmount || 0\n const shippingCents = Math.round((shippingMethod?.cost || 0) * 100)\n const surchargeCents = surchargeAmount * 100\n\n const cartTotalCents =\n Math.max(0, subtotalCents - discountCents) + shippingCents + surchargeCents\n const cartTotal = cartTotalCents / 100\n\n try {\n // Create transaction record\n const transaction = await payload.create({\n collection: transactionsSlug as 'transactions',\n data: {\n paymentMethod: 'cod' as const,\n status: 'pending' as const,\n amount: cartTotal,\n currency,\n cart: typeof cart.id === 'number' ? cart.id : undefined,\n customerEmail,\n paymentSurcharge: surchargeAmount,\n // Store additional data for order creation\n ...(additionalData.discount ? { discount: additionalData.discount } : {}),\n ...(additionalData.shippingMethod\n ? { shippingMethod: additionalData.shippingMethod }\n : {}),\n ...(shippingAddress ? { shippingAddress } : {}),\n ...(billingAddress ? { billingAddress } : {}),\n // Pricing breakdown (flat fields matching Transaction schema)\n ...(pricing\n ? {\n ...(pricing.subtotal != null ? { subtotal: pricing.subtotal } : {}),\n ...(pricing.discountAmount != null\n ? { discountAmount: pricing.discountAmount }\n : {}),\n ...(pricing.shippingCost != null ? { shippingCost: pricing.shippingCost } : {}),\n ...(pricing.grandTotal != null ? { grandTotal: pricing.grandTotal } : {}),\n ...(pricing.freeShipping != null ? { freeShipping: pricing.freeShipping } : {}),\n }\n : {}),\n ...(additionalData.invoiceType ? { invoiceType: additionalData.invoiceType } : {}),\n ...(additionalData.taxNumber ? { taxNumber: additionalData.taxNumber } : {}),\n ...(discountCents > 0 ? { subtotalBeforeDiscount: subtotalCents / 100 } : {}),\n },\n })\n\n payload.logger.info(\n `[COD] Transaction ${transaction.id} created for ${cartTotal} ${currency}`,\n )\n\n // No redirect — COD flow continues on the client\n return {\n message: 'COD payment initiated',\n transactionID: transaction.id,\n // No `redirect` field — this signals the client to handle order confirmation inline\n }\n } catch (error) {\n payload.logger.error(error, 'Error initiating COD payment')\n throw new Error(\n error instanceof Error ? error.message : 'Unknown error initiating COD payment',\n )\n }\n }\n\n /**\n * Confirm a COD order — creates the order from transaction data\n */\n const confirmOrder: PaymentAdapter['confirmOrder'] = async ({\n data,\n ordersSlug = 'orders',\n req,\n transactionsSlug = 'transactions',\n cartsSlug = 'carts',\n }) => {\n const payload = req.payload\n const transactionId = data.transactionId as string | number\n\n if (!transactionId) {\n throw new Error('Transaction ID is required for COD confirmation')\n }\n\n try {\n // Find the transaction with populated cart\n const transactionsResults = await payload.find({\n collection: transactionsSlug as 'transactions',\n where: { id: { equals: transactionId } },\n depth: 2,\n })\n\n if (transactionsResults.docs.length === 0) {\n throw new Error('Transaction not found')\n }\n\n interface CodTransaction {\n id: string | number\n amount: number\n currency?: string\n customerEmail?: string\n paymentSurcharge?: number\n order?: { id: number } | number | null\n cart?: {\n id: string | number\n items: Array<{ product: unknown; variant?: unknown; quantity: number }>\n }\n subtotal?: number\n discountAmount?: number\n shippingCost?: number\n grandTotal?: number\n freeShipping?: boolean\n billingAddress?: Record<string, unknown>\n invoiceType?: string\n taxNumber?: string\n [key: string]: unknown\n }\n const transaction = transactionsResults.docs[0] as unknown as CodTransaction\n\n // Idempotency guard — if order already created, return it\n if (transaction.order) {\n const existingOrderId = typeof transaction.order === 'object' && transaction.order !== null\n ? (transaction.order as { id: number }).id\n : (transaction.order as number)\n return { message: 'Order already confirmed', orderID: existingOrderId, transactionID: transaction.id, customerEmail: transaction.customerEmail }\n }\n\n // Extract order items from cart with currency-specific prices\n const priceField = `priceIn${transaction.currency || 'EUR'}`\n const salePriceField = `salePriceIn${transaction.currency || 'EUR'}`\n\n const orderItems =\n transaction.cart?.items?.map((item) => {\n const product =\n typeof item.product === 'object' && item.product !== null\n ? (item.product as Record<string, unknown>)\n : null\n const variant =\n item.variant && typeof item.variant === 'object'\n ? (item.variant as Record<string, unknown>)\n : null\n const source = variant || product\n const regularPrice = (source?.[priceField] as number) || 0\n const salePrice = source?.saleEnabled ? (source?.[salePriceField] as number) : null\n const price = salePrice && salePrice < regularPrice ? salePrice : regularPrice\n\n return {\n product: product ? (product.id as number) : item.product,\n variant: variant ? (variant.id as number) : item.variant,\n quantity: item.quantity,\n price,\n name: (product?.title as string) || undefined,\n }\n }) || []\n\n // Create order\n const orderData = {\n customer: req.user?.id || undefined,\n customerEmail: transaction.customerEmail,\n items: orderItems,\n amount: transaction.amount,\n currency: transaction.currency,\n status: 'processing',\n paymentMethod: 'cod',\n paymentSurcharge: transaction.paymentSurcharge || surchargeAmount,\n // Copy additional data from transaction\n ...(transaction.discount ? { discount: transaction.discount } : {}),\n ...(transaction.shippingMethod ? { shippingMethod: transaction.shippingMethod } : {}),\n ...(transaction.subtotalBeforeDiscount\n ? { subtotalBeforeDiscount: transaction.subtotalBeforeDiscount }\n : {}),\n ...(transaction.shippingAddress ? { shippingAddress: transaction.shippingAddress } : {}),\n ...(transaction.billingAddress ? { billingAddress: transaction.billingAddress } : {}),\n // Pricing fields (flat from transaction)\n ...(transaction.subtotal != null ? { subtotal: transaction.subtotal } : {}),\n ...(transaction.discountAmount != null\n ? { discountAmount: transaction.discountAmount }\n : {}),\n ...(transaction.shippingCost != null ? { shippingCost: transaction.shippingCost } : {}),\n ...(transaction.grandTotal != null ? { grandTotal: transaction.grandTotal } : {}),\n ...(transaction.freeShipping != null ? { freeShipping: transaction.freeShipping } : {}),\n // Invoice\n ...(transaction.invoiceType ? { invoiceType: transaction.invoiceType } : {}),\n ...(transaction.taxNumber ? { taxNumber: transaction.taxNumber } : {}),\n }\n\n const order = await payload.create({\n collection: ordersSlug as 'orders',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: orderData as any,\n })\n\n payload.logger.info(`[COD] Order ${order.id} created from transaction ${transaction.id}`)\n\n // Mark cart as purchased\n if (transaction.cart?.id) {\n await payload.update({\n id: transaction.cart.id,\n collection: cartsSlug as 'carts',\n data: { purchasedAt: new Date().toISOString() },\n })\n }\n\n // Update transaction — link to order, keep status 'pending' (payment not yet collected)\n await payload.update({\n id: transaction.id,\n collection: transactionsSlug as 'transactions',\n data: {\n order: order.id,\n // Status stays 'pending' — will be updated when courier collects payment\n },\n })\n\n return {\n message: 'COD order confirmed successfully',\n orderID: order.id as number,\n transactionID: transaction.id as number,\n customerEmail: transaction.customerEmail,\n }\n } catch (error) {\n payload.logger.error(error, 'Error confirming COD order')\n throw new Error(error instanceof Error ? error.message : 'Unknown error confirming COD order')\n }\n }\n\n // Define COD group field for transaction data\n const baseFields: Field[] = [\n {\n name: 'surchargeAmount',\n type: 'number',\n label: 'COD Surcharge',\n admin: { readOnly: true },\n },\n ]\n\n let finalFields: Field[] = baseFields\n\n if (groupOverrides?.fields) {\n if (typeof groupOverrides.fields === 'function') {\n finalFields = groupOverrides.fields({ defaultFields: baseFields })\n } else {\n finalFields = groupOverrides.fields\n }\n }\n\n const groupField: GroupField = {\n name: 'cod',\n type: 'group',\n admin: {\n condition: (data) => data?.paymentMethod === 'cod',\n ...(groupOverrides?.admin || {}),\n },\n fields: finalFields,\n }\n\n return {\n name: 'cod',\n label,\n initiatePayment,\n confirmOrder,\n group: groupField,\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "payload-cod-adapter",
3
+ "version": "0.1.0",
4
+ "description": "Cash on Delivery payment adapter for PayloadCMS ecommerce plugin",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./client": {
16
+ "types": "./dist/client.d.ts",
17
+ "import": "./dist/client.js",
18
+ "require": "./dist/client.cjs"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "vitest run"
25
+ },
26
+ "keywords": [
27
+ "payload",
28
+ "payloadcms",
29
+ "ecommerce",
30
+ "payment",
31
+ "cod",
32
+ "cash-on-delivery",
33
+ "pouzece"
34
+ ],
35
+ "author": "blaze IT s.r.o. <info@blazeit.sk>",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/blazeITHugo/payload-cod-adapter"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "peerDependencies": {
47
+ "@payloadcms/plugin-ecommerce": "^3.60.0",
48
+ "payload": "^3.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "tsup": "^8.5.1",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "3.2.3"
54
+ }
55
+ }