installed plugin Easy Digital Downloads version 3.1.0.3

This commit is contained in:
2022-11-27 15:03:07 +00:00
committed by Gitium
parent 555673545b
commit c5dce2cec6
1200 changed files with 238970 additions and 0 deletions

View File

@ -0,0 +1,291 @@
<?php
/**
* Webhook Event Handler
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal\Webhooks\Events
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal\Webhooks\Events;
use EDD\Gateways\PayPal\API;
use EDD\Gateways\PayPal\Exceptions\API_Exception;
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
use EDD\Orders\Order;
abstract class Webhook_Event {
/**
* API request
*
* @var \WP_REST_Request
* @since 2.11
*/
protected $request;
/**
* Data from the request.
*
* @var object
* @since 2.11
*/
protected $event;
/**
* Webhook_Event constructor.
*
* @param \WP_REST_Request $request
*
* @since 2.11
*/
public function __construct( $request ) {
$this->request = $request;
// `get_params()` returns an array, but we want an object.
$this->event = json_decode( json_encode( $this->request->get_params() ) );
}
/**
* Handles the webhook event.
*
* @throws \Exception
*/
public function handle() {
$this->process_event();
}
/**
* Processes the event.
*
* @since 2.11
* @return void
*/
abstract protected function process_event();
/**
* Retrieves an Order record from a capture event.
*
* @since 3.0
*
* @return Order
* @throws \Exception
*/
protected function get_order_from_capture() {
if ( 'capture' !== $this->request->get_param( 'resource_type' ) ) {
throw new \Exception( sprintf( 'get_payment_from_capture() - Invalid resource type: %s', $this->request->get_param( 'resource_type' ) ) );
}
if ( empty( $this->event->resource ) ) {
throw new \Exception( sprintf( 'get_payment_from_capture() - Missing event resource.' ) );
}
return $this->get_order_from_capture_object( $this->event->resource );
}
/**
* Retrieves an Order record from a capture object.
*
* @param object $resource
*
* @since 3.0
*
* @return Order
* @throws \Exception
*/
protected function get_order_from_capture_object( $resource ) {
$order = false;
if ( ! empty( $resource->custom_id ) && is_numeric( $resource->custom_id ) ) {
$order = edd_get_order( $resource->custom_id );
}
if ( empty( $order ) && ! empty( $resource->id ) ) {
$order_id = edd_get_order_id_from_transaction_id( $resource->id );
$order = $order_id ? edd_get_order( $order_id ) : false;
}
if ( ! $order instanceof Order ) {
throw new \Exception( 'get_order_from_capture_object() - Failed to locate order.', 200 );
}
/*
* Verify the transaction ID. This covers us in case we fetched the order via `custom_id`, but
* it wasn't actually an EDD-initiated payment.
*/
$order_transaction_id = $order->get_transaction_id();
if ( $order_transaction_id !== $resource->id ) {
throw new \Exception( sprintf(
'get_order_from_capture_object() - Transaction ID mismatch. Expected: %s; Actual: %s',
$order_transaction_id,
$resource->id
), 200 );
}
return $order;
}
/**
* Retrieves an Order record from a refund event.
*
* @since 3.0
*
* @return Order
* @throws API_Exception
* @throws Authentication_Exception
* @throws \Exception
*/
protected function get_order_from_refund() {
edd_debug_log( sprintf(
'PayPal Commerce Webhook - get_payment_from_capture_object() - Resource type: %s; Resource ID: %s',
$this->request->get_param( 'resource_type' ),
$this->event->resource->id
) );
if ( empty( $this->event->resource->links ) || ! is_array( $this->event->resource->links ) ) {
throw new \Exception( 'Missing resources.', 200 );
}
$order_link = current( array_filter( $this->event->resource->links, function ( $link ) {
return ! empty( $link->rel ) && 'up' === strtolower( $link->rel );
} ) );
if ( empty( $order_link->href ) ) {
throw new \Exception( 'Missing order link.', 200 );
}
// Based on the payment link, determine which mode we should act in.
if ( false === strpos( $order_link->href, 'sandbox.paypal.com' ) ) {
$mode = API::MODE_LIVE;
} else {
$mode = API::MODE_SANDBOX;
}
// Look up the full order record in PayPal.
$api = new API( $mode );
$response = $api->make_request( $order_link->href, array(), array(), $order_link->method );
if ( 200 !== $api->last_response_code ) {
throw new API_Exception( sprintf( 'Invalid response code when retrieving order record: %d', $api->last_response_code ) );
}
if ( empty( $response->id ) ) {
throw new API_Exception( 'Missing order ID from API response.' );
}
return $this->get_order_from_capture_object( $response );
}
/**
* Retrieves an EDD_Payment record from a capture event.
*
* @since 2.11
* @deprecated 3.0 In favour of `get_order_from_capture()`
* @see Webhook_Event::get_order_from_capture()
*
* @return \EDD_Payment
* @throws \Exception
*/
protected function get_payment_from_capture() {
if ( 'capture' !== $this->request->get_param( 'resource_type' ) ) {
throw new \Exception( sprintf( 'get_payment_from_capture() - Invalid resource type: %s', $this->request->get_param( 'resource_type' ) ) );
}
if ( empty( $this->event->resource ) ) {
throw new \Exception( sprintf( 'get_payment_from_capture() - Missing event resource.' ) );
}
return $this->get_payment_from_capture_object( $this->event->resource );
}
/**
* Retrieves an EDD_Payment record from a capture object.
*
* @param object $resource
*
* @since 2.11
* @deprecated 3.0 In favour of `get_order_from_capture_object()`
* @see Webhook_Event::get_order_from_capture_object
*
* @return \EDD_Payment
* @throws \Exception
*/
protected function get_payment_from_capture_object( $resource ) {
$payment = false;
if ( ! empty( $resource->custom_id ) && is_numeric( $resource->custom_id ) ) {
$payment = edd_get_payment( $resource->custom_id );
}
if ( empty( $payment ) && ! empty( $resource->id ) ) {
$payment_id = edd_get_purchase_id_by_transaction_id( $resource->id );
$payment = $payment_id ? edd_get_payment( $payment_id ) : false;
}
if ( ! $payment instanceof \EDD_Payment ) {
throw new \Exception( 'get_payment_from_capture_object() - Failed to locate payment.', 200 );
}
/*
* Verify the transaction ID. This covers us in case we fetched the payment via `custom_id`, but
* it wasn't actually an EDD-initiated payment.
*/
if ( $payment->transaction_id !== $resource->id ) {
throw new \Exception( sprintf( 'get_payment_from_capture_object() - Transaction ID mismatch. Expected: %s; Actual: %s', $payment->transaction_id, $resource->id ), 200 );
}
return $payment;
}
/**
* Retrieves an EDD_Payment record from a refund event.
*
* @since 2.11
* @deprecated 3.0 In favour of `get_order_from_refund`
* @see Webhook_Event::get_order_from_refund
*
* @return \EDD_Payment
* @throws API_Exception
* @throws Authentication_Exception
* @throws \Exception
*/
protected function get_payment_from_refund() {
edd_debug_log( sprintf( 'PayPal Commerce Webhook - get_payment_from_refund() - Resource type: %s; Resource ID: %s', $this->request->get_param( 'resource_type' ), $this->event->resource->id ) );
if ( empty( $this->event->resource->links ) || ! is_array( $this->event->resource->links ) ) {
throw new \Exception( 'Missing resources.', 200 );
}
$order_link = current( array_filter( $this->event->resource->links, function ( $link ) {
return ! empty( $link->rel ) && 'up' === strtolower( $link->rel );
} ) );
if ( empty( $order_link->href ) ) {
throw new \Exception( 'Missing order link.', 200 );
}
// Based on the payment link, determine which mode we should act in.
if ( false === strpos( $order_link->href, 'sandbox.paypal.com' ) ) {
$mode = API::MODE_LIVE;
} else {
$mode = API::MODE_SANDBOX;
}
// Look up the full order record in PayPal.
$api = new API( $mode );
$response = $api->make_request( $order_link->href, array(), array(), $order_link->method );
if ( 200 !== $api->last_response_code ) {
throw new API_Exception( sprintf( 'Invalid response code when retrieving order record: %d', $api->last_response_code ) );
}
if ( empty( $response->id ) ) {
throw new API_Exception( 'Missing order ID from API response.' );
}
return $this->get_payment_from_capture_object( $response );
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Webhook Event: PAYMENT.CAPTURE.COMPLETED
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal\Webhooks\Events
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal\Webhooks\Events;
use EDD\Gateways\PayPal\Exceptions\API_Exception;
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
class Payment_Capture_Completed extends Webhook_Event {
/**
* Processes the event.
*
* @throws API_Exception
* @throws Authentication_Exception
* @throws \Exception
*
* @since 2.11
*/
protected function process_event() {
$order = $this->get_order_from_capture();
// Bail if the payment has already been completed.
if ( 'complete' === $order->status ) {
edd_debug_log( 'PayPal Commerce - Exiting webhook, as order is already complete.' );
return;
}
if ( empty( $this->event->resource->status ) || 'COMPLETED' !== strtoupper( $this->event->resource->status ) ) {
throw new \Exception( 'Capture status is not complete.', 200 );
}
if ( ! isset( $this->event->resource->amount->value ) ) {
throw new \Exception( 'Missing amount value.', 200 );
}
if ( ! isset( $this->event->resource->amount->currency_code ) || strtoupper( $this->event->resource->amount->currency_code ) !== strtoupper( $order->currency ) ) {
throw new \Exception( sprintf( 'Missing or invalid currency code. Expected: %s; PayPal: %s', $order->currency, esc_html( $this->event->resource->amount->currency_code ) ), 200 );
}
$paypal_amount = (float) $this->event->resource->amount->value;
$order_amount = edd_get_payment_amount( $order->id );
if ( $paypal_amount < $order_amount ) {
edd_record_gateway_error(
__( 'Webhook Error', 'easy-digital-downloads' ),
sprintf(
/* Translators: %s is the webhook data */
__( 'Invalid payment about in webhook response. Webhook data: %s', 'easy-digital-downloads' ),
json_encode( $this->event )
)
);
edd_update_order_status( $order->id, 'failed' );
edd_add_note( array(
'object_type' => 'order',
'object_id' => $order->id,
'content' => sprintf(
__( 'Payment failed due to invalid amount in PayPal webhook. Expected amount: %s; PayPal amount: %s.', 'easy-digital-downloads' ),
$order_amount,
esc_html( $paypal_amount )
)
) );
throw new \Exception( sprintf( 'Webhook amount (%s) doesn\'t match payment amount (%s).', esc_html( $paypal_amount ), esc_html( $order_amount ) ), 200 );
}
edd_update_order_status( $order->id, 'complete' );
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Webhook Event: PAYMENT.CAPTURE.DENIED
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal\Webhooks\Events
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal\Webhooks\Events;
class Payment_Capture_Denied extends Webhook_Event {
/**
* Processes the webhook event
*
* @since 2.11
*
* @throws \Exception
*/
protected function process_event() {
$order = $this->get_order_from_capture();
edd_update_order_status( $order->id, 'failed' );
edd_add_note( array(
'object_type' => 'order',
'object_id' => $order->id,
'content' => __( 'PayPal transaction denied.', 'easy-digital-downloads' ),
) );
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Webhook Events:
*
* - PAYMENT.CAPTURE.REFUNDED - Merchant refunds a sale.
* - PAYMENT.CAPTURE.REVERSED - PayPal reverses a sale.
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal\Webhooks\Events
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal\Webhooks\Events;
use EDD\Gateways\PayPal\Exceptions\API_Exception;
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
class Payment_Capture_Refunded extends Webhook_Event {
/**
* Processes the event.
*
* @throws API_Exception
* @throws Authentication_Exception
* @throws \Exception
*
* @since 2.11
*/
protected function process_event() {
// Bail if this refund transaction already exists.
if ( $this->refund_transaction_exists() ) {
edd_debug_log( 'PayPal Commerce - Exiting webhook, as refund transaction already exists.' );
return;
}
$order = $this->get_order_from_refund();
if ( 'refunded' === $order->status ) {
edd_debug_log( 'PayPal Commerce - Exiting webhook, as payment status is already refunded.' );
return;
}
$order_amount = edd_get_payment_amount( $order->id );
$refunded_amount = isset( $this->event->resource->amount->value ) ? $this->event->resource->amount->value : $order_amount;
$currency = isset( $this->event->resource->amount->currency_code ) ? $this->event->resource->amount->currency_code : $order->currency;
/* Translators: %1$s - Amount refunded; %2$s - Original payment ID; %3$s - Refund transaction ID */
$payment_note = sprintf(
esc_html__( 'Amount: %1$s; Payment transaction ID: %2$s; Refund transaction ID: %3$s', 'easy-digital-downloads' ),
edd_currency_filter( edd_format_amount( $refunded_amount ), $currency ),
esc_html( $order->get_transaction_id() ),
esc_html( $this->event->resource->id )
);
// Partial refund.
if ( (float) $refunded_amount < (float) $order_amount ) {
edd_add_note( array(
'object_type' => 'order',
'object_id' => $order->id,
'content' => __( 'Partial refund processed in PayPal.', 'easy-digital-downloads' ) . ' ' . $payment_note,
) );
edd_update_order_status( $order->id, 'partially_refunded' );
} else {
// Full refund.
edd_add_note( array(
'object_type' => 'order',
'object_id' => $order->id,
'content' => __( 'Full refund processed in PayPal.', 'easy-digital-downloads' ) . ' ' . $payment_note,
) );
edd_update_order_status( $order->id, 'refunded' );
}
}
/**
* Determines whether a transaction record exists for this refund.
*
* @since 3.0
*
* @return bool
* @throws \Exception
*/
private function refund_transaction_exists() {
if ( ! isset( $this->event->resource->id ) ) {
throw new \Exception( 'No resource ID found.', 200 );
}
$transaction = edd_get_order_transaction_by( 'transaction_id', $this->event->resource->id );
return ! empty( $transaction );
}
}