"Stripe style" Gateway for third party API

I’ve built a few Gateways for third parties (Pipedrive and others) using ServiceStack.

I usually roughly keep a simplified version of the structure of the StripeGateway. Currently, the API I’m building a Gateway for accepts only POST, so it’s relatively simple and This is the base structure I’m using:

    public interface IRestGateway
    {
        HttpClient Client { get; set; }

        T Post<T>(IReturn<T> request);

        Task<T> PostAsync<T>(IReturn<T> request);
    }

    public class RestGateway : IRestGateway
    {
       .....
     }

With dtos like these:

    [Route($"/main/{ApiMethods.GetCustomerProperties}")]
    public class GetCustomerProperties : IPost, IReturn<GetCustomerPropertiesResult>
    {
        public string Customer { get; set; }
        public string[] PropertyNames { get; set; }
    }

    public class GetCustomerPropertiesResult
    {
        public string[]? Result { get; set; }

        public Error? Error { get; set; }
    }
    
    public class Error
    {
        public string? Message { get; set; }
    }

Years ago (2018!) @mythz said I should probably not base myself off the StripeGateway project since the Stripe API is peculiar etc. It’s a bit late to ask for clarifications but is there anything inherently wrong with this approach?

The issue I’m having is that sometimes the API will return things that do not translate well to a typed result.

For example, the API I’m building a gateway for right now is not uniform and will sometime return a simple string array when there are no error whereas 90% of the endpoints stick to the convention of returning a Result Properties (for whatever they are returning) and an Error with a message like in the class above if something goes wrong.

I’m trying to normalize what the API return. That GetCustomerProperties dto is a good example. The API returns an array of string when there’s no error and doesn’t stick to the convention and right now I’m resorting to this hack to map to my return type (is there better way to handle this maybe on the DTO itself somehow?):

        public async Task<T> PostAsync<T>(IReturn<T> request)
        {
            var httpReq = CommonPost(request);
        
            HttpResponseMessage httpRes = await Client.SendAsync(httpReq);
            string? responseBody = await (await httpRes.Content.ReadAsStreamAsync()).ReadToEndAsync(Encoding.UTF8);
        
            if (httpRes.IsSuccessStatusCode)
            {
                if (typeof(T) == typeof(GetCustomerPropertiesResult))
                {
                    if (IsJsonArray(responseBody))
                    {
                        // Deserialize as string[] and convert to GetAllCustomerPropertiesResult
                        var strings = responseBody.FromJson<string[]>();
                        var result = new GetCustomerPropertiesResult
                        {
                            Result = strings
                        };
                        return (T)(object)result; // cast back to T
                    }
                    else
                    {
                        // Deserialize as string[] and convert to GetAllCustomerPropertiesResult
                        var error = responseBody.FromJson<Error>();
                        var result = new GetCustomerPropertiesResult
                        {
                            Error = error
                        };
                        return (T)(object)result; // cast back to T
                    }
                }

                return responseBody.FromJson<T>();
            }
        
            ThrowDefaultRestApiException(httpRes, responseBody);
        
            throw httpRes.EnsureSuccessStatusCodeCreateException(); 
        }

The other issue I have is that if you send [“name”,“phone”] for PropertyNames the API returns [“mythz”,“yourphonenumber”] there are lot’s of properties and I’d like to be able to offer a typed experience how would you go about implementing this?

I’m not sure if you had these things in mind when you said it was probably a bad idea to base myself off the stripe gateway and I’ve been going down the wrong road all this time or you simply meant their api had a lot of specific quirks.

I’m looking to improve my skills and any advice will be much appreciated.

Cheers,

It’s hard to tell exactly what your Gateway is doing, but it sounds like you are using it as some form of proxy matching the API behind the gateway? If the API behind your gateway is inconsistent due to their own implementation not being typed, it makes sense that you will have a hard time mapping that.

If you are integrating with a domain you are highly familiar with, and your clients are not expecting the exact same API as those your are integrating with, you might have an opportunity to introduce your own endpoints with consistencies they are missing. Wrapping with “Message” and “PropertyNames” in a typed language is going to be likely the worst of both worlds since you are restricted to a typed system, trying to be more dynamic. In my experience, it is better to lean into strictness to add value via tooling or use a better suited tool if the dynamic nature needs to be preserved.

You can try a DTO inheriting from List<string> or better yet, get the client to use a query string value to send the data, assuming you have control of the client. If you don’t have control of the client, offering a typed experience likely won’t be of much value since the client already exists and won’t want to change. But if you can even influence the client usage, then yes, you will want to encourage using different API endpoints which lean into the message-centric/DTO/typed approach where possible. Hope that helps.

Sorry for the late reply.

I ended up matching to the third party API as closely as possible. I’m providing const string values for their field names so I’m retaining the flexibility, dynamic aspect and I’m reducing the risk of typos and the api can still be called with strings if needed.

On some response dto I implemented a HandleInconsistentResponse that I’m calling for some endpoints.

I’ll see with the third party provider if they are willing to improve and I’ll remove these patches.

Thanks for the pointers.

1 Like