2

I am using Stripe.Net to handle the payments. When I start to test the "charge.refund" webhook, I am getting NULL invoice property in code behind but invoice value exists in Stripe webhook event and I confirmed the invoice is exist dashboard too.

Noticed the versions in Stripe.Net and configured webhook API is different.

Dashboard Webhook API version: 2017-08-15

Stripe.Net version: 16.12.0.0 (It support 2018-02-06).

Here is the Stripe webhook event

enter image description here

Here is the breaking code(charge.Invoice.SubscriptionId breaking with Null reference)

enter image description here

Anybody faced this issue before?

Thanks

Linoy
  • 1,363
  • 1
  • 14
  • 29
  • check the mapper, the target class and the JSON being parsed. – Nkosi Jul 21 '18 at 18:04
  • Stripe Charge Source code https://github.com/stripe/stripe-dotnet/blob/master/src/Stripe.net/Entities/StripeCharge.cs#L182 – Nkosi Jul 21 '18 at 18:08
  • Looking at the [source code for the mapper](https://github.com/stripe/stripe-dotnet/blob/master/src/Stripe.net/Infrastructure/Public/Mapper.cs#L28) and the fact the that the [invoice property is marked with JsonIgnore](https://github.com/stripe/stripe-dotnet/blob/master/src/Stripe.net/Entities/StripeCharge.cs#L182). What you describe is designed behavior. – Nkosi Jul 21 '18 at 18:15

1 Answers1

5

Looking at the source code for the Stripe.Mapper<T>.MapFromJson

// the ResponseJson on a list method is the entire list (as json) returned from stripe.
// the ObjectJson is so we can store only the json for a single object in the list on that entity for
// logging and/or debugging
public static T MapFromJson(string json, string parentToken = null, StripeResponse stripeResponse = null)
{
    var jsonToParse = string.IsNullOrEmpty(parentToken) ? json : JObject.Parse(json).SelectToken(parentToken).ToString();

    var result = JsonConvert.DeserializeObject<T>(jsonToParse);

    // if necessary, we might need to apply the stripe response to nested properties for StripeList<T>
    ApplyStripeResponse(json, stripeResponse, result);

    return result;
}

public static T MapFromJson(StripeResponse stripeResponse, string parentToken = null)
{
    return MapFromJson(stripeResponse.ResponseJson, parentToken, stripeResponse);
}

private static void ApplyStripeResponse(string json, StripeResponse stripeResponse, object obj)
{
    if (stripeResponse == null)
    {
        return;
    }

    foreach (var property in obj.GetType().GetRuntimeProperties())
    {
        if (property.Name == nameof(StripeResponse))
        {
            property.SetValue(obj, stripeResponse);
        }
    }

    stripeResponse.ObjectJson = json;
}

which is deserializing the JSON using JSON.Net,

the fact that the StripeCharge.Invoice property is marked with [JsonIgnore] attribute.

#region Expandable Invoice

/// <summary>
/// ID of the invoice this charge is for if one exists.
/// </summary>
public string InvoiceId { get; set; }

[JsonIgnore]
public StripeInvoice Invoice { get; set; }

[JsonProperty("invoice")]
internal object InternalInvoice
{
    set
    {
        StringOrObject<StripeInvoice>.Map(value, s => this.InvoiceId = s, o => this.Invoice = o);
    }
}
#endregion

and finally how the InternalInvoice property is mapped via the StringOrObject<T>

StringOrObject<StripeInvoice>.Map(value, s => this.InvoiceId = s, o => this.Invoice = o);

You can see in the class definition

internal static class StringOrObject<T>
    where T : StripeEntityWithId
{
    public static void Map(object value, Action<string> updateId, Action<T> updateObject)
    {
        if (value is JObject)
        {
            T item = ((JToken)value).ToObject<T>();
            updateId(item.Id);
            updateObject(item);
        }
        else if (value is string)
        {
            updateId((string)value);
            updateObject(null);
        }
    }
}

That if the value passed is a string, that it will set the Invoice object property to null

else if (value is string)
{
    updateId((string)value);
    updateObject(null);
}

So the behavior you describe is as designed based on the shown data and code.

You would probably need to extract the InvoiceId and try to retrieve it (the invoice) in order to use its members.

Nkosi
  • 235,767
  • 35
  • 427
  • 472