stripe / stripe-dotnet

Stripe.net is a sync/async .NET 4.6.1+ client, and a portable class library for stripe.com.
Apache License 2.0
1.36k stars 571 forks source link

Zero documentation on web hook conversion to Stripe objects #1591

Closed roberthchapman closed 5 years ago

roberthchapman commented 5 years ago

"We’ve updated our API reference to include complete code samples for .NET"

Actually this isn't right at all and it's led to me being unable to implement web hooks, despite having done it twice in the past, when it was simple to implement.

There is simply no documentation existing on how to convert the incoming data to Stripe objects. Only the first step is mentioned in these two lines of code:

var json = new StreamReader(HttpContext.Request.Body).ReadToEnd();
var stripeEvent = EventUtility.ParseEvent(json);

Other than that, it's entirely undocumented. I have tried countless ways of trying to convert to say a Charge but get errors ranging from Index is out of bounds to null reference errors. Is it not possible to have a simple example to show this?!

I've tried things from:

charge = Mapper<Charge>.MapFromJson(stripeEvent.Data.Object.ToString());

to

charge = EventUtility.ParseEventDataItem<Charge>(stripeEvent.Data.Object);

It's frustrating to actually have zero examples of this while it's mentioned there are "complete code samples".

ob-stripe commented 5 years ago

Hi @roberthchapman, sorry to hear you're having trouble with webhooks. I agree that the webhooks documentation on Stripe's site needs to do a better job of illustrating how to access and use the nested object in event objects -- I'll share this feedback internally and we'll try to follow up before too long.

In the meantime, here is the information I believe you're looking for. As of v20.0.0, the Event.Data.Object attribute is typed as IHasObject, an interface for representing any Stripe object. When deserializing a JSON payload via EventUtility.ParseEvent(), the nested object will automatically be initialized with the correct type.

Since the type is not known at compile time, you will need to use either pattern matching or rely on the Event.Type attribute to access the object with the correct type. You can find brief examples of both approaches in the migration guide for v20.

Hope this helps! Let me know if you have any questions or if anything's unclear.

roberthchapman commented 5 years ago

Thanks for that, it's all crystal clear, it's a clear improvement over the old system, was just confusing because of the lack of clarity. Even just adding one more line of code to the docs would help -> charge = (Charge)stripeEvent.Data.Object;

leinzoeten commented 5 years ago

Wrestling with the same issue for more than two days now ;) I have also posted the same in the post: Error parsing JSON returned from webhook #1402. Not sure if this is working with the new version of stripe or that I am missing something. I have a . net 4.6 environment upgraded to the latest version of Stripe.net for .net (25.12.0.0) and also updated my version to 2019-03-14 on the dashboard, I have:

Stream req = Request.InputStream;
req.Seek(0, System.IO.SeekOrigin.Begin);
string json = new StreamReader(req).ReadToEnd();

(So far its working and if I would write the Json string to for example a database, its as the original event that was send from the dasboard) The next step always returns an error and stops there, with or without the , true/false parameter:

Event stripeEvent = EventUtility.ParseEvent(json);

or

stripeEvent = EventUtility.ParseEvent(json);

The only documentation I can find is either in regard to old versions of the appi and .net but not to .net 4.6 the new appi and webhooks Any idea or any working documentation? Thanks

ob-stripe commented 5 years ago

@leinzoeten What's the exact error you're seeing when calling EventUtility.ParseEvent()?

leinzoeten commented 5 years ago

@ob-stripe Thanks for looking into it. Looks like incompatability/version issues...

