Hi, I am attempting to setup self-service registration and subscription for customers. I am having an issue where when I try to go to the payment screen, it throws an error saying "Invalid URL", and when I go to Stripe, it shows a relative URL that I cannot seem to adjust.
For context, here is my register page model OnPostAsync:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
await OnGetAsync(); // reload editions
return Page();
}
// Confirm we are NOT in tenant context
if (_currentTenant.Id != null)
{
throw new Exception("Cannot register a tenant while already in a tenant context.");
//return Forbid(); // Registration should only be done as host
}
StartSubscriptionResultDto resultDto = await _multiTenancyAppService.RegisterAndSubscribeAsync(new SaasTenantCreateDto
{
Name = Input.TenantName,
AdminEmailAddress = Input.Email,
AdminPassword = Input.Password,
EditionId = Input.EditionId,
ActivationState = Volo.Saas.TenantActivationState.Passive
});
return LocalRedirectPreserveMethod("/Payment/GatewaySelection?paymentRequestId=" + resultDto.PaymentRequestId);
}
And here is my RegisterAndSubscribeAsync method:
public async Task<StartSubscriptionResultDto> RegisterAndSubscribeAsync(SaasTenantCreateDto input)
{
if (input.EditionId == null || input.EditionId == Guid.Empty)
{
throw new UserFriendlyException("Please select a valid edition.");
}
// 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);
// 2) Publish TenantCreatedEto to seed admin user (same as TenantAppService does)
await _eventBus.PublishAsync(new TenantCreatedEto
{
Id = tenant.Id,
Name = tenant.Name,
Properties =
{
{"AdminEmail", input.AdminEmailAddress},
{"AdminPassword", input.AdminPassword}
}
});
// 3) Start subscription (creates PaymentRequest with TenantId/EditionId extra props)
PaymentRequestWithDetailsDto paymentRequest = await _subscriptionAppService.CreateSubscriptionAsync(input.EditionId ?? Guid.Empty, tenant.Id);
return new StartSubscriptionResultDto
{
TenantId = tenant.Id,
PaymentRequestId = paymentRequest.Id,
};
}
I have tried to set the callbackurl, prepaymenturl, and postpaymenturl in appsettings, but that doesn't seem to do anything (stripe keys redacted for security):
"PaymentWebOptions": {
"RootUrl": "https://armadasoftware.io",
"CallBackUrl": "https://armadasoftware.io/Payment/Stripe/PostPayment",
"PaymentGatewayWebConfigurationDictionary": {
"Stripe": {
"PrePaymentUrl": "https://armadasoftware.io/Payment/Stripe/PrePayment",
"PostPaymentUrl": "https://armadasoftware.io/Payment/Stripe/PostPayment"
}
}
},
"Payment": {
"Stripe": {
"PublishableKey": "",
"SecretKey": "",
"WebhookSecret": ""
}
},
When I get this error, I go to Stripe Webhook logs, and I find this:
So it is saying the URL is invalid, and the success URL is a relative URL, which seems problematic, but I cannot seem to find a configuration or anything in the documentation that allows me to set a success URL such that it overrides this. I tried setting the success url in the stripe section of appsettings, but that didn't work either. Please advise me on how to set this so it overrides whatever default URL is being sent to Stripe here.
4 Answer(s)
-
0
To configure the Stripe success URL in ABP's Payment Module, you must ensure that the PaymentWebOptions are set up correctly with absolute URLs. Stripe requires absolute URLs for success and cancel URLs; relative URLs (like "/Payment/Stripe/PostPayment") will cause the "Not a valid URL" error you are seeing.
Here's how to resolve this:
- In your appsettings.json, set the PaymentWebOptions with absolute URLs:
"PaymentWebOptions": { "RootUrl": "https://armadasoftware.io", "CallbackUrl": "https://armadasoftware.io/Payment/Stripe/PostPayment" }
- In your module's PreConfigureServices (e.g., in your WebModule or AuthServerModule), configure PaymentWebOptions using the configuration:
public override void PreConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); Configure<PaymentWebOptions>(options => { options.RootUrl = configuration["PaymentWebOptions:RootUrl"]; options.CallbackUrl = configuration["PaymentWebOptions:CallbackUrl"]; }); }
Do not use relative URLs in PaymentGatewayWebConfigurationDictionary for Stripe. The Payment Module will use RootUrl + CallbackUrl to generate the absolute success_url for Stripe.
Make sure your Stripe webhook and dashboard are also configured to use absolute URLs.
After these changes, the success_url sent to Stripe will be something like:
https://armadasoftware.io/Payment/Stripe/PostPayment?SessionId={CHECKOUT_SESSION_ID}
This should resolve the "Invalid URL" error.
Sources:
- https://abp.io/docs/latest/modules/payment
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
That section in PreConfigureServices did the trick. However, I am having another related issue.
I am getting this error:
So it is treating my Stripe product ID as if it were a price. However, I was under the impression I could use the Stripe product id when configuring the gateway plan:
Am I doing this incorrectly? Is there some other way to tie my plan in to a Stripe product?
-
0
Hi,
It depends on a couple of different cases:
- First make sure you have a "default" price. If you have multiple prices for a specific prroduct, you should choose one of them as default.
If still doesn't help, you can use directly price ID that starts with
price_xx
Both product and price id works as
ExternalId
in payment module.My suggestion, you can directly try copying price id and using it, if it doesn't work please let us know.
Stripe or any other 3rd party service UIs are changing constanly, so we cannot mantain screenshots in our documentation, but here is the latest dashboard design of Stripe that I shared in this post
-
0
Thanks for the follow-up. It was indeed the price id. I thought you passed the product id in and it would let them select which price to use (since I am using annual and monthly), but looks like I will need to setup separate plans for each price then.
Thanks for your help.
Charlie