- 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 would like to use the payment module under Blazor Server. We would currently do this with Stripe as provider. In the end, a tenant should be able to buy a subscription and thus activate services on our platform.
We have studied the documentation, but it leaves some questions unanswered, especially in relation to Blazor. Much is still very unclear and is like a trial and error process to implement this.
- Is there an example project in which the payment module was integrated into a Blazor app? If possible with subscriptions etc.
- I haven't found a blog article on this topic either, which would also be very useful.
- Can this work at all if we are not running a public web project (MVC), but only the Blazor app?
- Does the
Code
property in thePaymentRequestProduct
correspond to the Product-ID in Stripe?
There will probably be a few more questions, but this is for now.
Thanks, Adrian
20 Answer(s)
-
0
We have now come a few steps further... One-time payment is working, but the subscription payment not.
Payment.razor.cs
private async Task AddOneTimePaymentAsync() { try { var paymentRequest = await PaymentRequestAppService.CreateAsync(new PaymentRequestCreateDto() { Currency = "CHF", Products = new List<PaymentRequestProductCreateDto>() { new PaymentRequestProductCreateDto { Code = "prod_QmPjjjJOhLTY6x", Name = "Sperrfristenrechner Einzelabfrage", Count = 1, UnitPrice = 250, TotalPrice = 250 } } }); await RedirectPreserveMethod($"/Payment/GatewaySelection?paymentRequestId={paymentRequest.Id}"); } catch (Exception e) { Logger.LogError(e, e.Message); } } private async Task AddSubscriptionPaymentAsync(string testEditionId) { try { // Prefered method (not working) //var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync(testEditionId, tenantId); // Code from documentation (not working) var paymentRequest = await PaymentRequestAppService.CreateAsync(new PaymentRequestCreateDto() { Currency = "CHF", Products = new List<PaymentRequestProductCreateDto>() { new PaymentRequestProductCreateDto { PaymentType = PaymentType.Subscription, Name = "Jahresabo Light", Code = "", Count = 1, PlanId = new Guid("9C404C8E-6CDF-A540-8BE2-3A14CA8412FE"), } } }); await RedirectPreserveMethod($"/Payment/GatewaySelection?paymentRequestId={paymentRequest.Id}"); } catch (Exception e) { Logger.LogError(e, e.Message); } } private async Task RedirectPreserveMethod(string uri) { await JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('Form').action = '{uri}'; document.getElementById('Form').submit()"); }
One-Time Payment seems to be working:
Subscription Payment not working:
StripeException: No such price: 'prod_QmPea3rNNFRqW4' Stripe.StripeClient.ProcessResponse<T>(StripeResponse response) in StripeClient.cs Stripe.StripeClient.RequestAsync<T>(HttpMethod method, string path, BaseOptions options, RequestOptions requestOptions, CancellationToken cancellationToken) in StripeClient.cs Stripe.Service<TEntityReturned>.RequestAsync<T>(HttpMethod method, string path, BaseOptions options, RequestOptions requestOptions, CancellationToken cancellationToken) in Service.cs Volo.Payment.Stripe.StripePaymentGateway.StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input) Volo.Payment.Requests.PaymentRequestAppService.StartAsync(string gateway, PaymentRequestStartDto inputDto) Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo) Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue<TResult>.ProceedAsync() Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation) Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter<TInterceptor>.InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed) Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo) Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue<TResult>.ProceedAsync() Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation) Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter<TInterceptor>.InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed) Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo) Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue<TResult>.ProceedAsync() Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, AbpAuditingOptions options, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope) Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation) Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter<TInterceptor>.InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed) Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo) Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue<TResult>.ProceedAsync() Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation) Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter<TInterceptor>.InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed) Volo.Payment.Stripe.Pages.Payment.Stripe.PrePaymentModel.OnPostAsync() Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+NonGenericTaskHandlerMethod.Execute(object receiver, object[] arguments) Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger) Volo.Abp.AspNetCore.Serilog.AbpSerilogMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Volo.Abp.AspNetCore.Security.Claims.AbpDynamicClaimsMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Volo.Abp.AspNetCore.Uow.AbpUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Volo.Abp.AspNetCore.MultiTenancy.MultiTenancyMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Microsoft.AspNetCore.Builder.ApplicationBuilderAbpOpenIddictMiddlewareExtension+<>c__DisplayClass0_0+<<UseAbpOpenIddictValidation>b__0>d.MoveNext() Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Volo.Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext() Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
-
0
Ok, I found the solution for the "No such price" problem.
It is not the product ID but the price ID that must be stored as the external ID for the gateway.
I only saw this by chance on the picture in the documentation. But I probably wouldn't have figured it out otherwise, because in my understanding you link a subscription product from Stripe to a plan in ABP.
It would be great if you could add this information to the documentation to explain exactly WHAT the External ID must be. That is quite unclear.
-
0
I still have the following problem: I want to use a payment method type (Twint) that only works for one-time payments and not for subscriptions. But I need to be able to offer both.
This means that if a one-time payment is to be made, the
PaymentMethodTypes
Twint should be added. And if it is a payment for a subscription, this should not be in the list, otherwise it will result in an error message:Is there a solution for this or do we have to do without Twint?
By the way: When we add "card" to the list, then there is also an error, as “card” is added by default and therefore appears twice.
-
0
hi
I want to use a payment method type (Twint) that only works for one-time payments and not for subscriptions. But I need to be able to offer both. This means that if a one-time payment is to be made, the PaymentMethodTypes Twint should be added. And if it is a payment for a subscription, this should not be in the list, otherwise it will result in an error message:
Can you share the screenshot of
list
page?I think you can only customize the
list
page to do this. -
0
Can you share the screenshot of
list
page?I think you can only customize the
list
page to do this.What do you mean by the List page? Which one?
-
0
-
0
-
0
-
0
Ok, that's a good hint and could work like this.
Is there a way to get the source code of the
StripePaymentGateway
or the PaymentModule in general?We may still have to implement Payrexx as a gateway. And it is quite time-consuming and complex if the
StripePaymentGateway
is a black box as a template.In addition, we have identified a problem with the WebHooks in connection with subscriptions at Stripe and it is now difficult to say whether the problem is in the payment module or whether we have not understood the mechanism. An insight into the implementation of the distributed events would be helpful.
The problem identified is as follows: We cancel a subscription with Stripe before the end (e.g. with a refund to the customer), the payment module processes the event (I can see this in the log file), but the
EditionEndDateUtc
field in the SaasTenant is not changed. Now we would like to know WHAT exactly the payment module processes when such an event occurs and what we have to do ourselves. We use theEditionEndDateUtc
to check whether the subscription is still valid and it still contains the previous date and not the updated one. -
0
hi
Is there a way to get the source code of the StripePaymentGateway or the PaymentModule in general?
Please send an email to liming.ma@volosoft.com I will share the source code.
Enabling WebHooks Configuring Web Hooks is highly important for subscriptions otherwise your application won't be able to get subscription changes, such as canceled or updated states. Each gateway has its own configuration:
https://abp.io/docs/commercial/5.1/modules/payment#enabling-webhooks
Stripe will send a request to your website(
yourdomain.com/payment/stripe/webhook
)You can override the
Volo.Payment.Stripe.Domain/Volo/Payment/Stripe/StripePaymentGateway.cs
to add more logic.Please send the mail, I will share the source code.
-
0
Please send an email to liming.ma@volosoft.com I will share the source code.
Thanks a lot for your mail.
Enabling WebHooks Configuring Web Hooks is highly important for subscriptions otherwise your application won't be able to get subscription changes, such as canceled or updated states. Each gateway has its own configuration:
https://abp.io/docs/commercial/5.1/modules/payment#enabling-webhooks
Stripe will send a request to your website(
yourdomain.com/payment/stripe/webhook
)Yes, exactly, I am aware of that. And it also works for renewing a subscription. But an early cancellation or change of the subscription duration is not implemented (I would have thought that this should come via the “customer.subscription.updated” event).
-
0
Sorry for the three new questions to the payment / subscriptions:
Where do I configure whether a subscription is automatically renewed or whether the user has to renew it manually? You at ABP have implemented the “Automatic Renewal” option in “Organization Management”. How does it work if this is changed? Is there a request to the payment provider and then this is stored there with the subscription?
How can you implement that a customer can cancel their subscription again?
We still need the option for the customer to view the invoice. Stripe has a link for this, but how can I access this link? It would be best if this could be displayed directly on the Payment Success page.
-
0
hi ageiter
We have implemented a basic payment module. You may need to implement the above feature by yourself.
I have shared the Stripe module's source code with you.
-
0
I thought I would have to implement this myself. I just hoped you could give me a few tips on how to do it. Or perhaps how you implemented it at Abp.
-
0
hi
I will ask our colleague.
-
0
Sorry for the three new questions to the payment / subscriptions:
Where do I configure whether a subscription is automatically renewed or whether the user has to renew it manually? You at ABP have implemented the “Automatic Renewal” option in “Organization Management”. How does it work if this is changed? Is there a request to the payment provider and then this is stored there with the subscription?
How can you implement that a customer can cancel their subscription again?
Hi,
The Payment Module implements only abstraction between payment gateways and makes payments only. Subscription logic is not implemented in the module. For the subscription feature, it uses Stripe API endpoints and it doesn't track or keep any data about subscription. It checks by using stripe APIs if the subscription ended or was canceled. So, If you need to implement a Subscription logic, you can build your own tracking & keeping data logic in a new module or use an existing subscription logic as a service. Payment module uses only payment APIs over gateways
-
0
Ok, thank you for the answer.
-
0
hi @ageiter
Is your problem solved?
-
0
Not yet definitely. We have to wait for a few decisions from the customer and I would therefore like to leave the question open so that we can ask again later if necessary.
-
0
ok