@porulle/adapter-woocommerce 0.74.5 → 0.76.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/webhooks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
- import { Err, Ok } from "@porulle/core";
2
+ import { Err, Ok, toMinorUnits } from "@porulle/core";
3
3
  import { z } from "zod";
4
4
  import { wooProductSchema, wooVariationSchema } from "./catalog.js";
5
5
  import { available } from "./inventory.js";
@@ -93,6 +93,8 @@ const trackingItem = z.object({
93
93
  });
94
94
  const refundSchema = z.object({
95
95
  id,
96
+ /** What the merchant refunded, positive, in the store's currency: part of a line is less than the line. */
97
+ amount: z.string().nullish(),
96
98
  line_items: z.array(z.object({ product_id: id, variation_id: id, quantity: z.number() })).default([]),
97
99
  });
98
100
  function meta(entries, key) {
@@ -176,6 +178,8 @@ export async function orderEvents(client, remoteOrderId) {
176
178
  const refund = await client.get(`/wc/v3/orders/${encodeURIComponent(order.id)}/refunds/${encodeURIComponent(summary.id)}`, refundSchema);
177
179
  if (!refund.ok)
178
180
  return refund;
181
+ const currency = order.currency ?? client.credentials.currency;
182
+ const amount = currency ? toMinorUnits(refund.value.data.amount, currency) : undefined;
179
183
  events.push({
180
184
  kind: "refund.created",
181
185
  remoteOrderId: order.id,
@@ -184,6 +188,7 @@ export async function orderEvents(client, remoteOrderId) {
184
188
  const quantity = Math.abs(line.quantity);
185
189
  return quantity > 0 ? [{ externalVariantId: line.variation_id !== "0" ? line.variation_id : line.product_id, quantity }] : [];
186
190
  }),
191
+ ...(amount === undefined ? {} : { amount: Math.abs(amount) }),
187
192
  });
188
193
  }
189
194
  return Ok(events);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/adapter-woocommerce",
3
- "version": "0.74.5",
3
+ "version": "0.76.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.74.5"
19
+ "@porulle/core": "0.76.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";
@@ -200,6 +200,34 @@ export async function cancelOrder(client: WooClient, remoteId: string, input: Ch
200
200
  return cancelled.ok ? Ok(undefined) : cancelled;
201
201
  }
202
202
 
203
+ /**
204
+ * Books at the store a refund the marketplace already paid the shopper: `api_refund: false` moves no
205
+ * money, `api_restock` puts the stock back, and each line is the store's own order line for the variant.
206
+ */
207
+ export async function recordRefund(client: WooClient, remoteId: string, input: ChannelRefundRecord): Promise<Result<{ remoteRefundId: string }, ChannelConnectorError>> {
208
+ const read = await client.get(`/wc/v3/orders/${encodeURIComponent(remoteId)}`, wooOrderSchema);
209
+ if (!read.ok) return read;
210
+ const order = read.value.data;
211
+ const currency = order.currency ?? client.credentials.currency;
212
+ if (!currency) return Err({ code: "WOO_CURRENCY_UNKNOWN", message: `Order ${remoteId} names no currency.`, retriable: false });
213
+ const decimals = client.credentials.priceDecimals;
214
+ const lineItems: Array<{ id: string; quantity: number; refund_total: string }> = [];
215
+ for (const line of input.lines) {
216
+ const storeLine = order.line_items.find((candidate) => (candidate.variation_id !== "0" ? candidate.variation_id : candidate.product_id) === line.externalVariantId);
217
+ if (!storeLine) return Err({ code: "CHANNEL_MAPPING_MISSING", message: `Order ${remoteId} has no line for ${line.externalVariantId}.`, retriable: false });
218
+ lineItems.push({ id: storeLine.id, quantity: line.quantity, refund_total: moneyString(line.amount, currency, decimals) });
219
+ }
220
+ const created = await client.send("POST", `/wc/v3/orders/${encodeURIComponent(remoteId)}/refunds`, {
221
+ amount: moneyString(input.amount, currency, decimals),
222
+ reason: input.reason,
223
+ api_refund: false,
224
+ api_restock: input.restock,
225
+ line_items: lineItems,
226
+ }, z.object({ id: z.union([z.number(), z.string()]).transform(String) }));
227
+ 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 });
228
+ return Ok({ remoteRefundId: created.value.id });
229
+ }
230
+
203
231
  export async function orderStatus(client: WooClient, remoteId: string): Promise<Result<ChannelOrderStatus, ChannelConnectorError>> {
204
232
  const read = await client.get(`/wc/v3/orders/${encodeURIComponent(remoteId)}`, wooOrderSchema);
205
233
  if (!read.ok) return read;
package/src/webhooks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
- import { Err, Ok } from "@porulle/core";
2
+ import { Err, Ok, toMinorUnits } from "@porulle/core";
3
3
  import type { ChannelConnectorError, ChannelEvent, ChannelShipment, ChannelStore, ChannelWebhookEvent, ChannelWebhookHealth, Result } from "@porulle/core";
4
4
  import { z } from "zod";
5
5
  import { wooProductSchema, wooVariationSchema } from "./catalog.js";
@@ -94,6 +94,8 @@ const trackingItem = z.object({
94
94
  });
95
95
  const refundSchema = z.object({
96
96
  id,
97
+ /** What the merchant refunded, positive, in the store's currency: part of a line is less than the line. */
98
+ amount: z.string().nullish(),
97
99
  line_items: z.array(z.object({ product_id: id, variation_id: id, quantity: z.number() })).default([]),
98
100
  });
99
101
 
@@ -171,6 +173,8 @@ export async function orderEvents(client: WooClient, remoteOrderId: string): Pro
171
173
  for (const summary of order.refunds) {
172
174
  const refund = await client.get(`/wc/v3/orders/${encodeURIComponent(order.id)}/refunds/${encodeURIComponent(summary.id)}`, refundSchema);
173
175
  if (!refund.ok) return refund;
176
+ const currency = order.currency ?? client.credentials.currency;
177
+ const amount = currency ? toMinorUnits(refund.value.data.amount, currency) : undefined;
174
178
  events.push({
175
179
  kind: "refund.created",
176
180
  remoteOrderId: order.id,
@@ -179,6 +183,7 @@ export async function orderEvents(client: WooClient, remoteOrderId: string): Pro
179
183
  const quantity = Math.abs(line.quantity);
180
184
  return quantity > 0 ? [{ externalVariantId: line.variation_id !== "0" ? line.variation_id : line.product_id, quantity }] : [];
181
185
  }),
186
+ ...(amount === undefined ? {} : { amount: Math.abs(amount) }),
182
187
  });
183
188
  }
184
189
  return Ok(events);