Пример #1
0
        public async Task CanGenerateTransactionReference_Unit()
        {
            PaystackSetting paystackSetting = new PaystackSetting()
            {
                UsePublicKey    = false,
                ApiBaseUrl      = "https://api.paystack.co/",
                ReferencePrefix = "PV",
                PublicKey       = "pk_test_0b413fb09eeb29832cded5a56918a0f5f6a4c2f0",
                SecretKey       = "sk_test_6ce9543acf5dd68127387f49ce437f7f82aafb47",

                ListTransactionEndPoint       = "/transaction",
                VerifyTransactionEndPoint     = "/transaction/verify/",
                FetchTransactionEndPoint      = "/transaction/",
                InitializeTransactionEndPoint = "/transaction/initialize",
                TransactionTotalEndPoint      = "/transaction/totals",
                FetchSettlementsEndPoint      = "/settlement",

                APIAcceptHeaderKey          = "accept",
                APIAcceptHeaderValue        = "application/json",
                APIAuthorizationHeaderKey   = "Authorization",
                APIAuthorizationHeaderValue = "Bearer",

                APIFromQueryParameterKey   = "from",
                APIToQueryParameterKey     = "to",
                APIAmountQueryParameterKey = "amount",
                APIStatusQueryParameterKey = "status",

                APIReferenceParameterKey    = "reference",
                APIAmountParameterKey       = "amount",
                APICallbackUrlParameterKey  = "callback_url",
                APIEmailParameterKey        = "email",
                APICustomFieldsParameterKey = "custom_fields",
                APIMetaDataParameterKey     = "metadata",

                TransactionSearchDateFormat = "yyyy-MM-dd",
            };

            string tranxRef = "099885321155456";
            PaystackTransactionLog transactionLog = new PaystackTransactionLog()
            {
                Reference       = tranxRef,
                TransactionDate = DateTime.UtcNow,
            };

            _settingService.Setup(x => x.GetSetting <PaystackSetting>()).Returns(Task.FromResult(paystackSetting));
            _gatewayLuncher.Setup(x => x.CreateTransactionRef(paystackSetting.ReferencePrefix)).Returns(Task.FromResult(tranxRef));
            _transactionLogService.Setup(x => x.SaveAsync(transactionLog)).Verifiable();

            var transactionRef = new TransactionReference(_transactionLogService.Object, _gatewayLuncher.Object, _settingService.Object, _widgetProvider.Object);
            var response       = await transactionRef.InvokeAsync();

            var result = (ViewViewComponentResult)response;

            Assert.NotNull(result);
            Assert.NotNull(response);
            Assert.IsType <ViewViewComponentResult>(response);
            Assert.IsType <string>(result.ViewData.Model);
        }
Пример #2
0
 public TerminalManageBuilder WithTransactionId(string value)
 {
     if (PaymentMethod == null || !(PaymentMethod is TransactionReference))
     {
         PaymentMethod = new TransactionReference();
     }
     (PaymentMethod as TransactionReference).TransactionId = value;
     return(this);
 }
