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)
-
0
- 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:
- In appsettings of the API Host:
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.
- That method ends up creating a PaymentRequest and sending users to GatewaySelection. To inject the email, either:
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.
- 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.
-
0
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.
-
0
hi
Can you share the
RegisterAndSubscribeAsyncmethod code? -
0
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, }; } -
0
hi
You can override the
StripePaymentGatewayservice.Please send an email to liming.ma@volosoft.com
I will share the source code of
StripePaymentGatewayThanks.
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) ) ); }); -
0
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?
-
0
hi
I have sent the source code.
You can add your custom field in the
StripePaymentGatewayservice.Thanks.
-
0
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.
-
0
hi
Rename it to
MyStripePaymentGatewayand 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) ) ); });