mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
settle pending transactions on backend side
This commit is contained in:
parent
d403087ed1
commit
04a84c0ae2
@ -8,6 +8,7 @@ import { SendCoinsArgs } from './model/SendCoinsArgs'
|
||||
import { revertSendCoins } from './query/revertSendCoins'
|
||||
import { settleSendCoins } from './query/settleSendCoins'
|
||||
import { voteForSendCoins } from './query/voteForSendCoins'
|
||||
import { revertSettledSendCoins } from './query/revertSettledSendCoins'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export class SendCoinsClient {
|
||||
@ -79,7 +80,7 @@ export class SendCoinsClient {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(settleSendCoins, { args })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.commitSendCoins?.acknowledged) {
|
||||
if (!data?.settleSendCoins?.acknowledged) {
|
||||
logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint)
|
||||
return false
|
||||
}
|
||||
@ -89,4 +90,24 @@ export class SendCoinsClient {
|
||||
throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
}
|
||||
}
|
||||
|
||||
revertSettledSendCoins = async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(revertSettledSendCoins, { args })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.revertSettledSendCoins?.acknowledged) {
|
||||
logger.warn(
|
||||
'X-Com: revertSettledSendCoins without response data from endpoint',
|
||||
this.endpoint,
|
||||
)
|
||||
return false
|
||||
}
|
||||
logger.debug(`X-Com: revertSettledSendCoins successful from endpoint=${this.endpoint}`)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(`X-Com: revertSettledSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSettledSendCoins = gql`
|
||||
mutation (
|
||||
$communityReceiverIdentifier: String!
|
||||
$userReceiverIdentifier: String!
|
||||
$creationDate: Date!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$communitySenderIdentifier: String!
|
||||
$userSenderIdentifier: String!
|
||||
$userSenderName: String!
|
||||
) {
|
||||
revertSettledSendCoins(
|
||||
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||
userReceiverIdentifier: $userReceiverIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communitySenderIdentifier: $communitySenderIdentifier
|
||||
userSenderIdentifier: $userSenderIdentifier
|
||||
userSenderName: $userSenderName
|
||||
)
|
||||
}
|
||||
`
|
||||
@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
WAIT_ON_PENDING = 2,
|
||||
PENDING = 3,
|
||||
WAIT_ON_CONFIRM = 4,
|
||||
CONFIRMED = 5,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
|
||||
registerEnumType(PendingTransactionState, {
|
||||
|
||||
@ -15,6 +15,7 @@ import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
||||
import { fullName } from '@/util/utilities'
|
||||
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
|
||||
|
||||
export async function processXComPendingSendCoins(
|
||||
receiverFCom: DbFederatedCommunity,
|
||||
@ -174,6 +175,31 @@ export async function processXComCommittingSendCoins(
|
||||
logger.debug(`X-Com: ready for settleSendCoins with args=`, args)
|
||||
const acknoleged = await client.settleSendCoins(args)
|
||||
logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged)
|
||||
if (acknoleged) {
|
||||
// settle the pending transaction on receiver-side was successfull, so now settle the sender side
|
||||
try {
|
||||
await settlePendingSenderTransaction(senderCom, sender, pendingTx)
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: `, err)
|
||||
// revert the existing pending transaction on receiver side
|
||||
let revertCount = 0
|
||||
logger.debug(`X-Com: first try to revertSetteledSendCoins of receiver`)
|
||||
do {
|
||||
if (await client.revertSettledSendCoins(args)) {
|
||||
logger.debug(
|
||||
`revertSettledSendCoins()-1_0... successfull after revertCount=`,
|
||||
revertCount,
|
||||
)
|
||||
// treat revertingSettledSendCoins as an error of the whole sendCoins-process
|
||||
throw new LogError('Error in settle sender pending transaction: `, err')
|
||||
}
|
||||
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
|
||||
throw new LogError(
|
||||
`Error in reverting receiver pending transaction even after revertCount=`,
|
||||
revertCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
|
||||
import { getLastTransaction } from './getLastTransaction'
|
||||
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
||||
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
|
||||
|
||||
export async function settlePendingSenderTransaction(
|
||||
homeCom: DbCommunity,
|
||||
senderUser: DbUser,
|
||||
pendingTx: DbPendingTransaction,
|
||||
): Promise<boolean> {
|
||||
// TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!!
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
logger.debug(`start Transaction for write-access...`)
|
||||
|
||||
try {
|
||||
logger.info('X-Com: settlePendingSenderTransaction:', homeCom, senderUser, pendingTx)
|
||||
|
||||
// ensure that no other pendingTx with the same sender or recipient exists
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) {
|
||||
throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient')
|
||||
}
|
||||
|
||||
const lastTransaction = await getLastTransaction(senderUser.id)
|
||||
|
||||
if (lastTransaction?.id !== pendingTx.previous) {
|
||||
throw new LogError(
|
||||
`X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`,
|
||||
)
|
||||
}
|
||||
|
||||
// transfer the pendingTx to the transactions table
|
||||
const transactionSend = new dbTransaction()
|
||||
transactionSend.typeId = pendingTx.typeId
|
||||
transactionSend.memo = pendingTx.memo
|
||||
transactionSend.userId = pendingTx.userId
|
||||
transactionSend.userGradidoID = pendingTx.userGradidoID
|
||||
transactionSend.userName = pendingTx.userName
|
||||
transactionSend.linkedUserId = pendingTx.linkedUserId
|
||||
transactionSend.linkedUserGradidoID = pendingTx.linkedUserGradidoID
|
||||
transactionSend.linkedUserName = pendingTx.linkedUserName
|
||||
transactionSend.amount = pendingTx.amount
|
||||
const sendBalance = await calculateSenderBalance(
|
||||
senderUser.id,
|
||||
pendingTx.amount,
|
||||
pendingTx.balanceDate,
|
||||
)
|
||||
if (sendBalance?.balance !== pendingTx.balance) {
|
||||
throw new LogError(
|
||||
`X-Com: Calculation-Error on receiver balance: receiveBalance=${sendBalance?.balance}, pendingTx.balance=${pendingTx.balance}`,
|
||||
)
|
||||
}
|
||||
transactionSend.balance = pendingTx.balance
|
||||
transactionSend.balanceDate = pendingTx.balanceDate
|
||||
transactionSend.decay = pendingTx.decay
|
||||
transactionSend.decayStart = pendingTx.decayStart
|
||||
transactionSend.previous = pendingTx.previous
|
||||
transactionSend.linkedTransactionId = pendingTx.linkedTransactionId
|
||||
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
||||
logger.debug(`send Transaction inserted: ${dbTransaction}`)
|
||||
|
||||
// and mark the pendingTx in the pending_transactions table as settled
|
||||
pendingTx.state = PendingTransactionState.SETTLED
|
||||
await queryRunner.manager.save(DbPendingTransaction, pendingTx)
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit send Transaction successful...`)
|
||||
|
||||
/*
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
recipient,
|
||||
sender,
|
||||
transactionReceive,
|
||||
transactionReceive.amount,
|
||||
)
|
||||
*/
|
||||
// trigger to send transaction via dlt-connector
|
||||
// void sendTransactionsToDltConnector()
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('X-Com: send Transaction was not successful', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
releaseLock()
|
||||
}
|
||||
/*
|
||||
void sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
email: recipient.emailContact.email,
|
||||
language: recipient.language,
|
||||
senderFirstName: sender.firstName,
|
||||
senderLastName: sender.lastName,
|
||||
senderEmail: sender.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
})
|
||||
if (transactionLink) {
|
||||
void sendTransactionLinkRedeemedEmail({
|
||||
firstName: sender.firstName,
|
||||
lastName: sender.lastName,
|
||||
email: sender.emailContact.email,
|
||||
language: sender.language,
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
senderEmail: recipient.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
logger.info(`finished executeTransaction successfully`)
|
||||
} finally {
|
||||
releaseLock()
|
||||
}
|
||||
*/
|
||||
return true
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user