Пример #3
0
 public TerminalAuthBuilder WithAuthCode(string value)
 {
     if (PaymentMethod == null || !(PaymentMethod is TransactionReference))
     {
         PaymentMethod = new TransactionReference();
     }
     (PaymentMethod as TransactionReference).AuthCode = value;
     return(this);
 }
        private static TransactionReference AddCredit(FifthweekDbContext databaseContext, Random random, UserId userId, decimal amount)
        {
            var transactionReference = TransactionReference.Random();

            AddAppendOnlyLedgerRecord(databaseContext, random, userId, null, -1.2m * amount, LedgerAccountType.Stripe, LedgerTransactionType.CreditAddition, transactionReference);
            AddAppendOnlyLedgerRecord(databaseContext, random, userId, null, amount, LedgerAccountType.FifthweekCredit, LedgerTransactionType.CreditAddition, transactionReference);
            AddAppendOnlyLedgerRecord(databaseContext, random, userId, null, (1.2m * amount) - amount, LedgerAccountType.SalesTax, LedgerTransactionType.CreditAddition, transactionReference);
            return(transactionReference);
        }
        public async Task <CreateTransactionRefundResult> ExecuteAsync(
            UserId enactingUserId,
            TransactionReference transactionReference,
            DateTime timestamp,
            string comment)
        {
            enactingUserId.AssertNotNull("enactingUserId");
            transactionReference.AssertNotNull("transactionReference");

            var formattedComment = string.IsNullOrWhiteSpace(comment)
                ? string.Format("Performed by {0}", enactingUserId)
                : string.Format("Performed by {0} - {1}", enactingUserId, comment);

            var records = await this.getRecordsForTransaction.ExecuteAsync(transactionReference);

            if (records.Count == 0)
            {
                throw new InvalidOperationException("No records found for transaction reference: " + transactionReference);
            }

            UserId subscriberId = null;
            UserId creatorId    = null;

            foreach (var record in records)
            {
                LedgerTransactionType refundType;
                switch (record.TransactionType)
                {
                case LedgerTransactionType.SubscriptionPayment:
                    refundType = LedgerTransactionType.SubscriptionRefund;
                    break;

                default:
                    // This is a safety against both refunding other transaction types,
                    // and refunding transactions which has already been refunded.
                    throw new InvalidOperationException(
                              "Cannot refund transaction of type " + record.TransactionType);
                }

                if (record.AccountType == LedgerAccountType.FifthweekCredit)
                {
                    subscriberId = new UserId(record.AccountOwnerId);
                    creatorId    = new UserId(record.CounterpartyId.Value);
                }

                record.Id              = this.guidCreator.CreateSqlSequential();
                record.Timestamp       = timestamp;
                record.TransactionType = refundType;
                record.Amount          = -record.Amount;
                record.Comment         = formattedComment;
            }

            await this.persistCommittedRecords.ExecuteAsync(records);

            return(new CreateTransactionRefundResult(subscriberId, creatorId));
        }
Пример #6
0
        public async Task WhenNoRecords_ItShouldReturnNull()
        {
            var transactionReference = TransactionReference.Random();

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference)).ReturnsAsync(new List <AppendOnlyLedgerRecord>());

            var result = await this.target.ExecuteAsync(transactionReference);

            Assert.IsNull(result);
        }
        public async Task <IReadOnlyList <AppendOnlyLedgerRecord> > ExecuteAsync(TransactionReference transactionReference)
        {
            transactionReference.AssertNotNull("transactionReference");

            using (var connection = this.connectionFactory.CreateConnection())
            {
                var records = (await connection.QueryAsync <AppendOnlyLedgerRecord>(
                                   GetTransactionRowsSql,
                                   new { TransactionReference = transactionReference.Value })).ToList();

                return(records);
            }
        }
Пример #8
0
        public async Task WhenNoMatchingRecords_ItShouldThrowAnException()
        {
            byte sequentialGuidIndex = 0;

            this.guidCreator.Setup(v => v.CreateSqlSequential()).Returns(() => CreateGuid(0, sequentialGuidIndex++));

            var transactionReference = TransactionReference.Random();

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference))
            .ReturnsAsync(new List <AppendOnlyLedgerRecord>());

            await this.target.ExecuteAsync(EnactingUserId, transactionReference, Now, Comment);
        }
