@chevre/domain 23.0.0-alpha.14 → 23.0.0-alpha.17
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/example/src/chevre/assetTransaction/processReserve.ts +102 -40
- package/example/src/chevre/roles/removeConsolePermissionIfExists.ts +1 -1
- package/example/src/chevre/roles/removePermissionIfExists.ts +1 -6
- package/example/src/signPayload.ts +12 -7
- package/lib/chevre/repo/categoryCode.d.ts +45 -6
- package/lib/chevre/repo/categoryCode.js +35 -6
- package/lib/chevre/service/assetTransaction/reserve/start.js +2 -2
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest/validateIssuedOfferIfExists.d.ts +15 -0
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest/validateIssuedOfferIfExists.js +159 -0
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest/validateMemberTierIfExists.d.ts +16 -0
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest/validateMemberTierIfExists.js +184 -0
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest.d.ts +4 -1
- package/lib/chevre/service/assetTransaction/reserve/validateStartRequest.js +25 -139
- package/lib/chevre/service/event.js +1 -1
- package/lib/chevre/service/offer/eventServiceByCOA/authorize.js +3 -3
- package/lib/chevre/service/offer/eventServiceByCOA/changeOffers.js +3 -3
- package/lib/chevre/service/task/onResourceUpdated/syncCategoryCode.js +1 -1
- package/lib/chevre/service/task/onResourceUpdated.js +1 -1
- package/package.json +3 -3
- package/example/src/chevre/categoryCode/findCategoryCodes.ts +0 -29
- package/example/src/chevre/searchCategoryCodesByAggregate.ts +0 -31
|
@@ -6,8 +6,16 @@ import * as redis from 'redis';
|
|
|
6
6
|
import { chevre } from '../../../../lib/index';
|
|
7
7
|
|
|
8
8
|
const project = { id: String(process.env.PROJECT_ID) };
|
|
9
|
+
const { EVENT_ID, OFFER_ID, APPLICATION_ID, OFFER_TOKEN } = process.env;
|
|
9
10
|
|
|
11
|
+
// tslint:disable-next-line:max-func-body-length
|
|
10
12
|
async function main() {
|
|
13
|
+
if (typeof EVENT_ID !== 'string'
|
|
14
|
+
|| typeof OFFER_ID !== 'string'
|
|
15
|
+
|| typeof APPLICATION_ID !== 'string') {
|
|
16
|
+
throw new Error('environment variables required');
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
const redisClient = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
|
|
12
20
|
socket: {
|
|
13
21
|
port: Number(<string>process.env.REDIS_PORT),
|
|
@@ -21,59 +29,113 @@ async function main() {
|
|
|
21
29
|
const transactionNumberRepo = await chevre.repository.TransactionNumber.createInstance({
|
|
22
30
|
connection: mongoose.connection
|
|
23
31
|
});
|
|
24
|
-
const assetTransactionRepo = await chevre.repository.AssetTransaction.createInstance(mongoose.connection);
|
|
25
|
-
|
|
26
32
|
const { transactionNumber } = await transactionNumberRepo.publishByTimestamp({ startDate: new Date() });
|
|
27
33
|
const provider: chevre.factory.reservation.IProvider = {
|
|
28
34
|
id: '59d20831e53ebc2b4e774466',
|
|
29
35
|
typeOf: chevre.factory.organizationType.Corporation
|
|
30
36
|
};
|
|
31
|
-
const reserveTransaction = await assetTransactionRepo.start<chevre.factory.assetTransactionType.Reserve>({
|
|
32
|
-
project: { id: project.id, typeOf: chevre.factory.organizationType.Project },
|
|
33
|
-
typeOf: chevre.factory.assetTransactionType.Reserve,
|
|
34
|
-
transactionNumber,
|
|
35
|
-
agent: { ...provider, name: 'sample agent name' },
|
|
36
|
-
object: {
|
|
37
|
-
provider,
|
|
38
|
-
reservationNumber: transactionNumber,
|
|
39
|
-
reservationStatus: chevre.factory.reservationStatusType.ReservationPending,
|
|
40
|
-
disablePendingReservations: true,
|
|
41
|
-
useHoldStockByTransactionNumber: true,
|
|
42
|
-
typeOf: chevre.factory.reservationType.ReservationPackage
|
|
43
|
-
},
|
|
44
|
-
expires: moment()
|
|
45
|
-
.add(1, 'minute')
|
|
46
|
-
.toDate()
|
|
47
|
-
});
|
|
48
|
-
let cancelResult = await assetTransactionRepo.cancel<chevre.factory.assetTransactionType.Reserve>({
|
|
49
|
-
typeOf: reserveTransaction.typeOf,
|
|
50
|
-
id: reserveTransaction.id
|
|
51
|
-
});
|
|
52
|
-
console.log('cancelResult:', cancelResult);
|
|
53
|
-
cancelResult = await assetTransactionRepo.cancel<chevre.factory.assetTransactionType.Reserve>({
|
|
54
|
-
typeOf: reserveTransaction.typeOf,
|
|
55
|
-
id: reserveTransaction.id
|
|
56
|
-
});
|
|
57
|
-
console.log('cancelResult:', cancelResult);
|
|
58
37
|
|
|
59
|
-
|
|
38
|
+
const { transaction } = await (await chevre.service.assetTransaction.createService()).reserve.start(
|
|
60
39
|
{
|
|
40
|
+
project: { id: project.id, typeOf: chevre.factory.organizationType.Project },
|
|
61
41
|
typeOf: chevre.factory.assetTransactionType.Reserve,
|
|
62
|
-
|
|
42
|
+
transactionNumber,
|
|
43
|
+
agent: { ...provider, name: 'sample agent name' },
|
|
44
|
+
object: {
|
|
45
|
+
acceptedOffer: [{
|
|
46
|
+
/**
|
|
47
|
+
* オファーID
|
|
48
|
+
*/
|
|
49
|
+
id: OFFER_ID,
|
|
50
|
+
/**
|
|
51
|
+
* アイテム情報
|
|
52
|
+
* 予約の詳細指定など
|
|
53
|
+
*/
|
|
54
|
+
itemOffered: {
|
|
55
|
+
serviceOutput: {
|
|
56
|
+
typeOf: chevre.factory.reservationType.EventReservation,
|
|
57
|
+
reservedTicket: {
|
|
58
|
+
/**
|
|
59
|
+
* チケット識別子
|
|
60
|
+
* 指定された場合、決済カードの対象チケット識別子と関連付けされます
|
|
61
|
+
* /^[0-9a-zA-Z]{8,16}$/
|
|
62
|
+
*/
|
|
63
|
+
// identifier?: string;
|
|
64
|
+
// issuedBy?: ReservationFactory.IUnderName;
|
|
65
|
+
typeOf: 'Ticket',
|
|
66
|
+
ticketedSeat: {
|
|
67
|
+
typeOf: chevre.factory.placeType.Seat,
|
|
68
|
+
seatNumber: 'A-01',
|
|
69
|
+
seatRow: '',
|
|
70
|
+
seatSection: 'Default'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}],
|
|
76
|
+
reservationFor: {
|
|
77
|
+
id: EVENT_ID,
|
|
78
|
+
offers: {
|
|
79
|
+
token: OFFER_TOKEN,
|
|
80
|
+
identifier: 'LegacyReservation'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
expires: moment()
|
|
85
|
+
// tslint:disable-next-line:no-magic-numbers
|
|
86
|
+
.add(5, 'seconds')
|
|
87
|
+
.toDate(),
|
|
88
|
+
validateEvent: false,
|
|
89
|
+
validateEventOfferPeriod: true,
|
|
90
|
+
validateAppliesToMovieTicket: true,
|
|
91
|
+
instrument: [],
|
|
92
|
+
availableAtOrFrom: { id: APPLICATION_ID }
|
|
93
|
+
// stockHoldUntilDaysAfterEventEnd: STOCK_HOLD_UNTIL_DAYS_AFTER_EVENT_END
|
|
63
94
|
},
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
-
console.log('transaction:', transaction);
|
|
67
|
-
|
|
68
|
-
transaction = await assetTransactionRepo.findByTransactionNumber<chevre.factory.assetTransactionType.Reserve>(
|
|
95
|
+
{ maxReservationGracePeriodInDays: 93 }
|
|
96
|
+
)(
|
|
69
97
|
{
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
advanceBookingRequirement: await chevre.repository.AdvanceBookingRequirement.createInstance(mongoose.connection),
|
|
99
|
+
project: await chevre.repository.Project.createInstance(mongoose.connection),
|
|
100
|
+
stockHolder: await chevre.repository.StockHolder.createInstance({ connection: mongoose.connection }),
|
|
101
|
+
event: await chevre.repository.Event.createInstance(mongoose.connection),
|
|
102
|
+
eventSeries: await chevre.repository.EventSeries.createInstance(mongoose.connection),
|
|
103
|
+
issuer: await chevre.repository.Issuer.createInstance(mongoose.connection),
|
|
104
|
+
memberProgram: await chevre.repository.MemberProgram.createInstance(mongoose.connection),
|
|
105
|
+
offer: await chevre.repository.Offer.createInstance(mongoose.connection),
|
|
106
|
+
offerCatalog: await chevre.repository.OfferCatalog.createInstance(mongoose.connection),
|
|
107
|
+
offerCatalogItem: await chevre.repository.OfferCatalogItem.createInstance(mongoose.connection),
|
|
108
|
+
offerRateLimit: await chevre.repository.rateLimit.Offer.createInstance(redisClient),
|
|
109
|
+
paymentService: await chevre.repository.PaymentService.createInstance(mongoose.connection),
|
|
110
|
+
priceSpecification: await chevre.repository.PriceSpecification.createInstance(mongoose.connection),
|
|
111
|
+
product: await chevre.repository.Product.createInstance(mongoose.connection),
|
|
112
|
+
productOffer: await chevre.repository.ProductOffer.createInstance(mongoose.connection),
|
|
113
|
+
seat: await chevre.repository.place.Seat.createInstance(mongoose.connection),
|
|
114
|
+
setting: await chevre.repository.Setting.createInstance(mongoose.connection),
|
|
115
|
+
task: await chevre.repository.Task.createInstance(mongoose.connection),
|
|
116
|
+
assetTransaction: await chevre.repository.AssetTransaction.createInstance(mongoose.connection)
|
|
72
117
|
},
|
|
73
|
-
|
|
118
|
+
new chevre.settings.Settings({
|
|
119
|
+
abortedTasksWithoutReport: [],
|
|
120
|
+
numTryConfirmReserveTransaction: 10,
|
|
121
|
+
deliverOrderLimit: 1,
|
|
122
|
+
coa: { timeout: 20000 },
|
|
123
|
+
gmo: {
|
|
124
|
+
timeout: 5000,
|
|
125
|
+
timeoutBackground: 5000,
|
|
126
|
+
useFetch: true
|
|
127
|
+
},
|
|
128
|
+
movieticketReserve: {
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
timeoutCheck: 5000,
|
|
131
|
+
minIntervalBetweenPayAndRefund: 1,
|
|
132
|
+
credentialsExpireInSeconds: 300
|
|
133
|
+
},
|
|
134
|
+
// useAssetTransactionSyncProcessing: process.env.USE_ASSET_TRANSACTION_SYNC_PROCESSING === '1',
|
|
135
|
+
useExperimentalFeature: false
|
|
136
|
+
})
|
|
74
137
|
);
|
|
75
138
|
console.log('transaction:', transaction);
|
|
76
|
-
|
|
77
139
|
}
|
|
78
140
|
|
|
79
141
|
main()
|
|
@@ -9,7 +9,7 @@ async function main() {
|
|
|
9
9
|
const roleRepo = await chevre.repository.Role.createInstance(mongoose.connection);
|
|
10
10
|
|
|
11
11
|
const permissions = [
|
|
12
|
-
'
|
|
12
|
+
'products.read'
|
|
13
13
|
];
|
|
14
14
|
for (const permission of permissions) {
|
|
15
15
|
const roles = await roleRepo.projectFields(
|
|
@@ -9,12 +9,7 @@ async function main() {
|
|
|
9
9
|
const roleRepo = await chevre.repository.Role.createInstance(mongoose.connection);
|
|
10
10
|
|
|
11
11
|
const permissions = [
|
|
12
|
-
'
|
|
13
|
-
'authorizations.create',
|
|
14
|
-
'permits.read',
|
|
15
|
-
'tasks.read',
|
|
16
|
-
'transactionNumbers.write',
|
|
17
|
-
'chevre.admin'
|
|
12
|
+
'admin.sellers.productOffers.*'
|
|
18
13
|
];
|
|
19
14
|
for (const permission of permissions) {
|
|
20
15
|
const roles = await roleRepo.projectFields(
|
|
@@ -3,13 +3,18 @@ import * as jwt from 'jsonwebtoken';
|
|
|
3
3
|
|
|
4
4
|
async function main(): Promise<void> {
|
|
5
5
|
const payload = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
/**
|
|
7
|
+
* アプリケーションオファーコード
|
|
8
|
+
*/
|
|
9
|
+
identifier: 'LegacyReservation',
|
|
10
|
+
validFrom: '2025-10-20T00:00:00Z',
|
|
11
|
+
validThrough: '2025-10-25T00:00:00Z',
|
|
12
|
+
eligibleQuantity: { maxValue: 1 },
|
|
13
|
+
itemOffered: {
|
|
14
|
+
/**
|
|
15
|
+
* イベント識別子
|
|
16
|
+
*/
|
|
17
|
+
identifier: '251025001001010900'
|
|
13
18
|
}
|
|
14
19
|
};
|
|
15
20
|
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { AnyExpression, Connection, FilterQuery, Types } from 'mongoose';
|
|
2
2
|
import * as factory from '../factory';
|
|
3
|
-
type IKeyOfProjection = keyof factory.categoryCode.ICategoryCode;
|
|
4
3
|
type IUnset = {
|
|
5
4
|
[key in keyof Pick<factory.categoryCode.ICategoryCode, 'additionalProperty' | 'color' | 'image' | 'paymentMethod'>]?: 1;
|
|
6
5
|
};
|
|
6
|
+
type CategorySetIdentifierExceptMovieTicketType = Exclude<factory.categoryCode.CategorySetIdentifier, factory.categoryCode.CategorySetIdentifier.MovieTicketType>;
|
|
7
|
+
type ICategoryCodeExceptMovieTicketType = Pick<factory.categoryCode.ICategoryCode, 'additionalProperty' | 'codeValue' | 'color' | 'id' | 'image' | 'name' | 'project' | 'typeOf'> & {
|
|
8
|
+
inCodeSet: {
|
|
9
|
+
typeOf: 'CategoryCodeSet';
|
|
10
|
+
identifier: CategorySetIdentifierExceptMovieTicketType;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
type IMovieTicketType = Pick<factory.categoryCode.ICategoryCode, 'additionalProperty' | 'codeValue' | 'color' | 'id' | 'image' | 'name' | 'project' | 'typeOf' | 'paymentMethod'> & {
|
|
14
|
+
inCodeSet: {
|
|
15
|
+
typeOf: 'CategoryCodeSet';
|
|
16
|
+
identifier: factory.categoryCode.CategorySetIdentifier.MovieTicketType;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type IKeyOfProjection = keyof factory.categoryCode.ICategoryCode;
|
|
20
|
+
type IKeyOfProjectionExceptMovieTicketType = keyof ICategoryCodeExceptMovieTicketType;
|
|
7
21
|
/**
|
|
8
22
|
* 区分リポジトリ
|
|
9
23
|
*/
|
|
@@ -17,19 +31,44 @@ export declare class CategoryCodeRepo {
|
|
|
17
31
|
[field: string]: AnyExpression;
|
|
18
32
|
};
|
|
19
33
|
/**
|
|
20
|
-
*
|
|
34
|
+
* 区分集計検索(publicな属性検索が目的)
|
|
21
35
|
*/
|
|
22
|
-
|
|
36
|
+
findCategoryCodesByAggregate(conditions: factory.categoryCode.ISearchConditions, inclusion: IKeyOfProjectionExceptMovieTicketType[]): Promise<(ICategoryCodeExceptMovieTicketType & {
|
|
37
|
+
id: string;
|
|
38
|
+
})[]>;
|
|
23
39
|
/**
|
|
24
|
-
*
|
|
40
|
+
* 区分検索
|
|
25
41
|
*/
|
|
26
|
-
|
|
42
|
+
projectCategoryCodeFields(params: Omit<factory.categoryCode.ISearchConditions, 'inCodeSet' | 'paymentMethod'> & {
|
|
43
|
+
inCodeSet?: {
|
|
44
|
+
identifier?: {
|
|
45
|
+
$eq?: CategorySetIdentifierExceptMovieTicketType;
|
|
46
|
+
$in?: CategorySetIdentifierExceptMovieTicketType[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
},
|
|
27
50
|
/**
|
|
28
51
|
* 空の場合無効
|
|
29
52
|
*/
|
|
30
53
|
inclusion: IKeyOfProjection[], options?: {
|
|
31
54
|
excludeMovieTicketType?: boolean;
|
|
32
|
-
}): Promise<(
|
|
55
|
+
}): Promise<(ICategoryCodeExceptMovieTicketType & {
|
|
56
|
+
id: string;
|
|
57
|
+
})[]>;
|
|
58
|
+
/**
|
|
59
|
+
* 決済カード区分検索
|
|
60
|
+
*/
|
|
61
|
+
projectMovieTicketTypeFields(params: Omit<factory.categoryCode.ISearchConditions, 'inCodeSet'> & {
|
|
62
|
+
inCodeSet?: {
|
|
63
|
+
identifier?: {
|
|
64
|
+
$eq?: factory.categoryCode.CategorySetIdentifier.MovieTicketType;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* 空の場合無効
|
|
70
|
+
*/
|
|
71
|
+
inclusion: IKeyOfProjection[]): Promise<(IMovieTicketType & {
|
|
33
72
|
id: string;
|
|
34
73
|
})[]>;
|
|
35
74
|
save(params: {
|
|
@@ -192,9 +192,9 @@ class CategoryCodeRepo {
|
|
|
192
192
|
return projectStage;
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
|
-
*
|
|
195
|
+
* 区分集計検索(publicな属性検索が目的)
|
|
196
196
|
*/
|
|
197
|
-
|
|
197
|
+
findCategoryCodesByAggregate(conditions, inclusion) {
|
|
198
198
|
return __awaiter(this, void 0, void 0, function* () {
|
|
199
199
|
var _a;
|
|
200
200
|
const matchStages = CategoryCodeRepo.CREATE_MONGO_CONDITIONS(conditions)
|
|
@@ -208,8 +208,6 @@ class CategoryCodeRepo {
|
|
|
208
208
|
: [],
|
|
209
209
|
{ $project: projectStage }
|
|
210
210
|
]);
|
|
211
|
-
// tslint:disable-next-line:no-single-line-block-comment
|
|
212
|
-
/* istanbul ignore else */
|
|
213
211
|
if (typeof conditions.limit === 'number' && conditions.limit > 0) {
|
|
214
212
|
const page = (typeof conditions.page === 'number' && conditions.page > 0) ? conditions.page : 1;
|
|
215
213
|
aggregate.limit(conditions.limit * page)
|
|
@@ -219,9 +217,9 @@ class CategoryCodeRepo {
|
|
|
219
217
|
});
|
|
220
218
|
}
|
|
221
219
|
/**
|
|
222
|
-
*
|
|
220
|
+
* 区分検索
|
|
223
221
|
*/
|
|
224
|
-
|
|
222
|
+
projectCategoryCodeFields(params,
|
|
225
223
|
/**
|
|
226
224
|
* 空の場合無効
|
|
227
225
|
*/
|
|
@@ -249,6 +247,37 @@ class CategoryCodeRepo {
|
|
|
249
247
|
.exec();
|
|
250
248
|
});
|
|
251
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* 決済カード区分検索
|
|
252
|
+
*/
|
|
253
|
+
projectMovieTicketTypeFields(params,
|
|
254
|
+
/**
|
|
255
|
+
* 空の場合無効
|
|
256
|
+
*/
|
|
257
|
+
inclusion) {
|
|
258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
const conditions = CategoryCodeRepo.CREATE_MONGO_CONDITIONS(params);
|
|
260
|
+
let positiveProjectionFields = AVAILABLE_PROJECT_FIELDS;
|
|
261
|
+
if (Array.isArray(inclusion) && inclusion.length > 0) {
|
|
262
|
+
positiveProjectionFields = inclusion.filter((key) => AVAILABLE_PROJECT_FIELDS.includes(key));
|
|
263
|
+
}
|
|
264
|
+
const projection = Object.assign({ _id: 0, id: { $toString: '$_id' } }, Object.fromEntries(positiveProjectionFields.map((key) => ([key, 1]))));
|
|
265
|
+
const query = this.categoryCodeModel.find((conditions.length > 0) ? { $and: conditions } : {}, projection);
|
|
266
|
+
if (typeof params.limit === 'number' && params.limit > 0) {
|
|
267
|
+
const page = (typeof params.page === 'number' && params.page > 0) ? params.page : 1;
|
|
268
|
+
query.limit(params.limit)
|
|
269
|
+
.skip(params.limit * (page - 1));
|
|
270
|
+
}
|
|
271
|
+
// tslint:disable-next-line:no-single-line-block-comment
|
|
272
|
+
/* istanbul ignore else */
|
|
273
|
+
if (params.sort !== undefined) {
|
|
274
|
+
query.sort(params.sort);
|
|
275
|
+
}
|
|
276
|
+
return query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
|
|
277
|
+
.lean() // 2024-08-19~
|
|
278
|
+
.exec();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
252
281
|
save(params) {
|
|
253
282
|
return __awaiter(this, void 0, void 0, function* () {
|
|
254
283
|
let savedId;
|
|
@@ -100,9 +100,9 @@ function fixEvent(params) {
|
|
|
100
100
|
// additionalProperty, // discontinue(2024-07-20~)
|
|
101
101
|
'name', 'doorTime', 'endDate', 'eventStatus',
|
|
102
102
|
'location', 'startDate', 'superEvent',
|
|
103
|
-
'offers', 'maximumPhysicalAttendeeCapacity'
|
|
103
|
+
'offers', 'maximumPhysicalAttendeeCapacity',
|
|
104
|
+
'identifier' // support identifier(2025-10-24~)
|
|
104
105
|
// 'coaInfo', // discontinue(2024-07-24~)
|
|
105
|
-
// 'identifier' // discontinue(2024-07-24~)
|
|
106
106
|
]);
|
|
107
107
|
const offeredThroughIdentifier = (_b = event.offers.offeredThrough) === null || _b === void 0 ? void 0 : _b.identifier;
|
|
108
108
|
if (offeredThroughIdentifier === factory.service.webAPI.Identifier.COA) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as factory from '../../../../factory';
|
|
2
|
+
import { IMinimizedIndividualEvent } from '../../../../factory/event';
|
|
3
|
+
import type { IssuerRepo } from '../../../../repo/issuer';
|
|
4
|
+
/**
|
|
5
|
+
* オファートークン検証(2025-10-21~)
|
|
6
|
+
*/
|
|
7
|
+
declare function validateIssuedOfferIfExists(params: {
|
|
8
|
+
event: Pick<IMinimizedIndividualEvent, 'offers' | 'id' | 'project' | 'identifier'>;
|
|
9
|
+
now: Date;
|
|
10
|
+
object: factory.assetTransaction.reserve.IObjectWithoutDetail;
|
|
11
|
+
makesOfferOnApplication: factory.event.screeningEvent.ISellerMakesOffer;
|
|
12
|
+
}): (repos: {
|
|
13
|
+
issuer: IssuerRepo;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
export { validateIssuedOfferIfExists };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.validateIssuedOfferIfExists = validateIssuedOfferIfExists;
|
|
13
|
+
const jwt = require("jsonwebtoken");
|
|
14
|
+
const moment = require("moment");
|
|
15
|
+
const factory = require("../../../../factory");
|
|
16
|
+
const NUM_ACCEPTED_OFFERS_MAX_VALUE = 50;
|
|
17
|
+
function verifyOfferToken(params) {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
let result;
|
|
20
|
+
const { issuer, secret, token } = params;
|
|
21
|
+
try {
|
|
22
|
+
result = yield new Promise((resolve, reject) => {
|
|
23
|
+
jwt.verify(token, secret, {
|
|
24
|
+
algorithms: ['HS256'],
|
|
25
|
+
issuer
|
|
26
|
+
// ...(Array.isArray(params.audience)) ? { audience: params.audience } : undefined
|
|
27
|
+
}, (err, decoded) => {
|
|
28
|
+
if (err instanceof Error) {
|
|
29
|
+
reject(err);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
resolve(decoded);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// JWTエラーをハンドリング
|
|
39
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
40
|
+
throw new factory.errors.Argument('reservationFor.offers.token', `invalid token. [${error.message} expiredAt:${error.expiredAt}]`);
|
|
41
|
+
}
|
|
42
|
+
else if (error instanceof jwt.JsonWebTokenError) {
|
|
43
|
+
throw new factory.errors.Argument('reservationFor.offers.token', `invalid token. [${error.message}]`);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const OFFER_TOKEN_DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
|
|
51
|
+
function validateOfferToken(params) {
|
|
52
|
+
return () => __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
const { event, acceptedDate, verifiedOffer, makesOfferOnApplication } = params;
|
|
55
|
+
// イベント識別子一致検証
|
|
56
|
+
const itemOfferedIdentifier = verifiedOffer.itemOffered.identifier;
|
|
57
|
+
if (typeof itemOfferedIdentifier !== 'string' || itemOfferedIdentifier === '') {
|
|
58
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'itemOffered.identifier must be string');
|
|
59
|
+
}
|
|
60
|
+
const itemOfferedIdentifierMustBe = event.identifier;
|
|
61
|
+
if (typeof itemOfferedIdentifierMustBe !== 'string') {
|
|
62
|
+
throw new factory.errors.NotFound('identifier', 'the event must have an identifier');
|
|
63
|
+
}
|
|
64
|
+
if (itemOfferedIdentifier !== itemOfferedIdentifierMustBe) {
|
|
65
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'itemOffered.identifier not matched');
|
|
66
|
+
}
|
|
67
|
+
// アプリケーションオファーコード一致検証
|
|
68
|
+
const applicationOfferIdentifier = verifiedOffer.identifier;
|
|
69
|
+
if (typeof applicationOfferIdentifier !== 'string' || applicationOfferIdentifier === '') {
|
|
70
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'identifier must be string');
|
|
71
|
+
}
|
|
72
|
+
const applicationOfferIdentifierMustBe = makesOfferOnApplication.identifier;
|
|
73
|
+
if (typeof applicationOfferIdentifierMustBe !== 'string') {
|
|
74
|
+
throw new factory.errors.NotFound('makesOffer.identifier');
|
|
75
|
+
}
|
|
76
|
+
if (applicationOfferIdentifier !== applicationOfferIdentifierMustBe) {
|
|
77
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'identifier not matched');
|
|
78
|
+
}
|
|
79
|
+
// 有効期間検証
|
|
80
|
+
let validThroughMoment;
|
|
81
|
+
let validFromMoment;
|
|
82
|
+
validThroughMoment = moment(verifiedOffer.validThrough, OFFER_TOKEN_DATE_FORMAT, true);
|
|
83
|
+
validFromMoment = moment(verifiedOffer.validFrom, OFFER_TOKEN_DATE_FORMAT, true);
|
|
84
|
+
if (!validThroughMoment.isValid()) {
|
|
85
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'invalid validThrough');
|
|
86
|
+
}
|
|
87
|
+
if (!validFromMoment.isValid()) {
|
|
88
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'invalid validFrom');
|
|
89
|
+
}
|
|
90
|
+
if (acceptedDate.isBefore(validFromMoment)) {
|
|
91
|
+
throw new factory.errors.Argument('reservationFor.offers.token', `the offer id valid from ${validFromMoment}`);
|
|
92
|
+
}
|
|
93
|
+
if (acceptedDate.isAfter(validThroughMoment)) {
|
|
94
|
+
throw new factory.errors.Argument('reservationFor.offers.token', `the offer id valid through ${validThroughMoment}`);
|
|
95
|
+
}
|
|
96
|
+
// maxValueを検証
|
|
97
|
+
const maxValue = (_a = verifiedOffer === null || verifiedOffer === void 0 ? void 0 : verifiedOffer.eligibleQuantity) === null || _a === void 0 ? void 0 : _a.maxValue;
|
|
98
|
+
if (typeof maxValue !== 'number') {
|
|
99
|
+
throw new factory.errors.Argument('reservationFor.offers.token', 'eligibleQuantity.maxValue must be number');
|
|
100
|
+
}
|
|
101
|
+
if (maxValue > NUM_ACCEPTED_OFFERS_MAX_VALUE) {
|
|
102
|
+
throw new factory.errors.Argument('reservationFor.offers.token', `eligibleQuantity.maxValue must be <= ${NUM_ACCEPTED_OFFERS_MAX_VALUE}`);
|
|
103
|
+
}
|
|
104
|
+
const numAcceptedOffers = (_b = params.object.acceptedOffer) === null || _b === void 0 ? void 0 : _b.length;
|
|
105
|
+
if (typeof numAcceptedOffers === 'number' && numAcceptedOffers > 0) {
|
|
106
|
+
if (numAcceptedOffers > maxValue) {
|
|
107
|
+
throw new factory.errors.Argument('reservationFor.id', `Maximum number of offers exceeded`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* オファートークン検証(2025-10-21~)
|
|
114
|
+
*/
|
|
115
|
+
function validateIssuedOfferIfExists(params) {
|
|
116
|
+
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
var _a, _b, _c, _d, _e;
|
|
118
|
+
const { event, makesOfferOnApplication } = params;
|
|
119
|
+
const acceptedDate = moment(params.now);
|
|
120
|
+
const offerTokenIssuer = (_a = makesOfferOnApplication.offeredBy) === null || _a === void 0 ? void 0 : _a.identifier;
|
|
121
|
+
const offerTokenRequired = typeof offerTokenIssuer === 'string';
|
|
122
|
+
if (offerTokenRequired) {
|
|
123
|
+
const offerIdentifierMustBe = (_c = (_b = params.object.reservationFor) === null || _b === void 0 ? void 0 : _b.offers) === null || _c === void 0 ? void 0 : _c.identifier;
|
|
124
|
+
const offerToken = (_e = (_d = params.object.reservationFor) === null || _d === void 0 ? void 0 : _d.offers) === null || _e === void 0 ? void 0 : _e.token;
|
|
125
|
+
if (typeof offerIdentifierMustBe !== 'string' || offerIdentifierMustBe === '') {
|
|
126
|
+
throw new factory.errors.ArgumentNull('object.reservationFor.offers.identifier');
|
|
127
|
+
}
|
|
128
|
+
if (typeof offerToken !== 'string' || offerToken === '') {
|
|
129
|
+
throw new factory.errors.ArgumentNull('object.reservationFor.offers.token');
|
|
130
|
+
}
|
|
131
|
+
if (typeof makesOfferOnApplication.identifier !== 'string' || makesOfferOnApplication.identifier === '') {
|
|
132
|
+
throw new factory.errors.NotFound('seller.makesOffer.identifier');
|
|
133
|
+
}
|
|
134
|
+
// アプリケーションオファーコード一致検証
|
|
135
|
+
if (makesOfferOnApplication.identifier !== offerIdentifierMustBe) {
|
|
136
|
+
throw new factory.errors.Argument('object.reservationFor.offers.identifier', 'offers.identifier not matched');
|
|
137
|
+
}
|
|
138
|
+
const issuer = yield repos.issuer.findByIdentifier({
|
|
139
|
+
project: { id: event.project.id },
|
|
140
|
+
identifier: offerTokenIssuer
|
|
141
|
+
});
|
|
142
|
+
if (typeof issuer.tokenSecret !== 'string' || issuer.tokenSecret === '') {
|
|
143
|
+
throw new factory.errors.NotFound('issuer.tokenSecret');
|
|
144
|
+
}
|
|
145
|
+
const verifiedOffer = yield verifyOfferToken({
|
|
146
|
+
secret: issuer.tokenSecret,
|
|
147
|
+
issuer: issuer.url,
|
|
148
|
+
token: offerToken
|
|
149
|
+
});
|
|
150
|
+
yield validateOfferToken({
|
|
151
|
+
event,
|
|
152
|
+
acceptedDate,
|
|
153
|
+
verifiedOffer,
|
|
154
|
+
makesOfferOnApplication,
|
|
155
|
+
object: params.object
|
|
156
|
+
})();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as factory from '../../../../factory';
|
|
2
|
+
import { IMinimizedIndividualEvent } from '../../../../factory/event';
|
|
3
|
+
import type { IssuerRepo } from '../../../../repo/issuer';
|
|
4
|
+
import type { MemberProgramRepo } from '../../../../repo/memberProgram';
|
|
5
|
+
import type { ProductOfferRepo } from '../../../../repo/productOffer';
|
|
6
|
+
declare function validateMemberTierIfExists(params: {
|
|
7
|
+
event: Pick<IMinimizedIndividualEvent, 'offers' | 'id' | 'project'>;
|
|
8
|
+
now: Date;
|
|
9
|
+
object: factory.assetTransaction.reserve.IObjectWithoutDetail;
|
|
10
|
+
makesOfferOnApplication: factory.event.screeningEvent.ISellerMakesOffer;
|
|
11
|
+
}): (repos: {
|
|
12
|
+
issuer: IssuerRepo;
|
|
13
|
+
memberProgram: MemberProgramRepo;
|
|
14
|
+
productOffer: ProductOfferRepo;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
export { validateMemberTierIfExists };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.validateMemberTierIfExists = validateMemberTierIfExists;
|
|
13
|
+
const jwt = require("jsonwebtoken");
|
|
14
|
+
const moment = require("moment");
|
|
15
|
+
const factory = require("../../../../factory");
|
|
16
|
+
function validateMemberTier(params) {
|
|
17
|
+
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
var _a, _b, _c, _d, _e;
|
|
19
|
+
const { acceptedDate, event, verifiedValidForMemberTier, memberProgramIdentifierMustBe, aggregateOfferIdentifier } = params;
|
|
20
|
+
const tierIdentifier = (_b = (_a = verifiedValidForMemberTier.member) === null || _a === void 0 ? void 0 : _a.memberOf) === null || _b === void 0 ? void 0 : _b.identifier;
|
|
21
|
+
const memberProgramIdentifier = (_e = (_d = (_c = verifiedValidForMemberTier.member) === null || _c === void 0 ? void 0 : _c.memberOf) === null || _d === void 0 ? void 0 : _d.isTierOf) === null || _e === void 0 ? void 0 : _e.identifier;
|
|
22
|
+
if (typeof tierIdentifier !== 'string') {
|
|
23
|
+
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'tier identifier must be string');
|
|
24
|
+
}
|
|
25
|
+
if (typeof memberProgramIdentifier !== 'string') {
|
|
26
|
+
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'member program must be string');
|
|
27
|
+
}
|
|
28
|
+
if (memberProgramIdentifier !== memberProgramIdentifierMustBe) {
|
|
29
|
+
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'member program not matched');
|
|
30
|
+
}
|
|
31
|
+
const sellerId = event.offers.seller.id;
|
|
32
|
+
if (typeof sellerId !== 'string' || sellerId === '') {
|
|
33
|
+
throw new factory.errors.NotFound('event.offers.seller.id');
|
|
34
|
+
}
|
|
35
|
+
// 有効なプロダクトオファーを検証
|
|
36
|
+
const productOfferForMemberTier = (yield repos.productOffer.findProductOffers({
|
|
37
|
+
limit: 1,
|
|
38
|
+
page: 1,
|
|
39
|
+
project: { id: { $eq: event.project.id } },
|
|
40
|
+
// availableAtOrFrom: { id: { $eq: availableAt.id } },
|
|
41
|
+
itemOffered: { identifier: { $eq: aggregateOfferIdentifier } }, // オファーコレクションコード
|
|
42
|
+
validForMemberTier: { identifier: { $eq: tierIdentifier } },
|
|
43
|
+
validFrom: { $lte: acceptedDate.toDate() },
|
|
44
|
+
validThrough: { $gte: acceptedDate.toDate() },
|
|
45
|
+
offeredBy: { id: { $eq: sellerId } } // オファー提供者も検証する
|
|
46
|
+
}, ['identifier'])).shift();
|
|
47
|
+
if (productOfferForMemberTier === undefined) {
|
|
48
|
+
throw new factory.errors.NotFound(factory.offerType.Offer, 'valid product offers for member tier not found');
|
|
49
|
+
}
|
|
50
|
+
// let validThroughMoment: moment.Moment;
|
|
51
|
+
// let validFromMoment: moment.Moment;
|
|
52
|
+
// validThroughMoment = moment(productOfferForMemberTier.validThrough, ROLE_DATE_FORMAT, true);
|
|
53
|
+
// validFromMoment = moment(productOfferForMemberTier.validFrom, ROLE_DATE_FORMAT, true);
|
|
54
|
+
// if (acceptedDate.isBefore(validFromMoment)) {
|
|
55
|
+
// throw new factory.errors.Argument(
|
|
56
|
+
// 'reservationFor.offers.validForMemberTier',
|
|
57
|
+
// `the offer id valid from ${validFromMoment}`
|
|
58
|
+
// );
|
|
59
|
+
// }
|
|
60
|
+
// if (acceptedDate.isAfter(validThroughMoment)) {
|
|
61
|
+
// throw new factory.errors.Argument(
|
|
62
|
+
// 'reservationFor.offers.validForMemberTier',
|
|
63
|
+
// `the offer id valid through ${validThroughMoment}`
|
|
64
|
+
// );
|
|
65
|
+
// }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function verifyMemberTierToken(params) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
let result;
|
|
71
|
+
const { issuer, secret, token } = params;
|
|
72
|
+
try {
|
|
73
|
+
result = yield new Promise((resolve, reject) => {
|
|
74
|
+
jwt.verify(token, secret, {
|
|
75
|
+
algorithms: ['HS256'],
|
|
76
|
+
issuer
|
|
77
|
+
// ...(Array.isArray(params.audience)) ? { audience: params.audience } : undefined
|
|
78
|
+
}, (err, decoded) => {
|
|
79
|
+
if (err instanceof Error) {
|
|
80
|
+
reject(err);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
resolve(decoded);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// JWTエラーをハンドリング
|
|
90
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
91
|
+
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', `invalid token. [${error.message} expiredAt:${error.expiredAt}]`);
|
|
92
|
+
}
|
|
93
|
+
else if (error instanceof jwt.JsonWebTokenError) {
|
|
94
|
+
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', `invalid token. [${error.message}]`);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function validateMemberTierIfExists(params) {
|
|
102
|
+
// tslint:disable-next-line:cyclomatic-complexity max-func-body-length
|
|
103
|
+
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
var _a, _b, _c, _d;
|
|
105
|
+
const { event, makesOfferOnApplication } = params;
|
|
106
|
+
const acceptedDate = moment(params.now);
|
|
107
|
+
const eventOffers = event.offers;
|
|
108
|
+
// 有効メンバープログラムティアが存在する場合
|
|
109
|
+
// support validForMemberTier(2025-05-14~)
|
|
110
|
+
const validForMemberTierExists = ((_a = makesOfferOnApplication.validForMemberTier) === null || _a === void 0 ? void 0 : _a.typeOf) === 'MemberProgramTier';
|
|
111
|
+
if (validForMemberTierExists) {
|
|
112
|
+
const tokenizedMemberProgramTier = (_c = (_b = params.object.reservationFor) === null || _b === void 0 ? void 0 : _b.offers) === null || _c === void 0 ? void 0 : _c.validForMemberTier;
|
|
113
|
+
const validForMemberTierToken = tokenizedMemberProgramTier === null || tokenizedMemberProgramTier === void 0 ? void 0 : tokenizedMemberProgramTier.token;
|
|
114
|
+
const memberProgramIdentifierMustBe = (_d = tokenizedMemberProgramTier === null || tokenizedMemberProgramTier === void 0 ? void 0 : tokenizedMemberProgramTier.isTierOf) === null || _d === void 0 ? void 0 : _d.identifier;
|
|
115
|
+
// ティアトークンが必須
|
|
116
|
+
if (typeof validForMemberTierToken !== 'string' || validForMemberTierToken === '') {
|
|
117
|
+
throw new factory.errors.ArgumentNull('reservationFor.offers.validForMemberTier.token');
|
|
118
|
+
}
|
|
119
|
+
// メンバープログラムコード指定が必須
|
|
120
|
+
if (typeof memberProgramIdentifierMustBe !== 'string' || memberProgramIdentifierMustBe === '') {
|
|
121
|
+
throw new factory.errors.ArgumentNull('reservationFor.offers.validForMemberTier.isTierOf.identifier');
|
|
122
|
+
}
|
|
123
|
+
// 有効メンバープログラムティアが存在する場合、オファーコレクションコードが必須
|
|
124
|
+
const aggregateOfferIdentifier = eventOffers.identifier;
|
|
125
|
+
if (typeof aggregateOfferIdentifier !== 'string' || aggregateOfferIdentifier === '') {
|
|
126
|
+
throw new factory.errors.NotFound('makesOfferOnApplication.identifier');
|
|
127
|
+
}
|
|
128
|
+
// トークン検証
|
|
129
|
+
const memberProgram = (yield repos.memberProgram.projectMemberPrograms({
|
|
130
|
+
limit: 1,
|
|
131
|
+
page: 1,
|
|
132
|
+
project: { id: { $eq: params.event.project.id } },
|
|
133
|
+
identifier: { $eq: memberProgramIdentifierMustBe }
|
|
134
|
+
})).shift();
|
|
135
|
+
if (memberProgram === undefined) {
|
|
136
|
+
throw new factory.errors.NotFound('MemberProgram', `MemberProgram '${memberProgramIdentifierMustBe}' not found`);
|
|
137
|
+
}
|
|
138
|
+
const issuer = yield repos.issuer.findByIdentifier({
|
|
139
|
+
project: { id: params.event.project.id },
|
|
140
|
+
identifier: memberProgram.hostingOrganization.identifier
|
|
141
|
+
});
|
|
142
|
+
if (typeof issuer.tokenSecret !== 'string' || issuer.tokenSecret === '') {
|
|
143
|
+
throw new factory.errors.NotFound('issuer.tokenSecret');
|
|
144
|
+
}
|
|
145
|
+
const verifiedValidForMemberTier = yield verifyMemberTierToken({
|
|
146
|
+
secret: issuer.tokenSecret,
|
|
147
|
+
issuer: issuer.url,
|
|
148
|
+
token: validForMemberTierToken
|
|
149
|
+
});
|
|
150
|
+
yield validateMemberTier({
|
|
151
|
+
event, acceptedDate, verifiedValidForMemberTier,
|
|
152
|
+
memberProgramIdentifierMustBe, aggregateOfferIdentifier
|
|
153
|
+
})(repos);
|
|
154
|
+
}
|
|
155
|
+
// tslint:disable-next-line:no-suspicious-comment
|
|
156
|
+
// TODO オファートークン検証(2025-10-21~)
|
|
157
|
+
// const offerTokenIssuer = makesOfferOnApplication.issuedBy?.identifier;
|
|
158
|
+
// const offerTokenRequired = typeof offerTokenIssuer === 'string';
|
|
159
|
+
// if (offerTokenRequired) {
|
|
160
|
+
// const offerToken = params.object.reservationFor?.offers?.token;
|
|
161
|
+
// if (typeof offerToken !== 'string' || offerToken === '') {
|
|
162
|
+
// throw new factory.errors.ArgumentNull('object.reservationFor.offers.token');
|
|
163
|
+
// }
|
|
164
|
+
// const issuer = await repos.issuer.findByIdentifier({
|
|
165
|
+
// project: { id: params.event.project.id },
|
|
166
|
+
// identifier: offerTokenIssuer
|
|
167
|
+
// });
|
|
168
|
+
// if (typeof issuer.tokenSecret !== 'string' || issuer.tokenSecret === '') {
|
|
169
|
+
// throw new factory.errors.NotFound('issuer.tokenSecret');
|
|
170
|
+
// }
|
|
171
|
+
// const verifiedOffer = await verifyOfferToken({
|
|
172
|
+
// secret: issuer.tokenSecret,
|
|
173
|
+
// issuer: issuer.url,
|
|
174
|
+
// token: offerToken
|
|
175
|
+
// });
|
|
176
|
+
// await validateOfferToken({
|
|
177
|
+
// acceptedDate,
|
|
178
|
+
// verifiedOffer,
|
|
179
|
+
// makesOfferOnApplication,
|
|
180
|
+
// object: params.object
|
|
181
|
+
// })();
|
|
182
|
+
// }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -5,7 +5,10 @@ import type { MemberProgramRepo } from '../../../repo/memberProgram';
|
|
|
5
5
|
import type { ProductOfferRepo } from '../../../repo/productOffer';
|
|
6
6
|
declare function validateStartRequest(params: {
|
|
7
7
|
object: factory.assetTransaction.reserve.IObjectWithoutDetail;
|
|
8
|
-
event: Pick<IMinimizedIndividualEvent, 'offers' | 'id' | 'project'>;
|
|
8
|
+
event: Pick<IMinimizedIndividualEvent, 'offers' | 'id' | 'project' | 'identifier'>;
|
|
9
|
+
/**
|
|
10
|
+
* アプリケーションごとのオファーを検証するかどうか
|
|
11
|
+
*/
|
|
9
12
|
validateEventOfferPeriod?: boolean;
|
|
10
13
|
validateEvent?: boolean;
|
|
11
14
|
now: Date;
|
|
@@ -10,119 +10,37 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.validateStartRequest = validateStartRequest;
|
|
13
|
-
const jwt = require("jsonwebtoken");
|
|
14
13
|
const moment = require("moment");
|
|
15
14
|
const factory = require("../../../factory");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let result;
|
|
19
|
-
const { issuer, secret, token } = params;
|
|
20
|
-
try {
|
|
21
|
-
result = yield new Promise((resolve, reject) => {
|
|
22
|
-
jwt.verify(token, secret, {
|
|
23
|
-
algorithms: ['HS256'],
|
|
24
|
-
issuer
|
|
25
|
-
// ...(Array.isArray(params.audience)) ? { audience: params.audience } : undefined
|
|
26
|
-
}, (err, decoded) => {
|
|
27
|
-
if (err instanceof Error) {
|
|
28
|
-
reject(err);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
resolve(decoded);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
// JWTエラーをハンドリング
|
|
38
|
-
if (error instanceof jwt.TokenExpiredError) {
|
|
39
|
-
throw new factory.errors.Argument('memberOfToken', `invalid token. [${error.message} expiredAt:${error.expiredAt}]`);
|
|
40
|
-
}
|
|
41
|
-
else if (error instanceof jwt.JsonWebTokenError) {
|
|
42
|
-
throw new factory.errors.Argument('memberOfToken', `invalid token. [${error.message}]`);
|
|
43
|
-
}
|
|
44
|
-
throw error;
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
});
|
|
48
|
-
}
|
|
15
|
+
const validateIssuedOfferIfExists_1 = require("./validateStartRequest/validateIssuedOfferIfExists");
|
|
16
|
+
const validateMemberTierIfExists_1 = require("./validateStartRequest/validateMemberTierIfExists");
|
|
49
17
|
function validateStartRequest(params) {
|
|
50
18
|
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
51
|
-
var _a
|
|
19
|
+
var _a;
|
|
52
20
|
const now = params.now;
|
|
53
21
|
const event = params.event;
|
|
54
22
|
if (params.validateEventOfferPeriod === true) {
|
|
55
23
|
if (typeof ((_a = params.store) === null || _a === void 0 ? void 0 : _a.id) !== 'string') {
|
|
56
24
|
throw new factory.errors.NotFound('store.id');
|
|
57
25
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
now,
|
|
26
|
+
yield validateApplicationOffer({
|
|
27
|
+
event,
|
|
28
|
+
now,
|
|
29
|
+
availableAt: { id: params.store.id },
|
|
30
|
+
object: params.object
|
|
31
|
+
})(repos);
|
|
61
32
|
}
|
|
62
33
|
if (params.validateEvent === true) {
|
|
63
34
|
validateEvent({ event, object: params.object });
|
|
64
35
|
}
|
|
65
36
|
});
|
|
66
37
|
}
|
|
67
|
-
function validateMemberTier(params) {
|
|
68
|
-
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
var _a, _b, _c, _d, _e;
|
|
70
|
-
const { acceptedDate, event, verifiedValidForMemberTier, memberProgramIdentifierMustBe, aggregateOfferIdentifier } = params;
|
|
71
|
-
const tierIdentifier = (_b = (_a = verifiedValidForMemberTier.member) === null || _a === void 0 ? void 0 : _a.memberOf) === null || _b === void 0 ? void 0 : _b.identifier;
|
|
72
|
-
const memberProgramIdentifier = (_e = (_d = (_c = verifiedValidForMemberTier.member) === null || _c === void 0 ? void 0 : _c.memberOf) === null || _d === void 0 ? void 0 : _d.isTierOf) === null || _e === void 0 ? void 0 : _e.identifier;
|
|
73
|
-
if (typeof tierIdentifier !== 'string') {
|
|
74
|
-
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'tier identifier must be string');
|
|
75
|
-
}
|
|
76
|
-
if (typeof memberProgramIdentifier !== 'string') {
|
|
77
|
-
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'member program must be string');
|
|
78
|
-
}
|
|
79
|
-
if (memberProgramIdentifier !== memberProgramIdentifierMustBe) {
|
|
80
|
-
throw new factory.errors.Argument('reservationFor.offers.validForMemberTier', 'member program not matched');
|
|
81
|
-
}
|
|
82
|
-
const sellerId = event.offers.seller.id;
|
|
83
|
-
if (typeof sellerId !== 'string' || sellerId === '') {
|
|
84
|
-
throw new factory.errors.NotFound('event.offers.seller.id');
|
|
85
|
-
}
|
|
86
|
-
// 有効なプロダクトオファーを検証
|
|
87
|
-
const productOfferForMemberTier = (yield repos.productOffer.findProductOffers({
|
|
88
|
-
limit: 1,
|
|
89
|
-
page: 1,
|
|
90
|
-
project: { id: { $eq: event.project.id } },
|
|
91
|
-
// availableAtOrFrom: { id: { $eq: availableAt.id } },
|
|
92
|
-
itemOffered: { identifier: { $eq: aggregateOfferIdentifier } }, // オファーコレクションコード
|
|
93
|
-
validForMemberTier: { identifier: { $eq: tierIdentifier } },
|
|
94
|
-
validFrom: { $lte: acceptedDate.toDate() },
|
|
95
|
-
validThrough: { $gte: acceptedDate.toDate() },
|
|
96
|
-
offeredBy: { id: { $eq: sellerId } } // オファー提供者も検証する
|
|
97
|
-
}, ['identifier'])).shift();
|
|
98
|
-
if (productOfferForMemberTier === undefined) {
|
|
99
|
-
throw new factory.errors.NotFound(factory.offerType.Offer, 'valid product offers for member tier not found');
|
|
100
|
-
}
|
|
101
|
-
// let validThroughMoment: moment.Moment;
|
|
102
|
-
// let validFromMoment: moment.Moment;
|
|
103
|
-
// validThroughMoment = moment(productOfferForMemberTier.validThrough, ROLE_DATE_FORMAT, true);
|
|
104
|
-
// validFromMoment = moment(productOfferForMemberTier.validFrom, ROLE_DATE_FORMAT, true);
|
|
105
|
-
// if (acceptedDate.isBefore(validFromMoment)) {
|
|
106
|
-
// throw new factory.errors.Argument(
|
|
107
|
-
// 'reservationFor.offers.validForMemberTier',
|
|
108
|
-
// `the offer id valid from ${validFromMoment}`
|
|
109
|
-
// );
|
|
110
|
-
// }
|
|
111
|
-
// if (acceptedDate.isAfter(validThroughMoment)) {
|
|
112
|
-
// throw new factory.errors.Argument(
|
|
113
|
-
// 'reservationFor.offers.validForMemberTier',
|
|
114
|
-
// `the offer id valid through ${validThroughMoment}`
|
|
115
|
-
// );
|
|
116
|
-
// }
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
38
|
/**
|
|
120
|
-
*
|
|
39
|
+
* アプリケーションごとのオファーを検証する
|
|
121
40
|
*/
|
|
122
|
-
function
|
|
41
|
+
function validateApplicationOffer(params) {
|
|
123
42
|
return (repos) => __awaiter(this, void 0, void 0, function* () {
|
|
124
|
-
|
|
125
|
-
const { event, availableAt, tokenizedMemberProgramTier } = params;
|
|
43
|
+
const { event, availableAt } = params;
|
|
126
44
|
const acceptedDate = moment(params.now);
|
|
127
45
|
const eventOffers = event.offers;
|
|
128
46
|
// アプリケーションごとの設定を参照する(2022-11-19~)
|
|
@@ -150,51 +68,19 @@ function validateEventOfferPeriod(params) {
|
|
|
150
68
|
}
|
|
151
69
|
}
|
|
152
70
|
// 有効メンバープログラムティアが存在する場合
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// 有効メンバープログラムティアが存在する場合、オファーコレクションコードが必須
|
|
167
|
-
const aggregateOfferIdentifier = eventOffers.identifier;
|
|
168
|
-
if (typeof aggregateOfferIdentifier !== 'string' || aggregateOfferIdentifier === '') {
|
|
169
|
-
throw new factory.errors.NotFound('makesOfferOnApplication.identifier');
|
|
170
|
-
}
|
|
171
|
-
// トークン検証
|
|
172
|
-
const memberProgram = (yield repos.memberProgram.projectMemberPrograms({
|
|
173
|
-
limit: 1,
|
|
174
|
-
page: 1,
|
|
175
|
-
project: { id: { $eq: params.event.project.id } },
|
|
176
|
-
identifier: { $eq: memberProgramIdentifierMustBe }
|
|
177
|
-
})).shift();
|
|
178
|
-
if (memberProgram === undefined) {
|
|
179
|
-
throw new factory.errors.NotFound('MemberProgram', `MemberProgram '${memberProgramIdentifierMustBe}' not found`);
|
|
180
|
-
}
|
|
181
|
-
const issuer = yield repos.issuer.findByIdentifier({
|
|
182
|
-
project: { id: params.event.project.id },
|
|
183
|
-
identifier: memberProgram.hostingOrganization.identifier
|
|
184
|
-
});
|
|
185
|
-
if (typeof issuer.tokenSecret !== 'string' || issuer.tokenSecret === '') {
|
|
186
|
-
throw new factory.errors.NotFound('issuer.tokenSecret');
|
|
187
|
-
}
|
|
188
|
-
const verifiedValidForMemberTier = yield verifyOfferedByToken({
|
|
189
|
-
secret: issuer.tokenSecret,
|
|
190
|
-
issuer: issuer.url,
|
|
191
|
-
token: validForMemberTierToken
|
|
192
|
-
});
|
|
193
|
-
yield validateMemberTier({
|
|
194
|
-
event, acceptedDate, verifiedValidForMemberTier,
|
|
195
|
-
memberProgramIdentifierMustBe, aggregateOfferIdentifier
|
|
196
|
-
})(repos);
|
|
197
|
-
}
|
|
71
|
+
yield (0, validateMemberTierIfExists_1.validateMemberTierIfExists)({
|
|
72
|
+
event,
|
|
73
|
+
now: params.now,
|
|
74
|
+
object: params.object,
|
|
75
|
+
makesOfferOnApplication
|
|
76
|
+
})(repos);
|
|
77
|
+
// オファートークン検証(2025-10-21~)
|
|
78
|
+
yield (0, validateIssuedOfferIfExists_1.validateIssuedOfferIfExists)({
|
|
79
|
+
event,
|
|
80
|
+
now: params.now,
|
|
81
|
+
object: params.object,
|
|
82
|
+
makesOfferOnApplication
|
|
83
|
+
})(repos);
|
|
198
84
|
});
|
|
199
85
|
}
|
|
200
86
|
/**
|
|
@@ -239,7 +239,7 @@ function saveScreeningEventSeries(params) {
|
|
|
239
239
|
kubunClass: '043'
|
|
240
240
|
});
|
|
241
241
|
debug('kubunNames found.');
|
|
242
|
-
const availablePaymentMethodTypes = yield repos.categoryCode.
|
|
242
|
+
const availablePaymentMethodTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
243
243
|
project: { id: { $eq: params.project.id } },
|
|
244
244
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.PaymentMethodType } }
|
|
245
245
|
}, ['additionalProperty', 'codeValue']);
|
|
@@ -53,15 +53,15 @@ function authorize(params) {
|
|
|
53
53
|
'superEvent', 'typeOf', 'identifier', 'name'
|
|
54
54
|
] // optimize(2024-07-18~)
|
|
55
55
|
);
|
|
56
|
-
const availablePaymentMethodTypes = yield repos.categoryCode.
|
|
56
|
+
const availablePaymentMethodTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
57
57
|
project: { id: { $eq: transaction.project.id } },
|
|
58
58
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.PaymentMethodType } }
|
|
59
59
|
}, []);
|
|
60
|
-
const seatingTypes = yield repos.categoryCode.
|
|
60
|
+
const seatingTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
61
61
|
project: { id: { $eq: transaction.project.id } },
|
|
62
62
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.SeatingType } }
|
|
63
63
|
}, []);
|
|
64
|
-
const videoFormatTypes = yield repos.categoryCode.
|
|
64
|
+
const videoFormatTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
65
65
|
project: { id: { $eq: transaction.project.id } },
|
|
66
66
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.VideoFormatType } }
|
|
67
67
|
}, []);
|
|
@@ -79,15 +79,15 @@ function changeOffers(params) {
|
|
|
79
79
|
'superEvent', 'typeOf', 'identifier', 'name'
|
|
80
80
|
] // optimize(2024-07-18~)
|
|
81
81
|
);
|
|
82
|
-
const availablePaymentMethodTypes = yield repos.categoryCode.
|
|
82
|
+
const availablePaymentMethodTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
83
83
|
project: { id: { $eq: transaction.project.id } },
|
|
84
84
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.PaymentMethodType } }
|
|
85
85
|
}, []);
|
|
86
|
-
const seatingTypes = yield repos.categoryCode.
|
|
86
|
+
const seatingTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
87
87
|
project: { id: { $eq: transaction.project.id } },
|
|
88
88
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.SeatingType } }
|
|
89
89
|
}, []);
|
|
90
|
-
const videoFormatTypes = yield repos.categoryCode.
|
|
90
|
+
const videoFormatTypes = yield repos.categoryCode.projectCategoryCodeFields({
|
|
91
91
|
project: { id: { $eq: transaction.project.id } },
|
|
92
92
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.VideoFormatType } }
|
|
93
93
|
}, []);
|
|
@@ -29,7 +29,7 @@ function syncCategoryCode(params) {
|
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
31
|
for (const categoryCodeId of params.ids) {
|
|
32
|
-
const syncingCategoryCode = (yield repos.categoryCode.
|
|
32
|
+
const syncingCategoryCode = (yield repos.categoryCode.projectCategoryCodeFields({
|
|
33
33
|
limit: 1,
|
|
34
34
|
page: 1,
|
|
35
35
|
project: { id: { $eq: params.project.id } },
|
|
@@ -348,7 +348,7 @@ function createInformCategoryCodeTasks(params, setting) {
|
|
|
348
348
|
// settings: Settings
|
|
349
349
|
) => __awaiter(this, void 0, void 0, function* () {
|
|
350
350
|
var _a;
|
|
351
|
-
const categoryCodes4inform = yield repos.categoryCode.
|
|
351
|
+
const categoryCodes4inform = yield repos.categoryCode.projectCategoryCodeFields({
|
|
352
352
|
id: { $in: params.ids },
|
|
353
353
|
// ひとまずDistributorTypeのみ
|
|
354
354
|
inCodeSet: { identifier: { $eq: factory.categoryCode.CategorySetIdentifier.DistributorType } }
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@aws-sdk/client-cognito-identity-provider": "3.600.0",
|
|
13
13
|
"@aws-sdk/credential-providers": "3.600.0",
|
|
14
|
-
"@chevre/factory": "5.1.0-alpha.
|
|
15
|
-
"@cinerino/sdk": "12.
|
|
14
|
+
"@chevre/factory": "5.1.0-alpha.6",
|
|
15
|
+
"@cinerino/sdk": "12.6.0-alpha.2",
|
|
16
16
|
"@motionpicture/coa-service": "9.6.0",
|
|
17
17
|
"@motionpicture/gmo-service": "5.4.0-alpha.1",
|
|
18
18
|
"@sendgrid/client": "8.1.4",
|
|
@@ -115,5 +115,5 @@
|
|
|
115
115
|
"postversion": "git push origin --tags",
|
|
116
116
|
"prepublishOnly": "npm run clean && npm run build && npm test && npm run doc"
|
|
117
117
|
},
|
|
118
|
-
"version": "23.0.0-alpha.
|
|
118
|
+
"version": "23.0.0-alpha.17"
|
|
119
119
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// tslint:disable:no-console
|
|
2
|
-
import * as mongoose from 'mongoose';
|
|
3
|
-
|
|
4
|
-
import { chevre } from '../../../../lib/index';
|
|
5
|
-
|
|
6
|
-
const project = { id: String(process.env.PROJECT_ID) };
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
await mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
|
|
10
|
-
|
|
11
|
-
const categoryCodeRepo = await chevre.repository.CategoryCode.createInstance(mongoose.connection);
|
|
12
|
-
|
|
13
|
-
const categoryCodes = await categoryCodeRepo.projectFields(
|
|
14
|
-
{
|
|
15
|
-
limit: 100,
|
|
16
|
-
page: 1,
|
|
17
|
-
project: { id: { $eq: project.id } },
|
|
18
|
-
inCodeSet: { identifier: { $eq: chevre.factory.categoryCode.CategorySetIdentifier.MovieTicketType } }
|
|
19
|
-
},
|
|
20
|
-
[],
|
|
21
|
-
{ excludeMovieTicketType: false }
|
|
22
|
-
);
|
|
23
|
-
// tslint:disable-next-line:no-null-keyword
|
|
24
|
-
console.dir(categoryCodes.map(({ inCodeSet }) => inCodeSet.identifier), { depth: null });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
main()
|
|
28
|
-
.then()
|
|
29
|
-
.catch(console.error);
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// tslint:disable:no-console
|
|
2
|
-
import * as mongoose from 'mongoose';
|
|
3
|
-
|
|
4
|
-
import { chevre } from '../../../lib/index';
|
|
5
|
-
|
|
6
|
-
// const project = { id: String(process.env.PROJECT_ID) };
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
await mongoose.connect(<string>process.env.MONGOLAB_URI);
|
|
10
|
-
|
|
11
|
-
const categoryCodeRepo = await chevre.repository.CategoryCode.createInstance(mongoose.connection);
|
|
12
|
-
|
|
13
|
-
const categoryCodes = await categoryCodeRepo.searchByAggregate(
|
|
14
|
-
{
|
|
15
|
-
limit: 100,
|
|
16
|
-
page: 1,
|
|
17
|
-
sort: { codeValue: chevre.factory.sortType.Ascending }
|
|
18
|
-
// id: { $eq: 'xxx' }
|
|
19
|
-
// project: { id: { $eq: project.id } }
|
|
20
|
-
// paymentAccepted: { paymentMethodType: { $eq: 'Cash' } },
|
|
21
|
-
// hasMerchantReturnPolicy: { applicablePaymentMethod: {} }
|
|
22
|
-
},
|
|
23
|
-
['id']
|
|
24
|
-
);
|
|
25
|
-
console.log('categoryCodes found', categoryCodes[0]);
|
|
26
|
-
console.log(categoryCodes.length, 'categoryCodes found');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
main()
|
|
30
|
-
.then()
|
|
31
|
-
.catch(console.error);
|