예제 #1
0
        public async Task <OperationStatusUpdateResultModel> AcceptAsync(Guid id, string hash)
        {
            if (Guid.Empty == id)
            {
                _log.Warning("Operation id is empty");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound));
            }

            if (string.IsNullOrEmpty(hash))
            {
                _log.Warning("Transaction hash is empty");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidTransactionHash));
            }

            if (!hash.IsValidTransactionHash())
            {
                _log.Warning("Transaction hash has invalid format");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidTransactionHash));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var operationRequest = await _operationRequestsRepository.GetByIdAsync(id);
                if (operationRequest == null)
                {
                    var operation = await _operationsRepository.GetByIdAsync(id);
                    if (operation == null)
                    {
                        _log.Warning("Operation request not found by id", context: id);
                        return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound);
                    }

                    if (operation.Status == OperationStatus.Accepted)
                    {
                        _log.Warning("Operation has already been accepted", context: id);
                    }

                    return OperationStatusUpdateResultModel.Succeeded();
                }

                if (operationRequest.Status != OperationStatus.Created)
                {
                    _log.Error(message: "Current operation request status is not expected",
                               context: new { id = operationRequest.Id, currentStatus = operationRequest.Status.ToString() });
                    return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidStatus);
                }

                await _operationRequestsRepository.AcceptAsync(
                    operationRequest.Id,
                    operationRequest.CustomerId,
                    operationRequest.Nonce,
                    operationRequest.MasterWalletAddress,
                    operationRequest.Type,
                    operationRequest.ContextJson,
                    operationRequest.CreatedAt,
                    hash);

                return OperationStatusUpdateResultModel.Succeeded();
            }));
        }
예제 #2
0
        public async Task HandleWalletLinkingAsync(
            string eventId, string customerId, string masterWalletAddress, string internalAddress, string publicAddress, Money18 fee)
        {
            await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                if (!string.IsNullOrEmpty(eventId))
                {
                    var isDuplicate = await _deduplicationLog.IsDuplicateAsync(eventId);

                    if (isDuplicate)
                    {
                        _log.Warning("There is already wallet linking operation with the same id", context: eventId);
                        return;
                    }
                }

                await _operationRequestsProducer.AddAsync(customerId, OperationType.WalletLinking,
                                                          new WalletLinkingContext
                {
                    InternalWalletAddress = internalAddress,
                    PublicWalletAddress   = publicAddress,
                    LinkingFee            = fee
                }, masterWalletAddress);
            });
        }
예제 #3
0
        public async Task <CustomerWalletCreationResultModel> CreateCustomerWalletAsync(string customerId)
        {
            #region Validation

            if (string.IsNullOrEmpty(customerId))
            {
                return(CustomerWalletCreationResultModel.Failed(CustomerWalletCreationError.InvalidCustomerId));
            }

            var walletAlreadyAssigned = await _walletOwnersRepository.GetByOwnerIdAsync(customerId);

            if (walletAlreadyAssigned != null)
            {
                _log.Info("Customer already has wallet assigned", walletAlreadyAssigned);
                return(CustomerWalletCreationResultModel.Failed(CustomerWalletCreationError.AlreadyCreated));
            }

            #endregion

            var createWalletResponse = await _quorumTransactionSignerClient.WalletsApi.CreateWalletAsync();

            try
            {
                await _transactionScopeHandler.WithTransactionAsync(async() =>
                {
                    await _walletOwnersRepository.AddAsync(customerId, createWalletResponse.Address);

                    await _operationRequestsProducer.AddAsync(
                        customerId,
                        OperationType.CustomerWalletCreation,
                        new CustomerWalletContext
                    {
                        CustomerId = customerId, WalletAddress = createWalletResponse.Address
                    });
                });
            }
            catch (Exception e)
            {
                if (e is WalletOwnerDuplicateException)
                {
                    return(CustomerWalletCreationResultModel.Failed(CustomerWalletCreationError.AlreadyCreated));
                }

                throw;
            }

            return(CustomerWalletCreationResultModel.Succeeded());
        }
        public Task HandleAsync(string operationId, Money18 amount, string reason)
        {
            return(_transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var isDuplicate = await _deduplicationLogRepository.IsDuplicateAsync(operationId);

                if (isDuplicate)
                {
                    _log.Warning("There is already seize operation with the same operation id",
                                 context: $"{nameof(operationId)}: {operationId}");
                    return;
                }

                await _operationRequestsProducer.AddAsync(OperationType.SeizeToInternal,
                                                          new SeizeToInternalContext(_privateBlockchainGatewayContractAddress, amount, reason));
            }));
        }
