-
ABP Framework version: v8.2.2
-
UI Type: Blazor Server
-
Database System: EF Core (SQL Server)
-
Tiered (for MVC) or Auth Server Separated (for Angular): no
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
53 Answer(s)
-
0
I will send you the new log file by e-mail in about 30 minutes...
OK, I will check the logs.
-
0
Update for those who also have this problem:
It works with a unit of work withIsolationLevel.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); }
-
0
Thanks ageiter
-
0
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.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.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.g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.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); paymentUpdatedEto.PeriodEndDate = ConvertToDateTime((int)stripeEvent.Data.RawObject.current_period_end); await EventBus.PublishAsync(paymentUpdatedEto); }
-
0
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 StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input) { ... var options = new SessionCreateOptions { ... Metadata = new Dictionary { { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()}, }, // THIS IS THE CHANGE YOU SHOULD MAKE // Add metadata to the subscription SubscriptionData = new SessionSubscriptionDataOptions() { Metadata = new Dictionary { { 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); 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
(notGuid.Parse(session.Metadata["PaymentRequestId"])
)
Here is the whole
StartAsync
method:public virtual async Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input) { var purchaseParameters = PurchaseParameterListGenerator.GetExtraParameterConfiguration(paymentRequest); var currency = paymentRequest.Currency.IsNullOrEmpty() ? purchaseParameters.Currency : paymentRequest.Currency; var lineItems = new List(); 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 { { "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 { { StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()}, }, // Add metadata to the subscription SubscriptionData = new SessionSubscriptionDataOptions() { Metadata = new Dictionary { { 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} } }; }
-
-
0
Many thanks for your solution. 👍
I will check our payment module
-
0
Your ticket has been refunded.
-
0
Thank you very much. Could you let me know in which version this has been implemented?
-
0
hi
Maybe 9.2
-
0
One last question about the payment process. You have probably also seen in my logs (which I sent you by e-mail) that the following error message always appears at the beginning of a payment request (after the StartAsync):
2025-01-16 19:55:43.645 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, Begin start payment request for payment gateway 'stripe'. ThreadId='37' 2025-01-16 19:55:44.898 +00:00 [INF] PalmaPaymentRequestAppService.StartAsync, End start payment request for payment gateway 'stripe'. ThreadId='38' 2025-01-16 19:55:45.102 +00:00 [WRN] The operation was canceled. System.OperationCanceledException: The operation was canceled. at System.Threading.CancellationToken.ThrowOperationCanceledException() at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAsync(String key, CancellationToken token) at Volo.Abp.Caching.DistributedCache`2.GetAsync(TCacheKey key, Nullable`1 hideErrors, Boolean considerUow, CancellationToken token) 2025-01-16 19:55:45.173 +00:00 [ERR] Error when dispatching 'OnDisconnectedAsync' on hub. System.Threading.Tasks.TaskCanceledException: A task was canceled. at Volo.Abp.Threading.SemaphoreSlimExtensions.LockAsync(SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken) at Volo.Abp.Caching.DistributedCache`2.GetOrAddAsync(TCacheKey key, Func`1 factory, Func`1 optionsFactory, Nullable`1 hideErrors, Boolean considerUow, CancellationToken token) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.GetAsync(Guid userId, Nullable`1 tenantId) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributor.ContributeAsync(AbpClaimsPrincipalContributorContext context) at Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal existsClaimsPrincipal, Boolean isDynamic) at Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.CreateDynamicAsync(ClaimsPrincipal existsClaimsPrincipal) at Volo.Abp.AspNetCore.SignalR.Authentication.AbpAuthenticationHubFilter.HandleDynamicClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal, IServiceProvider serviceProvider, HubCallerContext hubCallerContext, Boolean skipCheckDynamicClaimsInterval) at Volo.Abp.AspNetCore.SignalR.Authentication.AbpAuthenticationHubFilter.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnDisconnectedAsync(HubConnectionContext connection, Exception exception) at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnDisconnectedAsync(HubConnectionContext connection, Exception exception) at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.HubOnDisconnectedAsync(HubConnectionContext connection, Exception exception) 2025-01-16 19:55:45.203 +00:00 [ERR] Failed disposing connection 3FgE5QW7nXvr8qTHdKaf_A. System.Threading.Tasks.TaskCanceledException: A task was canceled. at Volo.Abp.Threading.SemaphoreSlimExtensions.LockAsync(SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken) at Volo.Abp.Caching.DistributedCache`2.GetOrAddAsync(TCacheKey key, Func`1 factory, Func`1 optionsFactory, Nullable`1 hideErrors, Boolean considerUow, CancellationToken token) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.GetAsync(Guid userId, Nullable`1 tenantId) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributor.ContributeAsync(AbpClaimsPrincipalContributorContext context) at Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal existsClaimsPrincipal, Boolean isDynamic) at Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.CreateDynamicAsync(ClaimsPrincipal existsClaimsPrincipal) at Volo.Abp.AspNetCore.SignalR.Authentication.AbpAuthenticationHubFilter.HandleDynamicClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal, IServiceProvider serviceProvider, HubCallerContext hubCallerContext, Boolean skipCheckDynamicClaimsInterval) at Volo.Abp.AspNetCore.SignalR.Authentication.AbpAuthenticationHubFilter.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.HubFilterFactory.OnDisconnectedAsync(HubLifetimeContext context, Exception exception, Func`3 next) at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnDisconnectedAsync(HubConnectionContext connection, Exception exception) at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnDisconnectedAsync(HubConnectionContext connection, Exception exception) at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.HubOnDisconnectedAsync(HubConnectionContext connection, Exception exception) at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.RunHubAsync(HubConnectionContext connection) at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.OnConnectedAsync(ConnectionContext connection) at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.OnConnectedAsync(ConnectionContext connection) at Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionContext.ExecuteApplication(ConnectionDelegate connectionDelegate) at Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionContext.WaitOnTasks(Task applicationTask, Task transportTask, Boolean closeGracefully) at Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionContext.DisposeAsync(Boolean closeGracefully) at Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionManager.DisposeAndRemoveAsync(HttpConnectionContext connection, Boolean closeGracefully, HttpConnectionStopStatus status)
Why is this the case? How can I correct this?
-
0
hi
A task was canceled.
This is usually because the http(web hook) request was canceled.
-
0
It just irritates me because it always happens (every time) after the StartAsync. So when redirecting to the PaymentGateway. At a time when no WebHooks are being received.
Maybe it has something to do with the way I submit the request...
private async Task RedirectToPaymentGateway(Guid paymentRequestId) { var tokens = Antiforgery.GetAndStoreTokens(HttpContextAccessor.HttpContext); var requestToken = tokens.RequestToken; var uri = $"/Payment/GatewaySelection?paymentRequestId={paymentRequestId}"; await JSRuntime.InvokeVoidAsync("eval", $@" (function() {{ var form = document.getElementById('PaymentGatewaySelectionForm'); // Check if the antiforgery token is present in the form, if the token is not present, create a new hidden input field for the token var tokenInput = form.querySelector('input[name=__RequestVerificationToken]'); if (!tokenInput) {{ tokenInput = document.createElement('input'); tokenInput.type = 'hidden'; tokenInput.name = '__RequestVerificationToken'; form.appendChild(tokenInput); }} // Set the value of the token tokenInput.value = '{requestToken}'; // Update the form's action with the new URI and submit the form form.action = '{uri}'; form.submit(); }})(); "); }
-
0
hi
Can you check your question in Chrome dev-tool?
You can consider moving the
js code
to a function(PostPaymentGatewaySelectionForm
), thenInvoke
this function in blazor.await JSRuntime.InvokeVoidAsync("PostPaymentGatewaySelectionForm")
By the way, can you share the cs files you have overwritten? I will check and merge into the payment module.
Thanks.
-
0
The changes of
HandleCustomerSubscriptionUpdatedAsync
method. -
0
Have you tested this?
It will only work if you have also adapted the StartAsync method (PaymentRequestId in the metadata of the subscription). This is because the RawObject is a subscription and not a session, as is the case with HandleCheckoutSessionCompletedAsync, for example.
In addition, the date is not assigned. And finally, you make an update to the PaymentRequest, although you have not changed anything.
-
0
hi
Yes, We have added
{ StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()}
in theStartAsync
method just like yours. -
0
hi
Yes, We have added
{ StripeConsts.ParameterNames.PaymentRequestId, paymentRequest.Id.ToString()}
in theStartAsync
method just like yours.Ok, then it should work.
Now you just have to assign the date (otherwise the method is useless). And I would not update the PaymentRequest, but solve this via the event (as before).
var paymentUpdatedEto = ObjectMapper.Map(paymentRequest); paymentUpdatedEto.PeriodEndDate = ConvertToDateTime((int)stripeEvent.Data.RawObject.current_period_end); await EventBus.PublishAsync(paymentUpdatedEto);
-
0
Can you check your question in Chrome dev-tool?
You can consider moving the
js code
to a function(PostPaymentGatewaySelectionForm
), thenInvoke
this function in blazor.await JSRuntime.InvokeVoidAsync("PostPaymentGatewaySelectionForm")
Unfortunately, no success in this matter. That didn't help and I don't see anything relevant via the dev-tools either.
But generally I have exceptions of this kind again and again... but the end user doesn't notice anything.
-
0
hi
How can I reproduce this exception in the template project?
Can you share a simple project or code and steps?
Thanks.
-
0
How can I reproduce this exception in the template project?
Can you share a simple project or code and steps?
Unfortunately, I can't explain how you could reproduce this (as I don't know myself where these errors are coming from). But at the moment I don't have the time to look into it. However, it seems that it still works and the end user doesn't notice anything.
-
0
I have noticed the following problem in connection with the payment.
The following scenario:
-
Customer already has a valid subscription with an end date > today
-
The tenant EditionEndDateUtc is a date in the future
-
Now he triggers a new PaymentRequest (e.g. because he wants to change the edition)
-
He cancels the payment (with the back button) and has therefore not made a payment
-> Result: His previously valid subscription has been deactivated, as the EditionEndDateUtc is automatically reset when the PaymentRequest is created.
How could the problem be solved? Why is the date changed in the tenant using the
SubscriptionAppService.CreateSubscriptionAsync
method, even if the payment has not yet been completed and there is already a valid date in it? What is the reason for this?var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync(editionId.Value, TenantId.Value); await RedirectToPaymentGateway(paymentRequest.Id);
-
-
0
hi
I will ask our colleague.
-
0
I have noticed the following problem in connection with the payment.
The following scenario:
-
Customer already has a valid subscription with an end date > today
-
The tenant EditionEndDateUtc is a date in the future
-
Now he triggers a new PaymentRequest (e.g. because he wants to change the edition)
-
He cancels the payment (with the back button) and has therefore not made a payment
-> Result: His previously valid subscription has been deactivated, as the EditionEndDateUtc is automatically reset when the PaymentRequest is created.
Hi
SubscriptionAppService.CreateSubscriptionAsync(editionId.Value, TenantId.Value)
Creates a freshly new subscription that also resets
EditionEndDateUtc
.Renewing or changing existing subscription isn't covered in this method. I'm not sure does stripe cover upgrade scenario and get payment only difference than previous subscription.
You may want to cancel the subscription first (Stripe'll refund cost for remaining period) then start payment process from the beginning.Cancelling process is done in Stripe panel UI, and it'll be handled by webhooks in your application
We'll also enhance this
SubscriptionAppService.CreateSubscriptionAsync
method for existing subscriptions -
-
0
Thank you for your answer @enisn.
Stripe offers different ways to upgrade, depending on the configuration. We use Stripe's customer portal, which the customer then uses to upgrade. This is also perfectly notified via the webhooks to our app and the StripePaymentGateway also handles this correctly (new edition is set to tenant). This is not the problem.
The problem is when the customer is already logged in and comes back to the page where he can choose the different subscriptions (no upgrade via our official way with the Stripe customer portal).
Can you provide me with the code for the method
CreateSubscriptionAsync
? Then I can overwrite it myself. -
0
This is the code of the method:
public virtual async Task<PaymentRequestWithDetailsDto> CreateSubscriptionAsync(Guid editionId, Guid tenantId) { var edition = await EditionManager.GetEditionForSubscriptionAsync(editionId); var paymentRequest = await PaymentRequestAppService.CreateAsync(new PaymentRequestCreateDto { Products = new List<PaymentRequestProductCreateDto> { new PaymentRequestProductCreateDto { PlanId = edition.PlanId, Name = edition.DisplayName, Code = $"{tenantId}_{edition.PlanId}", Count = 1, PaymentType = PaymentType.Subscription, } }, ExtraProperties = { { EditionConsts.EditionIdParameterName, editionId }, { TenantConsts.TenantIdParameterName, tenantId }, } }); var tenant = await TenantRepository.GetAsync(tenantId); tenant.EditionEndDateUtc = DateTime.UtcNow; await TenantRepository.UpdateAsync(tenant); return paymentRequest; }
You can override it and avoid calling
tenant.EditionEndDateUtc = DateTime.UtcNow;
this line according to your case