public async Task <StripeTransactionResult> HandleAsync( UserId userId, DateTime timestamp, TransactionReference transactionReference, TaxamoTransactionResult taxamoTransaction, UserPaymentOriginResult origin, UserType userType) { userId.AssertNotNull("userId"); transactionReference.AssertNotNull("transactionReference"); taxamoTransaction.AssertNotNull("taxamoTransaction"); origin.AssertNotNull("origin"); // Perform stripe transaction. if (origin.PaymentOriginKeyType != PaymentOriginKeyType.Stripe) { throw new InvalidOperationException("Unexpected payment origin: " + origin.PaymentOriginKeyType); } var stripeChargeId = await this.performStripeCharge.ExecuteAsync( origin.PaymentOriginKey, taxamoTransaction.TotalAmount, userId, transactionReference, taxamoTransaction.Key, userType); return(new StripeTransactionResult(timestamp, transactionReference, stripeChargeId)); }
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 <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 <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 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); } }
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); }