Пример #9
0
 /// <summary>
 /// Sets the related gateway transaction ID; where applicable.
 /// </summary>
 /// <remarks>
 /// This value is used to associated a previous transaction with the
 /// current transaction.
 /// </remarks>
 /// <param name="value">The gateway transaction ID</param>
 /// <returns>AuthorizationBuilder</returns>
 public AuthorizationBuilder WithTransactionId(string value)
 {
     if (PaymentMethod is TransactionReference)
     {
         ((TransactionReference)PaymentMethod).TransactionId = value;
     }
     else
     {
         PaymentMethod = new TransactionReference {
             TransactionId = value
         };
     }
     return(this);
 }
        private static TransactionReference PayCreator(
            FifthweekDbContext databaseContext,
            Random random,
            UserId sourceUserId,
            UserId destinationUserId,
            decimal amount)
        {
            var transactionReference = TransactionReference.Random();

            AddAppendOnlyLedgerRecord(databaseContext, random, sourceUserId, destinationUserId, -amount, LedgerAccountType.FifthweekCredit, LedgerTransactionType.SubscriptionPayment, transactionReference);
            AddAppendOnlyLedgerRecord(databaseContext, random, null, destinationUserId, amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference);
            AddAppendOnlyLedgerRecord(databaseContext, random, null, destinationUserId, -0.7m * amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference);
            AddAppendOnlyLedgerRecord(databaseContext, random, destinationUserId, destinationUserId, 0.7m * amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference);
            return(transactionReference);
        }
        private static List <AppendOnlyLedgerRecord> AddCredit(FifthweekDbContext databaseContext, DateTime timestamp, UserId userId, decimal amount, TransactionReference transactionReference = null)
        {
            var transactionType = amount < 0 ? LedgerTransactionType.CreditRefund : LedgerTransactionType.CreditAddition;
            var result          = new List <AppendOnlyLedgerRecord>();

            if (transactionReference == null)
            {
                transactionReference = TransactionReference.Random();
            }

            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, userId, null, -1.2m * amount, LedgerAccountType.Stripe, transactionType, transactionReference));
            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, userId, null, amount, LedgerAccountType.FifthweekCredit, transactionType, transactionReference));
            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, userId, null, (1.2m * amount) - amount, LedgerAccountType.SalesTax, transactionType, transactionReference));
            return(result);
        }
        private static List <AppendOnlyLedgerRecord> PayCreator(
            FifthweekDbContext databaseContext,
            DateTime timestamp,
            UserId sourceUserId,
            UserId destinationUserId,
            decimal amount)
        {
            var result = new List <AppendOnlyLedgerRecord>();
            var transactionReference = TransactionReference.Random();

            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, sourceUserId, destinationUserId, -amount, LedgerAccountType.FifthweekCredit, LedgerTransactionType.SubscriptionPayment, transactionReference));
            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, null, destinationUserId, amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference));
            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, null, destinationUserId, -0.7m * amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference));
            result.Add(AddAppendOnlyLedgerRecord(databaseContext, timestamp, destinationUserId, destinationUserId, 0.7m * amount, LedgerAccountType.FifthweekRevenue, LedgerTransactionType.SubscriptionPayment, transactionReference));
            return(result);
        }
        public async Task PostTransactionRefundAsync(string transactionReference, [FromBody] TransactionRefundData data)
        {
            transactionReference.AssertUrlParameterProvided("transactionReference");
            data.AssertBodyProvided("data");

            var transactionReferenceObject = new TransactionReference(transactionReference.DecodeGuid());
            var requester = await this.requesterContext.GetRequesterAsync();

            var timestamp = this.timestampCreator.Now();

            await this.createTransactionRefund.HandleAsync(new CreateTransactionRefundCommand(
                                                               requester,
                                                               transactionReferenceObject,
                                                               timestamp,
                                                               data.Comment));
        }
Пример #14
0
        public async Task ItShouldReturnCreditTransactionInformation()
        {
            var data = AddCredit(UserId, 100);
            var transactionReference = new TransactionReference(data.First().TransactionReference);

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference)).ReturnsAsync(data);

            var result = await this.target.ExecuteAsync(transactionReference);

            Assert.AreEqual(
                new GetCreditTransactionInformation.GetCreditTransactionResult(
                    UserId,
                    StripeChargeId,
                    TaxamoTransactionKey,
                    100,
                    100),
                result);
        }
Пример #15
0
        public async Task ItShouldReverseSpecifiedTransaction()
        {
            byte sequentialGuidIndex = 0;

            this.guidCreator.Setup(v => v.CreateSqlSequential()).Returns(() => CreateGuid(0, sequentialGuidIndex++));

            var data = PayCreator(SubscriberId, CreatorId, 100);
            var transactionReference = new TransactionReference(data.First().TransactionReference);

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference)).ReturnsAsync(data);

            int index           = 0;
            var expectedInserts = data.Select(
                v => new AppendOnlyLedgerRecord(
                    CreateGuid(0, (byte)(index++)),
                    v.AccountOwnerId,
                    v.CounterpartyId,
                    Now,
                    -v.Amount,
                    v.AccountType,
                    LedgerTransactionType.SubscriptionRefund,
                    transactionReference.Value,
                    v.InputDataReference,
                    "Performed by " + EnactingUserId + " - comment",
                    null,
                    null)).ToList();

            IReadOnlyList <AppendOnlyLedgerRecord> actualInserts = null;

            this.persistCommittedRecords.Setup(v => v.ExecuteAsync(It.IsAny <IReadOnlyList <AppendOnlyLedgerRecord> >()))
            .Callback <IReadOnlyList <AppendOnlyLedgerRecord> >(v => actualInserts = v)
            .Returns(Task.FromResult(0))
            .Verifiable();

            var result = await this.target.ExecuteAsync(EnactingUserId, transactionReference, Now, Comment);

            Assert.AreEqual(
                new CreateTransactionRefund.CreateTransactionRefundResult(SubscriberId, CreatorId),
                result);

            CollectionAssert.AreEquivalent(expectedInserts, actualInserts.ToList());

            this.persistCommittedRecords.Verify();
        }
