Open Closed

Customization options for Stripe gateway #10063


User avatar
0
cstobler created

For now, I am looking to pass along the email entered during registration to the Stripe payment page. I am using a custom registration page right now, and when the user submits, it runs this code in OnPostAsync():

StartSubscriptionResultDto resultDto = await _multiTenancyAppService.RegisterAndSubscribeAsync(new SaasTenantCreateDto
{
    Name = Input.TenantName,
    AdminEmailAddress = Input.Email,
    AdminPassword = Input.Password,
    EditionId = Input.EditionId,
    ActivationState = Volo.Saas.TenantActivationState.Passive
});

// If no payment required, redirect to login
if (resultDto.PaymentRequestId == Guid.Empty)
{
    return LocalRedirect("/Account/Login");
}

// Redirect to payment
return LocalRedirectPreserveMethod("/Payment/GatewaySelection?paymentRequestId=" + resultDto.PaymentRequestId);

(MultiTenancyAppService is my appservice, but it calls _subscriptionAppService.CreateSubscriptionAsync()). This works fine, but I don't see almost any customization options for the stripe checkout. In Stripe's API, they allow you to pass an email (along with numerous other parameters) to the checkout to prefill fields. However, I don't see a way to do that with this workflow.

Am I missing some setting or configuration in this workflow? Is there a better workflow I could use that would give me more customization options? I am only using Stripe for subscriptions, so perhaps there is a way to bypass the GatewaySelection and go straight to Stripe along with some additional customizations?


