UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

611 lines (610 loc) 30 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.start = start; exports.confirm = confirm; exports.exportTasksById = exportTasksById; /** * 通貨転送取引サービス */ const moment = require("moment"); const factory = require("../../factory"); const order_1 = require("../../factory/order"); // import { Settings } from '../../settings'; const factory_1 = require("./moneyTransfer/exportTasks/factory"); const factory_2 = require("./moneyTransfer/factory"); const potentialActions_1 = require("./moneyTransfer/potentialActions"); const MoneyTransferAssetTransactionService = require("../assetTransaction/moneyTransfer"); /** * 取引開始 * 通貨転送資産取引サービスを利用して転送取引を開始する */ function start(params) { return (repos // credentials: { // jwt: JWTCredentials; // } ) => __awaiter(this, void 0, void 0, function* () { const { passport } = yield repos.passport.validatePassportTokenIfExist(params); const sellers = yield repos.seller.projectFields({ limit: 1, page: 1, id: { $eq: params.seller.id } }, ['name', 'typeOf']); const seller = sellers.shift(); if (seller === undefined) { throw new factory.errors.NotFound(factory.organizationType.Corporation); } // 金額をfix const amount = params.object.amount; if (typeof amount.value !== 'number') { throw new factory.errors.ArgumentNull('amount.value'); } // fromとtoをfix const fromLocation = yield fixFromLocation(params)(repos); const toLocation = yield fixToLocation(params)(repos); // まず取引番号発行 const { transactionNumber } = yield repos.transactionNumber.publishByTimestamp({ startDate: new Date() }); // 取引開始 const startParams = (0, factory_2.createStartParams)(params, passport, seller, amount, fromLocation, toLocation, transactionNumber); let transaction; try { transaction = yield repos.transaction.start(startParams); yield authorizePaymentCard({ transaction: Object.assign(Object.assign({}, transaction), { object: startParams.object, seller: startParams.seller, agent: startParams.agent, project: startParams.project, typeOf: startParams.typeOf }) })(repos); } catch (error) { throw error; } return transaction; }); } function authorizePaymentCard(params) { return (repos // credentials: { // jwt: JWTCredentials; // } ) => __awaiter(this, void 0, void 0, function* () { var _a; const transaction = params.transaction; const fromLocation = transaction.object.fromLocation; if (typeof transaction.object.toLocation.typeOf === 'string') { const toLocation = transaction.object.toLocation; // 転送取引 yield processAuthorizePaymentCard({ project: { id: transaction.project.id }, agent: { id: transaction.agent.id }, object: Object.assign({ project: { typeOf: transaction.project.typeOf, id: transaction.project.id }, typeOf: factory.offerType.AggregateOffer, itemOffered: { typeOf: factory.actionType.MoneyTransfer, amount: Object.assign({ typeOf: 'MonetaryAmount', currency: transaction.object.amount.currency }, (typeof transaction.object.amount.value === 'number') ? { value: transaction.object.amount.value } : undefined), toLocation: toLocation, object: { pendingTransaction: { typeOf: factory.assetTransactionType.MoneyTransfer, // processAuthorizePaymentCard処理内で上書きされるので空でok transactionNumber: '' } } }, fromLocation: fromLocation, seller: { id: transaction.seller.id, typeOf: transaction.seller.typeOf, name: (typeof transaction.seller.name === 'string') ? transaction.seller.name : String((_a = transaction.seller.name) === null || _a === void 0 ? void 0 : _a.ja) }, price: 0, priceCurrency: factory.priceCurrency.JPY }, (typeof transaction.object.description === 'string') ? { description: transaction.object.description } : undefined), purpose: { typeOf: transaction.typeOf, id: transaction.id } })(repos); } else { throw new factory.errors.NotImplemented('Withdraw transaction not implemented'); } }); } function fixFromLocation(params) { return (__) => __awaiter(this, void 0, void 0, function* () { var _a; let fromLocation = params.object.fromLocation; if (typeof fromLocation === 'string') { // no op } else if (fromLocation.typeOf === factory.permit.PermitType.Permit) { fromLocation = { typeOf: factory.permit.PermitType.Permit, identifier: fromLocation.identifier, // ↓chevre/api側での制御に変更したので不要(2022-03-24~) // ↓指定しないとPermitのチェックが実行される(注文口座にはPermitが存在しない) // hasNoPermit: fromLocation.hasNoPermit, issuedThrough: { id: (_a = fromLocation.issuedThrough) === null || _a === void 0 ? void 0 : _a.id } }; } else if (fromLocation.typeOf === factory.order.OrderType.Order) { // 注文口座対応 fromLocation = { typeOf: factory.order.OrderType.Order, confirmationNumber: fromLocation.confirmationNumber, orderNumber: fromLocation.orderNumber }; } else { throw new factory.errors.Argument('fromLocation', 'location type must be specified'); } return fromLocation; }); } function fixToLocation(params) { return (__) => __awaiter(this, void 0, void 0, function* () { var _a; let toLocation = params.object.toLocation; if (typeof toLocation.typeOf === 'string') { toLocation = { typeOf: factory.permit.PermitType.Permit, identifier: toLocation.identifier, issuedThrough: { id: (_a = toLocation.issuedThrough) === null || _a === void 0 ? void 0 : _a.id } }; } else { // toLocation = { // ...toLocation // // typeOf: toLocation.typeOf, // // id: (typeof toLocation.id === 'string') ? toLocation.id : '', // // name: (typeof toLocation.name === 'string') ? toLocation.name : '' // }; } return toLocation; }); } /** * 口座残高差し押さえ * 口座取引は、出金取引あるいは転送取引のどちらかを選択できます */ function processAuthorizePaymentCard(params) { return (repos // credentials: { // jwt: JWTCredentials; // } ) => __awaiter(this, void 0, void 0, function* () { var _a; const transaction = yield repos.transaction.projectFieldsInProgressById({ typeOf: factory.transactionType.MoneyTransfer, id: params.purpose.id }, ['agent', 'project', 'recipient', 'seller', 'typeOf', 'object']); const recipient = { typeOf: transaction.recipient.typeOf, id: transaction.recipient.id, name: (typeof transaction.recipient.name === 'string') ? transaction.recipient.name : `A recipient of ${transaction.typeOf} Transaction ${transaction.id}` }; const transactionNumber = (_a = transaction.object.pendingTransaction) === null || _a === void 0 ? void 0 : _a.transactionNumber; // ↓事前に取引番号を発行しているので、ありえないフロー if (typeof transactionNumber !== 'string') { throw new factory.errors.Internal('transaction.object.pendingTransaction.transactionNumber undefined'); } // 承認アクションを開始する const actionAttributes = createAuthorizeMoneyTransferOfferActionAttributes({ object: params.object, recipient, transaction, transactionNumber }); const action = yield repos.action.start(actionAttributes); // 口座取引開始 let responseBody; try { responseBody = yield processMoneyTransferTransaction({ project: params.project, object: params.object, recipient: recipient, transaction: transaction, transactionNumber })(repos); // アクションにchevre取引情報を保管 yield repos.action.findByIdAndUpdate({ id: action.id, update: { 'object.itemOffered.amount.currency': params.object.itemOffered.amount.currency, 'object.itemOffered.object.pendingTransaction': { typeOf: responseBody.typeOf, id: responseBody.id, transactionNumber: responseBody.transactionNumber } } }); } catch (error) { try { yield repos.action.giveUp({ typeOf: action.typeOf, id: action.id, error }); } catch (__) { // no op } throw error; } const result = { price: 0, priceCurrency: factory.priceCurrency.JPY, // requestBody: requestBody, responseBody: responseBody // acceptedOffers: [] // PlaceOrderではないのでacceptedOffersとしては空 // discontinue(2024-06-17~) }; yield repos.action.completeWithVoid({ typeOf: action.typeOf, id: action.id, result }); }); } function createAuthorizeMoneyTransferOfferActionAttributes(params) { var _a; const transaction = params.transaction; const transactionNumber = params.transactionNumber; const recipient = params.recipient; return { project: transaction.project, typeOf: factory.actionType.AuthorizeAction, instrument: { typeOf: factory.assetTransactionType.MoneyTransfer, transactionNumber }, object: { project: { typeOf: transaction.project.typeOf, id: transaction.project.id }, typeOf: factory.offerType.Offer, itemOffered: { typeOf: factory.actionType.MoneyTransfer, amount: Object.assign({ typeOf: 'MonetaryAmount', currency: params.object.itemOffered.amount.currency }, (typeof params.object.itemOffered.amount.value === 'number') ? { value: params.object.itemOffered.amount.value } : undefined), toLocation: params.object.itemOffered.toLocation, object: { pendingTransaction: { typeOf: factory.assetTransactionType.MoneyTransfer, transactionNumber } } }, seller: { id: String(transaction.seller.id), name: (typeof transaction.seller.name === 'string') ? transaction.seller.name : String((_a = transaction.seller.name) === null || _a === void 0 ? void 0 : _a.ja), typeOf: transaction.seller.typeOf }, price: 0, priceCurrency: factory.priceCurrency.JPY }, agent: { typeOf: transaction.agent.typeOf, id: transaction.agent.id }, recipient: recipient, purpose: { typeOf: transaction.typeOf, id: transaction.id } }; } function processMoneyTransferTransaction(params) { // tslint:disable-next-line:max-func-body-length return (repos // credentials: { // jwt: JWTCredentials; // } ) => __awaiter(this, void 0, void 0, function* () { var _a, _b; let pendingTransaction; const transaction = params.transaction; // Sellerに変更(2022-05-30~) const agent = { typeOf: transaction.seller.typeOf, id: String(transaction.seller.id), name: (typeof transaction.seller.name === 'string') ? transaction.seller.name : String((_a = transaction.seller.name) === null || _a === void 0 ? void 0 : _a.ja) }; const recipient = { typeOf: params.recipient.typeOf, id: params.recipient.id, name: (typeof params.recipient.name === 'string') ? params.recipient.name : `${transaction.typeOf} Transaction ${transaction.id}` }; const description = (typeof params.object.description === 'string') ? params.object.description : `${transaction.typeOf}:${transaction.id}`; // 最大1ヵ月のオーソリ const expires = moment() .add(1, 'month') .toDate(); // 発行サービスIDはfromLocationあるいはtoLocationのどちらかに存在するはず const issuedThroughId = params.object.itemOffered.toLocation.issuedThrough.id; if (params.object.fromLocation !== undefined && params.object.itemOffered.toLocation === undefined) { throw new factory.errors.NotImplemented('Withdraw transaction not implemented'); } else if (params.object.fromLocation !== undefined && params.object.itemOffered.toLocation !== undefined) { const { fromLocation } = yield validateFromLocation({ id: params.project.id }, params.object.fromLocation, { id: issuedThroughId })(repos); const { toLocation } = yield validateToLocation({ id: params.project.id }, { typeOf: factory.permit.PermitType.Permit, identifier: params.object.itemOffered.toLocation.identifier, issuedThrough: { id: issuedThroughId } }, { id: issuedThroughId })(repos); // moneyTransferAssetTransactionで実装 pendingTransaction = yield MoneyTransferAssetTransactionService.start(Object.assign({ transactionNumber: params.transactionNumber, typeOf: factory.assetTransactionType.MoneyTransfer, project: { typeOf: factory.organizationType.Project, id: params.project.id }, agent: agent, expires: expires, recipient: recipient, object: { amount: { typeOf: 'MonetaryAmount', value: (typeof params.object.itemOffered.amount.value === 'number') ? params.object.itemOffered.amount.value : 0, currency: params.object.itemOffered.amount.currency }, description: description, fromLocation, toLocation, pendingTransaction: { typeOf: factory.account.transactionType.Transfer, transactionNumber: params.transactionNumber }, force: false } }, (transaction.typeOf === factory.transactionType.MoneyTransfer && typeof ((_b = transaction.object.pendingTransaction) === null || _b === void 0 ? void 0 : _b.identifier) === 'string') ? { identifier: transaction.object.pendingTransaction.identifier } : undefined))(repos); // pendingTransaction = await repos.moneyTransferAssetTransaction.start({ // // 既発行のtransactionNumberを引き継ぐ(2022-05-16~) // transactionNumber: params.transactionNumber, // typeOf: factory.assetTransactionType.MoneyTransfer, // project: { typeOf: factory.organizationType.Project, id: params.project.id }, // agent: agent, // expires: expires, // recipient: recipient, // object: { // amount: { // typeOf: 'MonetaryAmount', // value: (typeof params.object.itemOffered.amount.value === 'number') ? params.object.itemOffered.amount.value : 0, // currency: params.object.itemOffered.amount.currency // }, // description: description, // fromLocation: params.object.fromLocation, // toLocation: { // typeOf: factory.permit.PermitType.Permit, // identifier: params.object.itemOffered.toLocation.identifier, // issuedThrough: { id: params.object.itemOffered.toLocation.issuedThrough?.id } // }, // pendingTransaction: { typeOf: factory.account.transactionType.Transfer, id: '' } // }, // // ユニークネスを保証するために識別子を指定する // ...(transaction.typeOf === factory.transactionType.MoneyTransfer // && typeof transaction.object.pendingTransaction?.identifier === 'string') // ? { identifier: transaction.object.pendingTransaction.identifier } // : undefined // }); } else if (params.object.fromLocation === undefined && params.object.itemOffered.toLocation !== undefined) { throw new factory.errors.NotImplemented('Deposit transaction not implemented'); } else { throw new factory.errors.Argument('Object', 'At least one of accounts from and to must be specified'); } return pendingTransaction; }); } // tslint:disable-next-line:max-func-body-length function validateFromLocation(project, fromLocationBeforeStart, issuedThrough) { return (repos // credentials: { // jwt: JWTCredentials; // } ) => __awaiter(this, void 0, void 0, function* () { var _a, _b; let fromLocation = fromLocationBeforeStart; // discontinue token as fromLocation(ticketTokenへ移行するべき)(2024-12-18~) if (typeof fromLocation === 'string') { throw new factory.errors.NotImplemented('fromLocation as string not implemented'); // トークン化されたペイメントカード情報でリクエストされた場合、実ペイメントカード情報へ変換する // const { authorizedObject } = await CodeService.verifyToken({ // project: { id: project.id }, // agent: { id: project.id, typeOf: factory.organizationType.Project }, // token: fromLocation // })(repos); // const paymentCardOwnershipInfo = authorizedObject; // if (Array.isArray(paymentCardOwnershipInfo)) { // throw new factory.errors.NotImplemented('fromLocation as an array not implemented'); // } // if (paymentCardOwnershipInfo.typeOf !== 'OwnershipInfo') { // throw new factory.errors.Argument('fromLocation', 'must be OwnershipInfo'); // } // if (paymentCardOwnershipInfo.typeOfGood.typeOf !== factory.permit.PermitType.Permit) { // throw new factory.errors.Argument('fromLocation', 'must be Permit'); // } // if (paymentCardOwnershipInfo.typeOfGood.issuedThrough?.typeOf !== factory.product.ProductType.PaymentCard) { // throw new factory.errors.Argument('fromLocation', 'must be issued through PaymentCard'); // } // fromLocation = { // typeOf: paymentCardOwnershipInfo.typeOfGood.typeOf, // identifier: paymentCardOwnershipInfo.typeOfGood.identifier, // issuedThrough: { id: paymentCardOwnershipInfo.typeOfGood.issuedThrough.id } // }; } else { // fromLocationが注文の場合に対応 if (fromLocation.typeOf === factory.order.OrderType.Order) { fromLocation = fromLocation; // 注文検索 const orders = yield repos.order.projectFields({ limit: 1, page: 1, project: { id: { $eq: project.id } }, orderNumbers: [String(fromLocation.orderNumber)], confirmationNumbers: [String(fromLocation.confirmationNumber)] }, { inclusion: ['identifier', 'orderStatus'] }); const order = orders.shift(); if (order === undefined) { throw new factory.errors.NotFound('Order'); } // OrderDeliveredのみ許可 if (order.orderStatus !== factory.orderStatus.OrderDelivered) { throw new factory.errors.Argument('fromLocation', `Invalid order status '${order.orderStatus}''`); } let awardAccounts = []; const awardAccounsValue = (_b = (_a = order.identifier) === null || _a === void 0 ? void 0 : _a.find((i) => i.name === order_1.AWARD_ACCOUNTS_IDENTIFIER_NAME)) === null || _b === void 0 ? void 0 : _b.value; if (typeof awardAccounsValue === 'string' && awardAccounsValue.length > 0) { awardAccounts = JSON.parse(awardAccounsValue); } // 注文口座にPermitは存在しないので、typeOfはAccountで固定のはず const awardAccount = awardAccounts.shift(); if (awardAccount === undefined) { throw new factory.errors.NotFound('award account'); } fromLocation = { typeOf: factory.permit.PermitType.Permit, identifier: awardAccount.accountNumber, // ↓指定しないとPermitのチェックが実行される(注文口座にはPermitが存在しない) hasNoPermit: true, issuedThrough: { id: issuedThrough.id } }; } else if (fromLocation.typeOf === factory.permit.PermitType.Permit) { fromLocation = Object.assign(Object.assign({}, fromLocation), { hasNoPermit: false }); } } return { fromLocation }; }); } function validateToLocation(project, toLocationBeforeStart, issuedThrough) { return (repos) => __awaiter(this, void 0, void 0, function* () { var _a, _b; let toLocation = toLocationBeforeStart; if (typeof toLocation === 'string') { throw new factory.errors.Argument('toLocation', 'string not permitted'); } else { // fromLocationが注文の場合に対応 if (toLocation.typeOf === factory.order.OrderType.Order) { toLocation = toLocation; // 注文検索 const orders = yield repos.order.projectFields({ limit: 1, page: 1, project: { id: { $eq: project.id } }, orderNumbers: [String(toLocation.orderNumber)], confirmationNumbers: [String(toLocation.confirmationNumber)] }, { inclusion: [ 'identifier' ] }); const order = orders.shift(); if (order === undefined) { throw new factory.errors.NotFound('Order'); } let awardAccounts = []; const awardAccounsValue = (_b = (_a = order.identifier) === null || _a === void 0 ? void 0 : _a.find((i) => i.name === order_1.AWARD_ACCOUNTS_IDENTIFIER_NAME)) === null || _b === void 0 ? void 0 : _b.value; if (typeof awardAccounsValue === 'string' && awardAccounsValue.length > 0) { awardAccounts = JSON.parse(awardAccounsValue); } // 注文口座にPermitは存在しないので、typeOfはAccountで固定のはず const awardAccount = awardAccounts.shift(); if (awardAccount === undefined) { throw new factory.errors.NotFound('award account'); } toLocation = { typeOf: factory.permit.PermitType.Permit, identifier: awardAccount.accountNumber, // ↓指定しないとPermitのチェックが実行される(注文口座にはPermitが存在しない) hasNoPermit: true, issuedThrough: { id: issuedThrough.id } }; } else if (toLocation.typeOf === factory.permit.PermitType.Permit) { toLocation = Object.assign(Object.assign({}, toLocation), { hasNoPermit: false }); } } return { toLocation }; }); } /** * 取引確定 */ function confirm(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { const now = new Date(); const transaction = yield repos.transaction.projectFieldsById({ typeOf: factory.transactionType.MoneyTransfer, id: params.id }, ['typeOf', 'status', 'project']); if (transaction.status === factory.transactionStatusType.Confirmed) { // すでに確定済の場合 return; } else if (transaction.status === factory.transactionStatusType.Expired) { throw new factory.errors.Argument('transactionId', 'Transaction already expired'); } else if (transaction.status === factory.transactionStatusType.Canceled) { throw new factory.errors.Argument('transactionId', 'Transaction already canceled'); } // if (transaction.agent.id !== params.agent.id) { // throw new factory.errors.Forbidden('Transaction not yours'); // } // 取引に対する全ての承認アクションをマージ const authorizeActions = yield searchAuthorizeActions({ transaction: transaction, now: now })(repos); // ポストアクションを作成 const potentialActions = yield (0, potentialActions_1.createPotentialActions)({ authorizeActions, transaction }); // 取引確定 yield repos.transaction.confirm({ typeOf: factory.transactionType.MoneyTransfer, id: transaction.id, result: {}, potentialActions: potentialActions }); }); } function searchAuthorizeActions(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { let authorizeActions = yield repos.action.searchByPurpose({ typeOf: factory.actionType.AuthorizeAction, purpose: { typeOf: params.transaction.typeOf, id: params.transaction.id }, // Completedに絞る(2023-05-16~) actionStatus: { $eq: factory.actionStatusType.CompletedActionStatus } }); // 万が一このプロセス中に他処理が発生してもそれらを無視するように、endDateでフィルタリング authorizeActions = authorizeActions.filter((a) => { return (a.endDate instanceof Date) && moment(a.endDate) .isBefore(moment(params.now)); }); return authorizeActions; }); } /** * 取引のタスク出力 */ function exportTasksById(params) { return (repos // settings: Settings ) => __awaiter(this, void 0, void 0, function* () { const transaction = yield repos.transaction.projectFieldsById({ typeOf: factory.transactionType.MoneyTransfer, id: params.id }, ['endDate', 'status', 'project', 'startDate', 'typeOf', 'object', 'potentialActions']); // タスク実行日時バッファの指定があれば調整 let taskRunsAt = new Date(); if (typeof params.runsTasksAfterInSeconds === 'number') { taskRunsAt = moment(taskRunsAt) .add(params.runsTasksAfterInSeconds, 'seconds') .toDate(); } // search settings const setting = yield repos.setting.findOne({ project: { id: { $eq: '*' } } }, ['storage']); const taskAttributes = (0, factory_1.createTasks)({ transaction, runsAt: taskRunsAt }, setting // settings ); yield repos.task.saveMany(taskAttributes, { emitImmediately: true }); }); }