braintree / braintree_dotnet

Braintree .NET library
https://developer.paypal.com/braintree/docs/start/overview
MIT License
136 stars 73 forks source link

Cannot create / update plan with addons #130

Closed JadaVonRuth closed 1 year ago

JadaVonRuth commented 1 year ago

General information

Issue description

I am able to create / update plans without addons, but when I need to assign addons to a plan (which has not been used for anything yet) or create plan with addon right away. I am getting AuthorizationException when I set Addons property in PlanRequest.

        PlanRequest planRequest = new PlanRequest
        {
            Id = "my-plan",
            Name = "My plan",
            Description = "Awesome plan",
            BillingFrequency = 1,
            Price = 20,
            CurrencyIsoCode = "EUR",
            TrialPeriod = false,
            TrialDurationUnit = null,
            TrialDuration = null
        };

         List<AddOn> addons = Gateway.AddOn.All().ToList();

         planRequest.AddOns = new List<AddOn>();

         foreach (var addonId in addons)
         {
                planRequest.AddOns.Add(addon);
         }

        var planResult = await Gateway.Plan.CreateAsync(planRequest);
Braintree.Exceptions.AuthorizationException: Exception of type 'Braintree.Exceptions.AuthorizationException' was thrown.
   at Braintree.HttpService.ThrowExceptionIfErrorStatusCode(HttpStatusCode httpStatusCode, String message) in D:\Git\CTB\Ctb.Platform.App\api\IoLabs.BrainTree\Gateway\HttpService.cs:line 340
   at Braintree.HttpService.GetHttpResponse(HttpRequestMessage request) in D:\Git\CTB\Ctb.Platform.App\api\IoLabs.BrainTree\Gateway\HttpService.cs:line 137
   at Braintree.BraintreeService.GetXmlResponse(String URL, String method, Request requestBody, FileStream file) in D:\Git\CTB\Ctb.Platform.App\api\IoLabs.BrainTree\Gateway\BraintreeService.cs:line 153
   at Braintree.BraintreeService.Post(String URL, Request requestBody) in D:\Git\CTB\Ctb.Platform.App\api\IoLabs.BrainTree\Gateway\BraintreeService.cs:line 48
   at Braintree.PlanGateway.Create(PlanRequest request) in D:\Git\CTB\Ctb.Platform.App\api\IoLabs.BrainTree\Gateway\PlanGateway.cs:line 46

When creating a plan I am getting all available addons first and the setting them to PlanRequest which allows to set them, sandbox website also allows to configure addons for plans so there is no reason why this library should not be able to do it.

I noticed tests also do not contain use case with addons and looking closer I noticed addons are not being properly serialized into the request.

xml coming to http call:

<?xml version="1.0" encoding="UTF-8"?><plan><billing-frequency>1</billing-frequency><currency-iso-code>EUR</currency-iso-code><description>xxx</description><id>xxx</id><name>xxx</name><price>20.00</price><trial-period>false</trial-period><add-ons>System.Collections.Generic.List'1[Braintree.AddOn]</add-ons></plan>

Delete plan method is missing completely on PlanGateway for no obvious reason, that would be nice as well for full CRUD capability like in sandbox web admin.

We would like to make this work so we can go live. This is part of customer's admin interface.

hollabaq86 commented 1 year ago

👋 Please contact Support referencing this issue, your merchant ID, and timestamps of when you were attempting this operation and received the Authorization error.

JadaVonRuth commented 1 year ago

This authorization error is irrelevant here, request is badly formed by this library as I mentioned above.

<add-ons>System.Collections.Generic.List'1[Braintree.AddOn]</add-ons> is obviously wrongly serialized and without setting addons collection to request I can call the request successfully so it has nothing to do with authorization. Why is this closed? Seems like a bad call.

hollabaq86 commented 1 year ago

hey @JadaVonRuth you're right, my bad! I saw AuthorizationError and my first instinct was to send you to Support so they could help troubleshoot why you were getting those errors, but this should be open because of the serialization issue.

