Activities of "ageiter"

A possible bugfix / solution to this problem would be to also save the PaymentRequestId in the metadata of the subscription. Then you would not have to search for the PaymentRequest using the ExternalSubscriptionId, instead you would have the PaymentRequestId available directly.

I have now changed this for me and it works.

The adjustments in the StripePaymentGateway would be as follows:

public virtual async Task<PaymentRequestStartResult> StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input)
{

    ...

    var options = new SessionCreateOptions
    {
        ...

        Metadata = new Dictionary<string, string>
        {
            { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()},
        },

        // THIS IS THE CHANGE YOU SHOULD MAKE
        // Add metadata to the subscription
        SubscriptionData = new SessionSubscriptionDataOptions()
        {
            Metadata = new Dictionary<string, string>
            {
                { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString() }
            }
        }
    };
    
    ...
}

HandleCustomerSubscriptionUpdatedAsync would be as follows:

        protected override async Task HandleCustomerSubscriptionUpdatedAsync(Event stripeEvent)
        {
            var paymentRequestId = Guid.Parse(stripeEvent.Data.RawObject.metadata[StripeConsts.ParameterNames.PaymentRequestId]?.ToString());
            var paymentRequest = await PaymentRequestRepository.GetAsync(paymentRequestId);

            var paymentUpdatedEto =
                ObjectMapper.Map<PaymentRequest, SubscriptionUpdatedEto>(paymentRequest);

            paymentUpdatedEto.PeriodEndDate =
                ConvertToDateTime((int)stripeEvent.Data.RawObject.current_period_end);

            await EventBus.PublishAsync(paymentUpdatedEto);
        }

Additional improvements in the StripePaymentGateway:

  • Also adjusts method HandleCustomerSubscriptionDeletedAsync described above
  • Always uses the constant StripeConsts.ParameterNames.PaymentRequestId (not Guid.Parse(session.Metadata["PaymentRequestId"]))

Here is the whole StartAsync method:

public virtual async Task<PaymentRequestStartResult> StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input)
{
    var purchaseParameters = PurchaseParameterListGenerator.GetExtraParameterConfiguration(paymentRequest);

    var currency = paymentRequest.Currency.IsNullOrEmpty() ? purchaseParameters.Currency : paymentRequest.Currency;

    var lineItems = new List<SessionLineItemOptions>();

    foreach (var product in paymentRequest.Products)
    {
        var lineItem = new SessionLineItemOptions
        {
            Quantity = product.Count,
        };

        if (product.PaymentType == PaymentType.Subscription)
        {
            var gatewayPlan = await PlanRepository.GetGatewayPlanAsync(product.PlanId.Value, StripeConsts.GatewayName);
            lineItem.Price = gatewayPlan.ExternalId;
        }

        if (product.PaymentType == PaymentType.OneTime)
        {
            lineItem.PriceData = new SessionLineItemPriceDataOptions
            {
                UnitAmountDecimal = Convert.ToDecimal(product.UnitPrice) * 100,
                Currency = currency,
                ProductData = new SessionLineItemPriceDataProductDataOptions
                {
                    Name = product.Name,
                    Metadata = new Dictionary<string, string>
                        {
                            { "ProductCode", product.Code }
                        }
                }
            };
        }

        lineItems.Add(lineItem);
    }

    var options = new SessionCreateOptions
    {
        Locale = purchaseParameters.Locale,
        PaymentMethodTypes = purchaseParameters.PaymentMethodTypes,
        LineItems = lineItems,

        Mode = modeMapping[paymentRequest.Products.First().PaymentType],

        SuccessUrl = input.ReturnUrl,
        CancelUrl = input.CancelUrl,

        Metadata = new Dictionary<string, string>
        {
            { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()},
        },

        // Add metadata to the subscription
        SubscriptionData = new SessionSubscriptionDataOptions()
        {
            Metadata = new Dictionary<string, string>
            {
                { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString() }
            }
        }
    };

    var sessionService = new SessionService();
    var session = await sessionService.CreateAsync(options);

    return new PaymentRequestStartResult
    {
        CheckoutLink = "https://js.stripe.com/v3/",
        ExtraProperties =
        {
            { StripeConsts.ParameterNames.PublishableKey, StripeOptions.PublishableKey},
            { StripeConsts.ParameterNames.SessionId, session.Id}
        }
    };
}