Пример #16
0
        public async Task WhenCompleteRefundHasOccured_ItShouldReturnCreditTransactionInformation()
        {
            var data = AddCredit(UserId, 100);
            var transactionReference = new TransactionReference(data.First().TransactionReference);
            var refundData           = AddRefund(UserId, transactionReference.Value, 100);

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference)).ReturnsAsync(data.Concat(refundData).ToList());

            var result = await this.target.ExecuteAsync(transactionReference);

            Assert.AreEqual(
                new GetCreditTransactionInformation.GetCreditTransactionResult(
                    UserId,
                    StripeChargeId,
                    TaxamoTransactionKey,
                    100,
                    0),
                result);
        }
        public async Task PostCreditRequestAsync(string userId, [FromBody] CreditRequestData data)
        {
            userId.AssertUrlParameterProvided("userId");
            data.AssertBodyProvided("data");

            var parsedData   = data.Parse();
            var userIdObject = new UserId(userId.DecodeGuid());
            var requester    = await this.requesterContext.GetRequesterAsync();

            var timestamp            = this.timestampCreator.Now();
            var transactionReference = new TransactionReference(this.guidCreator.CreateSqlSequential());

            await this.applyCreditRequest.HandleAsync(new ApplyCreditRequestCommand(
                                                          requester,
                                                          userIdObject,
                                                          timestamp,
                                                          transactionReference,
                                                          parsedData.Amount,
                                                          parsedData.ExpectedTotalAmount));
        }
Пример #18
0
        public async Task WhenNotCreditRecord_ItThrowAnException()
        {
            var transactionReference = TransactionReference.Random();
            var refundData           = AddRefund(UserId, transactionReference.Value, 100);

            var record = CreateAppendOnlyLedgerRecord(
                new Random(),
                UserId,
                UserId.Random(),
                -100,
                LedgerAccountType.FifthweekCredit,
                LedgerTransactionType.SubscriptionPayment,
                transactionReference.Value);

            this.getRecordsForTransaction.Setup(v => v.ExecuteAsync(transactionReference)).ReturnsAsync(new List <AppendOnlyLedgerRecord> {
                record
            });

            await this.target.ExecuteAsync(transactionReference);
        }
Пример #19
0
        public static Transaction FromNetwork(decimal?amount, string authCode, NtsData originalNtsCode, IPaymentMethod originalPaymentMethod, string messageTypeIndicator = null, string stan = null, string originalTransactionTime = null, string originalProcessingCode = null, string acquirerId = null)
        {
            TransactionReference reference = new TransactionReference();

            reference.OriginalAmount         = amount;
            reference.AcquiringInstitutionId = acquirerId;
            reference.AuthCode             = authCode;
            reference.MessageTypeIndicator = messageTypeIndicator;
            reference.NtsData = originalNtsCode;
            reference.OriginalPaymentMethod   = originalPaymentMethod;
            reference.OriginalTransactionTime = originalTransactionTime;
            reference.SystemTraceAuditNumber  = stan;
            reference.OriginalProcessingCode  = originalProcessingCode;

            Transaction trans = new Transaction();

            trans.TransactionReference = reference;

            return(trans);
        }