JadaVonRuth commented 1 year ago

Thanks for noticing and reopening :)

DPoplin commented 1 year ago

Hey @JadaVonRuth thanks for the follow-up here. I have gone ahead and created an internal ticket for tracking this bug. We will take a look and reach out with any additional questions.

For internal reference: DTBTSDK-2158

saralvasquez commented 1 year ago

Hi @JadaVonRuth! Just wanted to give a quick update on this issue. We have investigated this issue and have a PR put together to address the problem. Unfortunately, in getting the SDK sorted we found there is some behavior on the API side that isn't ideal. We are working with one of the API teams to get stuff resolved, but it does mean a bit more time before a fix is out. But a fix is coming! :)

saralvasquez commented 1 year ago

A fix for this issue was released in version 5.16.0 of the .NET SDK. This allows users to create Plans with AddOns and Discounts (i.e. modifications), as well as update a Plan after it's made to include modifications.

There are some large changes to how a Plan request is made to include modifications. There are also some oddities about the underlying functionality for this feature that come from the Braintree API and we don't have an ETA for a fix, but the API owning engineering team is aware of the behavior. So use this feature with caution.

Firstly, to create a Plan with modifications it has to be in the following format. The only required value is the InheritedFromId which will be the id of the AddOn or Discount that has already been created.

AddOn:

AddOns = new PlanAddOnsRequest[]
{
    new PlanAddOnsRequest
    {
        Add = new AddPlanModificationRequest[] 
        {
             new AddPlanModificationRequest 
            {
                InheritedFromId = <string_id_of_existing_addon>,
                Amount = <int_amount>,
                Description = <string_addon_description>,
                Name = <string_addon_name>,
                NumberOfBillingCycles = <int_billing_cycles_for_addon>,
            }
        },
    }
},

Discount:

Discounts = new PlanDiscountsRequest[]
{
    new PlanDiscountsRequest
    {
        Add = new AddPlanModificationRequest[]
        {
            new AddPlanModificationRequest
            {
                InheritedFromId = <string_id_of_existing_discount>,
                Amount = <int_amount>,
                Description = <string_discount_description>
                Name = <string_discount_name>,
                NumberOfBillingCycles = <int_billing_cycles_for_discount>,
            }
        }
    }
}

To update a previously created plan to include a modification, you include the AddOn or Discount in the format from above inside of a Plan.Update request. IMPORTANT: If a Plan has modifications and a Plan.Update is called without them, they will be removed from the plan.

Secondly, to update the modifications' values inside of a Plan, perform a Plan.Update with the following format for the AddOn or Discount

AddOn:

AddOns = new PlanAddOnsRequest[]
{
    new PlanAddOnsRequest
    {
        Update = new UpdatePlanModificationRequest[] 
        {
            new UpdatePlanModificationRequest 
            {
                ExistingId = <string_id_of_existing_addon>,
                Amount = <int_amount>,
                Description = <string_addon_description>,
                NumberOfBillingCycles = <int_billing_cycles_for_addon>,
            }
        },
    },
}

Discount:

Discounts = new PlanDiscountsRequest[]
{
    new PlanDiscountsRequest
    {
        Update = new UpdatePlanModificationRequest[]
        {
            new UpdatePlanModificationRequest
            {
                ExistingId = <string_id_of_existing_discount>,
                Amount = <int_amount>,
                Description = <string_discount_description>,
                NumberOfBillingCycles = <int_billing_cycles_for_discount>,
            }
        }
    }
}

NOTE: Now the ID is now called ExistingId. It is required and must be the id of the modification that is already tied to the plan.

Now for the odd behavior I mentioned. Validation of input for Modifications is a bit spotty. If there are strange values for the amount or number of billing cycles for modifications, one of two things can happen. It may silently fail to update the modification or it may create a modification in the plan with the absolute value of whatever was sent in the request. So again, use this feature with caution.

JadaVonRuth commented 1 year ago

@saralvasquez awesome, thank you very much!!