private IUpdateTransactionCommand MapToUpdateCommand( CompleteOutTxCommand cmd, IPaymentRequestTransaction tx) { switch (tx.TransactionType) { case TransactionType.Payment: return(Mapper.Map <CompletePaymentOutTxCommand>(cmd, MapConfirmed())); case TransactionType.Refund: return(Mapper.Map <CompleteRefundOutTxCommand>(cmd, opt => { opt.Items["Confirmations"] = _transactionConfirmationCount; opt.Items["WalletAddress"] = tx.WalletAddress; })); case TransactionType.Exchange: return(Mapper.Map <CompleteExchangeOutTxCommand>(cmd, MapConfirmed())); case TransactionType.Settlement: return(Mapper.Map <CompleteSettlementOutTxCommand>(cmd, MapConfirmed())); case TransactionType.CashOut: return(Mapper.Map <CompleteCashoutTxCommand>(cmd, MapConfirmed())); default: throw new UnexpectedTransactionTypeException(tx.TransactionType); } }
public async Task <IPaymentRequestTransaction> CreateTransactionAsync(ICreateTransactionCommand request) { IPaymentRequest paymentRequest = null; if (!string.IsNullOrEmpty(request.WalletAddress)) { paymentRequest = await _paymentRequestRepository.FindAsync(request.WalletAddress); if (paymentRequest == null) { throw new PaymentRequestNotFoundException(request.WalletAddress); } IPaymentRequestTransaction existing = await _transactionRepository.GetByIdAsync(request.Blockchain, request.IdentityType, request.Identity, request.WalletAddress); if (existing != null) { await UpdateAsync(Mapper.Map <UpdateTransactionCommand>(request)); return(await _transactionRepository.GetByIdAsync(request.Blockchain, request.IdentityType, request.Identity, request.WalletAddress)); } } var transactionEntity = Mapper.Map <PaymentRequestTransaction>(request, opts => opts.Items["PaymentRequest"] = paymentRequest); return(await _transactionRepository.AddAsync(transactionEntity)); }
public async Task <IPaymentRequestTransaction> CreateTransactionAsync(ICreateTransactionCommand command) { IPaymentRequestTransaction transaction = await _transactionsService.CreateTransactionAsync(command); await _paymentRequestService.UpdateStatusAsync(command.WalletAddress); return(transaction); }
public async Task <IPaymentRequestTransaction> AddAsync(IPaymentRequestTransaction transaction) { PaymentRequestTransactionEntity entity = PaymentRequestTransactionEntity.ByWalletAddress.Create(transaction); await _storage.InsertOrMergeAsync(entity); AzureIndex indexByIdentity = PaymentRequestTransactionEntity.IndexByIdentity.Create(entity); await _indexByIdentityStorage.InsertOrMergeAsync(indexByIdentity); AzureIndex indexByDueDate = PaymentRequestTransactionEntity.IndexByDueDate.Create(entity); await _indexByDueDateStorage.InsertOrMergeAsync(indexByDueDate); return(Mapper.Map <PaymentRequestTransaction>(entity)); }
public async Task PublishAsync(IPaymentRequestTransaction transaction) { var message = new NewTransactionMessage { Id = transaction.TransactionId, AssetId = transaction.AssetId, Amount = transaction.Amount, Confirmations = transaction.Confirmations, BlockId = transaction.BlockId, Blockchain = Enum.Parse <BlockchainType>(transaction.Blockchain.ToString()), //todo DueDate = transaction.DueDate ?? DateTime.UtcNow.AddDays(1) }; _log.Info("Publishing new transaction message", message); await _publisher.ProduceAsync(message); }
public async Task PublishAsync(IPaymentRequestTransaction transaction) { var message = new NewTransactionMessage { Id = transaction.TransactionId, AssetId = transaction.AssetId, Amount = transaction.Amount, Confirmations = transaction.Confirmations, BlockId = transaction.BlockId, Blockchain = Enum.Parse <BlockchainType>(transaction.Blockchain.ToString()), DueDate = transaction.DueDate }; await _log.WriteInfoAsync(nameof(TransactionPublisher), nameof(PublishAsync), message.ToJson(), "Publishing new transaction message"); await _publisher.ProduceAsync(message); }
public async Task UpdateAsync(IUpdateTransactionCommand request) { var businessTransactions = new List <IPaymentRequestTransaction>(); if (!string.IsNullOrEmpty(request.WalletAddress)) { IPaymentRequestTransaction tx = await _transactionRepository.GetByIdAsync(request.Blockchain, request.IdentityType, request.Identity, request.WalletAddress); if (tx == null) { throw new TransactionNotFoundException(request.Blockchain, request.IdentityType, request.Identity, request.WalletAddress); } businessTransactions.Add(tx); } else { IReadOnlyList <IPaymentRequestTransaction> txs = await _transactionRepository.GetByBcnIdentityAsync(request.Blockchain, request.IdentityType, request.Identity); businessTransactions.AddRange(txs); } foreach (IPaymentRequestTransaction tx in businessTransactions) { tx.BlockId = request.BlockId; tx.Confirmations = request.Confirmations; tx.FirstSeen = request.FirstSeen; if (request.IsPayment()) { tx.Amount = request.Amount; } if (tx.IdentityType != TransactionIdentityType.Hash) { tx.TransactionId = request.Hash; } await _transactionRepository.UpdateAsync(tx); } }
public async Task <IPaymentRequestTransaction> UpdateAsync(IPaymentRequestTransaction transaction) { PaymentRequestTransactionEntity entity = await _storage.MergeAsync( PaymentRequestTransactionEntity.ByWalletAddress.GeneratePartitionKey(transaction.WalletAddress), PaymentRequestTransactionEntity.ByWalletAddress.GenerateRowKey(transaction.Blockchain, transaction.IdentityType, transaction.Identity), e => { e.TransactionId = transaction.TransactionId; e.Amount = transaction.Amount; e.BlockId = transaction.BlockId; e.Confirmations = transaction.Confirmations; e.FirstSeen = transaction.FirstSeen; e.PaymentRequestId = transaction.PaymentRequestId; return(e); }); return(Mapper.Map <PaymentRequestTransaction>(entity)); }
public IEnumerable <TransferAmount> Resolve(IPaymentRequestTransaction source, object destination, IEnumerable <TransferAmount> destMember, ResolutionContext context) { string destinationAddress = context.Items["destinationAddress"]?.ToString(); string bcnAddress = _walletManager.ResolveBlockchainAddressAsync(source.WalletAddress, source.AssetId) .GetAwaiter().GetResult(); return(new List <TransferAmount> { new TransferAmount { Amount = source.Amount, Source = bcnAddress, Destination = string.IsNullOrWhiteSpace(destinationAddress) ? source.SourceWalletAddresses.Single() : destinationAddress } }); }
public async Task <RefundResult> ExecuteAsync(string merchantId, string paymentRequestId, string destinationWalletAddress) { IPaymentRequest paymentRequest = await _paymentRequestService.GetAsync(merchantId, paymentRequestId); if (paymentRequest == null) { throw new RefundValidationException(RefundErrorType.PaymentRequestNotFound); } if (!paymentRequest.StatusValidForRefund()) { throw new RefundValidationException(RefundErrorType.NotAllowedInStatus); } IEnumerable <IPaymentRequestTransaction> paymentTxs = (await _transactionsService.GetByWalletAsync(paymentRequest.WalletAddress)).Where(x => x.IsPayment()).ToList(); if (!paymentTxs.Any()) { throw new RefundValidationException(RefundErrorType.NoPaymentTransactions); } if (paymentTxs.MoreThanOne()) { throw new RefundValidationException(RefundErrorType.MultitransactionNotSupported); } IPaymentRequestTransaction tx = paymentTxs.Single(); bool isValidAddress = string.IsNullOrWhiteSpace(destinationWalletAddress) || await _blockchainAddressValidator.Execute(destinationWalletAddress, tx.Blockchain); if (!isValidAddress) { throw new RefundValidationException(RefundErrorType.InvalidDestinationAddress); } if (!tx.SourceWalletAddresses.Any()) { throw new RefundValidationException(RefundErrorType.InvalidDestinationAddress); } if (string.IsNullOrWhiteSpace(destinationWalletAddress)) { if (tx.SourceWalletAddresses.MoreThanOne()) { throw new RefundValidationException(RefundErrorType.InvalidDestinationAddress); } } //validation finished, refund request accepted await _paymentRequestService.UpdateStatusAsync(paymentRequest.WalletAddress, PaymentRequestStatusInfo.RefundInProgress()); TransferResult transferResult; DateTime refundDueDate; try { TransferCommand refundTransferCommand = Mapper.Map <TransferCommand>(tx, opts => opts.Items["destinationAddress"] = destinationWalletAddress); transferResult = await _transferService.ExecuteAsync(refundTransferCommand); refundDueDate = transferResult.Timestamp.Add(_refundExpirationPeriod); foreach (var transferResultTransaction in transferResult.Transactions) { if (!string.IsNullOrEmpty(transferResultTransaction.Error)) { await _log.WriteWarningAsync(nameof(RefundService), nameof(ExecuteAsync), transferResultTransaction.ToJson(), "Transaction failed"); continue; } IPaymentRequestTransaction refundTransaction = await _transactionsService.CreateTransactionAsync( new CreateTransactionCommand { Amount = transferResultTransaction.Amount, AssetId = transferResultTransaction.AssetId, Confirmations = 0, Hash = transferResultTransaction.Hash, WalletAddress = paymentRequest.WalletAddress, Type = TransactionType.Refund, Blockchain = transferResult.Blockchain, FirstSeen = null, DueDate = refundDueDate, TransferId = transferResult.Id, IdentityType = transferResultTransaction.IdentityType, Identity = transferResultTransaction.Identity }); await _transactionPublisher.PublishAsync(refundTransaction); } if (transferResult.Transactions.All(x => x.HasError)) { throw new RefundOperationFailedException { TransferErrors = transferResult.Transactions.Select(x => x.Error) } } ; IEnumerable <TransferTransactionResult> errorTransactions = transferResult.Transactions.Where(x => x.HasError).ToList(); if (errorTransactions.Any()) { throw new RefundOperationPartiallyFailedException(errorTransactions.Select(x => x.Error)); } } catch (Exception) { await _paymentRequestService.UpdateStatusAsync(paymentRequest.WalletAddress, PaymentRequestStatusInfo.Error(PaymentRequestProcessingError.UnknownRefund)); throw; } return(await PrepareRefundResult(paymentRequest, transferResult, refundDueDate)); }
public async Task <SettlementResult> SettleAsync(string merchantId, string paymentRequestId) { IPaymentRequest paymentRequest = await _paymentRequestRepository.GetAsync(merchantId, paymentRequestId); if (paymentRequest == null) { throw new PaymentRequestNotFoundException(merchantId, paymentRequestId); } if (!paymentRequest.StatusValidForSettlement()) { throw new SettlementValidationException("Invalid status"); } string sourceWalletAddress = await _walletsManager.ResolveBlockchainAddressAsync( paymentRequest.WalletAddress, paymentRequest.PaymentAssetId); string destWalletAddress; // check asset to settle to merchant wallet if (_autoSettleSettingsResolver.AllowToSettleToMerchantWallet(paymentRequest.PaymentAssetId)) { destWalletAddress = (await _merchantWalletService.GetDefaultAsync( paymentRequest.MerchantId, paymentRequest.PaymentAssetId, PaymentDirection.Incoming))?.WalletAddress; } else { BlockchainType network = await _assetSettingsService.GetNetworkAsync(paymentRequest.PaymentAssetId); destWalletAddress = _autoSettleSettingsResolver.GetAutoSettleWallet(network); } if (string.IsNullOrEmpty(destWalletAddress)) { throw new SettlementValidationException( $"Destination wallet address is empty. Details: {new {paymentRequest.MerchantId, paymentRequest.PaymentAssetId}.ToJson()}"); } TransferResult transferResult = await _settlementRetryPolicy .ExecuteAsync(() => _transferService.SettleThrowFail( paymentRequest.PaymentAssetId, sourceWalletAddress, destWalletAddress)); foreach (var transferResultTransaction in transferResult.Transactions) { IPaymentRequestTransaction settlementTx = await _transactionsService.CreateTransactionAsync( new CreateTransactionCommand { Amount = transferResultTransaction.Amount, Blockchain = transferResult.Blockchain, AssetId = transferResultTransaction.AssetId, WalletAddress = paymentRequest.WalletAddress, DueDate = paymentRequest.DueDate, IdentityType = transferResultTransaction.IdentityType, Identity = transferResultTransaction.Identity, Confirmations = 0, Hash = transferResultTransaction.Hash, TransferId = transferResult.Id, Type = TransactionType.Settlement, SourceWalletAddresses = transferResultTransaction.Sources.ToArray() }); await _transactionPublisher.PublishAsync(settlementTx); } return(new SettlementResult { Blockchain = transferResult.Blockchain, WalletAddress = destWalletAddress, AssetId = paymentRequest.PaymentAssetId, Amount = transferResult.GetSuccedeedTxs().Sum(x => x.Amount) }); }
public async Task <PaymentResult> PayAsync(PaymentCommand cmd) { IPaymentRequest paymentRequest = await _paymentRequestRepository.GetAsync(cmd.MerchantId, cmd.PaymentRequestId); if (paymentRequest == null) { throw new PaymentRequestNotFoundException(cmd.MerchantId, cmd.PaymentRequestId); } string payerWalletAddress = (await _merchantWalletService.GetDefaultAsync( cmd.PayerMerchantId, paymentRequest.PaymentAssetId, PaymentDirection.Outgoing)).WalletAddress; string destinationWalletAddress = await _walletsManager.ResolveBlockchainAddressAsync( paymentRequest.WalletAddress, paymentRequest.PaymentAssetId); bool locked = await _paymentLocksService.TryAcquireLockAsync( paymentRequest.Id, cmd.MerchantId, paymentRequest.DueDate); if (!locked) { throw new DistributedLockAcquireException(paymentRequest.Id); } TransferResult transferResult; try { await UpdateStatusAsync(paymentRequest.WalletAddress, PaymentRequestStatusInfo.InProcess()); transferResult = await _paymentRetryPolicy .ExecuteAsync(() => _transferService.PayThrowFail( paymentRequest.PaymentAssetId, payerWalletAddress, destinationWalletAddress, cmd.Amount)); foreach (var transferResultTransaction in transferResult.Transactions) { IPaymentRequestTransaction paymentTx = await _transactionsService.CreateTransactionAsync( new CreateTransactionCommand { Amount = transferResultTransaction.Amount, Blockchain = transferResult.Blockchain, AssetId = transferResultTransaction.AssetId, WalletAddress = paymentRequest.WalletAddress, DueDate = paymentRequest.DueDate, IdentityType = transferResultTransaction.IdentityType, Identity = transferResultTransaction.Identity, Confirmations = 0, Hash = transferResultTransaction.Hash, TransferId = transferResult.Id, Type = TransactionType.Payment, SourceWalletAddresses = transferResultTransaction.Sources.ToArray() }); await _transactionPublisher.PublishAsync(paymentTx); } } catch (Exception e) { PaymentRequestStatusInfo newStatus = e is InsufficientFundsException ? PaymentRequestStatusInfo.New() : PaymentRequestStatusInfo.Error(PaymentRequestProcessingError.UnknownPayment); await UpdateStatusAsync(paymentRequest.WalletAddress, newStatus); await _paymentLocksService.ReleaseLockAsync(paymentRequest.Id, cmd.MerchantId); throw; } return(new PaymentResult { PaymentRequestId = paymentRequest.Id, PaymentRequestWalletAddress = paymentRequest.WalletAddress, AssetId = transferResult.Transactions.Unique(x => x.AssetId).Single(), Amount = transferResult.GetSuccedeedTxs().Sum(x => x.Amount) }); }