public async Task <InitializeCreditRequestResult> HandleAsync(
            UserId userId,
            PositiveInt amount,
            PositiveInt expectedTotalAmount,
            UserType userType)
        {
            userId.AssertNotNull("userId");
            amount.AssertNotNull("amount");

            var origin = await this.getUserPaymentOrigin.ExecuteAsync(userId);

            if (origin.PaymentOriginKey == null || origin.PaymentOriginKeyType == PaymentOriginKeyType.None)
            {
                throw new CreditCardDetailsDoNotExistException();
            }

            // Create taxamo transaction.
            var taxamoTransaction = await this.createTaxamoTransaction.ExecuteAsync(
                amount,
                origin.CountryCode,
                origin.CreditCardPrefix,
                origin.IpAddress,
                origin.OriginalTaxamoTransactionKey,
                userType);

            // Verify expected amount matches data returned from taxamo.
            if (expectedTotalAmount != null && taxamoTransaction.TotalAmount.Value != expectedTotalAmount.Value)
            {
                await this.deleteTaxamoTransaction.ExecuteAsync(taxamoTransaction.Key, userType);

                throw new BadRequestException("The expected total amount did not match the calculated total amount.");
            }

            return(new InitializeCreditRequestResult(taxamoTransaction, origin));
        }
예제 #2
0
        public async Task ExecuteAsync(
            UserId enactingUserId,
            string stripeChargeId,
            PositiveInt totalRefundAmount,
            RefundCreditReason reason,
            UserType userType)
        {
            enactingUserId.AssertNotNull("enactingUserId");
            stripeChargeId.AssertNotNull("stripeChargeId");
            totalRefundAmount.AssertNotNull("totalRefundAmount");

            var apiKey = this.apiKeyRepository.GetApiKey(userType);

            var options = new StripeRefundCreateOptions
            {
                Amount   = totalRefundAmount.Value,
                Reason   = this.GetReason(reason),
                Metadata = new Dictionary <string, string>
                {
                    { EnactingUserIdMetadataKey, enactingUserId.ToString() },
                }
            };

            await this.stripeService.RefundChargeAsync(stripeChargeId, options, apiKey);
        }
예제 #3
0
        public async Task <TaxamoRefundResult> ExecuteAsync(
            string taxamoTransactionKey,
            PositiveInt refundCreditAmount,
            UserType userType)
        {
            taxamoTransactionKey.AssertNotNull("taxamoTransactionKey");
            refundCreditAmount.AssertNotNull("refundCreditAmount");

            var amount = AmountInMinorDenomination.Create(refundCreditAmount).ToMajorDenomination();

            var apiKey = this.taxamoApiKeyRepository.GetApiKey(userType);

            var input = new CreateRefundIn
            {
                Amount   = amount,
                CustomId = CreateTaxamoTransaction.CustomId,
            };

            var result = await this.taxamoService.CreateRefundAsync(taxamoTransactionKey, input, apiKey);

            var totalAmount = AmountInMinorDenomination.FromMajorDenomination(result.TotalAmount.Value);
            var taxAmount   = AmountInMinorDenomination.FromMajorDenomination(result.TaxAmount.Value);

            return(new TaxamoRefundResult(totalAmount.ToPositiveInt(), taxAmount.ToNonNegativeInt()));
        }
예제 #4
0
        public async Task HandleAsync(
            UserId userId,
            PositiveInt amount)
        {
            userId.AssertNotNull("userId");
            amount.AssertNotNull("amount");

            var timestamp = this.timestampCreator.Now();

            // Just update account balance directly.
            await this.setTestUserAccountBalance.ExecuteAsync(userId, timestamp, amount);
        }
예제 #5
0
        public async Task <TaxamoCalculationResult> ExecuteAsync(
            PositiveInt amount,
            string billingCountryCode,
            string creditCardPrefix,
            string ipAddress,
            string originalTaxamoTransactionKey,
            UserType userType)
        {
            amount.AssertNotNull("amount");

            var apiKey = this.taxamoApiKeyRepository.GetApiKey(userType);

            var input = new CalculateTaxIn
            {
                Transaction = new InputTransaction
                {
                    CurrencyCode     = PaymentConstants.Usd,
                    TransactionLines = new List <InputTransactionLine>
                    {
                        new InputTransactionLine
                        {
                            CustomId = CreateTaxamoTransaction.CustomId,
                            Amount   = new AmountInMinorDenomination(amount.Value).ToMajorDenomination()
                        }
                    },
                    BuyerCreditCardPrefix = creditCardPrefix,
                    BuyerIp                = ipAddress,
                    BillingCountryCode     = billingCountryCode,
                    OriginalTransactionKey = originalTaxamoTransactionKey
                }
            };

            var result = await this.taxamoService.CalculateTaxAsync(input, apiKey);

            var countryDetected = this.IsCountryDetected(result);

            return(new TaxamoCalculationResult(
                       result.Transaction.Amount == null ? null : AmountInMinorDenomination.FromMajorDenomination(result.Transaction.Amount.Value),
                       result.Transaction.TotalAmount == null ? null : AmountInMinorDenomination.FromMajorDenomination(result.Transaction.TotalAmount.Value),
                       result.Transaction.TaxAmount == null ? null : AmountInMinorDenomination.FromMajorDenomination(result.Transaction.TaxAmount.Value),
                       result.Transaction.TransactionLines[0].TaxRate,
                       result.Transaction.TransactionLines[0].TaxName,
                       result.Transaction.TaxEntityName,
                       countryDetected ? result.Transaction.CountryName : null,
                       countryDetected ? null : this.GetPossibleCountries(result)));
        }
