cabloy 5.1.126 → 5.1.128
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 +37 -0
- package/cabloy-docs/backend/error-guide.md +17 -0
- package/cabloy-docs/backend/i18n-guide.md +86 -7
- package/e2e/specs/a-commerce/commerce.spec.ts +222 -6
- package/package.json +1 -1
- package/vona/env/.env +2 -0
- package/vona/env/.env.prod +6 -2
- package/vona/packages-cli/cabloy-cli/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-utils/module-glob/package.json +1 -1
- package/vona/packages-utils/utils/package.json +1 -1
- package/vona/packages-utils/utils/src/vona.ts +8 -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 +15 -44
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +37 -2
- 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/dto/productSelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.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/entity/product.tsx +23 -3
- 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/model/product.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.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-catalog/test/productPresentation.test.ts +214 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +39 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +14 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +5 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +17 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutCreate.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethod.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethods.tsx +17 -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 +53 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +94 -55
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +65 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentCallback.ts +32 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +26 -8
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +99 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +26 -3
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +214 -82
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +27 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +9 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +5 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +395 -84
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/paypalGateway.ts +129 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +7 -0
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- 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/packages-cli/cli/package.json +3 -3
- package/zova/packages-cli/cli-set-front/package.json +4 -4
- package/zova/packages-utils/zova-jsx/package.json +3 -3
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +16 -4
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +240 -19
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCheckout.ts +16 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +8 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +184 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCheckout.ts +12 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +11 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +25 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +27 -4
- 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
- package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +20 -8
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +292 -32
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +26 -4
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +2 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +12 -0
- package/zova/src/suite-vendor/a-pay/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +4 -4
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import { app } from 'vona-mock';
|
|
5
|
+
import {
|
|
6
|
+
DtoProductCreate,
|
|
7
|
+
DtoProductSelectResItem,
|
|
8
|
+
DtoProductUpdate,
|
|
9
|
+
DtoProductView,
|
|
10
|
+
} from 'vona-module-commerce-catalog';
|
|
11
|
+
|
|
12
|
+
function findComponent(apiJson: any, predicate: (properties: any) => boolean) {
|
|
13
|
+
return Object.values(apiJson.components!.schemas as any).find(item => {
|
|
14
|
+
return predicate((item as any).properties);
|
|
15
|
+
}) as any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getEntryForm(component: any) {
|
|
19
|
+
const pageEntry = component?.rest?.blocks?.[0];
|
|
20
|
+
const formBlock = pageEntry?.options?.blocks?.[0];
|
|
21
|
+
const toolbarBlock = pageEntry?.options?.blocks?.[1];
|
|
22
|
+
assert.equal(pageEntry?.render, 'basic-pageentry:blockPageEntry');
|
|
23
|
+
assert.equal(formBlock?.render, 'basic-pageentry:blockForm');
|
|
24
|
+
assert.equal(toolbarBlock?.render, 'basic-pageentry:blockToolbarRow');
|
|
25
|
+
return toolbarBlock;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('productPresentation.test.ts', () => {
|
|
29
|
+
it('action:product:presentationMetadata', async () => {
|
|
30
|
+
await app.bean.executor.mockCtx(async () => {
|
|
31
|
+
const createJson = await app.bean.openapi.generateJsonOfClass(DtoProductCreate);
|
|
32
|
+
const createComponent = findComponent(createJson, properties => {
|
|
33
|
+
return properties?.title && properties?.categoryId && properties?.published;
|
|
34
|
+
});
|
|
35
|
+
assert.deepEqual(Object.keys(createComponent.properties).sort(), [
|
|
36
|
+
'categoryId',
|
|
37
|
+
'description',
|
|
38
|
+
'published',
|
|
39
|
+
'title',
|
|
40
|
+
]);
|
|
41
|
+
assert.equal(
|
|
42
|
+
createComponent.properties.categoryId.rest.form.render,
|
|
43
|
+
'basic-resource:formFieldResourcePicker',
|
|
44
|
+
);
|
|
45
|
+
assert.deepEqual(createComponent.properties.categoryId.rest.form.options, {
|
|
46
|
+
resource: 'commerce-catalog:category',
|
|
47
|
+
relationName: 'category',
|
|
48
|
+
});
|
|
49
|
+
assert.equal(
|
|
50
|
+
createComponent.properties.published.rest.form.render,
|
|
51
|
+
'basic-select:formFieldSelect',
|
|
52
|
+
);
|
|
53
|
+
assert.deepEqual(
|
|
54
|
+
createComponent.properties.published.rest.form.options.items.map((item: any) => item.value),
|
|
55
|
+
[false, true],
|
|
56
|
+
);
|
|
57
|
+
const createToolbar = getEntryForm(createComponent);
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
createToolbar.options.actions.map((action: any) => action.render),
|
|
60
|
+
['basic-form:actionSubmit', 'basic-form:actionBack'],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const updateJson = await app.bean.openapi.generateJsonOfClass(DtoProductUpdate);
|
|
64
|
+
const updateComponent = findComponent(updateJson, properties => {
|
|
65
|
+
return properties?.title && properties?.categoryId && properties?.published;
|
|
66
|
+
});
|
|
67
|
+
assert.deepEqual(Object.keys(updateComponent.properties).sort(), [
|
|
68
|
+
'categoryId',
|
|
69
|
+
'description',
|
|
70
|
+
'published',
|
|
71
|
+
'title',
|
|
72
|
+
]);
|
|
73
|
+
assert.equal(
|
|
74
|
+
updateComponent.properties.categoryId.rest.form.render,
|
|
75
|
+
'basic-resource:formFieldResourcePicker',
|
|
76
|
+
);
|
|
77
|
+
assert.equal(
|
|
78
|
+
updateComponent.properties.published.rest.form.render,
|
|
79
|
+
'basic-select:formFieldSelect',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const viewJson = await app.bean.openapi.generateJsonOfClass(DtoProductView);
|
|
83
|
+
const viewComponent = findComponent(viewJson, properties => {
|
|
84
|
+
return (
|
|
85
|
+
properties?.id && properties?.categoryId && properties?.category && properties?.published
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
assert.deepEqual(Object.keys(viewComponent.properties.category.properties).sort(), [
|
|
89
|
+
'id',
|
|
90
|
+
'name',
|
|
91
|
+
'published',
|
|
92
|
+
]);
|
|
93
|
+
assert.equal(viewComponent.properties.iid.rest.visible, false);
|
|
94
|
+
assert.equal(viewComponent.properties.deleted.rest.visible, false);
|
|
95
|
+
assert.equal(
|
|
96
|
+
viewComponent.properties.categoryId.rest.form.render,
|
|
97
|
+
'basic-resource:formFieldResourcePicker',
|
|
98
|
+
);
|
|
99
|
+
assert.deepEqual(viewComponent.properties.categoryId.rest.form.options, {
|
|
100
|
+
resource: 'commerce-catalog:category',
|
|
101
|
+
relationName: 'category',
|
|
102
|
+
});
|
|
103
|
+
assert.equal(
|
|
104
|
+
viewComponent.properties.published.rest.form.render,
|
|
105
|
+
'basic-select:formFieldSelect',
|
|
106
|
+
);
|
|
107
|
+
const viewToolbar = getEntryForm(viewComponent);
|
|
108
|
+
assert.deepEqual(
|
|
109
|
+
viewToolbar.options.actions.map((action: any) => action.render),
|
|
110
|
+
['basic-form:actionBack'],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const listJson = await app.bean.openapi.generateJsonOfClass(DtoProductSelectResItem);
|
|
114
|
+
const listComponent = findComponent(listJson, properties => properties?._operationsRow);
|
|
115
|
+
assert.deepEqual(Object.keys(listComponent.properties.category.properties).sort(), [
|
|
116
|
+
'id',
|
|
117
|
+
'name',
|
|
118
|
+
'published',
|
|
119
|
+
]);
|
|
120
|
+
assert.equal(
|
|
121
|
+
listComponent.properties.categoryId.rest.table.render,
|
|
122
|
+
'basic-resource:resourcePicker',
|
|
123
|
+
);
|
|
124
|
+
assert.deepEqual(listComponent.properties.categoryId.rest.table.columnProps, {
|
|
125
|
+
resource: 'commerce-catalog:category',
|
|
126
|
+
relationName: 'category',
|
|
127
|
+
});
|
|
128
|
+
assert.equal(listComponent.properties.published.rest.table.render, 'basic-select:select');
|
|
129
|
+
assert.deepEqual(
|
|
130
|
+
listComponent.properties.published.rest.table.columnProps.items.map(
|
|
131
|
+
(item: any) => item.value,
|
|
132
|
+
),
|
|
133
|
+
[false, true],
|
|
134
|
+
);
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
listComponent.properties._operationsRow.rest.table.columnProps.actions.map(
|
|
137
|
+
(action: any) => action.render,
|
|
138
|
+
),
|
|
139
|
+
['basic-table:actionUpdate', 'basic-table:actionDelete'],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const filterBlock = listComponent.rest.blocks[0].options.blocks[0];
|
|
143
|
+
const filterLayout = filterBlock.options.blocks[0].options.formLayout;
|
|
144
|
+
const section = filterLayout.children[0];
|
|
145
|
+
assert.equal(section.type, 'section');
|
|
146
|
+
assert.equal(section.layout, 'flow');
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
section.children.slice(0, 2).map((field: any) => field.name),
|
|
149
|
+
['title', 'createdAt'],
|
|
150
|
+
);
|
|
151
|
+
assert.equal(section.children[2].block.render, 'basic-page:blockFilterActions');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('action:product:categoryProjection', async () => {
|
|
156
|
+
await app.bean.executor.mockCtx(async () => {
|
|
157
|
+
const suffix = randomUUID().slice(0, 12);
|
|
158
|
+
let categoryId: number | string | undefined;
|
|
159
|
+
let productId: number | string | undefined;
|
|
160
|
+
await app.bean.passport.signinMock();
|
|
161
|
+
try {
|
|
162
|
+
categoryId = await app.bean.executor.performAction('post', '/commerce/catalog/category', {
|
|
163
|
+
body: { name: `presentation-product-category-${suffix}`, published: true },
|
|
164
|
+
});
|
|
165
|
+
productId = await app.bean.executor.performAction('post', '/commerce/catalog/product', {
|
|
166
|
+
body: {
|
|
167
|
+
categoryId,
|
|
168
|
+
title: `presentation-product-${suffix}`,
|
|
169
|
+
published: false,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const view: any = await app.bean.executor.performAction(
|
|
174
|
+
'get',
|
|
175
|
+
'/commerce/catalog/product/:id',
|
|
176
|
+
{
|
|
177
|
+
params: { id: productId },
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
assert.equal(String(view.categoryId), String(categoryId));
|
|
181
|
+
assert.deepEqual(view.category, {
|
|
182
|
+
id: categoryId,
|
|
183
|
+
name: `presentation-product-category-${suffix}`,
|
|
184
|
+
published: true,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const select: any = await app.bean.executor.performAction(
|
|
188
|
+
'get',
|
|
189
|
+
'/commerce/catalog/product',
|
|
190
|
+
);
|
|
191
|
+
const item = select.list.find((entry: any) => String(entry.id) === String(productId));
|
|
192
|
+
assert.ok(item);
|
|
193
|
+
assert.deepEqual(item.category, {
|
|
194
|
+
id: categoryId,
|
|
195
|
+
name: `presentation-product-category-${suffix}`,
|
|
196
|
+
published: true,
|
|
197
|
+
});
|
|
198
|
+
assert.equal(item.published, false);
|
|
199
|
+
} finally {
|
|
200
|
+
if (productId) {
|
|
201
|
+
await app.bean.executor.performAction('delete', '/commerce/catalog/product/:id', {
|
|
202
|
+
params: { id: productId },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (categoryId) {
|
|
206
|
+
await app.bean.executor.performAction('delete', '/commerce/catalog/category/:id', {
|
|
207
|
+
params: { id: categoryId },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
await app.bean.passport.signout();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
EntityPaymentSession,
|
|
3
|
+
IPaymentOutcomeEvent,
|
|
4
|
+
IRefundOutcomeEvent,
|
|
5
|
+
} from 'vona-module-a-pay';
|
|
2
6
|
|
|
3
7
|
import { BeanBase } from 'vona';
|
|
4
8
|
import { PayScene } from 'vona-module-a-pay';
|
|
@@ -8,8 +12,33 @@ import { PayScene } from 'vona-module-a-pay';
|
|
|
8
12
|
{ key: 'mock', providerName: 'pay-mock:mock', clientName: 'default' },
|
|
9
13
|
{ key: 'paypal', providerName: 'pay-paypal:paypal', clientName: 'default' },
|
|
10
14
|
],
|
|
11
|
-
|
|
12
|
-
input.
|
|
15
|
+
isProviderAvailable: (_ctx, input) => {
|
|
16
|
+
if (input.candidate.key === 'mock') return true;
|
|
17
|
+
if (input.candidate.key !== 'paypal') return false;
|
|
18
|
+
const { clientOptions } = _ctx.bean.payProvider.resolveByName(
|
|
19
|
+
input.candidate.providerName,
|
|
20
|
+
input.candidate.clientName,
|
|
21
|
+
);
|
|
22
|
+
const options = clientOptions as {
|
|
23
|
+
secretCredential?: { clientId?: string; clientSecret?: string };
|
|
24
|
+
webhookId?: string;
|
|
25
|
+
merchantReference?: string;
|
|
26
|
+
};
|
|
27
|
+
const server = _ctx.app.config.server.serve;
|
|
28
|
+
return !!(
|
|
29
|
+
options.secretCredential?.clientId &&
|
|
30
|
+
options.secretCredential.clientSecret &&
|
|
31
|
+
options.webhookId &&
|
|
32
|
+
options.merchantReference &&
|
|
33
|
+
server.protocol &&
|
|
34
|
+
server.host
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
resolveProvider: (_ctx, input) => {
|
|
38
|
+
const mock = input.providers.find(item => item.key === 'mock');
|
|
39
|
+
if (!mock) return input.providers[0]!.key;
|
|
40
|
+
return mock.key;
|
|
41
|
+
},
|
|
13
42
|
currencies: ['USD'],
|
|
14
43
|
captureMode: 'automatic',
|
|
15
44
|
sessionExpiresIn: 30 * 60 * 1000,
|
|
@@ -19,11 +48,18 @@ import { PayScene } from 'vona-module-a-pay';
|
|
|
19
48
|
},
|
|
20
49
|
})
|
|
21
50
|
export class PaySceneCommerceOrder extends BeanBase {
|
|
51
|
+
async getPaymentCallbackPath(session: EntityPaymentSession): Promise<string> {
|
|
52
|
+
const attempt = await this.scope.model.paymentAttempt.get({ paymentSessionId: session.id });
|
|
53
|
+
if (!attempt) this.app.throw(404, 'commerce payment attempt not found');
|
|
54
|
+
return `/commerce/payment/${session.id}/${attempt.orderId}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
async onPaymentOutcome(event: IPaymentOutcomeEvent): Promise<void> {
|
|
23
58
|
await this.$scope.commerceTrade.service.order.settlePaymentFromProvider(event);
|
|
24
59
|
}
|
|
25
60
|
|
|
26
61
|
async onRefundOutcome(event: IRefundOutcomeEvent): Promise<void> {
|
|
62
|
+
if (event.businessReference.startsWith('late-capture:')) return;
|
|
27
63
|
await this.$scope.commerceTrade.service.order.settleRefundFromProvider(event);
|
|
28
64
|
}
|
|
29
65
|
}
|
|
@@ -9,7 +9,19 @@ import type { EntityPaymentAttempt } from '../entity/paymentAttempt.tsx';
|
|
|
9
9
|
|
|
10
10
|
@Service()
|
|
11
11
|
export class ServiceCommercePayScene extends BeanBase {
|
|
12
|
-
async
|
|
12
|
+
async availableProviderCandidates(userId: TableIdentity) {
|
|
13
|
+
return await this.bean.payScene.getAvailableProviderCandidates(
|
|
14
|
+
'commerce-payment:commerceOrder',
|
|
15
|
+
{
|
|
16
|
+
userId,
|
|
17
|
+
businessReference: 'checkout-preview',
|
|
18
|
+
amountMinor: 0,
|
|
19
|
+
currency: 'USD',
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async createSession(attempt: EntityPaymentAttempt, providerCandidateKey?: string) {
|
|
13
25
|
const session = await this.$scope.pay.service.paymentSession.create({
|
|
14
26
|
userId: attempt.userId,
|
|
15
27
|
payScene: 'commerce-payment:commerceOrder',
|
|
@@ -17,6 +29,7 @@ export class ServiceCommercePayScene extends BeanBase {
|
|
|
17
29
|
amountMinor: attempt.amountCents,
|
|
18
30
|
currency: attempt.currency,
|
|
19
31
|
correlationId: `${attempt.correlationId}:session`,
|
|
32
|
+
providerCandidateKey,
|
|
20
33
|
});
|
|
21
34
|
await this.scope.model.paymentAttempt.updateById(attempt.id, {
|
|
22
35
|
paymentSessionId: session.id,
|
|
@@ -12,6 +12,7 @@ export interface IPaymentAttemptCreateCommand {
|
|
|
12
12
|
currency: 'USD';
|
|
13
13
|
amountCents: number;
|
|
14
14
|
correlationId: string;
|
|
15
|
+
providerCandidateKey?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type TypePaymentAttemptFinalState = Extract<
|
|
@@ -30,7 +31,10 @@ export class ServicePaymentAttempt extends BeanBase {
|
|
|
30
31
|
amountCents: command.amountCents,
|
|
31
32
|
correlationId: command.correlationId,
|
|
32
33
|
});
|
|
33
|
-
const session = await this.scope.service.commercePayScene.createSession(
|
|
34
|
+
const session = await this.scope.service.commercePayScene.createSession(
|
|
35
|
+
attempt,
|
|
36
|
+
command.providerCandidateKey,
|
|
37
|
+
);
|
|
34
38
|
return {
|
|
35
39
|
...attempt,
|
|
36
40
|
paymentSessionId: session.id,
|
|
@@ -834,6 +834,8 @@ export * from '../dto/cartItem.tsx';
|
|
|
834
834
|
export * from '../dto/cartUpdateItem.tsx';
|
|
835
835
|
export * from '../dto/cartView.tsx';
|
|
836
836
|
export * from '../dto/checkoutCreate.tsx';
|
|
837
|
+
export * from '../dto/checkoutPaymentMethod.tsx';
|
|
838
|
+
export * from '../dto/checkoutPaymentMethods.tsx';
|
|
837
839
|
export * from '../dto/checkoutResult.tsx';
|
|
838
840
|
export * from '../dto/orderAddressSnapshot.tsx';
|
|
839
841
|
export * from '../dto/orderCouponSnapshot.tsx';
|
|
@@ -872,6 +874,8 @@ import type { IDtoOptionsCartItem } from '../dto/cartItem.tsx';
|
|
|
872
874
|
import type { IDtoOptionsCartUpdateItem } from '../dto/cartUpdateItem.tsx';
|
|
873
875
|
import type { IDtoOptionsCartView } from '../dto/cartView.tsx';
|
|
874
876
|
import type { IDtoOptionsCheckoutCreate } from '../dto/checkoutCreate.tsx';
|
|
877
|
+
import type { IDtoOptionsCheckoutPaymentMethod } from '../dto/checkoutPaymentMethod.tsx';
|
|
878
|
+
import type { IDtoOptionsCheckoutPaymentMethods } from '../dto/checkoutPaymentMethods.tsx';
|
|
875
879
|
import type { IDtoOptionsCheckoutResult } from '../dto/checkoutResult.tsx';
|
|
876
880
|
import type { IDtoOptionsOrderAddressSnapshot } from '../dto/orderAddressSnapshot.tsx';
|
|
877
881
|
import type { IDtoOptionsOrderCouponSnapshot } from '../dto/orderCouponSnapshot.tsx';
|
|
@@ -914,6 +918,8 @@ declare module 'vona-module-a-web' {
|
|
|
914
918
|
'commerce-trade:cartUpdateItem': IDtoOptionsCartUpdateItem;
|
|
915
919
|
'commerce-trade:cartView': IDtoOptionsCartView;
|
|
916
920
|
'commerce-trade:checkoutCreate': IDtoOptionsCheckoutCreate;
|
|
921
|
+
'commerce-trade:checkoutPaymentMethod': IDtoOptionsCheckoutPaymentMethod;
|
|
922
|
+
'commerce-trade:checkoutPaymentMethods': IDtoOptionsCheckoutPaymentMethods;
|
|
917
923
|
'commerce-trade:checkoutResult': IDtoOptionsCheckoutResult;
|
|
918
924
|
'commerce-trade:orderAddressSnapshot': IDtoOptionsOrderAddressSnapshot;
|
|
919
925
|
'commerce-trade:orderCouponSnapshot': IDtoOptionsOrderCouponSnapshot;
|
|
@@ -961,6 +967,8 @@ import type { DtoCartItem } from '../dto/cartItem.tsx';
|
|
|
961
967
|
import type { DtoCartUpdateItem } from '../dto/cartUpdateItem.tsx';
|
|
962
968
|
import type { DtoCartView } from '../dto/cartView.tsx';
|
|
963
969
|
import type { DtoCheckoutCreate } from '../dto/checkoutCreate.tsx';
|
|
970
|
+
import type { DtoCheckoutPaymentMethod } from '../dto/checkoutPaymentMethod.tsx';
|
|
971
|
+
import type { DtoCheckoutPaymentMethods } from '../dto/checkoutPaymentMethods.tsx';
|
|
964
972
|
import type { DtoCheckoutResult } from '../dto/checkoutResult.tsx';
|
|
965
973
|
import type { DtoOrderAddressSnapshot } from '../dto/orderAddressSnapshot.tsx';
|
|
966
974
|
import type { DtoOrderCouponSnapshot } from '../dto/orderCouponSnapshot.tsx';
|
|
@@ -1016,6 +1024,14 @@ declare module 'vona-module-commerce-trade' {
|
|
|
1016
1024
|
fields?: TypeEntityOptionsFields<DtoCheckoutCreate, IDtoOptionsCheckoutCreate[TypeSymbolKeyFieldsMore]>;
|
|
1017
1025
|
}
|
|
1018
1026
|
|
|
1027
|
+
export interface IDtoOptionsCheckoutPaymentMethod {
|
|
1028
|
+
fields?: TypeEntityOptionsFields<DtoCheckoutPaymentMethod, IDtoOptionsCheckoutPaymentMethod[TypeSymbolKeyFieldsMore]>;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export interface IDtoOptionsCheckoutPaymentMethods {
|
|
1032
|
+
fields?: TypeEntityOptionsFields<DtoCheckoutPaymentMethods, IDtoOptionsCheckoutPaymentMethods[TypeSymbolKeyFieldsMore]>;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1019
1035
|
export interface IDtoOptionsCheckoutResult {
|
|
1020
1036
|
fields?: TypeEntityOptionsFields<DtoCheckoutResult, IDtoOptionsCheckoutResult[TypeSymbolKeyFieldsMore]>;
|
|
1021
1037
|
}
|
|
@@ -1267,6 +1283,7 @@ declare module 'vona-module-commerce-trade' {
|
|
|
1267
1283
|
declare module 'vona-module-a-web' {
|
|
1268
1284
|
export interface IApiPathGetRecord{
|
|
1269
1285
|
'/commerce/trade/cart': undefined;
|
|
1286
|
+
'/commerce/trade/checkout/payment-methods': undefined;
|
|
1270
1287
|
'/commerce/trade/order/mine': undefined;
|
|
1271
1288
|
'/commerce/trade/order/viewMine/:id': undefined;
|
|
1272
1289
|
'/commerce/trade/order': undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IUser } from 'vona-module-a-user';
|
|
1
2
|
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
2
3
|
|
|
3
4
|
import { BeanBase } from 'vona';
|
|
@@ -5,12 +6,29 @@ import { Api } from 'vona-module-a-openapiutils';
|
|
|
5
6
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
6
7
|
|
|
7
8
|
import { DtoCheckoutCreate } from '../dto/checkoutCreate.tsx';
|
|
9
|
+
import { DtoCheckoutPaymentMethods } from '../dto/checkoutPaymentMethods.tsx';
|
|
8
10
|
import { DtoCheckoutResult } from '../dto/checkoutResult.tsx';
|
|
9
11
|
|
|
10
12
|
export interface IControllerOptionsCheckout extends IDecoratorControllerOptions {}
|
|
11
13
|
|
|
12
14
|
@Controller<IControllerOptionsCheckout>('checkout')
|
|
13
15
|
export class ControllerCheckout extends BeanBase {
|
|
16
|
+
@Web.get('payment-methods')
|
|
17
|
+
@Api.body(DtoCheckoutPaymentMethods)
|
|
18
|
+
async paymentMethods(@Arg.user() user: IUser): Promise<DtoCheckoutPaymentMethods> {
|
|
19
|
+
const candidates =
|
|
20
|
+
await this.$scope.commercePayment.service.commercePayScene.availableProviderCandidates(
|
|
21
|
+
user.id,
|
|
22
|
+
);
|
|
23
|
+
const items = candidates.map(candidate => ({
|
|
24
|
+
key: candidate.key,
|
|
25
|
+
label: candidate.key === 'paypal' ? 'PayPal' : 'Mock payment',
|
|
26
|
+
interaction: candidate.key === 'paypal' ? ('redirect' as const) : ('embedded' as const),
|
|
27
|
+
}));
|
|
28
|
+
const defaultKey = items.find(item => item.key === 'mock')?.key ?? items[0]!.key;
|
|
29
|
+
return { items, defaultKey };
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
@Web.post()
|
|
15
33
|
@Api.body(DtoCheckoutResult)
|
|
16
34
|
async create(@Arg.body() command: DtoCheckoutCreate): Promise<DtoCheckoutResult> {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
|
+
|
|
3
|
+
import { Api, v } from 'vona-module-a-openapiutils';
|
|
4
|
+
import { Dto } from 'vona-module-a-web';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
export interface IDtoOptionsCheckoutPaymentMethod extends IDecoratorDtoOptions {}
|
|
8
|
+
|
|
9
|
+
@Dto<IDtoOptionsCheckoutPaymentMethod>()
|
|
10
|
+
export class DtoCheckoutPaymentMethod {
|
|
11
|
+
@Api.field(v.required(), v.min(1), v.max(100))
|
|
12
|
+
key: string;
|
|
13
|
+
|
|
14
|
+
@Api.field(v.required(), v.min(1), v.max(100))
|
|
15
|
+
label: string;
|
|
16
|
+
|
|
17
|
+
@Api.field(z.enum(['redirect', 'embedded']))
|
|
18
|
+
interaction: 'redirect' | 'embedded';
|
|
19
|
+
}
|