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 +21 -0
- package/README.md +105 -0
- package/dist/client.cjs +16 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +23 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.js +14 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +222 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
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
|
+
[](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
|
package/dist/client.cjs
ADDED
|
@@ -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 };
|
package/dist/client.d.ts
ADDED
|
@@ -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":"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","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|