@porulle/plugin-channel-connector 0.70.0 → 0.70.2
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/service.js +23 -4
- package/package.json +2 -2
- package/src/service.ts +23 -3
package/dist/service.js
CHANGED
|
@@ -2285,6 +2285,7 @@ export class ChannelConnectorService {
|
|
|
2285
2285
|
"orders/fulfilled",
|
|
2286
2286
|
"orders/partially_fulfilled",
|
|
2287
2287
|
"orders/cancelled",
|
|
2288
|
+
"refunds/create",
|
|
2288
2289
|
"app/uninstalled",
|
|
2289
2290
|
], callbackUrl);
|
|
2290
2291
|
if (!registration.ok) {
|
|
@@ -4186,7 +4187,15 @@ export class ChannelConnectorService {
|
|
|
4186
4187
|
const existing = await this.db.select().from(channelRefundRequests).where(and(eq(channelRefundRequests.storeId, store.id), eq(channelRefundRequests.remoteRefundId, remoteRefundId)));
|
|
4187
4188
|
if (existing[0])
|
|
4188
4189
|
return Ok(existing[0]);
|
|
4189
|
-
|
|
4190
|
+
// Shopify names the refunded lines in `refund_line_items`, each with its order line under
|
|
4191
|
+
// `line_item`; other providers send a flat `line_items`. Both reduce to { variant_id, quantity }.
|
|
4192
|
+
const lineData = Array.isArray(data.refund_line_items)
|
|
4193
|
+
? data.refund_line_items.map((raw) => {
|
|
4194
|
+
const entry = (raw ?? {});
|
|
4195
|
+
const orderLine = (entry.line_item ?? {});
|
|
4196
|
+
return { variant_id: orderLine.variant_id, product_id: orderLine.product_id, quantity: entry.quantity };
|
|
4197
|
+
})
|
|
4198
|
+
: Array.isArray(data.line_items) ? data.line_items : Array.isArray(data.lineItems) ? data.lineItems : [];
|
|
4190
4199
|
const orderLines = await this.db.select().from(orderLineItems).where(eq(orderLineItems.orderId, orderId));
|
|
4191
4200
|
const mappings = await this.db.select().from(channelEntityMap).where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, store.id)));
|
|
4192
4201
|
const refundLines = [];
|
|
@@ -4217,8 +4226,12 @@ export class ChannelConnectorService {
|
|
|
4217
4226
|
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: request.id, fromState: null, toState: request.state, reason: auto ? "Automatic guarded refund" : "Operator approval required", changedBy: requireUserId(actor) });
|
|
4218
4227
|
if (auto) {
|
|
4219
4228
|
const result = await this.executeRefund(request, refundLines, actor);
|
|
4220
|
-
if (!result.ok)
|
|
4221
|
-
|
|
4229
|
+
if (!result.ok) {
|
|
4230
|
+
// Nothing moved: an operator decides instead, rather than the request sticking as approved.
|
|
4231
|
+
await this.db.update(channelRefundRequests).set({ state: "requested", approvedBy: null, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, request.id), eq(channelRefundRequests.state, "approved")));
|
|
4232
|
+
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: request.id, fromState: "approved", toState: "requested", reason: `Automatic refund failed: ${result.error}`, changedBy: requireUserId(actor) });
|
|
4233
|
+
return Ok({ ...request, state: "requested" });
|
|
4234
|
+
}
|
|
4222
4235
|
}
|
|
4223
4236
|
return Ok(request);
|
|
4224
4237
|
}
|
|
@@ -4239,7 +4252,13 @@ export class ChannelConnectorService {
|
|
|
4239
4252
|
if (!request)
|
|
4240
4253
|
return PluginErr("Refund request not found or already handled.", "NOT_FOUND");
|
|
4241
4254
|
const lines = await this.refundLinesForRequest(request);
|
|
4242
|
-
|
|
4255
|
+
const executed = await this.executeRefund(request, lines, createSystemActor(orgId));
|
|
4256
|
+
if (!executed.ok) {
|
|
4257
|
+
// Nothing moved: back to `requested`, so the operator can approve again once the cause is fixed.
|
|
4258
|
+
await this.db.update(channelRefundRequests).set({ state: "requested", approvedBy: null, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, id), eq(channelRefundRequests.state, "approved")));
|
|
4259
|
+
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: id, fromState: "approved", toState: "requested", reason: `Execution failed: ${executed.error}`, changedBy: actor.userId });
|
|
4260
|
+
}
|
|
4261
|
+
return executed;
|
|
4243
4262
|
}
|
|
4244
4263
|
async rejectRefund(orgId, id, actor) {
|
|
4245
4264
|
const [request] = await this.db.update(channelRefundRequests).set({ state: "rejected", approvedBy: actor.userId, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, id), eq(channelRefundRequests.state, "requested"))).returning();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/plugin-channel-connector",
|
|
3
|
-
"version": "0.70.
|
|
3
|
+
"version": "0.70.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@hono/zod-openapi": "^1.2.2",
|
|
24
24
|
"hono": "^4.12.5",
|
|
25
|
-
"@porulle/core": "0.70.
|
|
25
|
+
"@porulle/core": "0.70.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^24.5.2",
|
package/src/service.ts
CHANGED
|
@@ -3203,6 +3203,7 @@ export class ChannelConnectorService {
|
|
|
3203
3203
|
"orders/fulfilled",
|
|
3204
3204
|
"orders/partially_fulfilled",
|
|
3205
3205
|
"orders/cancelled",
|
|
3206
|
+
"refunds/create",
|
|
3206
3207
|
"app/uninstalled",
|
|
3207
3208
|
], callbackUrl);
|
|
3208
3209
|
if (!registration.ok) {
|
|
@@ -5313,7 +5314,15 @@ export class ChannelConnectorService {
|
|
|
5313
5314
|
if (!remoteRefundId || !orderId) return PluginErr("Refund webhook is missing a mapped order or refund id.", "REFUND_MAPPING_MISSING");
|
|
5314
5315
|
const existing = await this.db.select().from(channelRefundRequests).where(and(eq(channelRefundRequests.storeId, store.id), eq(channelRefundRequests.remoteRefundId, remoteRefundId)));
|
|
5315
5316
|
if (existing[0]) return Ok(existing[0] as ChannelRefundRequest);
|
|
5316
|
-
|
|
5317
|
+
// Shopify names the refunded lines in `refund_line_items`, each with its order line under
|
|
5318
|
+
// `line_item`; other providers send a flat `line_items`. Both reduce to { variant_id, quantity }.
|
|
5319
|
+
const lineData = Array.isArray(data.refund_line_items)
|
|
5320
|
+
? data.refund_line_items.map((raw) => {
|
|
5321
|
+
const entry = (raw ?? {}) as Record<string, unknown>;
|
|
5322
|
+
const orderLine = (entry.line_item ?? {}) as Record<string, unknown>;
|
|
5323
|
+
return { variant_id: orderLine.variant_id, product_id: orderLine.product_id, quantity: entry.quantity };
|
|
5324
|
+
})
|
|
5325
|
+
: Array.isArray(data.line_items) ? data.line_items : Array.isArray(data.lineItems) ? data.lineItems : [];
|
|
5317
5326
|
const orderLines = await this.db.select().from(orderLineItems).where(eq(orderLineItems.orderId, orderId));
|
|
5318
5327
|
const mappings = await this.db.select().from(channelEntityMap).where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, store.id)));
|
|
5319
5328
|
const refundLines: Array<{ lineItemId: string; quantity: number }> = [];
|
|
@@ -5341,7 +5350,12 @@ export class ChannelConnectorService {
|
|
|
5341
5350
|
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: request.id, fromState: null, toState: request.state, reason: auto ? "Automatic guarded refund" : "Operator approval required", changedBy: requireUserId(actor) });
|
|
5342
5351
|
if (auto) {
|
|
5343
5352
|
const result = await this.executeRefund(request, refundLines, actor);
|
|
5344
|
-
if (!result.ok)
|
|
5353
|
+
if (!result.ok) {
|
|
5354
|
+
// Nothing moved: an operator decides instead, rather than the request sticking as approved.
|
|
5355
|
+
await this.db.update(channelRefundRequests).set({ state: "requested", approvedBy: null, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, request.id), eq(channelRefundRequests.state, "approved")));
|
|
5356
|
+
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: request.id, fromState: "approved", toState: "requested", reason: `Automatic refund failed: ${result.error}`, changedBy: requireUserId(actor) });
|
|
5357
|
+
return Ok({ ...request, state: "requested" });
|
|
5358
|
+
}
|
|
5345
5359
|
}
|
|
5346
5360
|
return Ok(request);
|
|
5347
5361
|
}
|
|
@@ -5363,7 +5377,13 @@ export class ChannelConnectorService {
|
|
|
5363
5377
|
const [request] = await this.db.update(channelRefundRequests).set({ state: "approved", approvedBy: actor.userId, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, id), eq(channelRefundRequests.state, "requested"))).returning();
|
|
5364
5378
|
if (!request) return PluginErr("Refund request not found or already handled.", "NOT_FOUND");
|
|
5365
5379
|
const lines = await this.refundLinesForRequest(request as ChannelRefundRequest);
|
|
5366
|
-
|
|
5380
|
+
const executed = await this.executeRefund(request as ChannelRefundRequest, lines, createSystemActor(orgId));
|
|
5381
|
+
if (!executed.ok) {
|
|
5382
|
+
// Nothing moved: back to `requested`, so the operator can approve again once the cause is fixed.
|
|
5383
|
+
await this.db.update(channelRefundRequests).set({ state: "requested", approvedBy: null, updatedAt: new Date() }).where(and(eq(channelRefundRequests.organizationId, orgId), eq(channelRefundRequests.id, id), eq(channelRefundRequests.state, "approved")));
|
|
5384
|
+
await this.db.insert(channelRefundEvents).values({ organizationId: orgId, requestId: id, fromState: "approved", toState: "requested", reason: `Execution failed: ${executed.error}`, changedBy: actor.userId });
|
|
5385
|
+
}
|
|
5386
|
+
return executed;
|
|
5367
5387
|
}
|
|
5368
5388
|
|
|
5369
5389
|
async rejectRefund(orgId: string, id: string, actor: { userId: string }): Promise<PluginResult<ChannelRefundRequest>> {
|