UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

391 lines (390 loc) 20.8 kB
"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 }) }) }); }))); }); }