예제 #6
0
        public async Task ExecuteAsync(UserId userId, DateTime timestamp, PositiveInt amount)
        {
            userId.AssertNotNull("userId");
            amount.AssertNotNull("amount");

            using (var connection = this.connectionFactory.CreateConnection())
            {
                await connection.ExecuteAsync(
                    Sql,
                    new
                {
                    UserId    = userId.Value,
                    Timestamp = timestamp,
                    Amount    = amount.Value,
                });
            }
        }
        public async Task <GetNewsfeedDbResult> ExecuteAsync(
            UserId requestorId,
            UserId creatorId,
            IReadOnlyList <ChannelId> requestedChannelIds,
            DateTime now,
            DateTime origin,
            bool searchForwards,
            NonNegativeInt startIndex,
            PositiveInt count)
        {
            requestorId.AssertNotNull("requestorId");
            startIndex.AssertNotNull("startIndex");
            count.AssertNotNull("count");

            var parameters = new
            {
                RequestorId         = requestorId.Value,
                CreatorId           = creatorId == null ? null : (Guid?)creatorId.Value,
                RequestedChannelIds = requestedChannelIds == null ? null : requestedChannelIds.Select(v => v.Value).ToList(),
                Now        = now,
                Origin     = origin,
                StartIndex = startIndex.Value,
                Count      = count.Value
            };

            using (var connection = this.connectionFactory.CreateConnection())
            {
                var query = new StringBuilder();

                query.Append(GetSqlStart(requestorId, SqlQuerySource.Newsfeed));

                query.Append(CreateFilter(requestorId, creatorId, requestedChannelIds, now, origin, searchForwards, startIndex, count, false));

                query.Append(SqlEnd);

                var entities = (await connection.QueryAsync <NewsfeedPost.Builder>(query.ToString(), parameters)).ToList();
                ProcessNewsfeedResults(entities);

                return(new GetNewsfeedDbResult(entities.Select(_ => _.Build()).ToList()));
            }
        }
예제 #8
0
        public async Task ExecuteAsync(
            UserId userId,
            DateTime timestamp,
            TransactionReference transactionReference,
            PositiveInt amount,
            PositiveInt expectedTotalAmount,
            UserType userType)
        {
            userId.AssertNotNull("userId");
            transactionReference.AssertNotNull("transactionReference");
            amount.AssertNotNull("amount");

            // We split this up into three phases that have individual retry handlers.
            // The first phase can be retried without issue if there are transient failures.
            var initializeResult = await this.retryOnTransientFailure.HandleAsync(
                () => this.initializeCreditRequest.HandleAsync(userId, amount, expectedTotalAmount, userType));

            // This phase could be put at the end of the first phase, but it runs the risk of someone inserting
            // a statement afterwards that causes a transient failure, so for safety it has been isolated.
            var stripeTransactionResult = await this.retryOnTransientFailure.HandleAsync(
                () => this.performCreditRequest.HandleAsync(
                    userId,
                    timestamp,
                    transactionReference,
                    initializeResult.TaxamoTransaction,
                    initializeResult.Origin,
                    userType));

            try
            {
                Task commitToDatabaseTask;
                if (userType == UserType.StandardUser)
                {
                    // Finally we commit to the local database...
                    commitToDatabaseTask = this.retryOnTransientFailure.HandleAsync(
                        () => this.commitCreditToDatabase.HandleAsync(
                            userId,
                            initializeResult.TaxamoTransaction,
                            initializeResult.Origin,
                            stripeTransactionResult));
                }
                else
                {
                    commitToDatabaseTask = this.retryOnTransientFailure.HandleAsync(
                        () => this.commitTestUserCreditToDatabase.HandleAsync(
                            userId,
                            amount));
                }

                // ... and commit taxamo transaction.
                var commitToTaxamoTask = this.retryOnTransientFailure.HandleAsync(
                    () => this.commitTaxamoTransaction.ExecuteAsync(
                        initializeResult.TaxamoTransaction,
                        stripeTransactionResult,
                        userType));

                // We run the two committing tasks in parallel as even if one fails we would like the other to try and succeed.
                await Task.WhenAll(commitToDatabaseTask, commitToTaxamoTask);
            }
            catch (Exception t)
            {
                var json = JsonConvert.SerializeObject(
                    new
                {
                    UserId                  = userId,
                    InitializeResult        = initializeResult,
                    StripeTransactionResult = stripeTransactionResult,
                });

                throw new FailedToApplyCreditException(json, t);
            }
        }