Пример #20
0
 /// <summary>
 /// Sets the client transaction ID.
 /// </summary>
 /// <remarks>
 /// This is an application derived value that can be used to identify a
 /// transaction in case a gateway transaction ID is not returned, e.g.
 /// in cases of timeouts.
 ///
 /// The supplied value should be unique to the configured merchant or
 /// terminal account.
 /// </remarks>
 /// <param name="value">The client transaction ID</param>
 /// <returns>AuthorizationBuilder</returns>
 public AuthorizationBuilder WithClientTransactionId(string value)
 {
     if (TransactionType == TransactionType.Reversal || TransactionType == TransactionType.Refund)
     {
         if (PaymentMethod is TransactionReference)
         {
             ((TransactionReference)PaymentMethod).ClientTransactionId = value;
         }
         else
         {
             PaymentMethod = new TransactionReference {
                 ClientTransactionId = value
             };
         }
     }
     else
     {
         ClientTransactionId = value;
     }
     return(this);
 }
        public async Task <GetCreditTransactionResult> ExecuteAsync(TransactionReference transactionReference)
        {
            transactionReference.AssertNotNull("transactionReference");

            var records = await this.getRecordsForTransaction.ExecuteAsync(transactionReference);

            if (records.Count == 0)
            {
                return(null);
            }

            if (records.All(v => v.TransactionType != LedgerTransactionType.CreditAddition))
            {
                throw new InvalidOperationException(
                          "Transaction reference " + transactionReference + " was not a CreditAddition record.");
            }

            var firstRecord = records.First();

            var userId               = firstRecord.AccountOwnerId;
            var stripeChargeId       = firstRecord.StripeChargeId;
            var taxamoTransactionKey = firstRecord.TaxamoTransactionKey;

            var totalCreditAmount = records.First(
                v => v.TransactionType == LedgerTransactionType.CreditAddition &&
                v.AccountType == LedgerAccountType.FifthweekCredit).Amount;

            var existingRefundAmounts = records.Where(
                v => v.TransactionType == LedgerTransactionType.CreditRefund &&
                v.AccountType == LedgerAccountType.FifthweekCredit);

            var creditAmountAvailableForRefund = totalCreditAmount + existingRefundAmounts.Sum(v => v.Amount);

            return(new GetCreditTransactionResult(
                       new UserId(userId),
                       stripeChargeId,
                       taxamoTransactionKey,
                       totalCreditAmount,
                       creditAmountAvailableForRefund));
        }
        public async Task <string> ExecuteAsync(
            string stripeCustomerId,
            AmountInMinorDenomination amount,
            UserId userId,
            TransactionReference transactionReference,
            string taxamoTransactionKey,
            UserType userType)
        {
            stripeCustomerId.AssertNotNull("stripeCustomerId");
            amount.AssertNotNull("amount");
            userId.AssertNotNull("userId");

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

            var options = new StripeChargeCreateOptions
            {
                Amount     = amount.Value,
                Currency   = Currency,
                CustomerId = stripeCustomerId,
                Metadata   = new Dictionary <string, string>
                {
                    { TransactionReferenceMetadataKey, transactionReference.Value.ToString() },
                    { TaxamoTransactionKeyMetadataKey, taxamoTransactionKey },
                    { UserIdMetadataKey, userId.ToString() },
                }
            };

            try
            {
                var result = await this.stripeService.CreateChargeAsync(options, apiKey);

                return(result.Id);
            }
            catch (Exception t)
            {
                throw new StripeChargeFailedException(t);
            }
        }
        private static AppendOnlyLedgerRecord AddAppendOnlyLedgerRecord(
            FifthweekDbContext databaseContext, DateTime timestamp, UserId accountOwnerId, UserId counterpartyId, decimal amount,
            LedgerAccountType ledgerAccountType, LedgerTransactionType transactionType, TransactionReference transactionReference)
        {
            var item = new AppendOnlyLedgerRecord(
                Guid.NewGuid(),
                accountOwnerId == null ? Guid.Empty : accountOwnerId.Value,
                counterpartyId == null ? (Guid?)null : counterpartyId.Value,
                timestamp,
                amount,
                ledgerAccountType,
                transactionType,
                transactionReference.Value,
                Guid.NewGuid(),
                null,
                null,
                null);

            databaseContext.AppendOnlyLedgerRecords.Add(item);

            return(item);
        }
        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);
        }
 private static void AddAppendOnlyLedgerRecord(
     FifthweekDbContext databaseContext, Random random, UserId accountOwnerId, UserId counterpartyId, decimal amount, LedgerAccountType ledgerAccountType, LedgerTransactionType transactionType, TransactionReference transactionReference)
 {
     databaseContext.AppendOnlyLedgerRecords.Add(
         new AppendOnlyLedgerRecord(
             Guid.NewGuid(),
             accountOwnerId == null ? Guid.Empty : accountOwnerId.Value,
             counterpartyId == null ? (Guid?)null : counterpartyId.Value,
             Now.AddDays(random.Next(-100, 100)),
             amount,
             ledgerAccountType,
             transactionType,
             transactionReference.Value,
             Guid.NewGuid(),
             StripeChargeId,
             TaxamoTransactionKey,
             Comment));
 }