9 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    1. Solution You can pass the registration email to Stripe Checkout by using Payment Request ExtraProperties and a small Stripe customization point. In ABP Payment (Pro), the Stripe integration builds the Checkout Session from the PaymentRequest and allows adding metadata/customer info via options. Do this:
    • Put the email on the PaymentRequest when you create it (either in your RegisterAndSubscribe workflow or by creating the PaymentRequest yourself), using ExtraProperties:
    var paymentRequest = await _paymentRequestAppService.CreateAsync(
          new PaymentRequestCreateDto
          {
              Products =
              {
                  new PaymentRequestProductCreateDto
                  {
                      PaymentType = PaymentType.Subscription,
                      Name = "YourPlanName",
                      Code = $"tenant-{tenantId}-{planId}",
                      Count = 1,
                      PlanId = planId
                  }
              },
              // carry the email for Stripe prefill
              ExtraProperties = new ExtraPropertyDictionary
              {
                  { "CustomerEmail", Input.Email } // your registration email
              }
          });
    
      return LocalRedirectPreserveMethod("/Payment/GatewaySelection?paymentRequestId=" + paymentRequest.Id);
    
    • Override/extend the Stripe Checkout Session creation to map ExtraProperties["CustomerEmail"] into Stripe’s session options (CustomerEmail or CustomerCreation/Customer). Create a custom Stripe payment contributor by replacing or deriving from ABP’s default Stripe request starter:
    public class MyStripePaymentRequestHandler : StripePaymentRequestHandler
      {
          public MyStripePaymentRequestHandler(
              IOptions<StripePaymentOptions> options,
              // inject base deps
              ...) : base(options, /*...*/) { }
    
          protected override SessionCreateOptions BuildSessionOptions(PaymentRequest paymentRequest, string successUrl, string cancelUrl)
          {
              var options = base.BuildSessionOptions(paymentRequest, successUrl, cancelUrl);
    
              // Prefill email if provided
              if (paymentRequest.ExtraProperties.TryGetValue("CustomerEmail", out var emailObj)
                  && emailObj is string email && !string.IsNullOrWhiteSpace(email))
              {
                  options.CustomerEmail = email; // Stripe Checkout prefill
                  // Optionally: options.Metadata = options.Metadata ?? new Dictionary<string, string>();
                  // options.Metadata["abp-payment-request-id"] = paymentRequest.Id.ToString();
              }
    
              return options;
          }
      }
    

    Then register your handler to replace the default:

    context.Services.Replace(ServiceDescriptor.Transient<StripePaymentRequestHandler, MyStripePaymentRequestHandler>());
    
    • If you prefer to skip the GatewaySelection page and go straight to Stripe when you only use Stripe:
      • Add Volo.Payment.Web to the UI that performs the redirect (or configure it on the HttpApi.Host as documented).
      • Detect single-gateway and redirect directly:
    return LocalRedirectPreserveMethod("/Payment/Stripe/PrePayment?paymentRequestId=" + paymentRequest.Id);
    
    Note: The default GatewaySelection page already auto-redirects when only one subscription-supported gateway is available. If you still see the selection, ensure only Stripe is enabled and subscription-supported.
    
    • Keep required Stripe configuration:
      • In appsettings of the API Host:
        • Payment:Stripe:PublishableKey, SecretKey, WebhookSecret
        • Payment:Stripe:PaymentMethodTypes should be [] because ABP adds “card” by default.
      • Configure PaymentWebOptions (RootUrl and CallbackUrl) in the UI hosting the /Payment pages:
    public override void PreConfigureServices(ServiceConfigurationContext context)
      {
          var configuration = context.Services.GetConfiguration();
          Configure<PaymentWebOptions>(o =>
          {
              o.RootUrl = configuration["App:SelfUrl"];
              o.CallbackUrl = configuration["App:SelfUrl"] + "/PaymentSucceed";
          });
      }
    
    • About your RegisterAndSubscribeAsync path:

      • That method ends up creating a PaymentRequest and sending users to GatewaySelection. To inject the email, either:
        • Extend your MultiTenancyAppService to add ExtraProperties (like "CustomerEmail") to the PaymentRequest it creates, or
        • Switch to creating the PaymentRequest yourself (as shown above) to have full control over ExtraProperties.
      • The ABP Stripe implementation uses these data points to create the Checkout Session. With the override above, your email will prefill on Stripe.
    • Notes

      • If you need additional Stripe options (locale, metadata, billing address collection, customer creation), add them in your overridden BuildSessionOptions similarly.
      • Ensure Stripe webhooks are configured so that after successful payment, the tenant’s subscription is updated accordingly.
    1. Sources
    • ABP Payment Module (Pro) docs: https://docs.abp.io/en/abp/latest/Modules/Payment
    • ABP Framework repository (for Stripe module source reference): https://github.com/abpframework/abp

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    cstobler created

    I am seeing very little documentation which would support or even elucidate anything related to this. I checked PaymentRequestCreateDto and PaymentRequestWithDetailsDto, and it seems like ExtraProperties has protected set, so I can't just set it outright. In addition, the AI recommends using _paymentRequestAppService.CreateAsync(), but I would like to continue using _subscriptionAppService.CreateSubscriptionAsync() unless there is a good reason not to, since it seems to handle a lot of aspects surrounding the subscription and multitenancy.

    I'm not sure if I am misunderstanding something, or if the AI is incorrect, but any help here would be appreciated.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share the RegisterAndSubscribeAsync method code?

  • User Avatar
    0
    cstobler created
    public async Task<StartSubscriptionResultDto> RegisterAndSubscribeAsync(SaasTenantCreateDto input)
    {
        if (input.EditionId == null || input.EditionId == Guid.Empty)
        {
            throw new UserFriendlyException("Please select a valid edition.");
        }
    
        if (string.IsNullOrWhiteSpace(input.Name) || string.IsNullOrWhiteSpace(input.AdminEmailAddress) || string.IsNullOrWhiteSpace(input.AdminPassword))
        {
            throw new UserFriendlyException("Please fill all required fields before submission");
        }
    
        bool isEmailUnique = await IsEmailUnique(input.AdminEmailAddress);
        if (!isEmailUnique)
        {
            // Check that email is unique across tenants
            using (_dataFilter.Disable<IMultiTenant>())
            {
                // Throw error is tenant is active
                List<IdentityUser> users = await _userRepository.GetListAsync();
                IdentityUser? userWithSameEmail = users.FirstOrDefault(u => u.NormalizedEmail == input.AdminEmailAddress.Trim().ToUpperInvariant());
                Tenant? associatedTenant = await _tenantRepository.FindAsync(userWithSameEmail?.TenantId ?? Guid.Empty);
                if (associatedTenant != null && !associatedTenant.IsDeleted)
                {
                    throw new UserFriendlyException("Email address is already registered. Please use another email address.");
                }
            }
        }
    
        // 1) Create tenant via domain layer (no host permission needed)
        var tenant = await _tenantManager.CreateAsync(input.Name, editionId: input.EditionId);
    
        tenant.SetActivationState(input.ActivationState); // keep passive until payment succeeds
        await _tenantRepository.InsertAsync(tenant, autoSave: true);
    
        string email = input.AdminEmailAddress.Trim().ToLowerInvariant();
    
        // 2) Publish TenantCreatedEto to seed admin user (same as TenantAppService does)
        await _eventBus.PublishAsync(new TenantCreatedEto
        {
            Id = tenant.Id,
            Name = tenant.Name,
            Properties =
            {
                {"AdminEmail", email},
                {"AdminUserName", email },
                {"AdminPassword", input.AdminPassword}
            }
        });
    
        // 3) Start subscription (creates PaymentRequest with TenantId/EditionId extra props) 
        PaymentRequestWithDetailsDto? paymentRequest = null;
        try
        {
            paymentRequest = await _subscriptionAppService.CreateSubscriptionAsync(input.EditionId ?? Guid.Empty, tenant.Id);
        }
        catch
        {
            // No payment plan configured. Go directly to activation
            await ActivateTenantAsync(tenant.Id);
        }
    
        return new StartSubscriptionResultDto
        {
            TenantId = tenant.Id,
            PaymentRequestId = paymentRequest?.Id ?? Guid.Empty,
        };
    }
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can override the StripePaymentGateway service.

    Please send an email to liming.ma@volosoft.com

    I will share the source code of StripePaymentGateway

    Thanks.

    Configure<PaymentOptions>(options =>
    {
        options.Gateways.RemoveAll(x => x.Key == StripeConsts.GatewayName)
        
        options.Gateways.Add(
            new PaymentGatewayConfiguration(
                StripeConsts.GatewayName,
                new FixedLocalizableString("Stripe"),
                isSubscriptionSupported: true,
                typeof(StripePaymentGateway)
            )
        );
    });
    
  • User Avatar
    0
    cstobler created

    Thanks, I emailed you to request the source code.

    So I understand, how would that be implemented then? Would the gateway pull in the email from the registration into the payment request, since that is what is being passed to Stripe?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I have sent the source code.

    You can add your custom field in the StripePaymentGateway service.

    Thanks.

  • User Avatar
    0
    cstobler created

    Thanks for sending the source code.

    Where do I put this file so that it overrides the default? And how do I actually populate that field in StripePaymentGateway with the registration email? My confusion stems from the fact that I am only interfacing with _subscriptionAppService.CreateSubscriptionAsync() to create the PaymentRequest, and then passing the PaymentRequest.Id to the GatewaySelection URL. If I extend StripePaymentGateway, I have no idea how to interface with this in a meaningful way.

    Any guidance you can provide would be greatly appreciated.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Rename it to MyStripePaymentGateway and add it to Gateways.

    Configure<PaymentOptions>(options =>
    {
        options.Gateways.RemoveAll(x => x.Key == StripeConsts.GatewayName)
        
        options.Gateways.Add(
            new PaymentGatewayConfiguration(
                StripeConsts.GatewayName,
                new FixedLocalizableString("Stripe"),
                isSubscriptionSupported: true,
                typeof(MyStripePaymentGateway)
            )
        );
    });
    
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on November 04, 2025, 06:41