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); }
public TerminalManageBuilder WithTransactionId(string value) { if (PaymentMethod == null || !(PaymentMethod is TransactionReference)) { PaymentMethod = new TransactionReference(); } (PaymentMethod as TransactionReference).TransactionId = value; return(this); }
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)); }
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); } }
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); }
/// <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)); }
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); }
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(); }
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)); }
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); }
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); }
/// <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)); }
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 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); } }
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); } }