As I wrote yesterday in the mail to you @maliming, I have now changed my strategy and use the StripePaymentGateway almost without any changes on my part. Only at the end with CompleteAsync I do other things according to your code and retrieve e.g. the customer and billing information.

Now I have noticed that there is an error in the HandleCustomerSubscriptionUpdateAsync method when calling the StripePaymentGateway. I assume it is because the external SubscriptionId is not yet saved in the PaymentRequest at the time of the call. You should actually be able to reproduce this error.

The SubscriptionId from Stripe is saved in the HandleCheckoutSessionCompletedAsync() or CompleteAsync() method. However, since the update comes before, this ID is not yet present on the PaymentRequest and so this error occurs.

Here is the log, enriched with my additional log messages from my wrapper class:

2025-01-17 08:53:12.473 +00:00 [INF] PalmaPaymentRequestAppService.CreateAsync, Begin create payment request.   ThreadId='54'
2025-01-17 08:53:12.511 +00:00 [INF] PalmaPaymentRequestAppService.CreateAsync, End create payment request.   ThreadId='54'
2025-01-17 08:53:13.195 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, Begin start payment request for payment gateway 'stripe'.   ThreadId='86'
2025-01-17 08:53:14.024 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, End start payment request for payment gateway 'stripe'.   ThreadId='54'
2025-01-17 08:53:29.025 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'checkout.session.completed' for payment gateway 'stripe'.   ThreadId='42'

2025-01-17 08:53:29.069 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'customer.subscription.updated' for payment gateway 'stripe'.   ThreadId='42'
2025-01-17 08:53:29.130 +00:00 [ERR] ---------- RemoteServiceErrorInfo ----------
{
  "code": null,
  "message": "There is no entity PaymentRequest with id = !",
  "details": null,
  "data": null,
  "validationErrors": null
}

2025-01-17 08:53:29.130 +00:00 [ERR] There is no such an entity given id. Entity type: Volo.Payment.Requests.PaymentRequest
Volo.Abp.Domain.Entities.EntityNotFoundException: There is no such an entity given id. Entity type: Volo.Payment.Requests.PaymentRequest
   at Volo.Payment.EntityFrameworkCore.EfCorePaymentRequestRepository.GetBySubscriptionAsync(String externalSubscriptionId, CancellationToken cancellationToken)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Volo.Payment.Stripe.StripePaymentGateway.HandleCustomerSubscriptionUpdatedAsync(Event stripeEvent)
   at Volo.Payment.Stripe.StripePaymentGateway.HandleWebhookAsync(String payload, Dictionary`2 headers)
   at Volo.Payment.Requests.PaymentRequestAppService.HandleWebhookAsync(String paymentGateway, String payload, Dictionary`2 headers)
   at Palma.Payments.PalmaPaymentRequestAppService.HandleWebhookAsync(String paymentGateway, String payload, Dictionary`2 headers) in D:\a\1\s\Source\Palma\src\Palma.Application\Payments\PalmaPaymentRequestAppService.cs:line 60
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, AbpAuditingOptions options, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope)
   at Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Volo.Payment.Requests.PaymentRequestController.HandleWebhookAsync(String paymentMethod, String payload, Dictionary`2 headers)
   at lambda_method6557(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   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-17 08:53:29.310 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'checkout.session.completed' for payment gateway 'stripe'.   ThreadId='42'
2025-01-17 08:53:29.485 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'customer.subscription.created' for payment gateway 'stripe'.   ThreadId='42'
2025-01-17 08:53:29.494 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'customer.subscription.created' for payment gateway 'stripe'.   ThreadId='42'
2025-01-17 08:53:31.813 +00:00 [INF] PalmaPaymentRequestAppService.CompleteAsync, Begin complete payment request for payment gateway 'stripe'.   ThreadId='54'
2025-01-17 08:53:32.915 +00:00 [INF] PalmaPaymentRequestAppService.CompleteAsync, End complete payment request for payment gateway 'stripe'.   ThreadId='36'
2025-01-17 08:53:43.938 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'customer.subscription.updated' for payment gateway 'stripe'.   ThreadId='43'
2025-01-17 08:53:43.987 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'customer.subscription.updated' for payment gateway 'stripe'.   ThreadId='43'

This is the method from ABP StripePaymentGateway:

    protected virtual async Task HandleCustomerSubscriptionUpdatedAsync(Event stripeEvent)
    {
        var paymentRequest =
            await PaymentRequestRepository.GetBySubscriptionAsync(
                (string)stripeEvent.Data.RawObject.id);

        var paymentUpdatedEto =
            ObjectMapper.Map<PaymentRequest, SubscriptionUpdatedEto>(paymentRequest);

        paymentUpdatedEto.PeriodEndDate =
            ConvertToDateTime((int)stripeEvent.Data.RawObject.current_period_end);

        await EventBus.PublishAsync(paymentUpdatedEto);
    }

Update for those who also have this problem: It works with a unit of work with IsolationLevel.Serializable because a lock is then set at database level.

using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true, isolationLevel: IsolationLevel.Serializable))
{
    // get the entity 

    // update the entity

    await uow.CompleteAsync(cancellationToken);
}