예제 #5
0
        public async Task <GenericOperationResultModel> AddGenericOperationAsync(string operationId, string encodedData, string sourceAddress, string targetAddress)
        {
            if (string.IsNullOrEmpty(encodedData))
            {
                throw new ArgumentNullException(nameof(encodedData));
            }

            if (string.IsNullOrEmpty(sourceAddress))
            {
                throw new ArgumentNullException(nameof(sourceAddress));
            }

            if (string.IsNullOrEmpty(targetAddress))
            {
                throw new ArgumentNullException(nameof(targetAddress));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                if (!string.IsNullOrEmpty(operationId))
                {
                    var isDuplicate = await _deduplicationLog.IsDuplicateAsync(operationId);

                    if (isDuplicate)
                    {
                        _log.Warning("There is already generic operation with the same id", context: operationId);
                        return GenericOperationResultModel.Failed(AddOperationError.DuplicateRequest);
                    }
                }

                var result = await _operationRequestsProducer.AddAsync(
                    OperationType.GenericOperation,
                    new GenericOperationContext
                {
                    Data = encodedData,
                    SourceWalletAddress = sourceAddress,
                    TargetWalletAddress = targetAddress
                }, sourceAddress);

                return GenericOperationResultModel.Succeeded(result);
            }));
        }
