@chevre/domain
Version:
Chevre Domain Library for Node.js
391 lines (390 loc) • 20.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERROR_MESSAGE_ALREADY_REGISTERED = void 0;
exports.search = search;
exports.authorize = authorize;
exports.voidTransaction = voidTransaction;
const moment = require("moment");
const factory = require("../../factory");
const accountTransactionIdentifier_1 = require("../../factory/accountTransactionIdentifier");
const availableProductTypes_1 = require("../../factory/availableProductTypes");
const RegisterServiceTransaction = require("../assetTransaction/registerService");
const publishOrderNumberIfNotExist_1 = require("../transaction/placeOrder/publishOrderNumberIfNotExist");
const any_1 = require("./any");
const factory_1 = require("./product/factory");
const searchProductOffers_1 = require("./product/searchProductOffers");
exports.ERROR_MESSAGE_ALREADY_REGISTERED = 'Already registered';
/**
* プロダクトオファーを検索する
*/
function search(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a;
const now = moment();
const searchProductsResult = yield repos.product.projectFields({
limit: 1,
page: 1,
project: { id: { $eq: params.project.id } },
id: { $eq: params.itemOffered.id }
}, ['id', 'name', 'productID', 'project', 'serviceOutput', 'serviceType', 'typeOf', 'description']
// []
);
const product = searchProductsResult.shift();
if (product === undefined) {
throw new factory.errors.NotFound('Product');
}
// 販売者指定の場合、検証
if (product.typeOf === factory.product.ProductType.MembershipService
|| product.typeOf === factory.product.ProductType.PaymentCard) {
const sellerId = (_a = params.seller) === null || _a === void 0 ? void 0 : _a.id;
if (typeof sellerId === 'string') {
// const productOffers = product.offers;
// if (!Array.isArray(productOffers)) {
// return { offers: [], product };
// }
const productOffers = yield repos.productOffer.search({
project: { id: { $eq: params.project.id } },
itemOffered: { id: { $eq: params.itemOffered.id } },
seller: { id: { $eq: sellerId } }
});
const hasValidOffer = productOffers.some((o) => {
var _a;
return ((_a = o.seller) === null || _a === void 0 ? void 0 : _a.id) === sellerId
&& o.validFrom !== undefined
&& moment(o.validFrom)
.isSameOrBefore(now)
&& o.validThrough !== undefined
&& moment(o.validThrough)
.isSameOrAfter(now);
});
if (!hasValidOffer) {
return { offers: [], product };
}
}
}
const offers = yield (0, searchProductOffers_1.searchProductOffers)({
ids: params.ids,
itemOffered: { id: params.itemOffered.id },
availableAt: params.availableAt,
onlyValid: params.onlyValid,
includedInDataCatalog: params.includedInDataCatalog,
addSortIndex: params.addSortIndex,
useIncludeInDataCatalog: params.useIncludeInDataCatalog,
limit: params.limit,
page: params.page
})(repos);
return { offers, product };
});
}
/**
* サービス(Chevreプロダクト)オファー承認
*/
// tslint:disable-next-line:max-func-body-length
function authorize(params) {
// tslint:disable-next-line:max-func-body-length
return (repos) => __awaiter(this, void 0, void 0, function* () {
const now = new Date();
const transaction = yield repos.transaction.projectFieldsInProgressById({
typeOf: factory.transactionType.PlaceOrder,
id: params.transaction.id
}, ['agent', 'project', 'seller', 'typeOf', 'expires']);
if (transaction.agent.id !== params.agent.id) {
throw new factory.errors.Forbidden('Transaction not yours');
}
const { product, availableOffers } = yield fixProductAndOffers(params)(repos);
// 会員の場合のみ既登録か確認
if (params.agent.typeOf === factory.personType.Person) {
yield checkIfRegistered({
agent: { id: params.agent.id },
product: { id: String(product.id), typeOf: product.typeOf },
now: now
// iss: params.iss
})(repos);
}
// ポイント特典の識別子に利用するため注文番号を先に発行
const orderNumber = yield (0, publishOrderNumberIfNotExist_1.publishOrderNumberIfNotExist)({
project: { id: transaction.project.id },
id: transaction.id,
object: { orderDate: new Date() }
})(repos);
let acceptedOffer = yield validateAcceptedOffers({
object: params.object,
product,
availableOffers,
seller: transaction.seller,
orderNumber
})(repos);
acceptedOffer = yield createServiceOutputIdentifier({ acceptedOffer, product })(repos);
let requestBody;
let responseBody;
let result;
// まず取引番号発行
const { transactionNumber } = yield repos.transactionNumber.publishByTimestamp({
startDate: new Date()
});
const actionAttributes = (0, factory_1.createActionAttributes)({
acceptedOffer: acceptedOffer,
transaction: transaction,
transactionNumber: transactionNumber
});
const action = yield repos.action.start(actionAttributes);
try {
// 会員の場合のみ排他ロック
// if (params.agent.typeOf === factory.personType.Person) {
// }
// サービス登録開始
const startParams = (0, factory_1.createRegisterServiceStartParams)({
project: { typeOf: factory.organizationType.Project, id: params.project.id },
object: acceptedOffer,
transaction: transaction,
transactionNumber
});
requestBody = startParams;
responseBody = yield RegisterServiceTransaction.start(startParams)(repos);
const acceptedOffers4result = (0, factory_1.responseBody2resultAcceptedOffer)({
project: actionAttributes.project, responseBody, acceptedOffer
});
result = (0, factory_1.createResult)({ project: actionAttributes.project, requestBody, responseBody, acceptedOffer });
yield (0, any_1.acceptOffer)({
orderNumber,
project: transaction.project,
acceptedOffers: acceptedOffers4result
})(repos);
}
catch (error) {
try {
yield repos.action.giveUp({ typeOf: action.typeOf, id: action.id, error });
}
catch (__) {
// no op
}
throw error;
}
yield repos.action.completeWithVoid({ typeOf: action.typeOf, id: action.id, result: result });
return { id: action.id, result };
});
}
function fixProductAndOffers(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
const productId = String((_b = (_a = params.object[0]) === null || _a === void 0 ? void 0 : _a.itemOffered) === null || _b === void 0 ? void 0 : _b.id);
const searchProductsResult = yield repos.product.projectFields({
limit: 1,
page: 1,
project: { id: { $eq: params.project.id } },
id: { $eq: productId }
}, ['id', 'name', 'productID', 'project', 'serviceOutput', 'serviceType', 'typeOf']
// []
);
const product = searchProductsResult.shift();
if (product === undefined) {
throw new factory.errors.NotFound('Product');
}
const { offers } = yield search(Object.assign({ ids: params.object.map((o) => String(o.id)), project: { id: params.project.id }, itemOffered: { id: String(product.id) }, onlyValid: true, includedInDataCatalog: { id: '' }, addSortIndex: false, useIncludeInDataCatalog: false }, (typeof ((_c = params.location) === null || _c === void 0 ? void 0 : _c.id) === 'string') ? { availableAt: { id: params.location.id } } : undefined))(repos);
return { product, availableOffers: offers };
});
}
function voidTransaction(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a;
const transaction = yield repos.transaction.projectFieldsById({
typeOf: params.purpose.typeOf,
id: params.purpose.id
}, ['agent', 'status', 'typeOf']);
if (transaction.status !== factory.transactionStatusType.Canceled
&& transaction.status !== factory.transactionStatusType.Expired
&& transaction.status !== factory.transactionStatusType.InProgress) {
throw new factory.errors.Argument('purpose', `invalid transaction status: ${transaction.status}`);
}
if (typeof ((_a = params.agent) === null || _a === void 0 ? void 0 : _a.id) === 'string') {
if (transaction.agent.id !== params.agent.id) {
throw new factory.errors.Forbidden('Transaction not yours');
}
}
let authorizeActions;
if (typeof params.id === 'string') {
const action = yield repos.action.findById({ typeOf: factory.actionType.AuthorizeAction, id: params.id });
if (action.purpose.typeOf !== transaction.typeOf || action.purpose.id !== transaction.id) {
throw new factory.errors.Argument('Transaction', 'Action not found in the transaction');
}
authorizeActions = [action];
}
else {
authorizeActions = (yield repos.action.searchByPurpose({
typeOf: factory.actionType.AuthorizeAction,
purpose: {
typeOf: params.purpose.typeOf,
id: params.purpose.id
}
})
.then((actions) => actions
.filter((a) => Array.isArray(a.object)
&& a.object.length > 0
&& a.object[0].typeOf === factory.offerType.Offer
&& availableProductTypes_1.availableProductTypes.indexOf(a.object[0].itemOffered.typeOf) >= 0)));
}
yield Promise.all(authorizeActions.map((action) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const productId = (_b = (_a = action.object[0]) === null || _a === void 0 ? void 0 : _a.itemOffered) === null || _b === void 0 ? void 0 : _b.id;
if (typeof productId === 'string') {
// 廃止(2023-12-07~)
// await processUnlockRegisterMembershipService({
// agent: { id: transaction.agent.id },
// product: { id: productId },
// purpose: params.purpose
// })(repos);
}
yield repos.action.cancelWithVoid({ typeOf: action.typeOf, id: action.id });
yield processVoidRegisterServiceTransaction({
action,
project: params.project
})(repos);
})));
});
}
/**
* Chevre進行中取引を中止する
*/
function processVoidRegisterServiceTransaction(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a;
const transactionNumber = (_a = params.action.instrument) === null || _a === void 0 ? void 0 : _a.transactionNumber;
if (typeof transactionNumber === 'string') {
// 取引が存在すれば中止
const searchAssetTransactionsResult = yield repos.assetTransaction.search({
limit: 1,
page: 1,
project: { id: { $eq: params.project.id } },
typeOf: factory.assetTransactionType.RegisterService,
transactionNumber: { $eq: transactionNumber }
});
if (searchAssetTransactionsResult.length > 0) {
yield RegisterServiceTransaction.cancel({ transactionNumber })(repos);
// await repos.registerServiceTransaction.cancel({ transactionNumber: transactionNumber });
}
}
});
}
/**
* 受け入れらたオファーの内容を検証
*/
function validateAcceptedOffers(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
let acceptedOfferWithoutDetail = params.object;
if (!Array.isArray(acceptedOfferWithoutDetail)) {
acceptedOfferWithoutDetail = [acceptedOfferWithoutDetail];
}
if (acceptedOfferWithoutDetail.length === 0) {
throw new factory.errors.ArgumentNull('object');
}
const project = { typeOf: factory.organizationType.Project, id: params.product.project.id };
const issuedBy = {
id: params.seller.id,
name: params.seller.name,
typeOf: params.seller.typeOf
};
// 販売者を検証
// const productOffers = params.product.offers;
// if (!Array.isArray(productOffers)) {
// throw new factory.errors.Argument('Product', 'Product offers undefined');
// }
const productOffers = yield repos.productOffer.search({
project: { id: { $eq: params.product.project.id } },
itemOffered: { id: { $eq: String(params.product.id) } },
seller: { id: { $eq: params.seller.id } }
});
const hasValidOffer = productOffers.some((o) => {
var _a;
return ((_a = o.seller) === null || _a === void 0 ? void 0 : _a.id) === params.seller.id;
});
if (!hasValidOffer) {
throw new factory.errors.Argument('Product', 'Product has no valid offer');
}
// 利用可能なチケットオファーであれば受け入れる
return Promise.all(acceptedOfferWithoutDetail.map((offerWithoutDetail) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const offer = params.availableOffers.find((o) => o.id === offerWithoutDetail.id);
if (offer === undefined) {
throw new factory.errors.NotFound('Offer', `Offer ${offerWithoutDetail.id} not found`);
}
// ポイント特典入金先の指定があれば入金識別子を発行
let pointAward;
const pointAwardToLocationIdentifier = (_c = (_b = (_a = offerWithoutDetail.itemOffered) === null || _a === void 0 ? void 0 : _a.pointAward) === null || _b === void 0 ? void 0 : _b.toLocation) === null || _c === void 0 ? void 0 : _c.identifier;
const pointAwardToLocationIssuedThroughId = (_f = (_e = (_d = offerWithoutDetail.itemOffered) === null || _d === void 0 ? void 0 : _d.pointAward) === null || _e === void 0 ? void 0 : _e.toLocation) === null || _f === void 0 ? void 0 : _f.issuedThrough.id;
if (typeof pointAwardToLocationIdentifier === 'string' && pointAwardToLocationIdentifier.length > 0
&& typeof pointAwardToLocationIssuedThroughId === 'string' && pointAwardToLocationIssuedThroughId.length > 0) {
const pointAwardPurposeIdentifier = (0, accountTransactionIdentifier_1.createPointAwardIdentifier)({
project: project,
purpose: { orderNumber: params.orderNumber },
toLocation: { identifier: pointAwardToLocationIdentifier }
});
pointAward = Object.assign({ recipient: (_g = offerWithoutDetail.itemOffered.pointAward) === null || _g === void 0 ? void 0 : _g.recipient, toLocation: {
identifier: pointAwardToLocationIdentifier,
// toLocation.issuedThroughを保証する
issuedThrough: { id: pointAwardToLocationIssuedThroughId },
typeOf: factory.permit.PermitType.Permit
}, typeOf: factory.actionType.MoneyTransfer, purpose: { identifier: pointAwardPurposeIdentifier } }, (typeof ((_h = offerWithoutDetail.itemOffered.pointAward) === null || _h === void 0 ? void 0 : _h.description) === 'string')
? { description: offerWithoutDetail.itemOffered.pointAward.description }
: undefined);
}
const itemOffered = Object.assign({
// project: project,
typeOf: params.product.typeOf, id: params.product.id, name: params.product.name, serviceOutput: Object.assign(Object.assign(Object.assign({}, (_j = params.product) === null || _j === void 0 ? void 0 : _j.serviceOutput), (_k = offerWithoutDetail.itemOffered) === null || _k === void 0 ? void 0 : _k.serviceOutput), { project: project,
// 発行者は販売者でいったん固定
issuedBy: issuedBy, typeOf: factory.permit.PermitType.Permit }) }, (pointAward !== undefined) ? { pointAward } : undefined);
return Object.assign(Object.assign(Object.assign({}, offerWithoutDetail), offer), { itemOffered, seller: { project: project, typeOf: params.seller.typeOf, id: params.seller.id, name: params.seller.name } });
}));
});
}
function checkIfRegistered(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
// メンバーシップについては、登録済かどうか確認する
if (params.product.typeOf === factory.product.ProductType.MembershipService) {
// プロダクトによって発行されたPermitを所有していれば、登録済
const searchOwnershipInfosResult = yield repos.ownershipInfo.projectFields({
limit: 1,
page: 1,
ownedBy: { id: params.agent.id },
// プロダクトIDで検索
typeOfGood: {
issuedThrough: {
id: { $eq: params.product.id },
typeOf: { $eq: factory.product.ProductType.MembershipService }
}
},
ownedFrom: params.now,
ownedThrough: params.now
}, []);
const ownershipInfos = searchOwnershipInfosResult;
if (ownershipInfos.length > 0) {
// Already registered
throw new factory.errors.Argument('object', exports.ERROR_MESSAGE_ALREADY_REGISTERED);
}
}
});
}
function createServiceOutputIdentifier(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
const publishParams = params.acceptedOffer.map(() => {
return { project: { id: params.product.project.id } };
});
const publishIdentifierResult = yield Promise.all(publishParams.map(() => __awaiter(this, void 0, void 0, function* () {
const identifier = yield repos.serviceOutputIdentifier.publishByTimestamp({ startDate: new Date() });
return { identifier };
})));
// 識別子を発行
return Promise.all(params.acceptedOffer.map((o, key) => __awaiter(this, void 0, void 0, function* () {
var _a;
return Object.assign(Object.assign({}, o), { itemOffered: Object.assign(Object.assign({}, o.itemOffered), { serviceOutput: Object.assign(Object.assign({}, (_a = o.itemOffered) === null || _a === void 0 ? void 0 : _a.serviceOutput), { project: params.product.project, typeOf: factory.permit.PermitType.Permit, identifier: publishIdentifierResult[key].identifier }) }) });
})));
});
}