cabloy 5.1.126 → 5.1.127
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/.cabloy-version +1 -1
- package/CHANGELOG.md +21 -0
- package/cabloy-docs/backend/error-guide.md +17 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +135 -4
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorClass.ts +21 -4
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorObject.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/bean/type.ts +3 -1
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +6 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +5 -5
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +36 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +21 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +11 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/categoryPresentation.test.ts +218 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +8 -4
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +65 -19
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +7 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/bean/tableCell.select.tsx +12 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +6 -3
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { app } from 'vona-mock';
|
|
4
|
+
import {
|
|
5
|
+
DtoCategoryCreate,
|
|
6
|
+
DtoCategorySelectResItem,
|
|
7
|
+
DtoCategoryUpdate,
|
|
8
|
+
DtoCategoryView,
|
|
9
|
+
} from 'vona-module-commerce-catalog';
|
|
10
|
+
|
|
11
|
+
function findComponent(apiJson: any, predicate: (properties: any) => boolean) {
|
|
12
|
+
return Object.values(apiJson.components!.schemas as any).find(item => {
|
|
13
|
+
return predicate((item as any).properties);
|
|
14
|
+
}) as any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getEntryForm(component: any) {
|
|
18
|
+
const pageEntry = component?.rest?.blocks?.[0];
|
|
19
|
+
const formBlock = pageEntry?.options?.blocks?.[0];
|
|
20
|
+
const toolbarBlock = pageEntry?.options?.blocks?.[1];
|
|
21
|
+
assert.equal(pageEntry?.render, 'basic-pageentry:blockPageEntry');
|
|
22
|
+
assert.equal(formBlock?.render, 'basic-pageentry:blockForm');
|
|
23
|
+
assert.equal(toolbarBlock?.render, 'basic-pageentry:blockToolbarRow');
|
|
24
|
+
return toolbarBlock;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('categoryPresentation.test.ts', () => {
|
|
28
|
+
it('action:category:presentationMetadata', async () => {
|
|
29
|
+
await app.bean.executor.mockCtx(async () => {
|
|
30
|
+
const createJson = await app.bean.openapi.generateJsonOfClass(DtoCategoryCreate);
|
|
31
|
+
const createComponent = findComponent(createJson, properties => {
|
|
32
|
+
return (
|
|
33
|
+
properties?.name &&
|
|
34
|
+
properties?.parentId &&
|
|
35
|
+
properties?.published &&
|
|
36
|
+
properties?.description
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
const createProperties = createComponent.properties;
|
|
40
|
+
assert.deepEqual(Object.keys(createProperties).sort(), [
|
|
41
|
+
'description',
|
|
42
|
+
'name',
|
|
43
|
+
'parentId',
|
|
44
|
+
'published',
|
|
45
|
+
]);
|
|
46
|
+
assert.equal(
|
|
47
|
+
createProperties.parentId.rest.form.render,
|
|
48
|
+
'basic-resource:formFieldResourcePicker',
|
|
49
|
+
);
|
|
50
|
+
assert.deepEqual(createProperties.parentId.rest.form.options, {
|
|
51
|
+
resource: 'commerce-catalog:category',
|
|
52
|
+
relationName: 'parent',
|
|
53
|
+
});
|
|
54
|
+
assert.equal(createProperties.published.rest.form.render, 'basic-select:formFieldSelect');
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
createProperties.published.rest.form.options.items.map((item: any) => item.value),
|
|
57
|
+
[false, true],
|
|
58
|
+
);
|
|
59
|
+
assert.ok(
|
|
60
|
+
createProperties.published.rest.form.options.items.every(
|
|
61
|
+
(item: any) => item.title?.toJSON && item.title?.toString,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
const createToolbar = getEntryForm(createComponent);
|
|
65
|
+
assert.deepEqual(
|
|
66
|
+
createToolbar.options.actions.map((action: any) => action.render),
|
|
67
|
+
['basic-form:actionSubmit', 'basic-form:actionBack'],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const updateJson = await app.bean.openapi.generateJsonOfClass(DtoCategoryUpdate);
|
|
71
|
+
const updateComponent = findComponent(updateJson, properties => {
|
|
72
|
+
return (
|
|
73
|
+
properties?.name &&
|
|
74
|
+
properties?.parentId &&
|
|
75
|
+
properties?.published &&
|
|
76
|
+
properties?.description
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
assert.deepEqual(Object.keys(updateComponent.properties).sort(), [
|
|
80
|
+
'description',
|
|
81
|
+
'name',
|
|
82
|
+
'parentId',
|
|
83
|
+
'published',
|
|
84
|
+
]);
|
|
85
|
+
assert.equal(
|
|
86
|
+
updateComponent.properties.parentId.rest.form.render,
|
|
87
|
+
'basic-resource:formFieldResourcePicker',
|
|
88
|
+
);
|
|
89
|
+
assert.equal(
|
|
90
|
+
updateComponent.properties.published.rest.form.render,
|
|
91
|
+
'basic-select:formFieldSelect',
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const viewJson = await app.bean.openapi.generateJsonOfClass(DtoCategoryView);
|
|
95
|
+
const viewComponent = findComponent(viewJson, properties => {
|
|
96
|
+
return (
|
|
97
|
+
properties?.id && properties?.parentId && properties?.parent && properties?.published
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
assert.deepEqual(Object.keys(viewComponent.properties.parent.properties).sort(), [
|
|
101
|
+
'id',
|
|
102
|
+
'name',
|
|
103
|
+
]);
|
|
104
|
+
assert.equal(viewComponent.properties.iid.rest.visible, false);
|
|
105
|
+
assert.equal(viewComponent.properties.deleted.rest.visible, false);
|
|
106
|
+
assert.equal(
|
|
107
|
+
viewComponent.properties.parentId.rest.form.render,
|
|
108
|
+
'basic-resource:formFieldResourcePicker',
|
|
109
|
+
);
|
|
110
|
+
assert.equal(
|
|
111
|
+
viewComponent.properties.published.rest.form.render,
|
|
112
|
+
'basic-select:formFieldSelect',
|
|
113
|
+
);
|
|
114
|
+
const viewToolbar = getEntryForm(viewComponent);
|
|
115
|
+
assert.deepEqual(
|
|
116
|
+
viewToolbar.options.actions.map((action: any) => action.render),
|
|
117
|
+
['basic-form:actionBack'],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const listJson = await app.bean.openapi.generateJsonOfClass(DtoCategorySelectResItem);
|
|
121
|
+
const listComponent = findComponent(listJson, properties => properties?._operationsRow);
|
|
122
|
+
const listProperties = listComponent.properties;
|
|
123
|
+
assert.deepEqual(Object.keys(listProperties.parent.properties).sort(), ['id', 'name']);
|
|
124
|
+
assert.equal(listProperties.parentId.rest.table.render, 'basic-resource:resourcePicker');
|
|
125
|
+
assert.deepEqual(listProperties.parentId.rest.table.columnProps, {
|
|
126
|
+
resource: 'commerce-catalog:category',
|
|
127
|
+
relationName: 'parent',
|
|
128
|
+
});
|
|
129
|
+
assert.equal(listProperties.published.rest.table.render, 'basic-select:select');
|
|
130
|
+
assert.deepEqual(
|
|
131
|
+
listProperties.published.rest.table.columnProps.items.map((item: any) => item.value),
|
|
132
|
+
[false, true],
|
|
133
|
+
);
|
|
134
|
+
assert.deepEqual(
|
|
135
|
+
listProperties._operationsRow.rest.table.columnProps.actions.map(
|
|
136
|
+
(action: any) => action.render,
|
|
137
|
+
),
|
|
138
|
+
['basic-table:actionUpdate', 'basic-table:actionDelete'],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const filterBlock = listComponent.rest.blocks[0].options.blocks[0];
|
|
142
|
+
const filterLayout = filterBlock.options.blocks[0].options.formLayout;
|
|
143
|
+
const section = filterLayout.children[0];
|
|
144
|
+
assert.equal(section.type, 'section');
|
|
145
|
+
assert.equal(section.layout, 'flow');
|
|
146
|
+
assert.deepEqual(
|
|
147
|
+
section.children.slice(0, 2).map((field: any) => field.name),
|
|
148
|
+
['name', 'createdAt'],
|
|
149
|
+
);
|
|
150
|
+
assert.equal(section.children[2].block.render, 'basic-page:blockFilterActions');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('action:category:parentProjection', async () => {
|
|
155
|
+
await app.bean.executor.mockCtx(async () => {
|
|
156
|
+
const suffix = `${Date.now()}`;
|
|
157
|
+
let parentId: number | string | undefined;
|
|
158
|
+
let childId: number | string | undefined;
|
|
159
|
+
await app.bean.passport.signinMock();
|
|
160
|
+
try {
|
|
161
|
+
parentId = await app.bean.executor.performAction('post', '/commerce/catalog/category', {
|
|
162
|
+
body: { name: `presentation-parent-${suffix}`, published: false },
|
|
163
|
+
});
|
|
164
|
+
childId = await app.bean.executor.performAction('post', '/commerce/catalog/category', {
|
|
165
|
+
body: {
|
|
166
|
+
name: `presentation-child-${suffix}`,
|
|
167
|
+
parentId,
|
|
168
|
+
published: true,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const view: any = await app.bean.executor.performAction(
|
|
173
|
+
'get',
|
|
174
|
+
'/commerce/catalog/category/:id',
|
|
175
|
+
{ params: { id: childId } },
|
|
176
|
+
);
|
|
177
|
+
assert.equal(String(view.parentId), String(parentId));
|
|
178
|
+
assert.deepEqual(view.parent, { id: parentId, name: `presentation-parent-${suffix}` });
|
|
179
|
+
assert.equal(view.published, true);
|
|
180
|
+
|
|
181
|
+
const select: any = await app.bean.executor.performAction(
|
|
182
|
+
'get',
|
|
183
|
+
'/commerce/catalog/category',
|
|
184
|
+
);
|
|
185
|
+
const root = select.list.find((item: any) => String(item.id) === String(parentId));
|
|
186
|
+
const child = select.list.find((item: any) => String(item.id) === String(childId));
|
|
187
|
+
assert.equal(root.parent, undefined);
|
|
188
|
+
assert.equal(root.published, false);
|
|
189
|
+
assert.equal(String(child.parentId), String(parentId));
|
|
190
|
+
assert.deepEqual(child.parent, { id: parentId, name: `presentation-parent-${suffix}` });
|
|
191
|
+
assert.equal(child.published, true);
|
|
192
|
+
|
|
193
|
+
await assert.rejects(
|
|
194
|
+
app.bean.executor.performAction('patch', '/commerce/catalog/category/:id', {
|
|
195
|
+
params: { id: childId },
|
|
196
|
+
body: {
|
|
197
|
+
name: `presentation-child-${suffix}`,
|
|
198
|
+
parentId: childId,
|
|
199
|
+
published: true,
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
} finally {
|
|
204
|
+
if (childId) {
|
|
205
|
+
await app.bean.executor.performAction('delete', '/commerce/catalog/category/:id', {
|
|
206
|
+
params: { id: childId },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (parentId) {
|
|
210
|
+
await app.bean.executor.performAction('delete', '/commerce/catalog/category/:id', {
|
|
211
|
+
params: { id: parentId },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
await app.bean.passport.signout();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -18,6 +18,9 @@ export class DtoRefundResult {
|
|
|
18
18
|
@Api.field(v.optional(), v.tableIdentity())
|
|
19
19
|
refundAttemptId?: TableIdentity;
|
|
20
20
|
|
|
21
|
+
@Api.field(v.optional(), v.tableIdentity())
|
|
22
|
+
refundOperationId?: TableIdentity;
|
|
23
|
+
|
|
21
24
|
@Api.field(z.enum(['paid', 'refund_requested', 'refund_approved', 'refund_rejected', 'refunded']))
|
|
22
25
|
orderState: 'paid' | 'refund_requested' | 'refund_approved' | 'refund_rejected' | 'refunded';
|
|
23
26
|
|
|
@@ -1157,7 +1157,7 @@ export class ServiceOrder extends BeanBase {
|
|
|
1157
1157
|
private _refundResult(
|
|
1158
1158
|
order: Pick<EntityOrder, 'id' | 'state' | 'currency'>,
|
|
1159
1159
|
refundRequest: Pick<EntityRefundRequest, 'id' | 'state' | 'amountCents'>,
|
|
1160
|
-
refundAttempt?: Pick<EntityRefundAttempt, 'id' | 'state'>,
|
|
1160
|
+
refundAttempt?: Pick<EntityRefundAttempt, 'id' | 'state' | 'refundOperationId'>,
|
|
1161
1161
|
): DtoRefundResult {
|
|
1162
1162
|
if (
|
|
1163
1163
|
order.state !== 'paid' &&
|
|
@@ -1172,6 +1172,7 @@ export class ServiceOrder extends BeanBase {
|
|
|
1172
1172
|
orderId: order.id,
|
|
1173
1173
|
refundRequestId: refundRequest.id,
|
|
1174
1174
|
refundAttemptId: refundAttempt?.id,
|
|
1175
|
+
refundOperationId: refundAttempt?.refundOperationId,
|
|
1175
1176
|
orderState: order.state,
|
|
1176
1177
|
refundState: refundRequest.state,
|
|
1177
1178
|
refundAttemptState: refundAttempt?.state,
|
|
@@ -16,6 +16,7 @@ interface IFixture {
|
|
|
16
16
|
customerName?: string;
|
|
17
17
|
orderId?: number;
|
|
18
18
|
paymentAttemptId?: number;
|
|
19
|
+
paymentSessionId?: number;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
async function cleanup(fixture: IFixture) {
|
|
@@ -24,6 +25,21 @@ async function cleanup(fixture: IFixture) {
|
|
|
24
25
|
const promotion = app.scope('commerce-promotion');
|
|
25
26
|
const catalog = app.scope('commerce-catalog');
|
|
26
27
|
const member = app.scope('commerce-member');
|
|
28
|
+
const pay = app.scope('a-pay');
|
|
29
|
+
if (fixture.paymentSessionId !== undefined) {
|
|
30
|
+
const refundOperations = await pay.model.refundOperation.select({
|
|
31
|
+
where: { paymentSessionId: fixture.paymentSessionId },
|
|
32
|
+
});
|
|
33
|
+
for (const refundOperation of refundOperations) {
|
|
34
|
+
await pay.model.providerOperation.delete({ refundOperationId: refundOperation.id });
|
|
35
|
+
await pay.model.outboxEvent.delete({ refundOperationId: refundOperation.id });
|
|
36
|
+
await pay.model.webhookInbox.delete({ refundOperationId: refundOperation.id });
|
|
37
|
+
await pay.model.refundOperation.delete({ id: refundOperation.id });
|
|
38
|
+
}
|
|
39
|
+
await pay.model.outboxEvent.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
40
|
+
await pay.model.webhookInbox.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
41
|
+
await pay.model.providerOperation.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
42
|
+
}
|
|
27
43
|
if (fixture.orderId !== undefined) {
|
|
28
44
|
const requests = await payment.model.refundRequest.select({
|
|
29
45
|
where: { orderId: fixture.orderId },
|
|
@@ -39,6 +55,8 @@ async function cleanup(fixture: IFixture) {
|
|
|
39
55
|
await payment.model.paymentAudit.delete({ paymentAttemptId: fixture.paymentAttemptId });
|
|
40
56
|
await payment.model.paymentAttempt.delete({ id: fixture.paymentAttemptId });
|
|
41
57
|
}
|
|
58
|
+
if (fixture.paymentSessionId !== undefined)
|
|
59
|
+
await pay.model.paymentSession.delete({ id: fixture.paymentSessionId });
|
|
42
60
|
if (fixture.orderId !== undefined)
|
|
43
61
|
await trade.model.orderAudit.delete({ orderId: fixture.orderId });
|
|
44
62
|
if (fixture.skuId !== undefined) await trade.model.stockAudit.delete({ skuId: fixture.skuId });
|
|
@@ -152,10 +170,17 @@ async function createPaidOrder(suffix: string): Promise<IFixture> {
|
|
|
152
170
|
});
|
|
153
171
|
fixture.orderId = checkout.orderId as number;
|
|
154
172
|
fixture.paymentAttemptId = checkout.paymentAttemptId as number;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
fixture.paymentSessionId = checkout.paymentSessionId as number;
|
|
174
|
+
const pay = app.scope('a-pay');
|
|
175
|
+
await pay.service.paymentSession.start(fixture.paymentSessionId);
|
|
176
|
+
await app
|
|
177
|
+
.scope('pay-mock')
|
|
178
|
+
.service.payMock.completePaymentSession(fixture.paymentSessionId, 'succeeded');
|
|
179
|
+
const outbox = await pay.model.outboxEvent.get({
|
|
180
|
+
paymentSessionId: fixture.paymentSessionId,
|
|
181
|
+
eventType: 'payment.outcome.v1',
|
|
158
182
|
});
|
|
183
|
+
await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox!.id });
|
|
159
184
|
return fixture;
|
|
160
185
|
}
|
|
161
186
|
|
|
@@ -310,6 +335,89 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
|
|
|
310
335
|
});
|
|
311
336
|
});
|
|
312
337
|
|
|
338
|
+
it('settles an executed mock-provider refund through the durable outbox', async () => {
|
|
339
|
+
await app.bean.executor.mockCtx(async () => {
|
|
340
|
+
const fixture: IFixture = {};
|
|
341
|
+
try {
|
|
342
|
+
Object.assign(fixture, await createPaidOrder(randomUUID().slice(0, 12)));
|
|
343
|
+
const order = app.scope('commerce-trade').service.order;
|
|
344
|
+
await order.requestRefund(fixture.orderId!, {
|
|
345
|
+
reason: 'provider-backed refund',
|
|
346
|
+
idempotencyKey: 'provider-backed-request-1',
|
|
347
|
+
});
|
|
348
|
+
await app.bean.passport.signout();
|
|
349
|
+
await app.bean.passport.signinMock();
|
|
350
|
+
await order.approveRefund(fixture.orderId!, {
|
|
351
|
+
reason: 'provider-backed approval',
|
|
352
|
+
idempotencyKey: 'provider-backed-approve-1',
|
|
353
|
+
});
|
|
354
|
+
const executed = await order.executeRefund(fixture.orderId!);
|
|
355
|
+
assert.ok(executed.refundAttemptId);
|
|
356
|
+
assert.ok(executed.refundOperationId);
|
|
357
|
+
assert.deepEqual(
|
|
358
|
+
[executed.orderState, executed.refundState, executed.refundAttemptState],
|
|
359
|
+
['refund_approved', 'approved', 'created'],
|
|
360
|
+
);
|
|
361
|
+
const pay = app.scope('a-pay');
|
|
362
|
+
const refundOperation = await pay.model.refundOperation.getById(executed.refundOperationId);
|
|
363
|
+
const refundAttempt = await app
|
|
364
|
+
.scope('commerce-payment')
|
|
365
|
+
.model.refundAttempt.getById(executed.refundAttemptId);
|
|
366
|
+
assert.equal(String(refundAttempt?.refundOperationId), String(executed.refundOperationId));
|
|
367
|
+
assert.equal(refundOperation?.state, 'pending');
|
|
368
|
+
const receipt = await app
|
|
369
|
+
.scope('pay-mock')
|
|
370
|
+
.service.payMock.completeRefundOperation(executed.refundOperationId, 'succeeded');
|
|
371
|
+
assert.deepEqual(receipt, {
|
|
372
|
+
refundOperationId: executed.refundOperationId,
|
|
373
|
+
accepted: true,
|
|
374
|
+
});
|
|
375
|
+
const outbox = await pay.model.outboxEvent.get({
|
|
376
|
+
refundOperationId: executed.refundOperationId,
|
|
377
|
+
eventType: 'refund.outcome.v1',
|
|
378
|
+
});
|
|
379
|
+
assert.ok(outbox);
|
|
380
|
+
await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
|
|
381
|
+
await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
|
|
382
|
+
const [
|
|
383
|
+
dispatched,
|
|
384
|
+
settledOrder,
|
|
385
|
+
settledRequest,
|
|
386
|
+
settledAttempt,
|
|
387
|
+
line,
|
|
388
|
+
balance,
|
|
389
|
+
grant,
|
|
390
|
+
audits,
|
|
391
|
+
] = await Promise.all([
|
|
392
|
+
pay.model.outboxEvent.getById(outbox.id),
|
|
393
|
+
app.scope('commerce-trade').model.order.getById(fixture.orderId!),
|
|
394
|
+
app.scope('commerce-payment').model.refundRequest.getById(executed.refundRequestId),
|
|
395
|
+
app.scope('commerce-payment').model.refundAttempt.getById(executed.refundAttemptId),
|
|
396
|
+
app.scope('commerce-trade').model.orderLine.get({ orderId: fixture.orderId }),
|
|
397
|
+
app.scope('commerce-trade').model.stockBalance.getById(fixture.balanceId!),
|
|
398
|
+
app.scope('commerce-promotion').model.couponGrant.getById(fixture.grantId!),
|
|
399
|
+
app
|
|
400
|
+
.scope('commerce-payment')
|
|
401
|
+
.model.refundAudit.select({ where: { orderId: fixture.orderId } }),
|
|
402
|
+
]);
|
|
403
|
+
const reservation = await app
|
|
404
|
+
.scope('commerce-trade')
|
|
405
|
+
.model.stockReservation.get({ orderLineId: line?.id });
|
|
406
|
+
assert.deepEqual([dispatched?.state, dispatched?.attemptCount], ['dispatched', 1]);
|
|
407
|
+
assert.deepEqual(
|
|
408
|
+
[settledOrder?.state, settledRequest?.state, settledAttempt?.state, reservation?.state],
|
|
409
|
+
['refunded', 'refunded', 'succeeded', 'restored'],
|
|
410
|
+
);
|
|
411
|
+
assert.deepEqual([balance?.onHand, balance?.reserved, balance?.available], [1, 0, 1]);
|
|
412
|
+
assert.equal(grant?.state, 'redeemed');
|
|
413
|
+
assert.equal(audits.filter(audit => audit.toRefundState === 'refunded').length, 1);
|
|
414
|
+
} finally {
|
|
415
|
+
await app.bean.passport.signout();
|
|
416
|
+
await cleanup(fixture);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
313
421
|
it('preserves rejected and failed cycles while permitting a fresh request', async () => {
|
|
314
422
|
await app.bean.executor.mockCtx(async () => {
|
|
315
423
|
const fixture: IFixture = {};
|
|
@@ -165,10 +165,6 @@ export type ApiSchemaCommerceTradeDtoRefundReview =
|
|
|
165
165
|
components['schemas']['commerce-trade.dto.refundReview'];
|
|
166
166
|
export type ApiSchemaCommerceTradeDtoRefundReviewPartial =
|
|
167
167
|
Partial<ApiSchemaCommerceTradeDtoRefundReview>;
|
|
168
|
-
export type ApiSchemaCommerceTradeDtoRefundOutcomeCreate =
|
|
169
|
-
components['schemas']['commerce-trade.dto.refundOutcomeCreate'];
|
|
170
|
-
export type ApiSchemaCommerceTradeDtoRefundOutcomeCreatePartial =
|
|
171
|
-
Partial<ApiSchemaCommerceTradeDtoRefundOutcomeCreate>;
|
|
172
168
|
export type ApiSchemaCommerceTradeDtoShipmentView =
|
|
173
169
|
components['schemas']['commerce-trade.dto.shipmentView'];
|
|
174
170
|
export type ApiSchemaCommerceTradeDtoShipmentViewPartial =
|
|
@@ -461,6 +457,14 @@ export type ApiSchemaPayMockDtoMockPaymentComplete =
|
|
|
461
457
|
components['schemas']['pay-mock.dto.mockPaymentComplete'];
|
|
462
458
|
export type ApiSchemaPayMockDtoMockPaymentCompletePartial =
|
|
463
459
|
Partial<ApiSchemaPayMockDtoMockPaymentComplete>;
|
|
460
|
+
export type ApiSchemaPayMockDtoMockRefundReceipt =
|
|
461
|
+
components['schemas']['pay-mock.dto.mockRefundReceipt'];
|
|
462
|
+
export type ApiSchemaPayMockDtoMockRefundReceiptPartial =
|
|
463
|
+
Partial<ApiSchemaPayMockDtoMockRefundReceipt>;
|
|
464
|
+
export type ApiSchemaPayMockDtoMockRefundComplete =
|
|
465
|
+
components['schemas']['pay-mock.dto.mockRefundComplete'];
|
|
466
|
+
export type ApiSchemaPayMockDtoMockRefundCompletePartial =
|
|
467
|
+
Partial<ApiSchemaPayMockDtoMockRefundComplete>;
|
|
464
468
|
export type ApiSchemaAPaypalEntityPaypalRecord =
|
|
465
469
|
components['schemas']['a-paypal.entity.paypalRecord'];
|
|
466
470
|
export type ApiSchemaAPaypalEntityPaypalRecordPartial = Partial<ApiSchemaAPaypalEntityPaypalRecord>;
|
|
@@ -623,7 +623,7 @@ export interface paths {
|
|
|
623
623
|
patch?: never;
|
|
624
624
|
trace?: never;
|
|
625
625
|
};
|
|
626
|
-
'/api/commerce/trade/order/{id}/
|
|
626
|
+
'/api/commerce/trade/order/{id}/executeRefund': {
|
|
627
627
|
parameters: {
|
|
628
628
|
query?: never;
|
|
629
629
|
header?: never;
|
|
@@ -632,7 +632,7 @@ export interface paths {
|
|
|
632
632
|
};
|
|
633
633
|
get?: never;
|
|
634
634
|
put?: never;
|
|
635
|
-
post: operations['
|
|
635
|
+
post: operations['CommerceTradeOrder_executeRefund'];
|
|
636
636
|
delete?: never;
|
|
637
637
|
options?: never;
|
|
638
638
|
head?: never;
|
|
@@ -1264,6 +1264,22 @@ export interface paths {
|
|
|
1264
1264
|
patch?: never;
|
|
1265
1265
|
trace?: never;
|
|
1266
1266
|
};
|
|
1267
|
+
'/api/pay/mock/payment-session/refund-operation/{id}/complete': {
|
|
1268
|
+
parameters: {
|
|
1269
|
+
query?: never;
|
|
1270
|
+
header?: never;
|
|
1271
|
+
path?: never;
|
|
1272
|
+
cookie?: never;
|
|
1273
|
+
};
|
|
1274
|
+
get?: never;
|
|
1275
|
+
put?: never;
|
|
1276
|
+
post: operations['PayMockPayment_completeRefund'];
|
|
1277
|
+
delete?: never;
|
|
1278
|
+
options?: never;
|
|
1279
|
+
head?: never;
|
|
1280
|
+
patch?: never;
|
|
1281
|
+
trace?: never;
|
|
1282
|
+
};
|
|
1267
1283
|
'/api/paypal/getRecord/{recordId}': {
|
|
1268
1284
|
parameters: {
|
|
1269
1285
|
query?: never;
|
|
@@ -2446,9 +2462,9 @@ export interface components {
|
|
|
2446
2462
|
* @enum {string}
|
|
2447
2463
|
*/
|
|
2448
2464
|
currency: 'USD';
|
|
2449
|
-
/** @description Fixed Discount
|
|
2465
|
+
/** @description Fixed Discount */
|
|
2450
2466
|
discountCents: number;
|
|
2451
|
-
/** @description Minimum Spend
|
|
2467
|
+
/** @description Minimum Spend */
|
|
2452
2468
|
minSpendCents: number;
|
|
2453
2469
|
/**
|
|
2454
2470
|
* Format: date-time
|
|
@@ -2511,9 +2527,9 @@ export interface components {
|
|
|
2511
2527
|
* @enum {string}
|
|
2512
2528
|
*/
|
|
2513
2529
|
currency: 'USD';
|
|
2514
|
-
/** @description Fixed Discount
|
|
2530
|
+
/** @description Fixed Discount */
|
|
2515
2531
|
discountCents: number;
|
|
2516
|
-
/** @description Minimum Spend
|
|
2532
|
+
/** @description Minimum Spend */
|
|
2517
2533
|
minSpendCents: number;
|
|
2518
2534
|
/**
|
|
2519
2535
|
* Format: date-time
|
|
@@ -2576,9 +2592,9 @@ export interface components {
|
|
|
2576
2592
|
* @enum {string}
|
|
2577
2593
|
*/
|
|
2578
2594
|
currency: 'USD';
|
|
2579
|
-
/** @description Fixed Discount
|
|
2595
|
+
/** @description Fixed Discount */
|
|
2580
2596
|
discountCents: number;
|
|
2581
|
-
/** @description Minimum Spend
|
|
2597
|
+
/** @description Minimum Spend */
|
|
2582
2598
|
minSpendCents: number;
|
|
2583
2599
|
/**
|
|
2584
2600
|
* Format: date-time
|
|
@@ -2761,6 +2777,7 @@ export interface components {
|
|
|
2761
2777
|
orderId: number | string;
|
|
2762
2778
|
refundRequestId: number | string;
|
|
2763
2779
|
refundAttemptId?: number | string | undefined;
|
|
2780
|
+
refundOperationId?: number | string | undefined;
|
|
2764
2781
|
/** @enum {string} */
|
|
2765
2782
|
orderState: 'paid' | 'refund_requested' | 'refund_approved' | 'refund_rejected' | 'refunded';
|
|
2766
2783
|
/** @enum {string} */
|
|
@@ -2779,11 +2796,6 @@ export interface components {
|
|
|
2779
2796
|
reason: string;
|
|
2780
2797
|
idempotencyKey: string;
|
|
2781
2798
|
};
|
|
2782
|
-
'commerce-trade.dto.refundOutcomeCreate': {
|
|
2783
|
-
/** @enum {string} */
|
|
2784
|
-
outcome: 'succeeded' | 'failed';
|
|
2785
|
-
idempotencyKey: string;
|
|
2786
|
-
};
|
|
2787
2799
|
'commerce-trade.dto.shipmentView': {
|
|
2788
2800
|
/** @description ID */
|
|
2789
2801
|
id: number | string;
|
|
@@ -4483,6 +4495,14 @@ export interface components {
|
|
|
4483
4495
|
/** @enum {string} */
|
|
4484
4496
|
outcome: 'succeeded' | 'failed' | 'cancelled';
|
|
4485
4497
|
};
|
|
4498
|
+
'pay-mock.dto.mockRefundReceipt': {
|
|
4499
|
+
refundOperationId: number | string;
|
|
4500
|
+
accepted: boolean;
|
|
4501
|
+
};
|
|
4502
|
+
'pay-mock.dto.mockRefundComplete': {
|
|
4503
|
+
/** @enum {string} */
|
|
4504
|
+
outcome: 'succeeded' | 'failed' | 'cancelled';
|
|
4505
|
+
};
|
|
4486
4506
|
'a-paypal.entity.paypalRecord': {
|
|
4487
4507
|
/**
|
|
4488
4508
|
* Format: date-time
|
|
@@ -6273,7 +6293,7 @@ export interface operations {
|
|
|
6273
6293
|
};
|
|
6274
6294
|
authToken: true;
|
|
6275
6295
|
};
|
|
6276
|
-
|
|
6296
|
+
CommerceTradeOrder_executeRefund: {
|
|
6277
6297
|
parameters: {
|
|
6278
6298
|
query?: never;
|
|
6279
6299
|
header?: never;
|
|
@@ -6282,11 +6302,7 @@ export interface operations {
|
|
|
6282
6302
|
};
|
|
6283
6303
|
cookie?: never;
|
|
6284
6304
|
};
|
|
6285
|
-
requestBody
|
|
6286
|
-
content: {
|
|
6287
|
-
'application/json': components['schemas']['commerce-trade.dto.refundOutcomeCreate'];
|
|
6288
|
-
};
|
|
6289
|
-
};
|
|
6305
|
+
requestBody?: never;
|
|
6290
6306
|
responses: {
|
|
6291
6307
|
200: {
|
|
6292
6308
|
headers: {
|
|
@@ -7854,6 +7870,36 @@ export interface operations {
|
|
|
7854
7870
|
};
|
|
7855
7871
|
authToken: true;
|
|
7856
7872
|
};
|
|
7873
|
+
PayMockPayment_completeRefund: {
|
|
7874
|
+
parameters: {
|
|
7875
|
+
query?: never;
|
|
7876
|
+
header?: never;
|
|
7877
|
+
path: {
|
|
7878
|
+
id: number | string;
|
|
7879
|
+
};
|
|
7880
|
+
cookie?: never;
|
|
7881
|
+
};
|
|
7882
|
+
requestBody: {
|
|
7883
|
+
content: {
|
|
7884
|
+
'application/json': components['schemas']['pay-mock.dto.mockRefundComplete'];
|
|
7885
|
+
};
|
|
7886
|
+
};
|
|
7887
|
+
responses: {
|
|
7888
|
+
200: {
|
|
7889
|
+
headers: {
|
|
7890
|
+
[name: string]: unknown;
|
|
7891
|
+
};
|
|
7892
|
+
content: {
|
|
7893
|
+
'application/json': {
|
|
7894
|
+
code: string;
|
|
7895
|
+
message: string;
|
|
7896
|
+
data: components['schemas']['pay-mock.dto.mockRefundReceipt'];
|
|
7897
|
+
};
|
|
7898
|
+
};
|
|
7899
|
+
};
|
|
7900
|
+
};
|
|
7901
|
+
authToken: true;
|
|
7902
|
+
};
|
|
7857
7903
|
Paypal_getRecord: {
|
|
7858
7904
|
parameters: {
|
|
7859
7905
|
query?: never;
|
|
@@ -2462,9 +2462,9 @@ export interface components {
|
|
|
2462
2462
|
* @enum {string}
|
|
2463
2463
|
*/
|
|
2464
2464
|
currency: 'USD';
|
|
2465
|
-
/** @description Fixed Discount
|
|
2465
|
+
/** @description Fixed Discount */
|
|
2466
2466
|
discountCents: number;
|
|
2467
|
-
/** @description Minimum Spend
|
|
2467
|
+
/** @description Minimum Spend */
|
|
2468
2468
|
minSpendCents: number;
|
|
2469
2469
|
/**
|
|
2470
2470
|
* Format: date-time
|
|
@@ -2527,9 +2527,9 @@ export interface components {
|
|
|
2527
2527
|
* @enum {string}
|
|
2528
2528
|
*/
|
|
2529
2529
|
currency: 'USD';
|
|
2530
|
-
/** @description Fixed Discount
|
|
2530
|
+
/** @description Fixed Discount */
|
|
2531
2531
|
discountCents: number;
|
|
2532
|
-
/** @description Minimum Spend
|
|
2532
|
+
/** @description Minimum Spend */
|
|
2533
2533
|
minSpendCents: number;
|
|
2534
2534
|
/**
|
|
2535
2535
|
* Format: date-time
|
|
@@ -2592,9 +2592,9 @@ export interface components {
|
|
|
2592
2592
|
* @enum {string}
|
|
2593
2593
|
*/
|
|
2594
2594
|
currency: 'USD';
|
|
2595
|
-
/** @description Fixed Discount
|
|
2595
|
+
/** @description Fixed Discount */
|
|
2596
2596
|
discountCents: number;
|
|
2597
|
-
/** @description Minimum Spend
|
|
2597
|
+
/** @description Minimum Spend */
|
|
2598
2598
|
minSpendCents: number;
|
|
2599
2599
|
/**
|
|
2600
2600
|
* Format: date-time
|
|
@@ -2777,6 +2777,7 @@ export interface components {
|
|
|
2777
2777
|
orderId: number | string;
|
|
2778
2778
|
refundRequestId: number | string;
|
|
2779
2779
|
refundAttemptId?: number | string | undefined;
|
|
2780
|
+
refundOperationId?: number | string | undefined;
|
|
2780
2781
|
/** @enum {string} */
|
|
2781
2782
|
orderState: 'paid' | 'refund_requested' | 'refund_approved' | 'refund_rejected' | 'refunded';
|
|
2782
2783
|
/** @enum {string} */
|
|
@@ -26,13 +26,19 @@ export class TableCellSelect extends BeanBase implements ITableCellRender {
|
|
|
26
26
|
render(
|
|
27
27
|
options: ITableCellOptionsSelect,
|
|
28
28
|
_renderContext: IJsxRenderContextTableCell,
|
|
29
|
-
|
|
29
|
+
_next: NextTableCellRender,
|
|
30
30
|
) {
|
|
31
|
-
const value =
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
31
|
+
const value = _next();
|
|
32
|
+
const items = options.items;
|
|
33
|
+
const itemValue = options.itemValue;
|
|
34
|
+
const itemTitle = options.itemTitle;
|
|
35
|
+
const item = items?.find(item => {
|
|
36
|
+
const itemValue2 = item[String(itemValue)];
|
|
37
|
+
if (String(itemValue2) === String(value)) return true;
|
|
38
|
+
if (typeof itemValue2 !== 'boolean') return false;
|
|
39
|
+
return itemValue2 ? value === 1 || value === '1' : value === 0 || value === '0';
|
|
40
|
+
});
|
|
41
|
+
const value2 = item?.[String(itemTitle)];
|
|
36
42
|
if (!options.class) return value2;
|
|
37
43
|
return <div class={options.class}>{value2}</div>;
|
|
38
44
|
}
|