Open Closed

Stripe payment module, tenantName resolver issue #6003


User avatar
0
Sergei.Gorlovetsky created
  • ABP Framework version: v7.4.0
  • UI Type: Angular
  • Database System: MongoDB
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace: Stripe payment gateway, payment request with parameter "success_url": "https://{{tenantName}}.is.clienteleapp.net/Payment/Stripe/PostPayment?SessionId={CHECKOUT_SESSION_ID}" And "cancel_url": "https://{{tenantName}}.is.clienteleapp.net" gives error "The success_url parameter must correspond to a valid URL."
  • Steps to reproduce the issue: We have added the stripe payment module, we are using tenant specific url for both angular and identity server, in appsettings of Identity project self url is configured as "SelfUrl": "https://{{tenantName}}.is.clienteleapp.net". We expect the tenant name should be resolved at the time of PrePayment and PostPayment method of Stripe payment module but it's not resolving the tenant name and causing the error when trying to process payment with stripe payment module.

20 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I will check it

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    This looks like a bug. I will fix it and provide you with a temporary solution.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    You can try to overwrite the page: https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface

    public class UrlHelper : ITransientDependency
    {
        private readonly ICurrentTenant _currentTenant;
    
        public UrlHelper(ICurrentTenant currentTenant)
        {
            _currentTenant = currentTenant;
        }
    
        public const string TenantNamePlaceHolder = "{{tenantName}}";
        
        public string ReplaceTenantPlaceholder(string url)
        {
            var tenantNamePlaceHolder = TenantNamePlaceHolder;
    
            if (url.Contains(TenantNamePlaceHolder + '.'))
            {
                tenantNamePlaceHolder = TenantNamePlaceHolder + '.';
            }
    
            if (url.Contains(tenantNamePlaceHolder))
            {
                url = _currentTenant.Id.HasValue ? url.Replace(tenantNamePlaceHolder, _currentTenant.Name + ".") : url.Replace(tenantNamePlaceHolder, "");
            }
    
            return url;
        }
    }
    

    Pages/Payment/GatewaySelection.cshtml

    @page
    @using Microsoft.AspNetCore.Mvc.Localization
    @using Volo.Payment.Localization
    @model Volo.Payment.Pages.Payment.MyGatewaySelectionModel
    @inject IHtmlLocalizer<PaymentResource> Localizer
    @section scripts {
        <abp-script-bundle>
            <abp-script src="/client-proxies/payment-proxy.js" />
            <abp-script src="/Pages/Payment/gateway-selection.js" />
        </abp-script-bundle>
    }
    <div class="container">
        <div class="row">
            <div class="col-md-7 mx-auto">
                <div class="card">
                    <div class="card-header">
                        <h4 class="m-0 text-center">Select a Payment Gateway</h4>
                    </div>
                    <div class="card-body">
                        <form id="frmGatewaySelection" method="post" action="">
                            @Html.AntiForgeryToken()
                            <div class="row">
                                @for (int gatewayIndex = 0; gatewayIndex < Model.Gateways.Count; gatewayIndex++)
                                {
                                    var gateway = Model.Gateways[gatewayIndex];
    
                                    <div class="col">
                                        <div class="form-check">
                                            <input type="hidden" id="@($"{@gateway.Name}PrePaymentUrl")" value="@(Model.UrlHelper.ReplaceTenantPlaceholder(gateway.PrePaymentUrl))?paymentRequestId=@Model.PaymentRequestId"/>
                                            <input type="radio" name="gateway" class="form-check-input" checked="@(gatewayIndex == 0)" value="@gateway.Name" id="@gateway.Name">
                                            <label for="@gateway.Name" class="form-check-label">
                                                <strong>@Localizer["PayWithGateway", Localizer[gateway.Name]]</strong>
                                                @if (gateway.Recommended)
                                                {
                                                    <small class="text-muted">(Recommended)</small>
                                                }
                                            </label>
                                            @if (gateway.ExtraInfos.Any())
                                            {
                                                <div class="payment-info-boxes">
                                                    @foreach (var extraInfo in gateway.ExtraInfos)
                                                    {
                                                        <p class="mt-1 mb-0 text-muted">
                                                            <small class="d-block"><i class="fa fa-info-circle"></i> @Html.Raw(extraInfo)</small>
                                                        </p>
                                                    }
                                                </div>
                                            }
                                        </div>
                                    </div>
                                    <input type="hidden" name="paymentRequestId" value="@Model.PaymentRequestId" />
                                }
                            </div>
                        </form>
                    </div>
                    <div class="card-body">
                        <div class="d-grid gap-2">
                            <a id="btnSubmit" href="#" class="btn btn btn-success @Model.CheckoutButtonStyle">Continue to Checkout <i class="fas fa-arrow-right"></i></a>
                        </div>
                        <p class="mt-2 mb-1 text-muted text-center">
                            <small>
                                <i class="fa fa-info-circle"></i> Next, you will be redirected to the selected payment gateway's website for the transaction
                            </small>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    
    [ExposeServices(typeof(GatewaySelectionModel))]
    public class MyGatewaySelectionModel : GatewaySelectionModel
    {
        private readonly IOptions<PaymentWebOptions> _paymentWebOptions;
        private readonly IGatewayAppService _gatewayAppService;
        private readonly IPaymentRequestAppService _paymentRequestAppService;
        public UrlHelper UrlHelper { get; set; }
       
        public MyGatewaySelectionModel(
            IPaymentRequestAppService paymentRequestAppService,
            IOptions<PaymentWebOptions> paymentWebOptions, 
            IGatewayAppService gatewayAppService) : base(paymentRequestAppService, paymentWebOptions, gatewayAppService)
        {
            _paymentWebOptions = paymentWebOptions;
            _gatewayAppService = gatewayAppService;
            _paymentRequestAppService = paymentRequestAppService;
        }
    
        public override async Task<IActionResult> OnPostAsync()
        {
            CheckoutButtonStyle = _paymentWebOptions.Value.GatewaySelectionCheckoutButtonStyle;
    
            var paymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId);
    
            List<GatewayDto> gatewaysDtos;
    
            if (paymentRequest.Products.Any(a => a.PaymentType == PaymentType.Subscription))
            {
                gatewaysDtos = await _gatewayAppService.GetSubscriptionSupportedGatewaysAsync();
            }
            else
            {
                gatewaysDtos = await _gatewayAppService.GetGatewayConfigurationAsync();
            }
    
            Gateways = _paymentWebOptions.Value.Gateways
                .Where(x => gatewaysDtos.Any(a => a.Name == x.Key))
                .Select(x => x.Value)
                .ToList();
    
            if (!Gateways.Any())
            {
                throw new ApplicationException("No payment gateway configured!");
            }
    
            if (Gateways.Count == 1)
            {
                var gateway = Gateways.First();
                return LocalRedirectPreserveMethod(UrlHelper.ReplaceTenantPlaceholder((gateway.PrePaymentUrl) + "?paymentRequestId=" + PaymentRequestId));
            }
    
            return Page();
        }
    }
    
    [ExposeServices(typeof(PostPaymentModel))]
    public class MyPostPaymentModel : PostPaymentModel
    {
        public UrlHelper UrlHelper { get; set; }
    
        private IOptions<PaymentWebOptions> _paymentWebOptions;
        public MyPostPaymentModel(IPaymentRequestAppService paymentRequestAppService, IOptions<PaymentWebOptions> paymentWebOptions) : base(paymentRequestAppService, paymentWebOptions)
        {
            _paymentWebOptions = paymentWebOptions;
        }
        
        public override async Task<IActionResult> OnGetAsync()
        {
            if (SessionId.IsNullOrWhiteSpace())
            {
                return BadRequest();
            }
    
            var paymentRequest = await PaymentRequestAppService.CompleteAsync(
                StripeConsts.GatewayName,
                new()
                {
                    { StripeConsts.ParameterNames.SessionId, SessionId },
                });
    
            if (!_paymentWebOptions.Value.CallbackUrl.IsNullOrWhiteSpace())
            {
                var callbackUrl = UrlHelper.ReplaceTenantPlaceholder(_paymentWebOptions.Value.CallbackUrl + "?paymentRequestId=" + paymentRequest.Id);
    
                Response.Redirect(callbackUrl);
            }
    
            return Page();
        }
    }
    
  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi liangshiwei,

    Thanks for response

    We had configured solutions you provided and override page and page model but we still face issue of tenant resolving

    please look below screen that we have tried locally and on productions

    Also when trying with debugging we found this with ReplaceTenantPlaceholder() method in UrlHelper class

    In this both url.contains condition goes false,so url will remain as is

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I'm checking

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Will it work if you try this?

    [ExposeServices(typeof(PrePaymentModel))]
    public class MyPrePaymentModel: PrePaymentModel
    {
        public override async Task OnPostAsync()
        {
            StartResult = await PaymentRequestAppService.StartAsync(StripeConsts.GatewayName, new PaymentRequestStartDto
            {
                ReturnUrl = UrlHelper.ReplaceTenantPlaceholder(PaymentWebOptions.RootUrl.RemovePostFix("/") + StripeConsts.PostPaymentUrl +
                             "?SessionId={CHECKOUT_SESSION_ID}"),
                CancelUrl = UrlHelper.ReplaceTenantPlaceholder(PaymentWebOptions.RootUrl),
    
                PaymentRequestId = PaymentRequestId
            });
    
            PublishableKey = StartResult.ExtraProperties[StripeConsts.ParameterNames.PublishableKey].ToString();
            SessionId = StartResult.ExtraProperties[StripeConsts.ParameterNames.SessionId].ToString();
        }
    }
    
  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi,

    While integrating we face this issue

    The type 'StripeOptions' exists in both 'Volo.Payment.Stripe.Domain, Version=7.4.0.0, Culture=neutral, PublicKeyToken=null' and 'Volo.Payment.Stripe.Web, Version=7.4.0.0, Culture=neutral, PublicKeyToken=null'

    here our packages in identity server project

    can you take a look?

    Thanks in advance

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    You can try:

    using StripeOptions = Volo.Payment.Stripe.StripeOptions;
    
  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi,

    Yes i also tried this but same issue

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Sorry, I realize that it is impossible to replace the PrePaymentModel, you can try this:

    [ExposeServices(typeof(IPaymentRequestAppService))]
    public class MyPaymentRequestAppService : PaymentRequestAppService
    {
        private readonly UrlHelper _urlHelper;
        public MyPaymentRequestAppService(
            IPaymentRequestRepository paymentRequestRepository,
            PaymentGatewayResolver paymentMethodResolver, 
            IDistributedEventBus distributedEventBus, UrlHelper urlHelper) 
            : base(paymentRequestRepository, paymentMethodResolver, distributedEventBus)
        {
            _urlHelper = urlHelper;
        }
    
        public override Task<PaymentRequestStartResultDto> StartAsync(string gateway, PaymentRequestStartDto inputDto)
        {
            inputDto.CancelUrl = _urlHelper.ReplaceTenantPlaceholder(inputDto.CancelUrl);
            inputDto.ReturnUrl = _urlHelper.ReplaceTenantPlaceholder(inputDto.ReturnUrl);
            return base.StartAsync(gateway, inputDto);
        }
    }
    
  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi,

    Thanks for your response & Sorry for late reply due to weekends,

    We have congifured this solution but no luck ,look below screen

    also face same error

    while running project locally there is no any break points trigger in MyPaymentRequestAppService.cs method

    Solutions configured is on right class library or something i missed?

    Please let me know

    Thanks

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Solutions configured is on right class library or something i missed?

    I'm not sure. could you use the suite to create a new project to reproduce the problem and share it with me? my email is shiwei.liang@volosoft.com I will check it asap.

  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi,

    Ok i have asked for it and we will let you soon for that

    Thanks

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    ok

  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi, we are in process of giving access to the repository we are working on, so you can check and give the proper solution.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    May I ask which branch of the repo I should check?

  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi, Please check in branch "521 SocialExternalLogin", we have separate identity server and the changes you suggested are all in that web application only.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Because the IdentityServer does not reference the Application project, that's why you can't hit the breakpoint.

    You need to move the MyPaymentRequestAppService to the IdentityServer project

  • User Avatar
    0
    Sergei.Gorlovetsky created

    Hi, Thanks for all your help, the issue has been fixed now.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    :)

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11