I don't know what you mean exactly. This call alone does not lead to an exception. And I would like to know why this object is updated somewhere else outside of my code.
I have protected the HandleWebhookAsync method in my PalmaPaymentRequestAppService with a semaphore. This way, the various webhook events should not interfere with each other:
In addition, the error only occurs later when HandleInvoiceAsync has already been successfully processed.
I have sent you the log file by email. Also my implementation of StripePaymentGateway and PaymentRequestAppService.
Some background information: We need to save additional data from Stripe (invoice, CustomerId for link to the Stripe customer portal, ...). Since the WebHooks are processed in parallel and access the same PaymentRequest object, there are concurrency exceptions. I tried to secure this with a semaphore, but at the same time some of your code is still being executed somewhere. I have tried to switch off all distributed events so that I have it completely under my own control, but I have not yet succeeded.
Yes, I know, but then a complete deployment is necessary and that takes about 30 minutes... ;-) Do you need the EF messages?
I wanted to overwrite the configuration in Azure with the environment variables. This worked for the default MinimumLevel. For the EntityFramework, however, I don't think this works... I have no debug log messages in the file.
But I will send you the log file that I am currently receiving by e-mail.
I wanted to activate EnableSensitiveDataLogging according to your instructions.
But then I get the following error:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
I have solved the log configuration via appsettings.json. I think the following should be enough, right?
Hi, thanks for sending me the following files:
Unfortunately I could not find the cause in these files...
Where else is an update made on the PaymentRequest?
Part 2:
2025-01-08 09:49:43.229 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionUpdatedAsync: Begin handle Stripe event 'customer.subscription.updated' with id 'evt_1QevtSHPiPVaq9xpIZFViU7N'. Important payload data: SubscriptionId='sub_1QevtLHPiPVaq9xpu4IS2vSf', PaymentRequestId='9e476206-1f24-2f92-334b-3a17577e5568', PlanId='price_1PuqpAHPiPVaq9xpirMIDMuj', PeriodEndDate='08.01.2026 09:49:35', ThreadId='39'
2025-01-08 09:49:43.240 +00:00 [WRN] There is an entry which is not saved due to concurrency exception:
PaymentRequest {Id: 9e476206-1f24-2f92-334b-3a17577e5568} Modified
2025-01-08 09:49:43.241 +00:00 [WRN] There is an entry which is not saved due to concurrency exception:
PaymentRequest {Id: 9e476206-1f24-2f92-334b-3a17577e5568} Modified
2025-01-08 09:49:43.251 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '9e476206-1f24-2f92-334b-3a17577e5568': PeriodEndDateUtc='08.01.2026 09:49:35', ThreadId='38'
2025-01-08 09:49:43.261 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionUpdatedAsync: End handle Stripe event 'customer.subscription.updated'. Payment request updated with PeriodEndDateUtc='08.01.2026 09:49:35', ThreadId='38'
2025-01-08 09:49:43.262 +00:00 [ERR] ---------- RemoteServiceErrorInfo ----------
{
"code": null,
"message": "The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.",
"details": null,
"data": null,
"validationErrors": null
}
2025-01-08 09:49:43.287 +00:00 [ERR] ---------- RemoteServiceErrorInfo ----------
{
"code": null,
"message": "The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.",
"details": null,
"data": null,
"validationErrors": null
}
2025-01-08 09:49:43.270 +00:00 [ERR] The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Volo.Abp.Data.AbpDbConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
---> Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Update.Internal.SqlServerModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Volo.Abp.Uow.UnitOfWork.SaveChangesAsync(CancellationToken cancellationToken)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2025-01-08 09:49:43.291 +00:00 [ERR] The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Volo.Abp.Data.AbpDbConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
---> Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Update.Internal.SqlServerModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Volo.Abp.Uow.UnitOfWork.SaveChangesAsync(CancellationToken cancellationToken)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
at Volo.Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2025-01-08 09:49:44.065 +00:00 [INF] PalmaPaymentRequestAppService.CompleteAsync, paymentGateway='stripe', ThreadId='35'
2025-01-08 09:49:44.263 +00:00 [INF] PalmaStripePaymentGateway.CompleteAsync: Begin complete Stripe request. PaymentRequest='9e476206-1f24-2f92-334b-3a17577e5568'
2025-01-08 09:49:44.271 +00:00 [INF] PalmaStripePaymentGateway.CompleteAsync: End complete Stripe request. PaymentRequest='9e476206-1f24-2f92-334b-3a17577e5568' updated with currency & state, ThreadId='35'
Here is an excerpt from the log file, I have tried to make as many log messages as possible. But unfortunately I can't see where exactly the exceptions are happening, as this seems to come from your module.
Due to the character limitation in two parts...
Part 1:
2025-01-08 09:49:16.002 +00:00 [INF] PalmaPaymentRequestAppService.CreateAsync, ThreadId='38'
2025-01-08 09:49:16.103 +00:00 [INF] PalmaPaymentRequestAppService.UpdateWithExtraPropertiesAsync: PaymentRequest '9e476206-1f24-2f92-334b-3a17577e5568' updated with extra properties: TenantId='70938969-ef4a-ba91-a4ca-3a16f1add23d', TerminationId='01dfb6ef-fb21-6f9d-8b85-3a16f1af3030', ThreadId='21'
2025-01-08 09:49:16.614 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, ThreadId='21'
2025-01-08 09:49:17.940 +00:00 [INF] PalmaStripePaymentGateway.StartAsync: Start Stripe request. PaymentRequest='9e476206-1f24-2f92-334b-3a17577e5568'
2025-01-08 09:49:42.494 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync, paymentGateway='stripe', eventType='customer.subscription.created', ThreadId='39'
2025-01-08 09:49:42.505 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'customer.subscription.created' with id 'evt_1QevtRHPiPVaq9xpEy1e75VL'
2025-01-08 09:49:42.511 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionCreatedAsync: Begin handle Stripe event 'customer.subscription.created' with id 'evt_1QevtRHPiPVaq9xpEy1e75VL'. Important payload data: SubscriptionId='sub_1QevtLHPiPVaq9xpu4IS2vSf', PaymentRequestId='9e476206-1f24-2f92-334b-3a17577e5568', ThreadId='39'
2025-01-08 09:49:42.653 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync, paymentGateway='stripe', eventType='invoice.finalized', ThreadId='39'
2025-01-08 09:49:42.755 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'invoice.finalized' with id 'evt_1QevtRHPiPVaq9xpOSrJK4Kk'
2025-01-08 09:49:42.777 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync, paymentGateway='stripe', eventType='checkout.session.completed', ThreadId='38'
2025-01-08 09:49:42.787 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionCreatedAsync: End handle Stripe event 'customer.subscription.created'. Payment request updated with ExternalSubscriptionId='sub_1QevtLHPiPVaq9xpu4IS2vSf', ThreadId='39'
2025-01-08 09:49:42.804 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'checkout.session.completed' with id 'evt_1QevtRHPiPVaq9xp7aJCb7vy'
2025-01-08 09:49:42.819 +00:00 [WRN] PalmaStripePaymentGateway.HandleCheckoutSessionCompletedAsync: Unhandled Stripe event 'checkout.session.completed' with id 'evt_1QevtRHPiPVaq9xp7aJCb7vy'
2025-01-08 09:49:42.814 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: Begin handle Stripe event 'invoice.finalized' with id 'evt_1QevtRHPiPVaq9xpOSrJK4Kk'. Important payload data: SubscriptionId='sub_1QevtLHPiPVaq9xpu4IS2vSf', PaymentRequestId='9e476206-1f24-2f92-334b-3a17577e5568', InvoiceId='in_1QevtL***', CustomerName='Buyer Paypal', CustomerEmail='buyer.paypal@***', AmountDue='12000', AmountPaid='0', AmountRemaining=='12000', InvoiceUrl='https://invoice.stripe.com/i/a***', ThreadId='39'
2025-01-08 09:49:42.883 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '9e476206-1f24-2f92-334b-3a17577e5568': ExternalCustomerName='Buyer Paypal', ExternalCustomerEmailAddress='buyer.paypal@***', AmountDue='12000', AmountPaid='0', AmountRemaining='12000', InvoiceUrl='https://invoice.stripe.com/i/a***', ThreadId='39'
2025-01-08 09:49:42.894 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: End handle Stripe event 'invoice.finalized'. Payment request updated with invoice data, ThreadId='39'
2025-01-08 09:49:42.922 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync, paymentGateway='stripe', eventType='invoice.payment_succeeded', ThreadId='38'
2025-01-08 09:49:42.963 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'invoice.payment_succeeded' with id 'evt_1QevtSHPiPVaq9xpu6c9BDJ2'
2025-01-08 09:49:42.974 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: Begin handle Stripe event 'invoice.payment_succeeded' with id 'evt_1QevtSHPiPVaq9xpu6c9BDJ2'. Important payload data: SubscriptionId='sub_1QevtLHPiPVaq9xpu4IS2vSf', PaymentRequestId='9e476206-1f24-2f92-334b-3a17577e5568', InvoiceId='in_1QevtLHPiPVaq9xpj9VPg1w0', CustomerName='Buyer Paypal', CustomerEmail='buyer.paypal@***', AmountDue='12000', AmountPaid='12000', AmountRemaining=='0', InvoiceUrl='https://invoice.stripe.com/i/a***', ThreadId='38'
2025-01-08 09:49:43.023 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync, paymentGateway='stripe', eventType='customer.subscription.updated', ThreadId='39'
2025-01-08 09:49:43.027 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'customer.subscription.updated' with id 'evt_1QevtSHPiPVaq9xpIZFViU7N'
2025-01-08 09:49:43.219 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '9e476206-1f24-2f92-334b-3a17577e5568': ExternalCustomerName='Buyer Paypal', ExternalCustomerEmailAddress='buyer.paypal@***', AmountDue='12000', AmountPaid='12000', AmountRemaining='0', InvoiceUrl='https://invoice.stripe.com/i/a***', ThreadId='39'
2025-01-08 09:49:43.221 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: End handle Stripe event 'invoice.payment_succeeded'. Payment request updated with invoice data, ThreadId='39'
We use the PaymentModule for payment processing via Stripe. Because we want to store various additional information (e.g. about the invoice) in the payment request, we have derived from the StripePaymentGateway and overwritten the CompleteAsync & HandleWebhookAsync methods, for example.
Now we always have concurrency exceptions because the PaymentModule in the PaymentRequestAppService probably also updates the PaymentRequest at the same time.
We have secured our methods with a semaphore so that the processing of our webhook events is synchronized and they cannot access the PaymentRequest at the same time. I have the code from your StripePaymentRequestGateway and we have overwritten all the corresponding methods there, so no relevant code is executed there.
But what happens in your PaymentRequestAppService is still a black box for us.
could you provide me with the code? Preferably from the whole PaymentModule, if that is not possible, then at least from the PaymentRequestAppService.
how should the problem be solved from your point of view? If you send events to the distributed event bus in the app service, then I won't be able to synchronize this with my own event processing, will I?
Thanks, Adrian
You can publish an event in
PalmaTenantDatabaseMigrationHandler
Ah yes, that's it. Thank you!