Open Closed

Questions about the integration of the payment module in Blazor #7829


User avatar
0
ageiter created
  • 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.

  1. Is there an example project in which the payment module was integrated into a Blazor app? If possible with subscriptions etc.
  2. I haven't found a blog article on this topic either, which would also be very useful.
  3. Can this work at all if we are not running a public web project (MVC), but only the Blazor app?
  4. Does the Code property in the PaymentRequestProduct 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)
  • User Avatar
    0
    ageiter created

    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)
    
  • User Avatar
    0
    ageiter created

    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.

  • User Avatar
    0
    ageiter created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    ageiter created

    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?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Maybe I misunderstand your meaning?

  • User Avatar
    0
    ageiter created

    Sorry, I meant this list:

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can try to override the StripePaymentGateway to change this property based on your logic.

  • User Avatar
    0
    ageiter created

    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 the EditionEndDateUtcto check whether the subscription is still valid and it still contains the previous date and not the updated one.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    ageiter created

    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).

  • User Avatar
    0
    ageiter created

    Sorry for the three new questions to the payment / subscriptions:

    1. 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?

    2. How can you implement that a customer can cancel their subscription again?

    3. 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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    ageiter created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I will ask our colleague.

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Sorry for the three new questions to the payment / subscriptions:

    1. 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?

    2. 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

  • User Avatar
    0
    ageiter created

    Ok, thank you for the answer.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi @ageiter

    Is your problem solved?

  • User Avatar
    0
    ageiter created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok

Made with ❤️ on ABP v9.0.0-preview Updated on September 19, 2024, 10:13