Thank you for searching for the relevant places. Unfortunately, none of this was new to me...

I have now made the try-catch block you requested around all PaymentRequestRepository.UpdateAsync calls. But I don't think that will do any help, as I already had it in the PalmaPaymentRequestAppService.

Does anything else happen between the PaymentRequestAppService and the StripePaymentGateway, other than the requests being passed through?

I will send you the new log file by e-mail in about 30 minutes...

That's exactly what I want from you. Please search for “PaymentRequestRepository.UpdateAsync” in your code. The error is triggered from outside my code (as I wrote above). And in my case I have protected all places with locks and made corresponding log entries. But unfortunately I have no access to your code from PaymentModule & Co.

Here is an excerpt from the log (I have not included the debug messages and other unnecessary entries so that you can see the process a little better):

2025-01-14 07:20:54.728 +00:00 [INF] PalmaPaymentRequestAppService.CreateAsync, Begin create payment request.   ThreadId='28'
2025-01-14 07:20:55.284 +00:00 [INF] PalmaPaymentRequestAppService.CreateAsync, End create payment request.   ThreadId='28'
2025-01-14 07:20:55.771 +00:00 [INF] PalmaPaymentRequestAppService.UpdateWithExtraPropertiesAsync: PaymentRequest '26c70db7-96d2-8ddd-164e-3a1775dcaad2' updated with extra properties: TenantId='70938969-ef4a-ba91-a4ca-3a16f1add23d', TerminationId='01dfb6ef-fb21-6f9d-8b85-3a16f1af3030'.   ThreadId='39'
2025-01-14 07:20:56.747 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, Begin start payment request for payment gateway 'stripe'.   ThreadId='39'
2025-01-14 07:20:58.002 +00:00 [INF] PalmaStripePaymentGateway.StartAsync: Start Stripe request.   PaymentRequest='26c70db7-96d2-8ddd-164e-3a1775dcaad2'
2025-01-14 07:20:58.014 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, End start payment request for payment gateway 'stripe'.   ThreadId='39'
2025-01-14 07:21:20.862 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'customer.subscription.created' for payment gateway 'stripe'.   ThreadId='35'
2025-01-14 07:21:20.865 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'customer.subscription.updated' for payment gateway 'stripe'.   ThreadId='39'
2025-01-14 07:21:20.862 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'invoice.payment_succeeded' for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:21.066 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'invoice.finalized' for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:21.170 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'customer.subscription.created' with id 'evt_1Qh4R9HPiPVaq9xpthx9Gf80'
2025-01-14 07:21:21.335 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionCreatedAsync: Begin handle Stripe event 'customer.subscription.created' with id 'evt_1Qh4R9HPiPVaq9xpthx9Gf80'.   Important payload data: CustomerId='cus_RaEuASr7DTwiPR', SubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', PaymentRequestId='26c70db7-96d2-8ddd-164e-3a1775dcaad2', ThreadId='35'
2025-01-14 07:21:21.648 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'checkout.session.completed' for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:21.659 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionCreatedAsync: End handle Stripe event 'customer.subscription.created'.   Payment request updated with ExternalSubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', ThreadId='28'
2025-01-14 07:21:21.667 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'customer.subscription.created' for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:21.680 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'customer.subscription.updated' with id 'evt_1Qh4R9HPiPVaq9xpvZLiwRNx'
2025-01-14 07:21:21.738 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionUpdatedAsync: Begin handle Stripe event 'customer.subscription.updated' with id 'evt_1Qh4R9HPiPVaq9xpvZLiwRNx'.   Important payload data: SubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', PaymentRequestId='26c70db7-96d2-8ddd-164e-3a1775dcaad2', PlanId='price_1PuqpAHPiPVaq9xpirMIDMuj', PeriodEndDate='14.01.2026 07:21:17', ThreadId='35'
2025-01-14 07:21:21.931 +00:00 [INF] PalmaStripePaymentGateway.ChangeEditionEndDateForTenantAsync: Change edition end date for tenant '70938969-ef4a-ba91-a4ca-3a16f1add23d' from '14.01.2026 07:21:17' -> '14.01.2026 07:21:17'
2025-01-14 07:21:21.968 +00:00 [INF] PalmaStripePaymentGateway.UpdateTenantExtraPropertiesAsync: Add extra properties to Tenant '70938969-ef4a-ba91-a4ca-3a16f1add23d': ExternalSubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', ExternalPlanId='price_1PuqpAHPiPVaq9xpirMIDMuj', ExternalCustomerId='cus_RaEuASr7DTwiPR', ThreadId='28'
2025-01-14 07:21:21.986 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '26c70db7-96d2-8ddd-164e-3a1775dcaad2': PeriodEndDateUtc='14.01.2026 07:21:17', ThreadId='28'
2025-01-14 07:21:22.038 +00:00 [INF] PalmaStripePaymentGateway.HandleCustomerSubscriptionUpdatedAsync: End handle Stripe event 'customer.subscription.updated'.   Payment request updated with PeriodEndDateUtc='14.01.2026 07:21:17', ThreadId='28'
2025-01-14 07:21:22.046 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'customer.subscription.updated' for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:22.319 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'invoice.payment_succeeded' with id 'evt_1Qh4RAHPiPVaq9xpeIZBYPAJ'
2025-01-14 07:21:22.379 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: Begin handle Stripe event 'invoice.payment_succeeded' with id 'evt_1Qh4RAHPiPVaq9xpeIZBYPAJ'.   Important payload data: SubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', PaymentRequestId='26c70db7-96d2-8ddd-164e-3a1775dcaad2', InvoiceId='in_1Qh4R7HPiPVaq9xpwZhHWp3t', CustomerId='cus_RaEuASr7DTwiPR', ThreadId='30'
2025-01-14 07:21:22.396 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '26c70db7-96d2-8ddd-164e-3a1775dcaad2': ExternalCustomerId='cus_RaEuASr7DTwiPR', AmountDue='12000', AmountPaid='12000', AmountRemaining='0', ThreadId='30'
2025-01-14 07:21:22.417 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: End handle Stripe event 'invoice.payment_succeeded'.   Payment request updated with invoice data, ThreadId='39'
2025-01-14 07:21:22.419 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'invoice.payment_succeeded' for payment gateway 'stripe'.   ThreadId='39'
2025-01-14 07:21:22.426 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'invoice.finalized' with id 'evt_1Qh4RAHPiPVaq9xpmghDqy22'
2025-01-14 07:21:22.427 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: Begin handle Stripe event 'invoice.finalized' with id 'evt_1Qh4RAHPiPVaq9xpmghDqy22'.   Important payload data: SubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', PaymentRequestId='26c70db7-96d2-8ddd-164e-3a1775dcaad2', InvoiceId='in_1Qh4R7HPiPVaq9xpwZhHWp3t', CustomerId='cus_RaEuASr7DTwiPR', ThreadId='30'
2025-01-14 07:21:22.448 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '26c70db7-96d2-8ddd-164e-3a1775dcaad2': ExternalCustomerId='cus_RaEuASr7DTwiPR', AmountDue='12000', AmountPaid='0', AmountRemaining='12000', ThreadId='30'
2025-01-14 07:21:22.495 +00:00 [WRN] There is an entry which is not saved due to concurrency exception:
PaymentRequest {Id: 26c70db7-96d2-8ddd-164e-3a1775dcaad2} Modified