예제 #9
0
        public async Task ExecuteAsync(
            UserId enactingUserId,
            UserId userId,
            DateTime timestamp,
            PositiveInt totalAmount,
            PositiveInt creditAmount,
            TransactionReference transactionReference,
            string stripeChargeId,
            string taxamoTransactionKey,
            string comment)
        {
            enactingUserId.AssertNotNull("enactingUserId");
            userId.AssertNotNull("userId");
            totalAmount.AssertNotNull("totalAmount");
            creditAmount.AssertNotNull("creditAmount");
            transactionReference.AssertNotNull("transactionReference");
            stripeChargeId.AssertNotNull("stripeChargeId");
            taxamoTransactionKey.AssertNotNull("taxamoTransactionKey");
            comment.AssertNotNull("comment");

            if (totalAmount.Value < creditAmount.Value)
            {
                throw new InvalidOperationException(string.Format(
                                                        "The total charged amount ({0}) cannot be less than the amount credited ({1}).",
                                                        totalAmount.Value,
                                                        creditAmount.Value));
            }

            var formattedComment = string.Format("{0} (Performed By {1})", comment, enactingUserId);

            var committedRecords = new List <AppendOnlyLedgerRecord>();

            committedRecords.Add(new AppendOnlyLedgerRecord(
                                     this.guidCreator.CreateSqlSequential(),
                                     userId.Value,
                                     null,
                                     timestamp,
                                     totalAmount.Value,
                                     LedgerAccountType.Stripe,
                                     LedgerTransactionType.CreditRefund,
                                     transactionReference.Value,
                                     null,
                                     formattedComment,
                                     stripeChargeId,
                                     taxamoTransactionKey));

            committedRecords.Add(new AppendOnlyLedgerRecord(
                                     this.guidCreator.CreateSqlSequential(),
                                     userId.Value,
                                     null,
                                     timestamp,
                                     -creditAmount.Value,
                                     LedgerAccountType.FifthweekCredit,
                                     LedgerTransactionType.CreditRefund,
                                     transactionReference.Value,
                                     null,
                                     formattedComment,
                                     stripeChargeId,
                                     taxamoTransactionKey));

            var tax = totalAmount.Value - creditAmount.Value;

            if (tax > 0)
            {
                committedRecords.Add(new AppendOnlyLedgerRecord(
                                         this.guidCreator.CreateSqlSequential(),
                                         userId.Value,
                                         null,
                                         timestamp,
                                         -tax,
                                         LedgerAccountType.SalesTax,
                                         LedgerTransactionType.CreditRefund,
                                         transactionReference.Value,
                                         null,
                                         formattedComment,
                                         stripeChargeId,
                                         taxamoTransactionKey));
            }

            await this.persistCommittedRecords.ExecuteAsync(committedRecords);
        }
        public async Task <UserId> ExecuteAsync(
            UserId enactingUserId,
            TransactionReference transactionReference,
            DateTime timestamp,
            PositiveInt refundCreditAmount,
            RefundCreditReason reason,
            string comment)
        {
            enactingUserId.AssertNotNull("enactingUserId");
            transactionReference.AssertNotNull("transactionReference");
            refundCreditAmount.AssertNotNull("refundCreditAmount");

            var transaction = await this.retryOnTransientFailure.HandleAsync(
                () => this.getCreditTransaction.ExecuteAsync(transactionReference));

            if (transaction == null)
            {
                throw new BadRequestException(
                          "Transaction reference " + transactionReference + " was not a valid transaction, or the user is not a standard user.");
            }

            if (refundCreditAmount.Value > transaction.CreditAmountAvailableForRefund)
            {
                throw new BadRequestException(
                          "Requested refund amount was greater than amount available for refund.");
            }

            var userId = transaction.UserId;

            // Refund on taxamo, get tax refund amount.
            var taxamoResult = await this.retryOnTransientFailure.HandleAsync(
                () => this.createTaxamoRefund.ExecuteAsync(transaction.TaxamoTransactionKey, refundCreditAmount, UserType.StandardUser));

            try
            {
                // Call CreateCreditRefundDbStatement
                await this.retryOnTransientFailure.HandleAsync(
                    () => this.persistCreditRefund.ExecuteAsync(
                        enactingUserId,
                        userId,
                        timestamp,
                        taxamoResult.TotalRefundAmount,
                        refundCreditAmount,
                        transactionReference,
                        transaction.StripeChargeId,
                        transaction.TaxamoTransactionKey,
                        comment));

                // refund on stripe.
                await this.retryOnTransientFailure.HandleAsync(
                    () => this.createStripeRefund.ExecuteAsync(enactingUserId, transaction.StripeChargeId, taxamoResult.TotalRefundAmount, reason, UserType.StandardUser));
            }
            catch (Exception t)
            {
                var json = JsonConvert.SerializeObject(
                    new
                {
                    TransactionReference = transactionReference,
                    Transaction          = transaction,
                    TaxamoResult         = taxamoResult,
                });

                throw new FailedToRefundCreditException(json, t);
            }

            return(userId);
        }