Пример #26
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);
        }
Пример #27
0
 public async Task WhenEnactingUserIdIsNull_ItShouldThrowAnException()
 {
     await this.target.ExecuteAsync(null, TransactionReference.Random(), Now, Comment);
 }
        public async Task <bool> ExecuteAsync(
            IReadOnlyList <CalculatedAccountBalanceResult> updatedAccountBalances,
            List <PaymentProcessingException> errors,
            CancellationToken cancellationToken)
        {
            using (PaymentsPerformanceLogger.Instance.Log(typeof(TopUpUserAccountsWithCredit)))
            {
                updatedAccountBalances.AssertNotNull("updatedAccountBalances");
                errors.AssertNotNull("errors");

                var userIdsToRetry = await this.getUsersRequiringPaymentRetry.ExecuteAsync();

                var newUserIds = updatedAccountBalances
                                 .Where(v => v.AccountType == LedgerAccountType.FifthweekCredit &&
                                        v.Amount < MinimumAccountBalanceBeforeCharge)
                                 .Select(v => v.UserId);
                var allUserIds = userIdsToRetry.Concat(newUserIds).Distinct().ToList();

                // Increment all billing status immediately so the user maintains access to his newsfeed.
                await this.incrementPaymentStatus.ExecuteAsync(allUserIds);

                bool recalculateBalances = false;
                foreach (var userId in allUserIds)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }

                    try
                    {
                        // Work out what we should charge the user.
                        var amountToCharge = await this.getUserWeeklySubscriptionsCost.ExecuteAsync(userId);

                        if (amountToCharge == 0)
                        {
                            // No subscriptions, so no need to charge.
                            continue;
                        }

                        amountToCharge = Math.Max(MinimumPaymentAmount, amountToCharge);

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

                        if (origin.PaymentOriginKey == null ||
                            origin.PaymentOriginKeyType == PaymentOriginKeyType.None ||
                            origin.PaymentStatus == PaymentStatus.None)
                        {
                            // If the user doesn't have a stripe customer ID then they haven't given us
                            // credit card details and we can't bill them.
                            // If the user has manually topped up since we incremented the billing status,
                            // it will have been set back to None and we don't need to top up again.
                            continue;
                        }

                        var timestamp            = this.timestampCreator.Now();
                        var transactionReference = new TransactionReference(this.guidCreator.CreateSqlSequential());

                        // And apply the charge.
                        await this.applyUserCredit.ExecuteAsync(
                            userId,
                            timestamp,
                            transactionReference,
                            PositiveInt.Parse(amountToCharge),
                            null,
                            UserType.StandardUser);

                        recalculateBalances = true;
                    }
                    catch (Exception t)
                    {
                        errors.Add(new PaymentProcessingException(t, userId, null));
                    }
                }

                return(recalculateBalances);
            }
        }
        public async Task ExecuteAsync(
            UserId userId,
            DateTime timestamp,
            AmountInMinorDenomination totalAmount,
            AmountInMinorDenomination creditAmount,
            TransactionReference transactionReference,
            string stripeChargeId,
            string taxamoTransactionKey)
        {
            userId.AssertNotNull("userId");
            totalAmount.AssertNotNull("totalAmount");
            creditAmount.AssertNotNull("creditAmount");
            stripeChargeId.AssertNotNull("stripeChargeId");
            taxamoTransactionKey.AssertNotNull("taxamoTransactionKey");

            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 committedRecords = new List <AppendOnlyLedgerRecord>();

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

            committedRecords.Add(new AppendOnlyLedgerRecord(
                                     this.guidCreator.CreateSqlSequential(),
                                     userId.Value,
                                     null,
                                     timestamp,
                                     creditAmount.Value,
                                     LedgerAccountType.FifthweekCredit,
                                     LedgerTransactionType.CreditAddition,
                                     transactionReference.Value,
                                     null,
                                     null,
                                     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.CreditAddition,
                                         transactionReference.Value,
                                         null,
                                         null,
                                         stripeChargeId,
                                         taxamoTransactionKey));
            }

            using (var connection = this.connectionFactory.CreateConnection())
            {
                await connection.InsertAsync(committedRecords);
            }
        }
Пример #30
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);
            }
        }