Indexed formurlencoded body fields

I am building a client against the ChargeBee API

Chargebee supports form url encoded body, with a non-conventional structure that looks like this, where the subfields are indexed by key (I’ve only seen one level so far):

In raw form:

POST https://playbooks-test.chargebee.com/api/v2/subscriptions HTTP/1.1
Host: playbooks-test.chargebee.com
Accept-Charset: utf-8
Authorization: Basic dGVzrF9iazRwV2k0clg1R1ZkWGXkSUNIQXdxaTdvM09GUkZVZTo=
Accept: application/json
User-Agent: ChargeBee-DotNet-Client v2.8.9
Lang-Version: .NET Core 3.1.4
OS-Version: Microsoft Windows 10.0.19042
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 238

plan_id=playbooks-meet&plan_quantity=1&customer%5Bfirst_name%5D=afirstname&customer%5Blast_name%5D=alastname&customer%5Bemail%5D=testingonly%40company.com&billing_address%5Bfirst_name%5D=afirstname&billing_address%5Blast_name%5D=alastname

Given a request DTO like this:

[Route("/subscriptions", "POST")]
    [DataContract]
    public class ChargebeeCreateSubscriptionRequest : IReturn<ChargebeeCreateSubscriptionResponse>, IPost, IUrlFilter
    {
        [DataMember(Name = "plan_id")]
        public string PlanId { get; set; }

        [DataMember(Name = "plan_quantity")]
        public int PlanQuantity { get; set; }

        [DataMember(Name = "customer")]
        public ChargebeeCustomer Customer { get; set; }

        [DataMember(Name = "payment_intent")]
        public ChargebeePaymentIntent PaymentIntent { get; set; }

        [DataMember(Name = "billing_address")]
        public ChargebeeAddress BillingAddress { get; set; }
}

    [DataContract]
    public class ChargebeeCustomer
    {
        [DataMember(Name = "first_name")]
        public string FirstName { get; set; }

        [DataMember(Name = "last_name")]
        public string LastName { get; set; }

        [DataMember(Name = "email")]
        public string Email { get; set; }
    }

What might be the best way to get the body Chargebee needs when doing a POST?

Should I use one of the built-in clients (ie JsonServiceClient), or use reverse routing methods (ie request.ToPostUrl().PostToUrl(request, MimeTypes.Json, req=> req.ContentType=MimeTypes.FormUrlEncoded)?
What extensibility mechanism of SS should I use to format the body I need in the POST?

Yeah for non ServiceStack Requests it’s recommended to use a generic HTTP Client like HTTP Utils.

But there is some support in using Typed Request DTOs for custom 3rd Party APIs like we do in StripeGateway.cs by using a custom config scope to customize the serialization behavior, e.g. by using TextCase.SnakeCase so you don’t have to use [DataMember(Name)] on each property and using QueryStringStrategy.FormUrlEncoded which has similar behavior of encoding complex types that you’re looking for, e.g:

public class ConfigScope : IDisposable
{
    private readonly WriteComplexTypeDelegate holdQsStrategy;
    private readonly JsConfigScope jsConfigScope;

    public ConfigScope()
    {
        var config = Config.Defaults;
        config.DateHandler = DateHandler.UnixTime;
        config.PropertyConvention = PropertyConvention.Lenient;
        config.TextCase = TextCase.SnakeCase;
        config.TreatEnumAsInteger = false;

        jsConfigScope = JsConfig.With(config);

        holdQsStrategy = QueryStringSerializer.ComplexTypeStrategy;
        QueryStringSerializer.ComplexTypeStrategy = QueryStringStrategy.FormUrlEncoded;
    }

    public void Dispose()
    {
        QueryStringSerializer.ComplexTypeStrategy = holdQsStrategy;
        jsConfigScope.Dispose();
    }
}

Which you can make use by calling the API within a using scope, e.g;

using (new ConfigScope())
{
    client.Post(...);
}

For additional customization you can ignore the property with [IgnoreDataMember] and use a IUrlFilter to include it as required when modifying the URL Encoded body that gets sent, e.g:

Thank you, great tips