예제 #6
0
        public async Task <PaymentStatusUpdateErrorCodes> ApproveByCustomerAsync(string paymentRequestId, Money18 sendingAmount, string customerId)
        {
            if (string.IsNullOrEmpty(paymentRequestId))
            {
                throw new ArgumentNullException(nameof(paymentRequestId));
            }

            if (string.IsNullOrEmpty(customerId))
            {
                throw new ArgumentNullException(nameof(customerId));
            }

            if (sendingAmount <= 0)
            {
                return(PaymentStatusUpdateErrorCodes.InvalidAmount);
            }

            var customerBlockStatusResponse = await _walletManagementClient.Api.GetCustomerWalletBlockStateAsync(customerId);

            if (customerBlockStatusResponse.Status != CustomerWalletActivityStatus.Active)
            {
                return(PaymentStatusUpdateErrorCodes.CustomerWalletIsBlocked);
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var payment = await _paymentsRepository.GetByPaymentRequestIdAsync(paymentRequestId);

                if (payment == null)
                {
                    return PaymentStatusUpdateErrorCodes.PaymentDoesNotExist;
                }

                if (payment.CustomerId != customerId)
                {
                    return PaymentStatusUpdateErrorCodes.CustomerIdDoesNotMatch;
                }

                if (payment.Status != PaymentRequestStatus.Created)
                {
                    return PaymentStatusUpdateErrorCodes.PaymentIsInInvalidStatus;
                }

                if (payment.TokensAmount < sendingAmount)
                {
                    return PaymentStatusUpdateErrorCodes.InvalidAmount;
                }

                var calculatedFiatAmountFromPartnerRate = await CalculateAmountFromPartnerRate(Guid.Parse(payment.CustomerId),
                                                                                               Guid.Parse(payment.PartnerId),
                                                                                               sendingAmount, _tokenSymbol, payment.Currency);

                if (calculatedFiatAmountFromPartnerRate.ErrorCode == EligibilityEngineErrors.PartnerNotFound)
                {
                    return PaymentStatusUpdateErrorCodes.PartnerDoesNotExist;
                }

                if (calculatedFiatAmountFromPartnerRate.ErrorCode == EligibilityEngineErrors.ConversionRateNotFound)
                {
                    return PaymentStatusUpdateErrorCodes.InvalidTokensOrCurrencyRateInPartner;
                }

                payment.Status = PaymentRequestStatus.TokensTransferStarted;
                payment.TokensSendingAmount = sendingAmount;
                payment.FiatSendingAmount = (decimal)calculatedFiatAmountFromPartnerRate.Amount;

                await _paymentsRepository.UpdatePaymentAsync(payment);

                await _statusUpdatePublisher.PublishAsync(new PartnersPaymentStatusUpdatedEvent
                {
                    PaymentRequestId = paymentRequestId,
                    Status = payment.Status.ToContractModel()
                });

                var encodedPaymentData = _blockchainEncodingService.EncodePaymentRequestData(payment.PartnerId,
                                                                                             payment.LocationId, payment.Timestamp.ConvertToLongMs(), payment.CustomerId,
                                                                                             payment.PaymentRequestId);

                var pbfTransferResponse = await _pbfClient.GenericTransfersApi.GenericTransferAsync(
                    new GenericTransferRequestModel
                {
                    Amount = sendingAmount,
                    AdditionalData = encodedPaymentData,
                    RecipientAddress = _settingsService.GetPartnersPaymentsAddress(),
                    SenderCustomerId = customerId,
                    TransferId = paymentRequestId
                });

                if (pbfTransferResponse.Error != TransferError.None)
                {
                    return (PaymentStatusUpdateErrorCodes)pbfTransferResponse.Error;
                }

                await _paymentRequestBlockchainRepository.UpsertAsync(paymentRequestId, pbfTransferResponse.OperationId);
                return PaymentStatusUpdateErrorCodes.None;
            }));
        }
        public async Task <BonusRewardResultModel> RewardAsync(string customerId, Money18 amount, string rewardId,
                                                               string bonusReason, string campaignId, string conditionId)
        {
            if (string.IsNullOrEmpty(customerId))
            {
                return(BonusRewardResultModel.Failed(BonusRewardError.InvalidCustomerId));
            }

            if (string.IsNullOrEmpty(bonusReason))
            {
                return(BonusRewardResultModel.Failed(BonusRewardError.MissingBonusReason));
            }

            if (string.IsNullOrEmpty(campaignId))
            {
                return(BonusRewardResultModel.Failed(BonusRewardError.InvalidCampaignId));
            }

            if (amount <= 0)
            {
                return(BonusRewardResultModel.Failed(BonusRewardError.InvalidAmount));
            }

            var customerWalletAddressResult = await _walletsService.GetCustomerWalletAsync(customerId);

            switch (customerWalletAddressResult.Error)
            {
            case CustomerWalletAddressError.None:
                break;

            case CustomerWalletAddressError.CustomerWalletMissing:
                return(BonusRewardResultModel.Failed(BonusRewardError.CustomerWalletMissing));

            case CustomerWalletAddressError.InvalidCustomerId:
                return(BonusRewardResultModel.Failed(BonusRewardError.InvalidCustomerId));

            default:
                throw new ArgumentOutOfRangeException(nameof(customerWalletAddressResult.Error));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var isDuplicate = await _deduplicationLog.IsDuplicateAsync(rewardId);

                if (isDuplicate)
                {
                    _log.Warning("There is already bonus reward operation with the same id", context: rewardId);
                    return BonusRewardResultModel.Failed(BonusRewardError.DuplicateRequest);
                }

                await _operationRequestsProducer.AddAsync(
                    customerId,
                    OperationType.CustomerBonusReward,
                    new CustomerBonusRewardContext
                {
                    CustomerId = customerId,
                    Amount = amount,
                    // todo: temporary solution
                    MinterAddress = _walletCreationMasterWalletAddress,
                    WalletAddress = customerWalletAddressResult.WalletAddress,
                    RequestId = rewardId,
                    BonusReason = bonusReason,
                    CampaignId = campaignId,
                    ConditionId = conditionId
                });

                return BonusRewardResultModel.Succeeded();
            }));
        }