2025-01-14 07:21:22.496 +00:00 [ERR] PalmaPaymentRequestAppService.HandleWebhookAsync: Exception when handling webhook event 'invoice.finalized' for payment gateway 'stripe'.   ExceptionMessage='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.'
2025-01-14 07:21:22.504 +00:00 [WRN] There is an entry which is not saved due to concurrency exception:
PaymentRequest {Id: 26c70db7-96d2-8ddd-164e-3a1775dcaad2} Modified

2025-01-14 07:21:22.522 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'checkout.session.completed' with id 'evt_1Qh4R9HPiPVaq9xpsGIoml0k'
2025-01-14 07:21:22.525 +00:00 [WRN] PalmaStripePaymentGateway.HandleWebhookAsync: Unhandled Stripe event 'checkout.session.completed' with id 'evt_1Qh4R9HPiPVaq9xpsGIoml0k'
2025-01-14 07:21:22.535 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'checkout.session.completed' for payment gateway 'stripe'.   ThreadId='30'
2025-01-14 07:21:22.574 +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-14 07:21:22.581 +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-14 07:21:24.500 +00:00 [INF] PalmaPaymentRequestAppService.CompleteAsync, Begin complete payment request for payment gateway 'stripe'.   ThreadId='30'
2025-01-14 07:21:24.713 +00:00 [INF] PalmaStripePaymentGateway.CompleteAsync: Begin complete Stripe request.   PaymentRequest='26c70db7-96d2-8ddd-164e-3a1775dcaad2'
2025-01-14 07:21:24.741 +00:00 [INF] PalmaStripePaymentGateway.CompleteAsync: End complete Stripe request.   PaymentRequest='26c70db7-96d2-8ddd-164e-3a1775dcaad2' updated with currency & state, ThreadId='28'
2025-01-14 07:21:24.761 +00:00 [INF] PalmaPaymentRequestAppService.CompleteAsync, End complete payment request for payment gateway 'stripe'.   ThreadId='28'
2025-01-14 07:21:39.400 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: Begin handle webhook event 'invoice.finalized' for payment gateway 'stripe'.   ThreadId='35'
2025-01-14 07:21:39.419 +00:00 [INF] PalmaStripePaymentGateway.HandleWebhookAsync: Receive Stripe event 'invoice.finalized' with id 'evt_1Qh4RAHPiPVaq9xpmghDqy22'
2025-01-14 07:21:39.421 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: Begin handle Stripe event 'invoice.finalized' with id 'evt_1Qh4RAHPiPVaq9xpmghDqy22'.   Important payload data: SubscriptionId='sub_1Qh4R7HPiPVaq9xpdJEx46hQ', PaymentRequestId='26c70db7-96d2-8ddd-164e-3a1775dcaad2', InvoiceId='in_1Qh4R7HPiPVaq9xpwZhHWp3t', CustomerId='cus_RaEuASr7DTwiPR', AmountDue='12000', AmountPaid='0', AmountRemaining=='12000', ThreadId='35'
2025-01-14 07:21:39.440 +00:00 [INF] PalmaStripePaymentGateway.UpdatePaymentRequestWithExtraPropertiesAsync: Add extra properties to PaymentRequest '26c70db7-96d2-8ddd-164e-3a1775dcaad2': ExternalCustomerId='cus_RaEuASr7DTwiPR', AmountDue='12000', AmountPaid='0', AmountRemaining='12000', ThreadId='39'
2025-01-14 07:21:39.460 +00:00 [INF] PalmaStripePaymentGateway.HandleInvoiceAsync: End handle Stripe event 'invoice.finalized'.   Payment request updated with invoice data, ThreadId='39'
2025-01-14 07:21:39.462 +00:00 [INF] PalmaPaymentRequestAppService.HandleWebhookAsync: End handle webhook event 'invoice.finalized' for payment gateway 'stripe'.   ThreadId='39'

I have now implemented this as you said. But unfortunately that doesn't help either. I have been working on this problem for several days now and with all my observations and tests I can say that the exception does not always occur at the same time. It always tends to occur in the second half of the workflow. And it is definitely triggered from outside my code. Unfortunately, you can't see exactly from where in the exception's StackTrace.

That's why I suspected that you might still be sending distributed events somewhere, which then access the PaymentRequest. I overwrote everything I knew and I didn't find anything in the classes / handlers you sent me by email. Is there anywhere else in your codebase from the payment module (or in relation to the subscription) that still handles corresponding events?

I haven't mentioned anything else here, but I hope you have seen the mail with the logs (Log3.txt).

Sorry, but the log file is not complete... Loglevel was wrong again. I'll send another one later.

Unfortunately the same problem, even if I have now set autoSave: true for all UpdateAsync methods. I have sent you the log file.

Showing 51 to 60 of 312 entries
Learn More, Pay Less
33% OFF
All Trainings!
Get Your Deal
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 12, 2025, 10:20