예제 #1
0
        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));
        }
예제 #3
0
        public async Task <IPaymentRequestTransaction> CreateTransactionAsync(ICreateTransactionCommand command)
        {
            IPaymentRequestTransaction transaction = await _transactionsService.CreateTransactionAsync(command);

            await _paymentRequestService.UpdateStatusAsync(command.WalletAddress);

            return(transaction);
        }
예제 #4
0
        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);
        }
예제 #6
0
        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);
            }
        }
예제 #8
0
        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));
        }
예제 #9
0
        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
                }
            });
        }
예제 #10
0
        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));
        }
예제 #11
0
        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)
            });
        }
예제 #12
0
        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)
            });
        }