package com.paypal.api.payments; import com.paypal.base.Constants; import com.paypal.base.SDKUtil; import com.paypal.base.SSLUtil; import com.paypal.base.rest.*; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @Getter @Setter @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) public class Event extends PayPalResource { private static final Logger log = LoggerFactory.getLogger(Event.class); /** * Identifier of the Webhooks event resource. */ private String id; /** * Time the resource was created. */ private String createTime; /** * Name of the resource contained in resource element. */ private String resourceType; /** * Name of the event type that occurred on resource, identified by data_resource element, to trigger the Webhooks event. */ private String eventType; /** * A summary description of the event. E.g. A successful payment authorization was created for $$ */ private String summary; /** * This contains the resource that is identified by resource_type element. */ private Object resource; /** * Hateoas links. */ private List<Links> links; /** * Default Constructor */ public Event() { } /** * Retrieves the Webhooks event resource identified by event_id. Can be used to retrieve the payload for an event. * @deprecated Please use {@link #get(APIContext, String)} instead. * @param accessToken * Access Token used for the API call. * @param eventId * String * @return Event * @throws PayPalRESTException */ @Deprecated public static Event get(String accessToken, String eventId) throws PayPalRESTException { APIContext apiContext = new APIContext(accessToken); return get(apiContext, eventId); } /** * Retrieves the Webhooks event resource identified by event_id. Can be used to retrieve the payload for an event. * @param apiContext * {@link APIContext} used for the API call. * @param eventId * String * @return Event * @throws PayPalRESTException */ public static Event get(APIContext apiContext, String eventId) throws PayPalRESTException { if (eventId == null) { throw new IllegalArgumentException("eventId cannot be null"); } Object[] parameters = new Object[] {eventId}; String pattern = "v1/notifications/webhooks-events/{0}"; String resourcePath = RESTUtil.formatURIPath(pattern, parameters); String payLoad = ""; return configureAndExecute(apiContext, HttpMethod.GET, resourcePath, payLoad, Event.class); } /** * Resends the Webhooks event resource identified by event_id. * @deprecated Please use {@link #resend(APIContext)} instead. * @param accessToken * Access Token used for the API call. * @return Event * @throws PayPalRESTException */ @Deprecated public Event resend(String accessToken) throws PayPalRESTException { APIContext apiContext = new APIContext(accessToken); return resend(apiContext); } /** * Resends the Webhooks event resource identified by event_id. * @param apiContext * {@link APIContext} used for the API call. * @return Event * @throws PayPalRESTException */ public Event resend(APIContext apiContext) throws PayPalRESTException { if (this.getId() == null) { throw new IllegalArgumentException("Id cannot be null"); } Object[] parameters = new Object[] {this.getId()}; String pattern = "v1/notifications/webhooks-events/{0}/resend"; String resourcePath = RESTUtil.formatURIPath(pattern, parameters); String payLoad = ""; return configureAndExecute(apiContext, HttpMethod.POST, resourcePath, payLoad, Event.class); } /** * Retrieves the list of Webhooks events resources for the application associated with token. The developers can use it to see list of past webhooks events. * @deprecated Please use {@link #list(APIContext, String)} instead. * @param accessToken * Access Token used for the API call. * @return EventList * @throws PayPalRESTException */ @Deprecated public static EventList list(String accessToken, String queryParams) throws PayPalRESTException { APIContext apiContext = new APIContext(accessToken); return list(apiContext, queryParams); } /** * Retrieves the list of Webhooks events resources for the application associated with token. The developers can use it to see list of past webhooks events. * @param apiContext * {@link APIContext} used for the API call. * @return EventList * @throws PayPalRESTException */ public static EventList list(APIContext apiContext, String queryParams) throws PayPalRESTException { String resourcePath = "v1/notifications/webhooks-events" + queryParams; String payLoad = ""; return configureAndExecute(apiContext, HttpMethod.GET, resourcePath, payLoad, EventList.class); } /** * Validates received event received from PayPal to webhook endpoint set for particular webhook Id with PayPal trust source, to verify Data and Certificate integrity. * It validates both certificate chain, as well as data integrity. * * @param apiContext APIContext object * @param headers Map of Headers received in the event, from request * @param requestBody Request body received in the provided webhook * @return true if valid, false otherwise * @throws PayPalRESTException * @throws InvalidKeyException * @throws NoSuchAlgorithmException * @throws SignatureException */ public static boolean validateReceivedEvent(APIContext apiContext, Map<String, String> headers, String requestBody) throws PayPalRESTException, InvalidKeyException, NoSuchAlgorithmException, SignatureException { if (headers == null) { throw new PayPalRESTException("Headers cannot be null"); } Map<String, String> cmap; Boolean isChainValid = false, isDataValid = false; Collection<X509Certificate> trustCerts, clientCerts; // Load the configurations from all possible sources cmap = getConfigurations(apiContext); // Fetch Certificate Locations String clientCertificateLocation = SDKUtil.validateAndGet(headers, Constants.PAYPAL_HEADER_CERT_URL); // Default to `DigiCertSHA2ExtendedValidationServerCA` if none provided if (cmap != null && !cmap.containsKey(Constants.PAYPAL_TRUST_CERT_URL)) { cmap.put(Constants.PAYPAL_TRUST_CERT_URL, Constants.PAYPAL_TRUST_DEFAULT_CERT); } String trustCertificateLocation = SDKUtil.validateAndGet(cmap, Constants.PAYPAL_TRUST_CERT_URL); // Load certificates clientCerts = SSLUtil.getCertificateFromStream(SSLUtil.downloadCertificateFromPath(clientCertificateLocation, cmap)); trustCerts = SSLUtil.getCertificateFromStream(Event.class.getClassLoader().getResourceAsStream(trustCertificateLocation)); // Check if Chain Valid isChainValid = SSLUtil.validateCertificateChain(clientCerts, trustCerts, SDKUtil.validateAndGet(cmap, Constants.PAYPAL_WEBHOOK_CERTIFICATE_AUTHTYPE)); log.debug("Is Chain Valid: " + isChainValid); if (isChainValid) { // If Chain Valid, check for data signature valid // Lets check for data now String webhookId = SDKUtil.validateAndGet(cmap, Constants.PAYPAL_WEBHOOK_ID); String actualSignatureEncoded = SDKUtil.validateAndGet(headers, Constants.PAYPAL_HEADER_TRANSMISSION_SIG); String authAlgo = SDKUtil.validateAndGet(headers, Constants.PAYPAL_HEADER_AUTH_ALGO); String transmissionId = SDKUtil.validateAndGet(headers, Constants.PAYPAL_HEADER_TRANSMISSION_ID); String transmissionTime = SDKUtil.validateAndGet(headers, Constants.PAYPAL_HEADER_TRANSMISSION_TIME); String expectedSignature = String.format("%s|%s|%s|%s", transmissionId, transmissionTime, webhookId, SSLUtil.crc32(requestBody)); // Validate Data isDataValid = SSLUtil.validateData(clientCerts, authAlgo, actualSignatureEncoded, expectedSignature, requestBody, webhookId); log.debug("Is Data Valid: " + isDataValid); // Return true if both data and chain valid return isDataValid; } return false; } /** * Returns configurations by merging apiContext configurations in Map format * * @param apiContext * @return Map of configurations to be used for particular request */ private static Map<String, String> getConfigurations(APIContext apiContext) { Map<String, String> cmap; if (apiContext != null) { if (apiContext.getConfigurationMap() == null) { apiContext.setConfigurationMap(new HashMap<String, String>()); } cmap = SDKUtil.combineDefaultMap(apiContext.getConfigurationMap()); cmap = SDKUtil.combineMap(cmap, PayPalResource.getConfigurations()); } else { cmap = SDKUtil.combineDefaultMap(PayPalResource.getConfigurations()); } return cmap; } }