@porulle/adapter-woocommerce 0.75.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/adapter-woocommerce",
3
- "version": "0.75.0",
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.75.0"
19
+ "@porulle/core": "0.76.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^24.5.2",
@@ -25,8 +25,8 @@
25
25
  "vitest": "^3.2.4",
26
26
  "esbuild": "0.25.12",
27
27
  "miniflare": "4.20260722.1",
28
- "@porulle/typescript-config": "0.1.0",
29
- "@porulle/eslint-config": "0.1.0"
28
+ "@porulle/eslint-config": "0.1.0",
29
+ "@porulle/typescript-config": "0.1.0"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"
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;