public async Task HandleAsync(RefundPointsCommand command, CancellationToken cancellationToken) { await commandValidator.ValidateAndThrowAsync(command, cancellationToken : cancellationToken).ConfigureAwait(false); try { using (var context = dbContextFactory.CreateDbContext()) { var consumptionEvent = await context.PointsConsumedEvents.FirstAsync(e => e.TransactionId == command.TransactionId).ConfigureAwait(false); var points = consumptionEvent.PointChange; var transactionId = command.TransactionId; context.PointsRefundedEvents.Add(new PointsRefundedEvent { PointChange = points, Reason = $"Refunded {points} points consumed by transaction {transactionId}.", UtcDateTimeRecorded = DateTime.UtcNow, UserId = consumptionEvent.UserId, TransactionId = command.TransactionId }); await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } } catch (DbUpdateException ex) when(ex.IsUniqueConstraintViolationError()) { // This means this consumption has already been refunded, can be ignored. return; } }
public async Task HandleAsync(EarnPointsCommand command, CancellationToken cancellationToken) { commandValidator.ValidateAndThrow(command); try { using (var context = dbContextFactory.CreateDbContext()) { context.PointsEarnedEvents.Add(new PointsEarnedEvent { PointChange = command.Points, Reason = EarnPointsReason, UtcDateTimeRecorded = DateTime.UtcNow, UserId = command.UserId, TransactionId = command.TransactionId }); await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } } catch (DbUpdateException ex) when(ex.IsUniqueConstraintViolationError()) { // This means this earning has already been registered, can be ignored. return; } }
public async Task HandleAsync(ConsumePointsCommand command, CancellationToken cancellationToken) { await commandValidator.ValidateAndThrowAsync(command, cancellationToken : cancellationToken).ConfigureAwait(false); try { using (var context = dbContextFactory.CreateDbContext()) { context.PointsConsumedEvents.Add(new PointsConsumedEvent { PointChange = command.Points, UtcDateTimeRecorded = DateTime.UtcNow, UserId = command.UserId, TransactionId = command.TransactionId, Reason = ConsumePointsReason, }); // Assume optimistic concurrency so that points aren't changed between retrieving the sum and adding the consume record. await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } } catch (DbUpdateException ex) when(ex.IsUniqueConstraintViolationError()) { // This means this consumption has already been registered, can be ignored. return; } }
public async Task <int> CalculateTotalAsync(int userId, CancellationToken cancellationToken) { using (var context = dbContextFactory.CreateDbContext()) { var totalEarned = await context.PointsEarnedEvents.Where(evt => evt.UserId == userId).SumAsync(e => e.PointChange).ConfigureAwait(false); var totalRefunded = await context.PointsRefundedEvents.Where(evt => evt.UserId == userId).SumAsync(e => e.PointChange).ConfigureAwait(false); var totalConsumed = await context.PointsConsumedEvents.Where(evt => evt.UserId == userId).SumAsync(e => e.PointChange).ConfigureAwait(false); var total = totalEarned + totalRefunded - totalConsumed; return(total); } }
public RefundPointsCommandValidator(ILoyaltyDbContextFactory dbContextFactory) { RuleFor(cmd => cmd.TransactionId) .NotEmpty() .WithMessage(CommonValidationMessages.CannotBeNullOrEmpty) .DependentRules(() => { RuleFor(cmd => cmd.TransactionId) .MustAsync(async(string transactionId, CancellationToken cancellationToken) => { using (var context = dbContextFactory.CreateDbContext()) { var consumptionEvent = await context .PointsConsumedEvents .FirstOrDefaultAsync(e => e.TransactionId == transactionId).ConfigureAwait(false); return(consumptionEvent != null); } }) .WithMessage(ValidationMessages.PointsConsumptionNotFound); }); }