예제 #8
0
        public async Task <PaymentTransfersErrorCodes> ProcessPaymentTransferAsync(string transferId)
        {
            if (string.IsNullOrEmpty(transferId))
            {
                _log.Error(message: "TransferId is empty when trying to update payment transfer status");
                throw new ArgumentNullException(nameof(transferId));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var paymentTransfer = await _paymentTransfersRepository.GetByTransferIdAsync(transferId);
                if (paymentTransfer == null)
                {
                    _log.Warning("Payment transfer not find by transfer id", context: transferId);
                    return PaymentTransfersErrorCodes.PaymentTransferNotFound;
                }

                if (paymentTransfer.Status == PaymentTransferStatus.Processing)
                {
                    return PaymentTransfersErrorCodes.PaymentTransferAlreadyProcessing;
                }

                if (paymentTransfer.Status != PaymentTransferStatus.Pending)
                {
                    _log.Info("Payment transfer's status cannot be updated because it is not in Pending status");
                    return PaymentTransfersErrorCodes.InvalidStatus;
                }

                var walletAddressResponse = await _pbfClient.CustomersApi.GetWalletAddress(Guid.Parse(paymentTransfer.CustomerId));
                if (walletAddressResponse.Error != CustomerWalletAddressError.None)
                {
                    _log.Warning("Customer transfer does not have a BC wallet when payment transfer is being processed");
                    return PaymentTransfersErrorCodes.CustomerWalletDoesNotExist;
                }

                var payInvoiceResponse = await _realEstateIntegrationClient.Api.PayInvoiceAsync(new InvoicePayRequestModel
                {
                    CurrencyCode = paymentTransfer.Currency,
                    ReceiptNumber = paymentTransfer.ReceiptNumber,
                    CustomerAccountNumber = paymentTransfer.CustomerAccountNumber,
                    CustomerTrxId = paymentTransfer.CustomerTrxId,
                    LocationCode = paymentTransfer.LocationCode,
                    InstallmentType = paymentTransfer.InstallmentType,
                    PaidAmount = paymentTransfer.AmountInFiat,
                    OrgId = paymentTransfer.OrgId,
                });

                _log.Info("Pay invoice response",
                          context: new
                {
                    payInvoiceResponse.Status,
                    payInvoiceResponse.ErrorCode,
                    payInvoiceResponse.ReceiptNumber,
                    paymentTransfer.TransferId
                });

                var status = payInvoiceResponse.Status.ToLower() == PaidInvoiceSuccessfullyStatus
                    ? PaymentTransferStatus.Accepted
                    : PaymentTransferStatus.Rejected;

                await CreateApproveOrRejectTransactionInPbf(paymentTransfer.SpendRuleId, paymentTransfer.InvoiceId,
                                                            paymentTransfer.TransferId, status);
                await _paymentTransfersRepository.SetStatusAsync(paymentTransfer.TransferId, PaymentTransferStatus.Processing);

                _log.Info($"Successfully changed payment transfer's status to Processing", transferId);
                return PaymentTransfersErrorCodes.None;
            }));
        }
예제 #9
0
        public async Task <TransferResultModel> P2PTransferAsync(
            string senderId,
            string recipientId,
            Money18 amount,
            string transferRequestId)
        {
            #region Validation
            if (string.IsNullOrEmpty(recipientId))
            {
                return(TransferResultModel.Failed(TransferError.InvalidRecipientId));
            }

            if (string.IsNullOrEmpty(senderId))
            {
                return(TransferResultModel.Failed(TransferError.InvalidSenderId));
            }

            if (amount <= 0)
            {
                return(TransferResultModel.Failed(TransferError.InvalidAmount));
            }

            var senderAddressResult = await _walletsService.GetCustomerWalletAsync(senderId);

            var senderAddressValidationResultError = ValidateSenderAddress(senderAddressResult.Error);

            if (senderAddressValidationResultError != TransferError.None)
            {
                return(TransferResultModel.Failed(senderAddressValidationResultError));
            }

            var recipientAddressResult = await _walletsService.GetCustomerWalletAsync(recipientId);

            var recipientAddressValidationResultError = ValidateRecipientAddress(recipientAddressResult.Error);

            if (recipientAddressValidationResultError != TransferError.None)
            {
                return(TransferResultModel.Failed(recipientAddressValidationResultError));
            }

            var senderBalanceValidationError = await ValidateSenderBalance(senderId, amount);

            if (senderBalanceValidationError != TransferError.None)
            {
                return(TransferResultModel.Failed(senderBalanceValidationError));
            }

            #endregion

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                if (!string.IsNullOrEmpty(transferRequestId))
                {
                    var isDuplicate = await _deduplicationLog.IsDuplicateAsync(transferRequestId);

                    if (isDuplicate)
                    {
                        _log.Warning("There is already transfer operation with the same request id", context: transferRequestId);
                        return TransferResultModel.Failed(TransferError.DuplicateRequest);
                    }
                }

                var result = await _operationRequestsProducer.AddAsync(
                    senderId,
                    OperationType.TokensTransfer,
                    new TokensTransferContext
                {
                    SenderWalletAddress = senderAddressResult.WalletAddress,
                    RecipientWalletAddress = recipientAddressResult.WalletAddress,
                    Amount = amount,
                    RequestId = transferRequestId,
                },
                    senderAddressResult.WalletAddress);

                return TransferResultModel.Succeeded(result);
            }));
        }