Skip to content
11 changes: 6 additions & 5 deletions src/main/java/com/stripe/StripeClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.StripeObject;
import com.stripe.model.ThinEvent;
import com.stripe.model.v2.EventNotification;
import com.stripe.net.*;
import com.stripe.net.Webhook.Signature;
import java.net.PasswordAuthentication;
Expand Down Expand Up @@ -52,9 +52,9 @@ protected StripeResponseGetter getResponseGetter() {
* @return the StripeEvent instance
* @throws SignatureVerificationException if the verification fails.
*/
public ThinEvent parseThinEvent(String payload, String sigHeader, String secret)
public EventNotification parseEventNotification(String payload, String sigHeader, String secret)
throws SignatureVerificationException {
return parseThinEvent(payload, sigHeader, secret, Webhook.DEFAULT_TOLERANCE);
return parseEventNotification(payload, sigHeader, secret, Webhook.DEFAULT_TOLERANCE);
}

/**
Expand All @@ -70,11 +70,12 @@ public ThinEvent parseThinEvent(String payload, String sigHeader, String secret)
* @return the StripeEvent instance
* @throws SignatureVerificationException if the verification fails.
*/
public ThinEvent parseThinEvent(String payload, String sigHeader, String secret, long tolerance)
public EventNotification parseEventNotification(
String payload, String sigHeader, String secret, long tolerance)
throws SignatureVerificationException {
Signature.verifyHeader(payload, sigHeader, secret, tolerance);

return ApiResource.GSON.fromJson(payload, ThinEvent.class);
return EventNotification.fromJson(payload, this);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.stripe.exception.StripeException;
import com.stripe.model.billing.Meter;
import com.stripe.model.v2.Event;
import com.stripe.model.v2.Event.RelatedObject;
import java.time.Instant;
import java.util.List;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// File generated from our OpenAPI spec
package com.stripe.events;

import com.google.gson.annotations.SerializedName;
import com.stripe.exception.StripeException;
import com.stripe.model.billing.Meter;
import com.stripe.model.v2.Event.RelatedObject;
import com.stripe.model.v2.EventNotification;
import lombok.Getter;

@Getter
public final class V1BillingMeterErrorReportTriggeredEventNotification extends EventNotification {
@SerializedName("related_object")

/** Object containing the reference to API resource relevant to the event. */
RelatedObject relatedObject;

/** Retrieves the related object from the API. Make an API request on every call. */
public Meter fetchRelatedObject() throws StripeException {
return (Meter) super.fetchRelatedObject(this.relatedObject);
}
/** Retrieve the corresponding full event from the Stripe API. */
@Override
public V1BillingMeterErrorReportTriggeredEvent fetchEvent() throws StripeException {
return (V1BillingMeterErrorReportTriggeredEvent) super.fetchEvent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// File generated from our OpenAPI spec
package com.stripe.events;

import com.stripe.exception.StripeException;
import com.stripe.model.v2.EventNotification;

public final class V1BillingMeterNoMeterFoundEventNotification extends EventNotification {
/** Retrieve the corresponding full event from the Stripe API. */
@Override
public V1BillingMeterNoMeterFoundEvent fetchEvent() throws StripeException {
return (V1BillingMeterNoMeterFoundEvent) super.fetchEvent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.gson.annotations.SerializedName;
import com.stripe.exception.StripeException;
import com.stripe.model.v2.Event;
import com.stripe.model.v2.Event.RelatedObject;
import com.stripe.model.v2.EventDestination;
import lombok.Getter;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// File generated from our OpenAPI spec
package com.stripe.events;

import com.google.gson.annotations.SerializedName;
import com.stripe.exception.StripeException;
import com.stripe.model.v2.Event.RelatedObject;
import com.stripe.model.v2.EventDestination;
import com.stripe.model.v2.EventNotification;
import lombok.Getter;

@Getter
public final class V2CoreEventDestinationPingEventNotification extends EventNotification {
@SerializedName("related_object")

/** Object containing the reference to API resource relevant to the event. */
RelatedObject relatedObject;

/** Retrieves the related object from the API. Make an API request on every call. */
public EventDestination fetchRelatedObject() throws StripeException {
return (EventDestination) super.fetchRelatedObject(this.relatedObject);
}
/** Retrieve the corresponding full event from the Stripe API. */
@Override
public V2CoreEventDestinationPingEvent fetchEvent() throws StripeException {
return (V2CoreEventDestinationPingEvent) super.fetchEvent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.stripe.StripeClient;
import com.stripe.events.V1BillingMeterErrorReportTriggeredEvent;
import com.stripe.events.V1BillingMeterErrorReportTriggeredEventNotification;
import com.stripe.exception.StripeException;
import com.stripe.model.ThinEvent;
import com.stripe.model.billing.Meter;
import com.stripe.model.v2.Event;
import com.stripe.model.v2.EventNotification;
import com.stripe.model.v2.UnknownEventNotification;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
Expand All @@ -16,18 +18,18 @@
import java.nio.charset.StandardCharsets;

/**
* Receive and process thin events like the v1.billing.meter.error_report_triggered event.
* Receive and process EventNotifications like the v1.billing.meter.error_report_triggered event.
*
* <p>In this example, we:
*
* <ul>
* <li>use parseThinEvent to parse the received thin event webhook body
* <li>call StripeClient.v2.core.events.retrieve to retrieve the flil event object
* <li>use parseEventNotification to parse the received event notification webhook body
* <li>call StripeClient.v2.core.events.retrieve to retrieve the full event object
* <li>if it is a V1BillingMeterErrorReportTriggeredEvent event type, call fetchRelatedObject to
* retrieve the Billing Meter object associated with the event.
* </ul>
*/
public class ThinEventWebhookHandler {
public class EventNotificationWebhookHandler {
private static final String API_KEY = System.getenv("STRIPE_API_KEY");
private static final String WEBHOOK_SECRET = System.getenv("WEBHOOK_SECRET");

Expand Down Expand Up @@ -65,20 +67,34 @@ public void handle(HttpExchange exchange) throws IOException {
String sigHeader = exchange.getRequestHeaders().getFirst("Stripe-Signature");

try {
ThinEvent thinEvent = client.parseThinEvent(webhookBody, sigHeader, WEBHOOK_SECRET);
EventNotification eventNotif =
client.parseEventNotification(webhookBody, sigHeader, WEBHOOK_SECRET);

// Fetch the event data to understand the failure
Event baseEvent = client.v2().core().events().retrieve(thinEvent.getId());
if (baseEvent instanceof V1BillingMeterErrorReportTriggeredEvent) {
V1BillingMeterErrorReportTriggeredEvent event =
(V1BillingMeterErrorReportTriggeredEvent) baseEvent;
Meter meter = event.fetchRelatedObject();
// determine what sort of event you have
if (eventNotif instanceof V1BillingMeterErrorReportTriggeredEventNotification) {
V1BillingMeterErrorReportTriggeredEventNotification eventNotification =
(V1BillingMeterErrorReportTriggeredEventNotification) eventNotif;

String meterId = meter.getId();
System.out.println(meterId);
// after casting, can fetch the related object (which is correctly typed)
Meter meter = eventNotification.fetchRelatedObject();
System.out.println(meter.getId());

// Record the failures and alert your team
// Add your logic here
V1BillingMeterErrorReportTriggeredEvent event = eventNotification.fetchEvent();
System.out.println(event.getData().getDeveloperMessageSummary());

// add additional logic
}
// ... check other event types you know about
else if (eventNotif instanceof UnknownEventNotification) {
UnknownEventNotification unknownEvent = (UnknownEventNotification) eventNotif;
System.out.println("Received unknown event: " + unknownEvent.getId());
// can keep matching on the "type" field
// other helper methods still work, but you'll have to handle types yourself
if (unknownEvent.getType().equals("some.new.event")) {
Event event = unknownEvent.fetchEvent();
System.out.println(event.getReason());
// handle
}
}

exchange.sendResponseHeaders(200, -1);
Expand Down
42 changes: 0 additions & 42 deletions src/main/java/com/stripe/model/ThinEvent.java

This file was deleted.

16 changes: 0 additions & 16 deletions src/main/java/com/stripe/model/ThinEventRelatedObject.java

This file was deleted.

134 changes: 134 additions & 0 deletions src/main/java/com/stripe/model/v2/EventNotification.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.stripe.model.v2;

import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import com.stripe.StripeClient;
import com.stripe.exception.StripeException;
import com.stripe.model.StripeObject;
import com.stripe.model.v2.Event.RelatedObject;
import com.stripe.net.ApiMode;
import com.stripe.net.ApiResource;
import com.stripe.net.ApiResource.RequestMethod;
import com.stripe.net.RawRequestOptions;
import com.stripe.net.StripeResponse;
import java.time.Instant;
import lombok.AccessLevel;
import lombok.Getter;

/**
* `EventNotification` represents the common properties for json that's delivered from an Event
* Destination. A concrete child of `EventNotification` will be returned from
* `StripeClient.parseEventNotificaion()`. You will likely want to cast that object to a more
* specific child of `EventNotification`, like `PushedV1BillingMeterErrorReportTriggeredEvent`
*/
@Getter
public abstract class EventNotification {
/**
* For more details about Request, please refer to the <a href="https://docs.stripe.com/api">API
* Reference.</a>
*/
@Getter
public static class Request {
/** ID of the API request that caused the event. */
@SerializedName("id")
String id;

/** The idempotency key transmitted during the request. */
@SerializedName("idempotency_key")
String idempotencyKey;
}

@Getter
public static class Reason {
/** Information on the API request that instigated the event. */
@SerializedName("request")
Request request;

/**
* Event reason type.
*
* <p>Equal to {@code request}.
*/
@SerializedName("type")
String type;
}

/** Unique identifier for the event. */
@SerializedName("id")
public String id;

/** The type of the event. */
@SerializedName("type")
public String type;

/** Time at which the object was created. */
@SerializedName("created")
public Instant created;

/** Livemode indicates if the event is from a production(true) or test(false) account. */
@SerializedName("livemode")
public Boolean livemode;

/** [Optional] Authentication context needed to fetch the event or related object. */
@SerializedName("context")
public String context;

/** [Optional] Reason for the event. */
@SerializedName("reason")
public Reason reason;

@Getter(AccessLevel.NONE)
protected transient StripeClient client;

/**
* Helper for constructing an Event Notification. Doesn't perform signature validation, so you
* should use {@link com.stripe.StripeClient#parseEventNotification} instead for initial handling.
* This is useful in unit tests and working with EventNotifications that you've already validated
* the authenticity of.
*/
public static EventNotification fromJson(String payload, StripeClient client) {
// don't love the double json parse here, but I don't think we can avoid it?
JsonObject jsonObject = ApiResource.GSON.fromJson(payload, JsonObject.class).getAsJsonObject();

Class<? extends EventNotification> cls =
EventNotificationClassLookup.eventClassLookup.get(jsonObject.get("type").getAsString());
if (cls == null) {
cls = UnknownEventNotification.class;
}

EventNotification e = ApiResource.GSON.fromJson(payload, cls);
e.client = client;
return e;
}

private RawRequestOptions getRequestOptions() {
if (context == null) {
return null;
}
return new RawRequestOptions.RawRequestOptionsBuilder().setStripeContext(context).build();
}

/* retrieves the full payload for an event. Protected because individual push classes use it, but type it correctly */
protected Event fetchEvent() throws StripeException {
StripeResponse response =
client.rawRequest(
RequestMethod.GET, String.format("/v2/core/events/%s", id), null, getRequestOptions());

return (Event) client.deserialize(response.body(), ApiMode.V2);
}

/** Retrieves the object associated with the event. */
protected StripeObject fetchRelatedObject(RelatedObject relatedObject) throws StripeException {
if (relatedObject == null) {
// used by UnknownEventNotification, so be a little defensive
return null;
}

String relativeUrl = relatedObject.getUrl();

StripeResponse response =
client.rawRequest(RequestMethod.GET, relativeUrl, null, getRequestOptions());

return client.deserialize(response.body(), ApiMode.getMode(relativeUrl));
}
}
Loading
Loading