Activities of "cstobler"

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.

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?

Can you explain this in more detail please? If self-registration is disabled on the host side, will it still allow self-registration for tenants? I need tenants to be able to sign up themselves, but I don't want anyone accessing the host side.

If this will work for my purposes, then is there some code I can implement that will set that rather than configuring it in the host settings in the app? Mainly so it is more streamlined when I need to deploy elsewhere.

Well, I set it up this way because I didn't want to utilize the "host" section of the app (as in, I wanted all users to be tenants), and I wanted to make sure that whenever someone was not logged in, they were redirected to the login page. This seemed like a good way to do it at the time since it forced login before users could access the app and it forced them to be tenants.

Is there a better way to go about this?

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,
    };
}

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.

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?

That doesn't really recommend anything aside from removing the whitelisting on some of my other pages, which I need for other purposes in my app (my own Stripe implementation).

Any help would be appreciated.

For context, I have a page where my users can add users to their tenant account. I am calling the IdentityUserAppService methods to do this. This is working fine. I am also authorizing the whole app and then whitelisting certain paths and pages in the WebModule to force users to login if they are not:

Configure<RazorPagesOptions>(options =>
{
    options.Conventions.AuthorizeFolder("/");

    options.Conventions.AllowAnonymousToAreaPage("Account", "/Login");
    options.Conventions.AllowAnonymousToAreaPage("Account", "/Register");
    options.Conventions.AllowAnonymousToAreaPage("Account", "/ForgotPassword");
    options.Conventions.AllowAnonymousToAreaPage("Account", "/ResetPassword");
    options.Conventions.AllowAnonymousToAreaPage("Account", "/EmailConfirmation");
    options.Conventions.AllowAnonymousToAreaPage("Account", "/TwoFactor");

    options.Conventions.AllowAnonymousToFolder("/Account");
    options.Conventions.AllowAnonymousToFolder("/Payment");
    options.Conventions.AllowAnonymousToFolder("/Payment/Stripe");
    options.Conventions.AllowAnonymousToFolder("/Public");
    options.Conventions.AllowAnonymousToPage("/Error");
    options.Conventions.AllowAnonymousToPage("/PrivacyPolicy");
    options.Conventions.AllowAnonymousToPage("/Payment/GatewaySelection");
    options.Conventions.AllowAnonymousToPage("/Payment/Stripe/PrePayment");
    options.Conventions.AllowAnonymousToPage("/Payment/Stripe/PostPayment");
});

My issue is that when the user selects ShouldChangePasswordOnNextLogin, it presumably redirects the new user to reset their password. What happens, however, is that the user is kicked back to the login page repeatedly. I am guessing that the user is not logged in at this point for some reason, and the reset password page is not being whitelisted.

Please help me understand what this path would be so I can whitelist it, or let me know if there is a more elegant way to approach this.

Charlie

So nuking the databases (along with changing a connection string I missed, which might have been a big part of it) worked and everything is functional. I guess I was mostly just rubber duck debugging here so I will close this out.

Showing 11 to 20 of 62 entries
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 December 17, 2025, 07:08
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.