System.IO.FileLoadException
  HResult=0x80131040
  Message=Could not load file or assembly 'System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
  Source=Stripe.net
  StackTrace:
   at Stripe.Infrastructure.StripeTypeRegistry.GetConcreteType(Type potentialType, String objectValue)
   at Stripe.Infrastructure.StripeObjectConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Populate(JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerProxy.PopulateInternal(JsonReader reader, Object target)
   at Stripe.Infrastructure.EventConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at Stripe.Mapper`1.MapFromJson(String json, String parentToken, StripeResponse stripeResponse)
   at Stripe.EventUtility.ParseEvent(String json, Boolean throwOnApiVersionMismatch)
   at Results.Models.PayPalController.PayStripehoekje() in C:\Users\leinz\Documents\Visual Studio 2015\18062018\Kennis18062016\Controllers\PayPalController.cs:line 208
   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
   at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f()
leinzoeten commented 5 years ago

And after installing a new version System.Collections.Immutable, Version=1.2.3.0 resolved;) Sorry to have bothered you!

ob-stripe commented 5 years ago

@leinzoeten No worries, glad you were able to fix the issue :)

AndyEdmonds commented 5 years ago

Your documentation is still wrong though. At https://stripe.com/docs/payments/checkout/fulfillment The webhook example casts the posted object to type "CheckoutSession" which doesn't seem to exist. Presumably you mean "Session". The last time I tried to write code that responds to events generated during a subscription lifetime some months ago I gleaned what I thought were the set of messages based on the documentation, only to find in the live system that they were entirely different! I don't know if there still is no guidance on this, but your documentation has not been reliable.

remi-stripe commented 5 years ago

@AndyEdmonds Sorry about the trouble, we'll get this fixed!

facundofarias commented 5 years ago

Hey, still suffering the issue. Any idea how to deserialize Stripe Events?

Thanks

remi-stripe commented 5 years ago

@facundofarias Did you try the code in our documentation here for .NET?

AndyEdmonds commented 5 years ago

Hi @facundofarias Something like this is what I've used: var json = await new StreamReader(req.Body).ReadToEndAsync(); try { var stripeEvent = EventUtility.ConstructEvent(json, req.Headers["Stripe-Signature"], _config.GetValue<string>("stripeSecret"), throwOnApiVersionMismatch: false); logger.LogInformation($"Webhook call: {stripeEvent.Type}"); switch (stripeEvent.Type) { case "customer.subscription.trial_will_end": //Send trial will end email. {

facundofarias commented 5 years ago

Yes @remi-stripe, and I've also tried the code suggested by @AndyEdmonds. But I always end up with the same exception:

Exception: Index was outside the bounds of the array..

So, I cannot convert from a String to a Stripe Event. Any idea? Thanks!

remi-stripe commented 5 years ago

@facundofarias Unfortunately, we would need a bit more details to be able to debug this. It looks more like something after the deserialization, trying to access an array element that does not exist. I'd recommend reaching out to our support team instead here https://support.stripe.com/contact and providing your code and example events.

facundofarias commented 5 years ago

OK, so @remi-stripe It's quite easy: I've taken the event payment_intent.succeeded from the Stripe console, and then my code looks like this:

var json = new StreamReader(HttpContext.Request.Body).ReadToEnd();
var stripeEvent = Stripe.EventUtility.ParseEvent(json);

But, when I am parsing the event, I got the exception that I've mentioned before (so, basically, it's no the actual webhook from Stripe, but a manual re-send that I would like to use, since I have reached the maximum attempts, and I cannot use the automatic re-try from Stripe anymore). Still need more context than this? I think it's quite clear an easy to reproduce.

Thanks

remi-stripe commented 5 years ago

I would definitely need a lot more context. There's no way to reproduce without seeing the exact raw JSON body we sent you and that's why it'd be better done with our support team as it's unlikely to be a bug in the library.

facundofarias commented 5 years ago

I see @remi-stripe It's a test event, do you want me to share it here? But as I said, following the steps that I've mentioned it's 100% reproducible.

remi-stripe commented 5 years ago

@facundofarias The steps you mentioned though don't provide any context. It could depend on your version of stripe-dotnet, on the raw JSON, on your framework parsing it incorrectly ahead of time, on the API version of your endpoint, versus the one on your account.

Instead, I think the best solution is to reach out to our support team directly who will be able to help investigate and clarify what's causing this: https://support.stripe.com/contact

facundofarias commented 5 years ago

OK, so, I ended up doing this:

Passing by the eventId as a parameter, and then using the EventService to get the Stripe.Event:

var eventService = new EventService();
var stripeEvent = eventService.Get(eventId);

Thanks @remi-stripe

floresdwm commented 2 years ago

@roberthchapman I'm also have problems with this, but i figured it out by understanding that I need to convert Event type to PaymentIntent to get more relevant data (var paymentIntent = stripeEvent.Data.Object as PaymentIntent) . Check documentation here https://stripe.com/docs/webhooks

`var stripeEvent = EventUtility.ParseEvent(json);

            // Handle the event
            if (stripeEvent.Type == Events.PaymentIntentSucceeded)
            {
                **var paymentIntent = stripeEvent.Data.Object as PaymentIntent**;
                // Then define and call a method to handle the successful payment intent.
                // handlePaymentIntentSucceeded(paymentIntent);
            }
            else if (stripeEvent.Type == Events.PaymentMethodAttached)
            {
                var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
                // Then define and call a method to handle the successful attachment of a PaymentMethod.
                // handlePaymentMethodAttached(paymentMethod);
            }
            // ... handle other event types`