@porulle/adapter-woocommerce 0.75.0 → 0.77.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/dist/index.js +4 -1
- package/dist/orders.d.ts +12 -1
- package/dist/orders.js +46 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webhooks.js +6 -0
- package/package.json +2 -2
- package/src/index.ts +4 -1
- package/src/orders.ts +43 -2
- package/src/webhooks.ts +6 -0
package/dist/webhooks.js
CHANGED
|
@@ -96,6 +96,8 @@ const refundSchema = z.object({
|
|
|
96
96
|
/** What the merchant refunded, positive, in the store's currency: part of a line is less than the line. */
|
|
97
97
|
amount: z.string().nullish(),
|
|
98
98
|
line_items: z.array(z.object({ product_id: id, variation_id: id, quantity: z.number() })).default([]),
|
|
99
|
+
/** Delivery refunded: one entry per order shipping item, total negative. */
|
|
100
|
+
shipping_lines: z.array(z.object({ total: z.string().nullish() })).default([]),
|
|
99
101
|
});
|
|
100
102
|
function meta(entries, key) {
|
|
101
103
|
return entries.find((entry) => entry.key === key)?.value;
|
|
@@ -180,6 +182,9 @@ export async function orderEvents(client, remoteOrderId) {
|
|
|
180
182
|
return refund;
|
|
181
183
|
const currency = order.currency ?? client.credentials.currency;
|
|
182
184
|
const amount = currency ? toMinorUnits(refund.value.data.amount, currency) : undefined;
|
|
185
|
+
const shipping = currency
|
|
186
|
+
? refund.value.data.shipping_lines.reduce((sum, line) => sum + Math.abs(toMinorUnits(line.total, currency) ?? 0), 0)
|
|
187
|
+
: 0;
|
|
183
188
|
events.push({
|
|
184
189
|
kind: "refund.created",
|
|
185
190
|
remoteOrderId: order.id,
|
|
@@ -189,6 +194,7 @@ export async function orderEvents(client, remoteOrderId) {
|
|
|
189
194
|
return quantity > 0 ? [{ externalVariantId: line.variation_id !== "0" ? line.variation_id : line.product_id, quantity }] : [];
|
|
190
195
|
}),
|
|
191
196
|
...(amount === undefined ? {} : { amount: Math.abs(amount) }),
|
|
197
|
+
...(shipping > 0 ? { shippingAmount: shipping } : {}),
|
|
192
198
|
});
|
|
193
199
|
}
|
|
194
200
|
return Ok(events);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/adapter-woocommerce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"zod": "^4.1.11",
|
|
19
|
-
"@porulle/core": "0.
|
|
19
|
+
"@porulle/core": "0.77.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^24.5.2",
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { pushCatalog, pushCaches } from "./catalog-push.js";
|
|
|
6
6
|
import { discoverStore, normalizeStoreDomain, probeStore, refusedStoreUrl, wooClient, wooCredentialsSchema } from "./client.js";
|
|
7
7
|
import type { WooClient, WooTransportOptions } from "./client.js";
|
|
8
8
|
import { inventoryFor, inventoryPage } from "./inventory.js";
|
|
9
|
-
import { cancelOrder, orderStatus, pushOrder } from "./orders.js";
|
|
9
|
+
import { cancelOrder, orderStatus, pushOrder, recordRefund } from "./orders.js";
|
|
10
10
|
import { decodeDelivery, orderEvents, registerWebhooks, unregisterWebhooks, verifyDelivery, webhookHealth, WOO_WEBHOOK_TOPICS } from "./webhooks.js";
|
|
11
11
|
|
|
12
12
|
export { normalizeStoreDomain, probeStore, refusedStoreUrl, WOO_BLOCKED_BY_FIREWALL } from "./client.js";
|
|
@@ -164,6 +164,9 @@ export function wooConnector(options: WooConnectorOptions = {}): ChannelConnecto
|
|
|
164
164
|
async cancelOrder(store, remoteId, input) {
|
|
165
165
|
return withClient(store, (client) => cancelOrder(client, remoteId, input));
|
|
166
166
|
},
|
|
167
|
+
async recordRefund(store, remoteId, input) {
|
|
168
|
+
return withClient(store, (client) => recordRefund(client, remoteId, input));
|
|
169
|
+
},
|
|
167
170
|
async pushCatalog(store, items, opts) {
|
|
168
171
|
return withClient(store, (client) => pushCatalog(client, store, items, opts, caches));
|
|
169
172
|
},
|
package/src/orders.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CHANNEL_CANCEL_REFUSED, CHANNEL_OUT_OF_STOCK, CHANNEL_TOTAL_MISMATCH, currencyExponent, Err, Ok, toMinorUnits } from "@porulle/core";
|
|
2
|
-
import type { ChannelCancelOrderInput, ChannelConnectorError, ChannelOrderSlice, ChannelOrderStatus, ChannelPushOrderResult, Result } from "@porulle/core";
|
|
2
|
+
import type { ChannelCancelOrderInput, ChannelConnectorError, ChannelRefundRecord, ChannelOrderSlice, ChannelOrderStatus, ChannelPushOrderResult, Result } from "@porulle/core";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import type { WooClient } from "./client.js";
|
|
5
5
|
import { available } from "./inventory.js";
|
|
@@ -14,6 +14,7 @@ export const wooOrderSchema = z.object({
|
|
|
14
14
|
date_created_gmt: z.string().nullish(),
|
|
15
15
|
meta_data: z.array(z.object({ key: z.string(), value: z.unknown() })).default([]),
|
|
16
16
|
line_items: z.array(z.object({ id, product_id: id, variation_id: id, quantity: z.number(), total: z.string().nullish() })).default([]),
|
|
17
|
+
shipping_lines: z.array(z.object({ id, total: z.string().nullish() })).default([]),
|
|
17
18
|
refunds: z.array(z.object({ id, total: z.string().nullish() })).default([]),
|
|
18
19
|
});
|
|
19
20
|
export type WooOrder = z.infer<typeof wooOrderSchema>;
|
|
@@ -111,7 +112,12 @@ export async function pushOrder(client: WooClient, slice: ChannelOrderSlice, opt
|
|
|
111
112
|
|
|
112
113
|
const currency = slice.currency;
|
|
113
114
|
const decimals = client.credentials.priceDecimals;
|
|
114
|
-
|
|
115
|
+
// The platform's own per-line discount when it sends one, so the store's line totals are what the
|
|
116
|
+
// platform refunds a line at; otherwise the slice's discount split here.
|
|
117
|
+
const shares = slice.lines.map((line) => line.discountAmount ?? 0);
|
|
118
|
+
const discounts = slice.discount !== undefined && shares.reduce((sum, share) => sum + share, 0) === slice.discount.amount
|
|
119
|
+
? shares
|
|
120
|
+
: allocate(slice.lines.map((line) => line.totalPrice), slice.discount?.amount ?? 0);
|
|
115
121
|
const [firstName = "", ...rest] = slice.customer.name.trim().split(/\s+/);
|
|
116
122
|
const source = slice.customer.shippingAddress;
|
|
117
123
|
const address = {
|
|
@@ -200,6 +206,41 @@ export async function cancelOrder(client: WooClient, remoteId: string, input: Ch
|
|
|
200
206
|
return cancelled.ok ? Ok(undefined) : cancelled;
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Books at the store a refund the marketplace already paid the shopper: `api_refund: false` moves no
|
|
211
|
+
* money, `api_restock` puts the stock back, and each line is the store's own order line for the variant.
|
|
212
|
+
*/
|
|
213
|
+
export async function recordRefund(client: WooClient, remoteId: string, input: ChannelRefundRecord): Promise<Result<{ remoteRefundId: string }, ChannelConnectorError>> {
|
|
214
|
+
const read = await client.get(`/wc/v3/orders/${encodeURIComponent(remoteId)}`, wooOrderSchema);
|
|
215
|
+
if (!read.ok) return read;
|
|
216
|
+
const order = read.value.data;
|
|
217
|
+
const currency = order.currency ?? client.credentials.currency;
|
|
218
|
+
if (!currency) return Err({ code: "WOO_CURRENCY_UNKNOWN", message: `Order ${remoteId} names no currency.`, retriable: false });
|
|
219
|
+
const decimals = client.credentials.priceDecimals;
|
|
220
|
+
const lineItems: Array<{ id: string; quantity: number; refund_total: string }> = [];
|
|
221
|
+
for (const line of input.lines) {
|
|
222
|
+
const storeLine = order.line_items.find((candidate) => (candidate.variation_id !== "0" ? candidate.variation_id : candidate.product_id) === line.externalVariantId);
|
|
223
|
+
if (!storeLine) return Err({ code: "CHANNEL_MAPPING_MISSING", message: `Order ${remoteId} has no line for ${line.externalVariantId}.`, retriable: false });
|
|
224
|
+
lineItems.push({ id: storeLine.id, quantity: line.quantity, refund_total: moneyString(line.amount, currency, decimals) });
|
|
225
|
+
}
|
|
226
|
+
// Delivery is refunded against the order's shipping item, as wp-admin does it.
|
|
227
|
+
const shippingAmount = input.shippingAmount ?? 0;
|
|
228
|
+
if (shippingAmount > 0) {
|
|
229
|
+
const shippingLine = order.shipping_lines[0];
|
|
230
|
+
if (!shippingLine) return Err({ code: "CHANNEL_MAPPING_MISSING", message: `Order ${remoteId} has no delivery line to refund.`, retriable: false });
|
|
231
|
+
lineItems.push({ id: shippingLine.id, quantity: 0, refund_total: moneyString(shippingAmount, currency, decimals) });
|
|
232
|
+
}
|
|
233
|
+
const created = await client.send("POST", `/wc/v3/orders/${encodeURIComponent(remoteId)}/refunds`, {
|
|
234
|
+
amount: moneyString(input.amount, currency, decimals),
|
|
235
|
+
reason: input.reason,
|
|
236
|
+
api_refund: false,
|
|
237
|
+
api_restock: input.restock,
|
|
238
|
+
line_items: lineItems,
|
|
239
|
+
}, z.object({ id: z.union([z.number(), z.string()]).transform(String) }));
|
|
240
|
+
if (!created.ok) return Err({ code: created.error.code, message: `The store would not record the refund: ${created.error.message}`, retriable: created.error.retriable === true });
|
|
241
|
+
return Ok({ remoteRefundId: created.value.id });
|
|
242
|
+
}
|
|
243
|
+
|
|
203
244
|
export async function orderStatus(client: WooClient, remoteId: string): Promise<Result<ChannelOrderStatus, ChannelConnectorError>> {
|
|
204
245
|
const read = await client.get(`/wc/v3/orders/${encodeURIComponent(remoteId)}`, wooOrderSchema);
|
|
205
246
|
if (!read.ok) return read;
|
package/src/webhooks.ts
CHANGED
|
@@ -97,6 +97,8 @@ const refundSchema = z.object({
|
|
|
97
97
|
/** What the merchant refunded, positive, in the store's currency: part of a line is less than the line. */
|
|
98
98
|
amount: z.string().nullish(),
|
|
99
99
|
line_items: z.array(z.object({ product_id: id, variation_id: id, quantity: z.number() })).default([]),
|
|
100
|
+
/** Delivery refunded: one entry per order shipping item, total negative. */
|
|
101
|
+
shipping_lines: z.array(z.object({ total: z.string().nullish() })).default([]),
|
|
100
102
|
});
|
|
101
103
|
|
|
102
104
|
function meta(entries: Array<{ key: string; value: unknown }>, key: string): unknown {
|
|
@@ -175,6 +177,9 @@ export async function orderEvents(client: WooClient, remoteOrderId: string): Pro
|
|
|
175
177
|
if (!refund.ok) return refund;
|
|
176
178
|
const currency = order.currency ?? client.credentials.currency;
|
|
177
179
|
const amount = currency ? toMinorUnits(refund.value.data.amount, currency) : undefined;
|
|
180
|
+
const shipping = currency
|
|
181
|
+
? refund.value.data.shipping_lines.reduce((sum, line) => sum + Math.abs(toMinorUnits(line.total, currency) ?? 0), 0)
|
|
182
|
+
: 0;
|
|
178
183
|
events.push({
|
|
179
184
|
kind: "refund.created",
|
|
180
185
|
remoteOrderId: order.id,
|
|
@@ -184,6 +189,7 @@ export async function orderEvents(client: WooClient, remoteOrderId: string): Pro
|
|
|
184
189
|
return quantity > 0 ? [{ externalVariantId: line.variation_id !== "0" ? line.variation_id : line.product_id, quantity }] : [];
|
|
185
190
|
}),
|
|
186
191
|
...(amount === undefined ? {} : { amount: Math.abs(amount) }),
|
|
192
|
+
...(shipping > 0 ? { shippingAmount: shipping } : {}),
|
|
187
193
|
});
|
|
188
194
|
}
|
|
189
195
|
return Ok(events);
|