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,146 @@
<?php
/**
* Orders API - Address Object.
*
* @package EDD
* @subpackage Customers
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Base_Object;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Order Address Class.
*
* @since 3.0
*
* @property int $id
* @property int $order_id
* @property string $first_name
* @property string $last_name
* @property string $address
* @property string $address2
* @property string $city
* @property string $region
* @property string $postal_code
* @property string $country
* @property string $date_created
* @property string $date_modified
*/
class Order_Address extends Base_Object {
/**
* Order address ID.
*
* @since 3.0
* @access protected
* @var int
*/
protected $id;
/**
* Order ID.
*
* @since 3.0
* @access protected
* @var int
*/
protected $order_id;
/**
* First name.
*
* @since 3.0
* @access protected
* @var string
*/
protected $first_name;
/**
* Last name.
*
* @since 3.0
* @access protected
* @var string
*/
protected $last_name;
/**
* Address.
*
* @since 3.0
* @access protected
* @var string
*/
protected $address;
/**
* Address line 2.
*
* @since 3.0
* @access protected
* @var string
*/
protected $address2;
/**
* City.
*
* @since 3.0
* @access protected
* @var string
*/
protected $city;
/**
* Region.
*
* @since 3.0
* @access protected
* @var string
*/
protected $region;
/**
* Postal code.
*
* @since 3.0
* @access protected
* @var string
*/
protected $postal_code;
/**
* Country.
*
* @since 3.0
* @access protected
* @var string
*/
protected $country;
/**
* Date created.
*
* @since 3.0
* @access protected
* @var string
*/
protected $date_created;
/**
* Date modified.
*
* @since 3.0
* @access protected
* @var string
*/
protected $date_modified;
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Order Adjustment Object.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Refundable_Item;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Order_Adjustment Class.
*
* @since 3.0
*
* @property int $id
* @property int $parent
* @property int $object_id
* @property string $object_type
* @property int|null $type_id
* @property string $type
* @property string $description
* @property float $subtotal
* @property float $tax
* @property float $total
* @property string $date_completed
* @property string $date_modified
*/
class Order_Adjustment extends \EDD\Database\Rows\Order_Adjustment {
use Refundable_Item;
/**
* Order Discount ID.
*
* @since 3.0
* @var int
*/
protected $id;
/**
* Parent ID. This is only used for order adjustments attached to refunds. The ID references the
* original adjustment that was refunded.
*
* @since 3.0
* @var int
*/
protected $parent;
/**
* Object ID.
*
* @since 3.0
* @var int
*/
protected $object_id;
/**
* Object type.
*
* @since 3.0
* @var string
*/
protected $object_type;
/**
* Type ID.
*
* @since 3.0
* @var int|null
*/
protected $type_id;
/**
* Type.
*
* @since 3.0
* @var string
*/
protected $type;
/**
* Description.
*
* @since 3.0
* @var string
*/
protected $description;
/**
* Subtotal.
*
* @since 3.0
* @var float
*/
protected $subtotal;
/**
* Tax.
*
* @since 3.0
* @var float
*/
protected $tax;
/**
* Total.
*
* @since 3.0
* @var float
*/
protected $total;
/**
* Date created.
*
* @since 3.0
* @var string
*/
protected $date_created;
/**
* Date modified.
*
* @since 3.0
* @var string
*/
protected $date_modified;
/**
* Magic __toString method.
*
* @since 3.0
*/
public function __toString() {
return $this->description;
}
/**
* Retrieves order adjustment records that were refunded from this original adjustment.
*
* @since 3.0
*
* @return Order_Adjustment[]|false
*/
public function get_refunded_items() {
if ( null !== $this->refunded_items ) {
return $this->refunded_items;
}
// Only fees and credits are supported.
if ( ! in_array( $this->type, array( 'fee', 'credit' ) ) ) {
return false;
}
return edd_get_order_adjustments( array(
'parent' => $this->id
) );
}
}

View File

@ -0,0 +1,274 @@
<?php
/**
* Order Item Object.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Refundable_Item;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Order_Item Class.
*
* @since 3.0
*
* @property int $id
* @property int $parent
* @property int $order_id
* @property int $product_id
* @property string $product_name
* @property int|null $price_id
* @property int $cart_index
* @property string $type
* @property string $status
* @property int $quantity
* @property int $amount
* @property float $subtotal
* @property float $tax
* @property float $discount
* @property float $total
* @property float $rate
* @property string $date_created
* @property string $date_modified
* @property Order_Adjustment[] $adjustments
*/
class Order_Item extends \EDD\Database\Rows\Order_Item {
use Refundable_Item;
/**
* Order Item ID.
*
* @since 3.0
* @var int
*/
protected $id;
/**
* Parent ID. This is only used for order items attached to refunds. The ID references the
* original order item that was refunded.
*
* @since 3.0
* @var int
*/
protected $parent;
/**
* Order ID.
*
* @since 3.0
* @var int
*/
protected $order_id;
/**
* Product ID.
*
* @since 3.0
* @var int
*/
protected $product_id;
/**
* Product Name.
*
* @since 3.0
* @var string
*/
protected $product_name;
/**
* Price ID.
*
* @since 3.0
* @var int|null
*/
protected $price_id;
/**
* Cart index.
*
* @since 3.0
* @var int
*/
protected $cart_index;
/**
* Item type.
*
* @since 3.0
* @var string
*/
protected $type;
/**
* Item status.
*/
protected $status;
/**
* Item quantity.
*
* @since 3.0
* @var int
*/
protected $quantity;
/**
* Item amount.
*
* @since 3.0
* @var float
*/
protected $amount;
/**
* Item subtotal.
*
* @since 3.0
* @var float
*/
protected $subtotal;
/**
* Item tax.
*
* @since 3.0
* @var float
*/
protected $tax;
/**
* Item discount.
*
* @since 3.0
* @var float
*/
protected $discount;
/**
* Item total.
*
* @since 3.0
* @var float
*/
protected $total;
/**
* Date created.
*
* @since 3.0
* @var string
*/
protected $date_created;
/**
* Date modified.
*
* @since 3.0
* @var string
*/
protected $date_modified;
/**
* Order item adjustments.
*
* @since 3.0
* @var \EDD\Orders\Order_Adjustment[]
*/
protected $adjustments = null;
/**
* Magic getter for immutability.
*
* @since 3.0
*
* @param string $key
*
* @return mixed
*/
public function __get( $key = '' ) {
if ( 'adjustments' === $key && null === $this->adjustments ) {
$this->adjustments = edd_get_order_adjustments( array(
'object_id' => $this->id,
'object_type' => 'order_item',
'no_found_rows' => true,
'order' => 'ASC',
) );
}
return parent::__get( $key );
}
/**
* Retrieve fees applied to this order item.
*
* @since 3.0
*
* @return array $fees Fees applied to this item.
*/
public function get_fees() {
return edd_get_order_adjustments( array(
'object_id' => $this->id,
'object_type' => 'order_item',
'type' => 'fee',
'order' => 'ASC',
) );
}
/**
* Get an order item name, including any price ID name appended to the end.
*
* @since 3.0
*
* @return string The product name including any price ID name.
*/
public function get_order_item_name() {
if ( is_admin() && ( function_exists( 'edd_doing_ajax' ) && ! edd_doing_ajax() ) ) {
/**
* Allow the product name to be filtered within the admin.
* @since 3.0
* @param string $product_name The order item name.
* @param EDD\Orders\Order_Item The order item object.
*/
return apply_filters( 'edd_order_details_item_name', $this->product_name, $this );
}
return $this->product_name;
}
/**
* Retrieves order item records that were refunded from this original order item.
*
* @since 3.0
*
* @return Order_Item[]|false
*/
public function get_refunded_items() {
if ( null !== $this->refunded_items ) {
return $this->refunded_items;
}
return edd_get_order_items( array(
'parent' => $this->id
) );
}
/**
* Checks the order item status to determine whether assets can be delivered.
*
* @since 3.0
* @return bool
*/
public function is_deliverable() {
return in_array( $this->status, edd_get_deliverable_order_item_statuses(), true );
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* Order Transaction Object.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Database\Rows as Rows;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Order Transaction Class.
*
* @since 3.0
*
* @property int $id
* @property int $object_id
* @property string $object_type
* @property string $transaction_id
* @property string $gateway
* @property string $status
* @property float $total
* @property string $date_created
* @property string $date_modified
*/
class Order_Transaction extends Rows\Order_Transaction {
/**
* Order Transaction ID.
*
* @since 3.0
* @var int
*/
protected $id;
/**
* Object ID.
*
* @since 3.0
* @var int
*/
protected $object_id;
/**
* Object type
*
* @since 3.0
* @var int
*/
protected $object_type;
/**
* Transaction ID.
*
* @since 3.0
* @var string
*/
protected $transaction_id;
/**
* Gateway.
*
* @since 3.0
* @var string
*/
protected $gateway;
/**
* Status.
*
* @since 3.0
* @var string
*/
protected $status;
/**
* Total amount.
*
* @since 3.0
* @var float
*/
protected $total;
/**
* Date created.
*
* @since 3.0
* @var string
*/
protected $date_created;
/**
* Date modified.
*
* @since 3.0
* @var string
*/
protected $date_modified;
/**
* Check if a transaction is complete.
*
* @since 3.0
*
* @return bool True if the transaction is complete, false otherwise.
*/
public function is_complete() {
return ( 'complete' === $this->status );
}
}

View File

@ -0,0 +1,699 @@
<?php
/**
* Order Object.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Database\Rows as Rows;
use EDD\Database\Rows\Adjustment;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Order Class.
*
* @since 3.0
*
* @property int $id
* @property int $parent
* @property string $order_number
* @property string $type
* @property string $status
* @property string $date_created
* @property string $date_modified
* @property string|null $date_completed
* @property string|null $date_refundable
* @property int $user_id
* @property int $customer_id
* @property string $email
* @property string $ip
* @property string $gateway
* @property string $mode
* @property string $currency
* @property string $payment_key
* @property int|null $tax_rate_id
* @property float $subtotal
* @property float $tax
* @property float $discount
* @property float $total
* @property float $rate
* @property Order_Item[] $items
* @property Order_Adjustment[] $adjustments
* @property Order_Address $address
*/
class Order extends Rows\Order {
/**
* Order ID.
*
* @since 3.0
* @var int
*/
protected $id;
/**
* Parent order.
*
* @since 3.0
* @var int
*/
protected $parent;
/**
* Order number.
*
* @since 3.0
* @var string
*/
protected $order_number;
/**
* Order status.
*
* @since 3.0
* @var string
*/
protected $status;
/**
* Order type.
*
* @since 3.0
* @var string
*/
protected $type;
/**
* Date created.
*
* @since 3.0
* @var string
*/
protected $date_created;
/**
* Date modified.
*
* @since 3.0
* @var string
*/
protected $date_modified;
/**
* Date completed.
*
* @since 3.0
* @var string|null
*/
protected $date_completed;
/**
* Date refundable.
*
* @since 3.0
* @var string|null
*/
protected $date_refundable;
/**
* User ID.
*
* @since 3.0
* @var int
*/
protected $user_id;
/**
* Customer ID.
*
* @since 3.0
* @var int
*/
protected $customer_id;
/**
* Email.
*
* @since 3.0
* @var string
*/
protected $email;
/**
* IP.
*
* @since 3.0
* @var string
*/
protected $ip;
/**
* Order gateway.
*
* @since 3.0
* @var string
*/
protected $gateway;
/**
* Order mode.
*
* @since 3.0
* @var string
*/
protected $mode;
/**
* Order currency.
*
* @since 3.0
* @var string
*/
protected $currency;
/**
* Payment key.
*
* @since 3.0
* @var string
*/
protected $payment_key;
/**
* Tax rate ID.
*
* @since 3.0
* @var int|null
*/
protected $tax_rate_id;
/**
* Tax rate Adjustment object.
*
* @since 3.0
* @var Adjustment|null
*/
protected $tax_rate = null;
/**
* Subtotal.
*
* @since 3.0
* @var float
*/
protected $subtotal;
/**
* Tax.
*
* @since 3.0
* @var float
*/
protected $tax;
/**
* Order discount.
*
* @since 3.0
* @var float
*/
protected $discount;
/**
* Order total.
*
* @since 3.0
* @var float
*/
protected $total;
/**
* Order items.
*
* @since 3.0
* @var \EDD\Orders\Order_Item[]
*/
protected $items = null;
/**
* Order adjustments.
*
* @since 3.0
* @var \EDD\Orders\Order_Adjustment[]
*/
protected $adjustments = null;
/**
* Order address.
*
* @since 3.0
* @var \EDD\Orders\Order_Address
*/
protected $address = null;
/**
* Magic getter for immutability.
*
* @since 3.0
*
* @param string $key
* @return mixed
*/
public function __get( $key = '' ) {
if ( 'adjustments' === $key && null === $this->adjustments ) {
$this->adjustments = edd_get_order_adjustments( array(
'object_id' => $this->id,
'object_type' => 'order',
'no_found_rows' => true,
'order' => 'ASC',
) );
} elseif ( 'items' === $key ) {
$this->get_items();
}
return parent::__get( $key );
}
/**
* Retrieve order number.
*
* An order number is only retrieved if sequential order numbers are enabled,
* otherwise the order ID is returned.
*
* @since 3.0
*
* @return string
*/
public function get_number() {
if ( $this->order_number && edd_get_option( 'enable_sequential' ) ) {
$number = $this->order_number;
} else {
$number = $this->id;
}
/**
* The edd_payment_number filter allows the order_number value to be changed.
*
* This filter used to run in the EDD_Payment class's get_number method upon its setup.
* It now exists only here in EDD_Order since EDD 3.0. EDD Payment gets its order_number
* value from EDD_Order (this class), so it gets run for both EDD_Payment and EDD_Order this way.
*
* @since 2.5
* @since 3.0 Updated the 3rd paramater from an EDD_Payment object to an EDD_Order object.
*
* @param string The unique value to represent this order. This is a string because pre-fixes and post-fixes can be appended via the filter.
* @param int The row ID of the Payment/Order.
* @param Order Prior to EDD 3.0, this was an EDD_Payment object. Now it is an EDD_Order object.
*/
$number = apply_filters( 'edd_payment_number', $number, $this->ID, $this );
/**
* This filter is exactly the same as edd_payment_number, and exists purely so that
* the "order" terminology has a filter as well.
*
* @since 3.0
*
* @param string The unique value to represent this order. This is a string because pre-fixes and post-fixes can be appended via the filter.
* @param int The row ID of the Payment/Order.
* @param Order The EDD_Order object.
*/
$number = apply_filters( 'edd_order_number', $number, $this->ID, $this );
return $number;
}
/**
* Retrieve all the items in the order.
*
* @since 3.0
*
* @return Order_Item[] Order items.
*/
public function get_items() {
if ( null === $this->items ) {
$this->items = edd_get_order_items( array(
'order_id' => $this->id,
'orderby' => 'cart_index',
'order' => 'ASC',
'no_found_rows' => true,
'number' => 200,
) );
}
return $this->items;
}
/**
* Retrieve all the items in the order and transform bundle products into regular items.
*
* @since 3.0.2
*
* @return Order_Item[] Order items.
*/
public function get_items_with_bundles() {
$items = $this->get_items();
foreach ( $items as $index => $item ) {
if ( edd_is_bundled_product( $item->product_id ) ) {
$new_items = array();
$bundled_products = edd_get_bundled_products( $item->product_id, $item->price_id );
foreach ( $bundled_products as $bundle_item ) {
$order_item_args = array(
'order_id' => $this->ID,
'status' => $item->status,
'product_id' => edd_get_bundle_item_id( $bundle_item ),
'product_name' => edd_get_bundle_item_title( $bundle_item ),
'price_id' => edd_get_bundle_item_price_id( $bundle_item ),
);
$new_items[] = new \EDD\Orders\Order_Item( $order_item_args );
}
if ( ! empty( $new_items ) ) {
// The parent item should be replaced at its original position with its bundle items.
array_splice( $items, $index, 1, $new_items );
}
}
}
return $items;
}
/**
* Retrieve all the adjustments applied to the order.
*
* @since 3.0
*
* @return array Order adjustments.
*/
public function get_adjustments() {
if ( null === $this->adjustments ) {
$this->adjustments = edd_get_order_adjustments( array(
'object_id' => $this->id,
'object_type' => 'order',
'no_found_rows' => true,
'order' => 'ASC',
) );
}
return $this->adjustments;
}
/**
* Retrieve the discounts applied to the order.
*
* @since 3.0
*
* @return Order_Adjustment[] Order discounts.
*/
public function get_discounts() {
$discounts = array();
foreach ( $this->get_adjustments() as $adjustment ) {
/** @var Order_Adjustment $adjustment */
if ( 'discount' === $adjustment->type ) {
$discounts[] = $adjustment;
}
}
return $discounts;
}
/**
* Retrieve the fees applied to the order. This retrieves the fees applied to the entire order and to individual items.
*
* @since 3.0
*
* @return Order_Adjustment[] Order fees.
*/
public function get_fees() {
// Default values
$fees = array();
// Fetch the fees that applied to the entire order.
foreach ( $this->get_adjustments() as $adjustment ) {
/** @var Order_Adjustment $adjustment */
if ( 'fee' === $adjustment->type ) {
$fees[] = $adjustment;
}
}
// Ensure items exist.
if ( null === $this->items ) {
$this->items = $this->get_items();
}
// Fetch the fees that applied to specific items in the order.
foreach ( $this->items as $item ) {
/** @var Order_Item $item */
foreach ( $item->get_fees() as $fee ) {
/** @var Order_Adjustment $fee */
$fees[] = $fee;
}
}
return $fees;
}
/**
* Retrieve the credits applied to the order.
* These exist only for manually added orders.
*
* @since 3.0
*
*@return Order_Adjustment[] Order credits.
*/
public function get_credits() {
// Default values
$credits = array();
// Fetch the fees that applied to the entire order.
foreach ( $this->get_adjustments() as $adjustment ) {
/** @var Order_Adjustment $adjustment */
if ( 'credit' === $adjustment->type ) {
$credits[] = $adjustment;
}
}
return $credits;
}
/**
* Retrieve the transaction ID associated with the order.
*
* @since 3.0
*
* @param string $type Transaction type. Default `primary`.
* @return string|array $retval Transaction ID(s).
*/
public function get_transaction_id( $type = 'primary' ) {
$retval = '';
// Retrieve first transaction ID only.
if ( 'primary' === $type ) {
$transactions = array_values( edd_get_order_transactions( array(
'object_id' => $this->id,
'object_type' => 'order',
'orderby' => 'date_created',
'order' => 'ASC',
'fields' => 'transaction_id',
'number' => 1,
) ) );
if ( $transactions ) {
$retval = esc_attr( $transactions[0] );
}
// Retrieve all transaction IDs.
} else {
$retval = edd_get_order_transactions( array(
'object_id' => $this->id,
'object_type' => 'order',
'orderby' => 'date_created',
'order' => 'ASC',
'fields' => 'transaction_id',
) );
}
return $retval;
}
/**
* Retrieves the tax rate Adjustment object associated with the order.
*
* @since 3.0
* @return Adjustment|false|null
*/
public function get_tax_rate_object() {
if ( $this->tax_rate_id && null === $this->tax_rate ) {
$this->tax_rate = edd_get_adjustment( $this->tax_rate_id );
}
return $this->tax_rate;
}
/**
* Retrieve the tax rate associated with the order.
*
* @since 3.0
*
* @return float Tax rate percentage (0 - 100).
*/
public function get_tax_rate() {
// Default rate
$rate = 0;
// Get rates from adjustments
$tax_rate_object = $this->get_tax_rate_object();
if ( is_object( $tax_rate_object ) && isset( $tax_rate_object->amount ) ) {
$rate = $tax_rate_object->amount;
}
/*
* If we have a tax_amount, but no rate, check in order meta. This is where legacy rates are stored
* if they cannot be resolved to an actual adjustment object.
*/
if ( empty( $rate ) && abs( $this->tax ) > 0 ) {
$rate = edd_get_order_meta( $this->id, 'tax_rate', true );
}
return floatval( $rate );
}
/**
* Retrieve the address associated with the order.
*
* @since 3.0
*
* @return \EDD\Orders\Order_Address|false Object if successful, false otherwise.
*/
public function get_address() {
// Attempt to get the order address.
$address = edd_get_order_address_by( 'order_id', $this->id );
// Fallback object if not found.
if ( empty( $address ) ) {
$address = (object) array(
'id' => 0,
'order_id' => 0,
'first_name' => '',
'last_name' => '',
'address' => '',
'address2' => '',
'city' => '',
'region' => '',
'postal_code' => '',
'country' => '',
);
}
// Return address (from DB or fallback).
return $address;
}
/**
* Retrieve whether or not unlimited downloads have been enabled on this order.
*
* @since 3.0
*
* @return bool True if unlimited downloads are enabled, false otherwise.
*/
public function has_unlimited_downloads() {
return (bool) edd_get_order_meta( $this->id, 'unlimited_downloads', true );
}
/**
* Retrieve all the notes for this order.
*
* @since 3.0
*
* @return array Notes associated with this order.
*/
public function get_notes() {
return edd_get_notes( array(
'object_id' => $this->id,
'object_type' => 'order',
'order' => 'ASC',
) );
}
/**
* Check if an order is complete.
*
* @since 3.0
*
* @return bool True if the order is complete, false otherwise.
*/
public function is_complete() {
return ( 'complete' === $this->status );
}
/**
* Determines if this order is able to be resumed by the user.
*
* @since 3.0
*
* @return bool
*/
public function is_recoverable() {
$recoverable_statuses = edd_recoverable_order_statuses();
if ( in_array( $this->status, $recoverable_statuses, true ) && empty( $this->get_transaction_id() ) ) {
return true;
}
return false;
}
/**
* Returns the URL that a customer can use to resume an order, or false if it's not recoverable.
*
* @since 3.0
*
* @return bool|string
*/
public function get_recovery_url() {
if ( ! $this->is_recoverable() ) {
return false;
}
$recovery_url = add_query_arg(
array(
'edd_action' => 'recover_payment',
'payment_id' => urlencode( $this->id ),
),
edd_get_checkout_uri()
);
/**
* Legacy recovery URL filter.
*
* @param \EDD_Payment $payment The EDD payment object.
*/
if ( has_filter( 'edd_payment_recovery_url' ) ) {
$recovery_url = apply_filters( 'edd_payment_recovery_url', $recovery_url, edd_get_payment( $this->id ) );
}
/**
* The order recovery URL.
*
* @since 3.0
* @param string $recovery_url The order recovery URL.
* @param \EDD\Orders\Order $this The order object.
*/
return apply_filters( 'edd_order_recovery_url', $recovery_url, $this );
}
}

View File

@ -0,0 +1,511 @@
<?php
/**
* Refund Validator
*
* @package easy-digital-downloads
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 3.0
*/
namespace EDD\Orders;
use EDD\Utils\Exception;
use EDD\Utils\Exceptions\Invalid_Argument;
class Refund_Validator {
/**
* Original order being refunded.
*
* @var Order
*/
protected $order;
/**
* All fees and credits associated with the original order. Includes both order-level and order-item-level.
*
* @var Order_Adjustment[]
*/
protected $order_adjustments;
/**
* Array of order item IDs and amounts to refund. If empty, then all items will be refunded.
*
* @var array
*/
protected $order_items_to_refund;
/**
* Array of adjustment IDs and amounts to refund. If empty, then no adjustments are refunded.
*
* @var array
*/
protected $adjustments_to_refund;
/**
* Final subtotal for the refund. Includes all selected order items and adjustments.
*
* @var float
*/
public $subtotal = 0.00;
/**
* Final tax amount to refund. Includes all selected order items and adjustments.
*
* @var float
*/
public $tax = 0.00;
/**
* Final total for the refund (subtotal + tax). Includes all selected order items and adjustments.
*
* @var float
*/
public $total = 0.00;
/**
* Refund_Validator constructor.
*
* @param Order $order
* @param array|string $order_items
* @param array|string $adjustments
*
* @throws \Exception
*/
public function __construct( Order $order, $order_items = 'all', $adjustments = 'all' ) {
$this->order = $order;
$this->order_adjustments = $this->get_order_adjustments();
$this->order_items_to_refund = $this->validate_and_format_order_items( $order_items );
$this->adjustments_to_refund = $this->validate_and_format_adjustments( $adjustments );
}
/**
* Returns all refund-eligible adjustments associated with the order.
* Note that this doesn't exclude items that have already reached their refund max; it just
* returns all objects that could possibly be refunded. (Essentially `discount` adjustments
* are excluded.)
*
* @since 3.0
* @return Order_Adjustment[]
*/
private function get_order_adjustments() {
$fees = $this->order->get_fees();
$credits = edd_get_order_adjustments( array(
'object_id' => $this->order->id,
'object_type' => 'order',
'type' => 'credit'
) );
return array_merge( $fees, $credits );
}
/**
* Validates the supplied order items and does a little formatting.
* If `all` is supplied, then all items eligible for refund are included.
*
* @param array|string $order_items
*
* @return array
* @throws Invalid_Argument
*/
private function validate_and_format_order_items( $order_items ) {
$keyed_order_items = array();
if ( 'all' === $order_items ) {
$order_items = $this->get_all_refundable_order_items();
}
if ( ! empty( $order_items ) && is_array( $order_items ) ) {
$order_item_ids = wp_list_pluck( $this->order->items, 'id' );
foreach ( $order_items as $order_item_data ) {
// order_item_id must be supplied and in the list attached to the original order.
if ( empty( $order_item_data['order_item_id'] ) || ! in_array( $order_item_data['order_item_id'], $order_item_ids ) ) {
throw Invalid_Argument::from( 'order_item_id', __METHOD__ );
}
$this->validate_required_fields( $order_item_data, __METHOD__ );
if ( ! isset( $order_item_data['total'] ) ) {
$order_item_data['total'] = $order_item_data['subtotal'] + $order_item_data['tax'];
}
// Set the array key to be the order item ID for easier lookups as we go.
$keyed_order_items[ intval( $order_item_data['order_item_id'] ) ] = $order_item_data;
}
}
return $keyed_order_items;
}
/**
* Returns an array of all order items that can be refunded.
* This is used if `all` is supplied for order items.
*
* @since 3.0
* @return array
*/
private function get_all_refundable_order_items() {
$order_items_to_refund = array();
foreach ( $this->order->items as $item ) {
if ( 'refunded' !== $item->status ) {
$order_items_to_refund[] = array_merge( array(
'order_item_id' => $item->id,
'quantity' => $item->quantity,
), $item->get_refundable_amounts() );
}
}
return $order_items_to_refund;
}
/**
* Validates the supplied adjustments and does a little formatting.
* If `all` is supplied, then all adjustments eligible for refund are included.
*
* @param array|string $adjustments
*
* @return array
* @throws Invalid_Argument
*/
private function validate_and_format_adjustments( $adjustments ) {
$keyed_adjustments = array();
if ( 'all' === $adjustments ) {
$adjustments = $this->get_all_refundable_adjustments();
}
if ( ! empty( $adjustments ) && is_array( $adjustments ) ) {
$adjustment_ids = wp_list_pluck( $this->order_adjustments, 'id' );
foreach ( $adjustments as $adjustment_data ) {
// adjustment_id must be supplied and in the list attached to the original order/items.
if ( empty( $adjustment_data['adjustment_id'] ) || ! in_array( $adjustment_data['adjustment_id'], $adjustment_ids ) ) {
throw Invalid_Argument::from( 'adjustment_id', __METHOD__ );
}
$this->validate_required_fields( $adjustment_data, __METHOD__ );
if ( ! isset( $adjustment_data['total'] ) ) {
$adjustment_data['total'] = $adjustment_data['subtotal'] + $adjustment_data['tax'];
}
// Set the array key to be the adjustment ID for easier lookups as we go.
$keyed_adjustments[ intval( $adjustment_data['adjustment_id'] ) ] = $adjustment_data;
}
}
return $keyed_adjustments;
}
/**
* Returns an array of all adjustments that can be refunded.
* This is used if `all` is supplied for adjustments.
*
* @since 3.0
* @return array
*/
private function get_all_refundable_adjustments() {
$adjustments_to_refund = array();
foreach ( $this->order_adjustments as $adjustment ) {
if ( 'refunded' !== $adjustment->status ) {
$adjustments_to_refund[] = array_merge( array(
'adjustment_id' => $adjustment->id
), $adjustment->get_refundable_amounts() );
}
}
return $adjustments_to_refund;
}
/**
* Validates required fields for both order items and taxes.
*
* @param array $input Input to be validated.
* @param string $context Context, for error message.
*
* @since 3.0
* @throws Invalid_Argument
*/
private function validate_required_fields( $input, $context ) {
// subtotal and total are both required.
$required_fields = array( 'subtotal' );
if ( edd_use_taxes() ) {
$required_fields[] = 'tax';
}
foreach ( $required_fields as $required_field ) {
if ( ! isset( $input[ $required_field ] ) ) {
throw Invalid_Argument::from( $required_field, $context );
}
}
}
/**
* Validates final amounts and calculates refund total.
*
* @throws \Exception
*/
public function validate_and_calculate_totals() {
$this->validate_order_item_amounts();
$this->validate_adjustment_amounts();
// Some items or adjustments have to be selected to refund.
if ( empty( $this->order_items_to_refund ) && empty( $this->adjustments_to_refund ) ) {
throw new Exception(
__( 'No items have been selected to refund.', 'easy-digital-downloads' )
);
}
// Refund amount cannot be 0
if ( $this->total <= 0 ) {
throw new Exception( sprintf(
/* Translators: %s - 0.00 formatted in store currency */
__( 'The refund amount must be greater than %s.', 'easy-digital-downloads' ),
edd_currency_filter( edd_format_amount( 0.00 ) )
) );
}
// Overall refund total cannot be over total refundable amount.
$order_total = edd_get_order_total( $this->order->id );
if ( $this->is_over_refund_amount( $this->total, $order_total ) ) {
throw new Exception( sprintf(
/* Translators: %s - maximum refund amount as formatted currency */
__( 'The maximum refund amount is %s.', 'easy-digital-downloads' ),
edd_currency_filter( edd_format_amount( $order_total ) )
) );
}
}
/**
* Validates the order item amounts.
*
* @throws \Exception
*/
private function validate_order_item_amounts() {
foreach ( $this->order->items as $item ) {
if ( ! array_key_exists( $item->id, $this->order_items_to_refund ) ) {
continue;
}
$amount_to_refund = wp_parse_args( $this->order_items_to_refund[ $item->id ], array(
'subtotal' => $item->subtotal,
'tax' => $item->tax,
'total' => $item->total
) );
$this->order_items_to_refund[ $item->id ]['original_item_status'] = $this->validate_item_and_add_totals( $item, $amount_to_refund );
}
}
/**
* Validates the adjustment amounts.
*
* @throws \Exception
*/
private function validate_adjustment_amounts() {
foreach ( $this->order_adjustments as $adjustment ) {
if ( ! array_key_exists( $adjustment->id, $this->adjustments_to_refund ) ) {
continue;
}
$amount_to_refund = wp_parse_args( $this->adjustments_to_refund[ $adjustment->id ], array(
'subtotal' => $adjustment->subtotal,
'tax' => $adjustment->tax,
'total' => $adjustment->total
) );
$this->adjustments_to_refund[ $adjustment->id ]['original_item_status'] = $this->validate_item_and_add_totals( $adjustment, $amount_to_refund );
}
}
/**
* Validates the amount attempting to be refunded against the total that can be refunded.
*
* The refund amount for each item cannot exceed the original amount minus what's already been refunded.
* Note: quantity is not checked because you might process multiple partial refunds for the same order item.
*
* @param Order_Item|Order_Adjustment $original_item Original item being refunded.
* @param array $amounts_to_refund Amounts *attempting* to be refunded. These will be
* matched against the maximums.
*
* @return string Either `refunded` if this is a complete refund, or `partially_refunded` if it's a partial.
* This should be the new status for the original item.
* @throws Exception
*/
private function validate_item_and_add_totals( $original_item, $amounts_to_refund ) {
$item_status = 'refunded';
$maximum_refundable_amounts = $original_item->get_refundable_amounts();
foreach ( array( 'subtotal', 'tax', 'total' ) as $column_name ) {
// Hopefully this should never happen, but just in case!
if ( ! array_key_exists( $column_name, $maximum_refundable_amounts ) ) {
throw new Exception( sprintf(
/* Translators: %s is the type of amount being refunded (e.g. "subtotal" or "tax"). Not translatable at this time. */
__( 'An unexpected error occurred while validating the maximum %s amount.', 'easy-digital-downloads' ),
$column_name
) );
}
// This is our fallback.
$attempted_amount = isset( $original_item->{$column_name} ) ? $original_item->{$column_name} : 0.00;
$maximum_amount = $maximum_refundable_amounts[ $column_name ];
// Only order items are included in the subtotal.
if ( ! $original_item instanceof Order_Item && 'subtotal' === $column_name ) {
continue;
}
// But grab from specified amounts if available. It should always be available.
if ( isset( $amounts_to_refund[ $column_name ] ) ) {
$attempted_amount = $amounts_to_refund[ $column_name ];
}
if ( $this->is_over_refund_amount( $attempted_amount, $maximum_amount ) ) {
if ( $original_item instanceof Order_Item ) {
$error_message = sprintf(
/*
* Translators:
* %1$s - type of amount being refunded (subtotal, tax, or total);
* %1$s - product name;
* %3$s - maximum amount allowed for refund
*/
__( 'The maximum refund %1$s for the product "%2$s" is %3$s.', 'easy-digital-downloads' ),
$column_name,
$original_item->product_name,
edd_currency_filter( $maximum_refundable_amounts[ $column_name ] )
);
} else {
$error_message = sprintf(
/*
* Translators:
* %1$s - type of amount being refunded (subtotal, tax, or total);
* %1$s - adjustment description;
* %3$s - maximum amount allowed for refund
*/
__( 'The maximum refund %s for the adjustment "%s" is %s.', 'easy-digital-downloads' ),
$column_name,
$original_item->description,
edd_currency_filter( $maximum_refundable_amounts[ $column_name ] )
);
}
throw new Exception( $error_message );
}
if ( 'total' === $column_name && $attempted_amount < $maximum_refundable_amounts['total'] ) {
$item_status = 'partially_refunded';
}
// If this is an adjustment, and it's _credit_, negate the amount because credit _reduces_ the total.
if ( $original_item instanceof Order_Adjustment && 'credit' === $original_item->type ) {
$attempted_amount = edd_negate_amount( $attempted_amount );
}
$this->{$column_name} += $attempted_amount;
}
return $item_status;
}
/**
* Returns an array of order items to refund.
*
* @since 3.0
* @return array
*/
public function get_refunded_order_items() {
$order_items = array();
foreach ( $this->order->items as $item ) {
if ( array_key_exists( $item->id, $this->order_items_to_refund ) ) {
$defaults = $allowed_keys = $item->to_array();
if ( array_key_exists( 'original_item_status', $this->order_items_to_refund[ $item->id ] ) ) {
$allowed_keys['original_item_status'] = $this->order_items_to_refund[ $item->id ]['original_item_status'];
}
$args = array_intersect_key( $this->order_items_to_refund[ $item->id ], $allowed_keys );
$order_items[] = $this->set_common_item_args( wp_parse_args( $args, $defaults ) );
}
}
return $order_items;
}
/**
* Returns an array of all adjustments to refund.
*
* @since 3.0
* @return array
*/
public function get_refunded_adjustments() {
$order_item_adjustments = array();
foreach ( $this->order_adjustments as $adjustment ) {
if ( array_key_exists( $adjustment->id, $this->adjustments_to_refund ) ) {
$defaults = $allowed_keys = $adjustment->to_array();
if ( array_key_exists( 'original_item_status', $this->adjustments_to_refund[ $adjustment->id ] ) ) {
$allowed_keys['original_item_status'] = $this->adjustments_to_refund[ $adjustment->id ]['original_item_status'];
}
$args = array_intersect_key( $this->adjustments_to_refund[ $adjustment->id ], $defaults );
$order_item_adjustments[] = $this->set_common_item_args( wp_parse_args( $args, $defaults ) );
}
}
return $order_item_adjustments;
}
/**
* Sets common arguments for refunded order items and adjustments.
*
* @param array $new_args
*
* @since 3.0
* @return array
*/
private function set_common_item_args( $new_args ) {
// Set the `parent` to the original item ID.
if ( isset( $new_args['id'] ) ) {
$new_args['parent'] = $new_args['id'];
}
// Negate amounts.
if ( array_key_exists( 'quantity', $new_args ) ) {
$new_args['quantity'] = edd_negate_int( $new_args['quantity'] );
}
foreach ( array( 'subtotal', 'tax', 'total' ) as $field_to_negate ) {
if ( array_key_exists( $field_to_negate, $new_args ) ) {
$new_args[ $field_to_negate ] = edd_negate_amount( $new_args[ $field_to_negate ] );
}
}
// Strip out the keys we don't want.
$keys_to_remove = array( 'id', 'order_id', 'discount', 'date_created', 'date_modified', 'uuid' );
$new_args = array_diff_key( $new_args, array_flip( $keys_to_remove ) );
// Status is always `complete`.
$new_args['status'] = 'complete';
return $new_args;
}
/**
* Checks if the attempted refund amount is over the maximum allowed refund amount.
*
* @since 3.0
* @param float $attempted_amount The amount to refund.
* @param float $maximum_amount The maximum amount which can be refunded.
* @return boolean
*/
private function is_over_refund_amount( $attempted_amount, $maximum_amount ) {
return edd_sanitize_amount( $attempted_amount ) > edd_sanitize_amount( $maximum_amount );
}
}

View File

@ -0,0 +1,472 @@
<?php
/**
* Order Action Functions
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
use EDD\Adjustments\Adjustment;
defined( 'ABSPATH' ) || exit;
/**
* Manually add an order.
*
* @since 3.0
*
* @param array $args Order form data.
* @return void
*/
function edd_add_manual_order( $args = array() ) {
// Bail if user cannot manage shop settings or no data was passed.
if ( empty( $args ) || ! current_user_can( 'manage_shop_settings' ) ) {
return;
}
// Set up parameters.
$nonce = isset( $_POST['edd_add_order_nonce'] )
? sanitize_text_field( $_POST['edd_add_order_nonce'] )
: '';
// Bail if nonce fails.
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'edd_add_order_nonce' ) ) {
return;
}
// Get now one time to avoid microsecond issues
$now = EDD()->utils->date( 'now', null, true )->timestamp;
// Parse args.
$order_data = wp_parse_args( $args, array(
'downloads' => array(),
'adjustments' => array(),
'subtotal' => 0.00,
'tax' => 0.00,
'total' => 0.00,
'discount' => 0.00,
'edd-payment-status' => 'complete',
'payment_key' => '',
'gateway' => '',
'transaction_id' => '',
'receipt' => '',
'edd-payment-date' => date( 'Y-m-d', $now ),
'edd-payment-time-hour' => date( 'G', $now ),
'edd-payment-time-min' => date( 'i', $now ),
'edd-unlimited-downloads' => 0,
) );
/** Customer data *********************************************************/
// Defaults.
$customer_id = 0;
$user_id = 0;
$email = '';
$name = '';
// Create a new customer record.
if ( isset( $order_data['edd-new-customer'] ) && 1 === absint( $order_data['edd-new-customer'] ) ) {
// Sanitize first name.
$first_name = isset( $order_data['edd-new-customer-first-name'] )
? sanitize_text_field( $order_data['edd-new-customer-first-name'] )
: '';
// Sanitize last name.
$last_name = isset( $order_data['edd-new-customer-last-name'] )
? sanitize_text_field( $order_data['edd-new-customer-last-name'] )
: '';
// Combine.
$name = trim( $first_name . ' ' . $last_name );
// Sanitize the email address.
$email = isset( $order_data['edd-new-customer-email'] )
? sanitize_email( $order_data['edd-new-customer-email'] )
: '';
$new_customer_args = array(
'name' => $name,
'email' => $email,
);
// Determine if there is an existing user with this email address.
$possible_user = get_user_by( 'email', $email );
if ( $possible_user instanceof WP_User ) {
$new_customer_args['user_id'] = $possible_user->ID;
}
// Save to database.
$customer_id = edd_add_customer(
$new_customer_args
);
$customer = edd_get_customer( $customer_id );
// Existing customer.
} elseif ( isset( $order_data['edd-new-customer'] ) && 0 === absint( $order_data['edd-new-customer'] ) && isset( $order_data['customer-id'] ) ) {
$customer_id = absint( $order_data['customer-id'] );
$customer = edd_get_customer( $customer_id );
if ( $customer ) {
$email = $customer->email;
$user_id = $customer->user_id;
$name = $customer->name;
}
}
/** Insert order **********************************************************/
// Parse order status.
$status = sanitize_text_field( $order_data['edd-payment-status'] );
if ( empty( $status ) || ! in_array( $status, array_keys( edd_get_payment_statuses() ), true ) ) {
$status = 'complete';
}
// Get the date string.
$date_string = EDD()->utils->get_date_string(
sanitize_text_field( $order_data['edd-payment-date'] ),
sanitize_text_field( $order_data['edd-payment-time-hour'] ),
sanitize_text_field( $order_data['edd-payment-time-min'] )
);
// The date is entered in the WP timezone. We need to convert it to UTC prior to saving now.
$date = edd_get_utc_equivalent_date( EDD()->utils->date( $date_string, edd_get_timezone_id(), false ) );
$date = $date->format( 'Y-m-d H:i:s' );
// Get mode
$mode = edd_is_test_mode()
? 'test'
: 'live';
// Amounts
$order_subtotal = floatval( $order_data['subtotal'] );
$order_tax = floatval( $order_data['tax'] );
$order_discount = floatval( $order_data['discount'] );
$order_total = floatval( $order_data['total'] );
$tax_rate = false;
// If taxes are enabled, get the tax rate for the order location.
if ( edd_use_taxes() ) {
$country = ! empty( $order_data['edd_order_address']['country'] )
? $order_data['edd_order_address']['country']
: false;
$region = ! empty( $order_data['edd_order_address']['region'] )
? $order_data['edd_order_address']['region']
: false;
$tax_rate = edd_get_tax_rate_by_location(
array(
'country' => $country,
'region' => $region,
)
);
}
// Add the order ID
$order_id = edd_add_order(
array(
'status' => 'pending', // Always insert as pending initially.
'user_id' => $user_id,
'customer_id' => $customer_id,
'email' => $email,
'ip' => sanitize_text_field( $order_data['ip'] ),
'gateway' => sanitize_text_field( $order_data['gateway'] ),
'mode' => $mode,
'currency' => edd_get_currency(),
'payment_key' => $order_data['payment_key'] ? sanitize_text_field( $order_data['payment_key'] ) : edd_generate_order_payment_key( $email ),
'tax_rate_id' => ! empty( $tax_rate->id ) ? $tax_rate->id : null,
'subtotal' => $order_subtotal,
'tax' => $order_tax,
'discount' => $order_discount,
'total' => $order_total,
'date_created' => $date,
)
);
// Attach order to the customer record.
if ( ! empty( $customer ) ) {
$customer->attach_payment( $order_id, false );
}
// If we have tax, but no tax rate, manually save the percentage.
if ( empty( $tax_rate->id ) && $order_tax > 0 ) {
$tax_rate_percentage = $order_data['tax_rate'];
if ( ! empty( $tax_rate_percentage ) ) {
if ( $tax_rate_percentage > 0 && $tax_rate_percentage < 1 ) {
$tax_rate_percentage = $tax_rate_percentage * 100;
}
edd_update_order_meta( $order_id, 'tax_rate', $tax_rate_percentage );
}
}
/** Insert order address **************************************************/
if ( isset( $order_data['edd_order_address'] ) ) {
// Parse args
$address = wp_parse_args( $order_data['edd_order_address'], array(
'name' => $name,
'address' => '',
'address2' => '',
'city' => '',
'postal_code' => '',
'country' => '',
'region' => '',
) );
$order_address_data = $address;
$order_address_data['order_id'] = $order_id;
// Remove empty data.
$order_address_data = array_filter( $order_address_data );
// Add to edd_order_addresses table.
edd_add_order_address( $order_address_data );
// Maybe add the address to the edd_customer_addresses.
$customer_address_data = $order_address_data;
// We don't need to pass this data to edd_maybe_add_customer_address().
unset( $customer_address_data['order_id'] );
edd_maybe_add_customer_address( $customer->id, $customer_address_data );
}
/** Insert order items ****************************************************/
// Any adjustments specific to an order item need to be added to the item.
foreach ( $order_data['adjustments'] as $key => $adjustment ) {
if ( 'order_item' === $adjustment['object_type'] ) {
$order_data['downloads'][ $adjustment['object_id'] ]['adjustments'][] = $adjustment;
unset( $order_data['adjustments'][ $key ] );
}
}
if ( ! empty( $order_data['downloads'] ) ) {
// Re-index downloads.
$order_data['downloads'] = array_values( $order_data['downloads'] );
$downloads = array_reverse( $order_data['downloads'] );
foreach ( $downloads as $cart_key => $download ) {
$d = edd_get_download( absint( $download['id'] ) );
// Skip if download no longer exists
if ( empty( $d ) ) {
continue;
}
// Quantity.
$quantity = isset( $download['quantity'] )
? absint( $download['quantity'] )
: 1;
// Price ID.
$price_id = isset( $download['price_id'] ) && is_numeric( $download['price_id'] )
? absint( $download['price_id'] )
: null;
// Amounts.
$amount = isset( $download[ 'amount' ] )
? floatval( $download[ 'amount' ] )
: 0.00;
$subtotal = isset( $download[ 'subtotal' ] )
? floatval( $download[ 'subtotal' ] )
: 0.00;
$discount = isset( $download[ 'discount' ] )
? floatval( $download[ 'discount' ] )
: 0.00;
$tax = isset( $download[ 'tax' ] )
? floatval( $download[ 'tax' ] )
: 0.00;
$total = isset( $download[ 'total' ] )
? floatval( $download[ 'total' ] )
: 0.00;
// Add to edd_order_items table.
$order_item_id = edd_add_order_item( array(
'order_id' => $order_id,
'product_id' => absint( $download['id'] ),
'product_name' => edd_get_download_name( $download['id'], absint( $price_id ) ),
'price_id' => $price_id,
'cart_index' => $cart_key,
'type' => 'download',
'status' => 'complete',
'quantity' => $quantity,
'amount' => $amount,
'subtotal' => $subtotal,
'discount' => $discount,
'tax' => $tax,
'total' => $total,
) );
if ( false !== $order_item_id ) {
if ( isset( $download['adjustments'] ) ) {
$order_item_adjustments = array_reverse( $download['adjustments'] );
foreach ( $order_item_adjustments as $index => $order_item_adjustment ) {
// Discounts are not tracked at the Order Item level.
if ( 'discount' === $order_item_adjustment['type'] ) {
continue;
}
$type_key = ! empty( $order_item_adjustment['description'] )
? sanitize_text_field( strtolower( sanitize_title( $order_item_adjustment['description'] ) ) )
: $index;
$order_item_adjustment_subtotal = floatval( $order_item_adjustment['subtotal'] );
$order_item_adjustment_tax = floatval( $order_item_adjustment['tax'] );
$order_item_adjustment_total = floatval( $order_item_adjustment['total'] );
edd_add_order_adjustment( array(
'object_id' => $order_item_id,
'object_type' => 'order_item',
'type' => sanitize_text_field( $order_item_adjustment['type'] ),
'type_key' => $type_key,
'description' => sanitize_text_field( $order_item_adjustment['description'] ),
'subtotal' => $order_item_adjustment_subtotal,
'tax' => $order_item_adjustment_tax,
'total' => $order_item_adjustment_total,
) );
}
}
}
}
}
/** Insert adjustments ****************************************************/
// Adjustments.
if ( isset( $order_data['adjustments'] ) ) {
$adjustments = array_reverse( $order_data['adjustments'] );
foreach ( $adjustments as $index => $adjustment ) {
if ( 'order_item' === $adjustment['object_type'] ) {
continue;
}
$type_key = ! empty( $adjustment['description'] )
? sanitize_text_field( strtolower( sanitize_title( $adjustment['description'] ) ) )
: $index;
$adjustment_subtotal = floatval( $adjustment['subtotal'] );
$adjustment_tax = floatval( $adjustment['tax'] );
$adjustment_total = floatval( $adjustment['total'] );
edd_add_order_adjustment( array(
'object_id' => $order_id,
'object_type' => 'order',
'type' => sanitize_text_field( $adjustment['type'] ),
'type_key' => $type_key,
'description' => sanitize_text_field( $adjustment['description'] ),
'subtotal' => $adjustment_subtotal,
'tax' => $adjustment_tax,
'total' => $adjustment_total,
) );
}
}
// Discounts.
if ( isset( $order_data['discounts'] ) ) {
$discounts = array_reverse( $order_data['discounts'] );
foreach ( $discounts as $discount ) {
$d = edd_get_discount( absint( $discount['type_id'] ) );
if ( empty( $d ) ) {
continue;
}
$discount_subtotal = floatval( $discount['subtotal'] );
$discount_total = floatval( $discount['total'] );
// Store discount.
edd_add_order_adjustment( array(
'object_id' => $order_id,
'object_type' => 'order',
'type_id' => intval( $discount['type_id'] ),
'type' => 'discount',
'description' => sanitize_text_field( $discount['code'] ),
'subtotal' => $discount_subtotal,
'total' => $discount_total,
) );
}
}
// Insert transaction ID.
if ( ! empty( $order_data['transaction_id'] ) ) {
edd_add_order_transaction( array(
'object_id' => $order_id,
'object_type' => 'order',
'transaction_id' => sanitize_text_field( $order_data['transaction_id'] ),
'gateway' => sanitize_text_field( $order_data['gateway'] ),
'status' => 'complete',
'total' => $order_total,
) );
}
// Unlimited downloads.
if ( isset( $order_data['edd-unlimited-downloads'] ) && 1 === (int) $order_data['edd-unlimited-downloads'] ) {
edd_update_order_meta( $order_id, 'unlimited_downloads', 1 );
}
// Setup order number.
$order_number = '';
if ( edd_get_option( 'enable_sequential' ) ) {
$number = edd_get_next_payment_number();
$order_number = edd_format_payment_number( $number );
update_option( 'edd_last_payment_number', $number );
// Update totals & maybe add order number.
edd_update_order( $order_id, array(
'order_number' => $order_number,
) );
}
// Stop purchase receipt from being sent.
if ( ! isset( $order_data['edd_order_send_receipt'] ) ) {
remove_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999 );
}
// Trigger edd_complete_purchase.
if ( 'complete' === $status ) {
edd_update_order_status( $order_id, $status );
}
/**
* Action hook which runs after a manual order has been added to the database.
*
* @since 3.0
* @param int $order_id The new order ID.
* @param array $order_data The array of order data.
* @param array $args The original form data.
*/
do_action( 'edd_post_add_manual_order', $order_id, $order_data, $args );
// Redirect to `Edit Order` page.
edd_redirect( edd_get_admin_url( array(
'page' => 'edd-payment-history',
'view' => 'view-order-details',
'id' => urlencode( $order_id ),
'edd-message' => 'order_added',
) ) );
}
add_action( 'edd_add_order', 'edd_add_manual_order' );

View File

@ -0,0 +1,203 @@
<?php
/**
* Order Address Functions.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Add an order address.
*
* @since 3.0
*
* @param array $data {
* Array of order address data. Default empty.
*
* The `date_created` and `date_modified` parameters do not need to be passed.
* They will be automatically populated if empty.
*
* @type int $order_id Order ID. Default `0`.
* @type string $name Customer's full name. Default empty.
* @type string $address First line of address. Default empty.
* @type string $address2 Second line of address. Default empty.
* @type string $city City. Default empty.
* @type string $region Region. See `edd_get_shop_states()` for
* accepted values. Default empty.
* @type string $postal_code Postal code. Default empty.
* @type string $country Country. See `edd_get_country_list()` for
* accepted values. Default empty.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the address was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the address was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
* @return int|false ID of newly created order address, false on error.
*/
function edd_add_order_address( $data ) {
// An order ID must be supplied for every address inserted.
if ( empty( $data['order_id'] ) ) {
return false;
}
// Set up an array with empty address keys. If all of these are empty in $data, the address should not be added.
$empty_address = array(
'address' => '',
'address2' => '',
'city' => '',
'region' => '',
'country' => '',
'postal_code' => '',
);
$address_to_check = array_intersect_key( $data, $empty_address );
$address_to_check = array_filter( $address_to_check );
if ( empty( $address_to_check ) ) {
return false;
}
// Instantiate a query object
$order_addresses = new EDD\Database\Queries\Order_Address();
return $order_addresses->add_item( $data );
}
/**
* Delete an order address.
*
* @since 3.0
*
* @param int $order_address_id Order address ID.
* @return int|false `1` if the address was deleted successfully, false on error.
*/
function edd_delete_order_address( $order_address_id = 0 ) {
$order_addresses = new EDD\Database\Queries\Order_Address();
return $order_addresses->delete_item( $order_address_id );
}
/**
* Update an order address.
*
* @since 3.0
*
* @param int $order_address_id Order address ID.
* @param array $data {
* Array of order address data. Default empty.
*
* @type int $order_id Order ID. Default `0`.
* @type string $name Customer's full name. Default empty.
* @type string $address First line of address. Default empty.
* @type string $address2 Second line of address. Default empty.
* @type string $city City. Default empty.
* @type string $region Region. See `edd_get_shop_states()` for
* accepted values. Default empty.
* @type string $postal_code Postal code. Default empty.
* @type string $country Country. See `edd_get_country_list()` for
* accepted values. Default empty.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the address was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the address was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
*
* @return bool Whether or not the API request order was updated.
*/
function edd_update_order_address( $order_address_id = 0, $data = array() ) {
$order_addresses = new EDD\Database\Queries\Order_Address();
return $order_addresses->update_item( $order_address_id, $data );
}
/**
* Get an order address by ID.
*
* @since 3.0
*
* @param int $order_address_id Order address ID.
* @return \EDD\Orders\Order_Address|false Order_Address if successful, false
* otherwise.
*/
function edd_get_order_address( $order_address_id = 0 ) {
$order_addresses = new EDD\Database\Queries\Order_Address();
// Return order address
return $order_addresses->get_item( $order_address_id );
}
/**
* Get an order address by a specific field value.
*
* @since 3.0
*
* @param string $field Database table field.
* @param string $value Value of the row.
*
* @return \EDD\Orders\Order_Address|false Order_Address if successful, false otherwise.
*/
function edd_get_order_address_by( $field = '', $value = '' ) {
$order_addresses = new EDD\Database\Queries\Order_Address();
// Return order address
return $order_addresses->get_item_by( $field, $value );
}
/**
* Query for order addresses.
*
* @see \EDD\Database\Queries\Order_Address::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Address` for
* accepted arguments.
* @return \EDD\Orders\Order_Address[] Array of `Order_Address` objects.
*/
function edd_get_order_addresses( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'number' => 30,
) );
// Instantiate a query object
$order_addresses = new EDD\Database\Queries\Order_Address();
// Return orders
return $order_addresses->query( $r );
}
/**
* Count order addresses.
*
* @see \EDD\Database\Queries\Order_Address::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Address` for
* accepted arguments.
* @return int Number of order addresses returned based on query arguments passed.
*/
function edd_count_order_addresses( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'count' => true,
) );
// Query for count(s)
$order_addresses = new EDD\Database\Queries\Order_Address( $r );
// Return count(s)
return absint( $order_addresses->found_items );
}

View File

@ -0,0 +1,264 @@
<?php
/**
* Order Adjustment Functions.
*
* @package EDD
* @subpackage Orders\Adjustments
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Add an order adjustment.
*
* @since 3.0
*
* @param array $data {
* Array of order adjustment data. Default empty.
*
* The `date_created` and `date_modified` parameters do not need to be passed.
* They will be automatically populated if empty.
*
* @type int $parent Parent ID. Only used when creating refunds to link
* a refund order adjustment to the original order adjustment.
* Default 0.
* @type int $object_id Object ID that the adjustment refers to. This would
* be an ID that corresponds to the object type
* specified. E.g. an object ID of 25 with object
* type of `order` refers to order 25 in the
* `edd_orders` table. Default empty.
* @type string $object_type Object type that the adjustment refers to.
* E.g. `order` or `order_item`. Default empty.
* @type int $type_id Object ID of the adjustment type. E.g. a type
* ID of 25 with type of `discount` refers to
* discount 25 in te `edd_discounts` table.
* Default empty.
* @type string $type Object type of the adjustment type. E.g. `discount`.
* Default empty.
* @type string $description Description. Default empty.
* @type float $subtotal Subtotal. Default 0.
* @type float $tax Tax applicable. Default 0.
* @type float $total Adjustment total. Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the adjustment was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the adjustment was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
* @return int|false ID of newly created order adjustment, false on error.
*/
function edd_add_order_adjustment( $data ) {
// An object ID and object ID must be supplied for every order that is
// inserted into the database.
if ( empty( $data['object_id'] ) || empty( $data['object_type'] ) ) {
return false;
}
// Instantiate a query object
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
$order_adjustment_id = $order_adjustments->add_item( $data );
if ( ! empty( $order_adjustment_id ) ) {
/**
* Action that runs when an order item is successfully added.
*
* @since 3.1
* @param int Order adjustment ID.
* @param array Array of order adjustment data.
*/
do_action( 'edd_order_adjustment_added', $order_adjustment_id, $data );
}
return $order_adjustment_id;
}
/**
* Delete an order adjustment.
*
* @since 3.0
*
* @param int $order_adjustment_id Order adjustment ID.
* @return int|false `1` if the adjustment was deleted successfully, false on error.
*/
function edd_delete_order_adjustment( $order_adjustment_id = 0 ) {
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
return $order_adjustments->delete_item( $order_adjustment_id );
}
/**
* Update an order adjustment.
*
* @since 3.0
*
* @param int $order_adjustment_id Order adjustment ID.
* @param array $data {
* Array of order adjustment data. Default empty.
*
* @type int $object_id Object ID that the adjustment refers to. This would
* be an ID that corresponds to the object type
* specified. E.g. an object ID of 25 with object
* type of `order` refers to order 25 in the
* `edd_orders` table. Default empty.
* @type string $object_type Object type that the adjustment refers to.
* E.g. `order` or `order_item`. Default empty.
* @type int $type_id Object ID of the adjustment type. E.g. a type
* ID of 25 with type of `discount` refers to
* discount 25 in te `edd_discounts` table.
* Default empty.
* @type string $type Object type of the adjustment type. E.g. `discount`.
* Default empty.
* @type string $description Description. Default empty.
* @type float $subtotal Subtotal. Default 0.
* @type float $tax Tax applicable. Default 0.
* @type float $total Adjustment total. Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the adjustment was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the adjustment was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
*
* @return int|false Number of rows updated if successful, false otherwise.
*/
function edd_update_order_adjustment( $order_adjustment_id = 0, $data = array() ) {
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
$previous_adjustment = edd_get_order_adjustment( $order_adjustment_id );
$order_adjustment_updated = $order_adjustments->update_item( $order_adjustment_id, $data );
if ( ! empty( $order_adjustment_updated ) ) {
/**
* Action that runs when an order item is updated.
*
* @since 3.1
* @param int The order adjustment ID.
* @param array The array of data to update.
* @param EDD\Orders\Order_Adjustment The original order adjustment object.
*/
do_action( 'edd_order_adjustment_updated', $order_adjustment_id, $data, $previous_adjustment );
}
return $order_adjustment_updated;
}
/**
* Get an order adjustment by ID.
*
* @since 3.0
*
* @param int $order_adjustment_id Order adjustment ID.
* @return EDD\Orders\Order_Adjustment|false Order_Adjustment object if successful,
* false otherwise.
*/
function edd_get_order_adjustment( $order_adjustment_id = 0 ) {
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
// Return order adjustment
return $order_adjustments->get_item( $order_adjustment_id );
}
/**
* Get an order adjustment by a specific field value.
*
* @since 3.0
*
* @param string $field Database table field.
* @param string $value Value of the row.
*
* @return EDD\Orders\Order_Adjustment|false Order_Adjustment object if successful,
* false otherwise.
*/
function edd_get_order_adjustment_by( $field = '', $value = '' ) {
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
// Return order adjustment
return $order_adjustments->get_item_by( $field, $value );
}
/**
* Query for order adjustments.
*
* @see \EDD\Database\Queries\Order_Adjustment::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Adjustment` for
* accepted arguments.
* @return \EDD\Orders\Order_Adjustment[] Array of `Order_Adjustment` objects.
*/
function edd_get_order_adjustments( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'number' => 30,
) );
// Instantiate a query object
$order_adjustments = new EDD\Database\Queries\Order_Adjustment();
// Return orders
return $order_adjustments->query( $r );
}
/**
* Count order adjustments.
*
* @see \EDD\Database\Queries\Order_Adjustment::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Adjustment` for
* accepted arguments.
* @return int Number of order adjustments returned based on query arguments passed.
*/
function edd_count_order_adjustments( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'count' => true,
) );
// Query for count(s)
$order_adjustments = new EDD\Database\Queries\Order_Adjustment( $r );
// Return count(s)
return absint( $order_adjustments->found_items );
}
/**
* Query for and return array of order adjustment counts, keyed by status.
*
* @see \EDD\Database\Queries\Order_Adjustment::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Adjustment` for
* accepted arguments.
*
* @return array Order adjustment counts keyed by status.
*/
function edd_get_order_adjustment_counts( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'object_id' => 0,
'object_type' => 'order',
'count' => true,
'groupby' => 'type',
) );
// Query for count
$counts = new EDD\Database\Queries\Order_Adjustment( $r );
// Format & return
return edd_format_counts( $counts, $r['groupby'] );
}

View File

@ -0,0 +1,273 @@
<?php
/**
* Order Item Functions.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Add an order item.
*
* @since 3.0
*
* @param array $data {
* Array of order item data. Default empty.
*
* The `date_created` and `date_modified` parameters do not need to be passed.
* They will be automatically populated if empty.
*
* @type int $parent Parent ID. Only used when creating refunds to link
* a refund order item to the original order item.
* Default 0.
* @type int $order_id Order ID. Default 0.
* @type int $product_id Product ID of the order item purchased. ID
* refers to the download in the `wp_posts` table.
* Default 0.
* @type string $product_name Name of the order item. Default empty.
* @type int|null $price_id ID of the price option purchased. Default null (no price ID).
* @type int $cart_index Position of the order item in the cart.
* Default 0.
* @type string $type Order item type. Default `download`.
* @type string $status Status of the order item. Default `pending`.
* @type int $quantity Quantity purchased of the order item. Default 0.
* @type float $amount Amount for the order item. Default 0.
* @type float $subtotal Subtotal of the order item. Default 0.
* @type float $discount Discount applied to the order item. Default 0.
* @type float $tax Tax applied to the order item. Default 0.
* @type float $total Item total. Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the order item was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the order item was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
* @return int|false ID of newly created order, false on error.
*/
function edd_add_order_item( $data = array() ) {
// An order ID and product ID must be supplied for every order that is
// inserted into the database.
if ( empty( $data['order_id'] ) || empty( $data['product_id'] ) ) {
return false;
}
// Instantiate a query object
$order_items = new EDD\Database\Queries\Order_Item();
$order_item_id = $order_items->add_item( $data );
if ( ! empty( $order_item_id ) ) {
/**
* Action that runs when an order item is successfully added.
*
* @since 3.1
* @param int Order item ID.
* @param array Array of order item data.
*/
do_action( 'edd_order_item_added', $order_item_id, $data );
}
return $order_item_id;
}
/**
* Delete an order item.
*
* @since 3.0
*
* @param int $order_item_id Order item ID.
* @return int|false `1` if the item was deleted successfully, false on error.
*/
function edd_delete_order_item( $order_item_id = 0 ) {
$order_items = new EDD\Database\Queries\Order_Item();
$order_item_deleted = $order_items->delete_item( $order_item_id );
if ( ! empty( $order_item_deleted ) ) {
/**
* Action that runs when an order item is deleted.
*
* @since 3.0.5
*
* @param int $order_item_id Order item ID being deleted.
*/
do_action( 'edd_order_item_deleted', $order_item_id );
}
return $order_item_deleted;
}
/**
* Update an order item.
*
* @since 3.0
*
* @param int $order_item_id Order item ID.
* @param array $data {
* Array of order item data. Default empty.
*
* @type int $order_id Order ID. Default 0.
* @type int $product_id Product ID of the order item purchased. ID
* refers to the download in the `wp_posts` table.
* Default 0.
* @type string $product_name Name of the order item. Default empty.
* @type int $price_id ID of the price option purchased. Default 0.
* @type int $cart_index Position of the order item in the cart.
* Default 0.
* @type string $type Order item type. Default `download`.
* @type string $status Status of the order item. Default `inherit`.
* @type int $quantity Quantity purchased of the order item. Default 0.
* @type float $amount Amount for the order item. Default 0.
* @type float $subtotal Subtotal of the order item. Default 0.
* @type float $discount Discount applied to the order item. Default 0.
* @type float $tax Tax applied to the order item. Default 0.
* @type float $total Item total. Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the order item was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the order item was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
*
* @return int|false Number of rows updated if successful, false otherwise.
*/
function edd_update_order_item( $order_item_id = 0, $data = array() ) {
$order_items = new EDD\Database\Queries\Order_Item();
$previous_order_item_data = edd_get_order_item( $order_item_id );
$order_item_updated = $order_items->update_item( $order_item_id, $data );
if ( ! empty( $order_item_updated ) ) {
/**
* Action that runs when an order item is updated.
*
* @since 3.1
* @param int The order item ID.
* @param array The array of data to update.
* @param EDD\Orders\Order_Item The original order item object.
*/
do_action( 'edd_order_item_updated', $order_item_id, $data, $previous_order_item_data );
}
return $order_item_updated;
}
/**
* Get an order item by ID.
*
* @since 3.0
*
* @param int $order_item_id Order item ID.
* @return EDD\Orders\Order_Item|false Order_Item object if successful, false
* otherwise.
*/
function edd_get_order_item( $order_item_id = 0 ) {
$order_items = new EDD\Database\Queries\Order_Item();
// Return order item
return $order_items->get_item( $order_item_id );
}
/**
* Get an order item by field and value.
*
* @since 3.0
*
* @param string $field Database table field.
* @param string $value Value of the row.
*
* @return EDD\Orders\Order_Item|false Order_Item object if successful, false
* otherwise.
*/
function edd_get_order_item_by( $field = '', $value = '' ) {
$order_items = new EDD\Database\Queries\Order_Item();
// Return order item
return $order_items->get_item_by( $field, $value );
}
/**
* Query for order items.
*
* @see \EDD\Database\Queries\Order_Item::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Item` for
* accepted arguments.
* @return \EDD\Orders\Order_Item[] Array of `Order_Item` objects.
*/
function edd_get_order_items( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'number' => 30,
) );
// Instantiate a query object
$order_items = new EDD\Database\Queries\Order_Item();
// Return items
return $order_items->query( $r );
}
/**
* Count order items.
*
* @see \EDD\Database\Queries\Order_Item::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Item` for
* accepted arguments.
* @return int Number of order items returned based on query arguments passed.
*/
function edd_count_order_items( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'count' => true,
) );
// Query for count(s)
$order_items = new EDD\Database\Queries\Order_Item( $r );
// Return count(s)
return absint( $order_items->found_items );
}
/**
* Query for and return array of order item counts, keyed by status.
*
* @see \EDD\Database\Queries\Order_Item::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Item` for
* accepted arguments.
* @return array Order items keyed by status.
*/
function edd_get_order_item_counts( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'order_id' => 0,
'count' => true,
'groupby' => 'status',
) );
// Query for count
$counts = new EDD\Database\Queries\Order_Item( $r );
// Format & return
return edd_format_counts( $counts, $r['groupby'] );
}

View File

@ -0,0 +1,271 @@
<?php
/**
* Order Meta Functions
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/** Orders ********************************************************************/
/**
* Add meta data field to an order.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added. Default false.
*
* @return int|false Meta ID on success, false on failure.
*/
function edd_add_order_meta( $order_id, $meta_key, $meta_value, $unique = false ) {
return add_metadata( 'edd_order', $order_id, $meta_key, $meta_value, $unique );
}
/**
* Remove meta data matching criteria from an order.
*
* You can match based on the key, or key and value. Removing based on key and value, will keep from removing duplicate
* meta data with the same key. It also allows removing all meta data matching key, if needed.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Optional. Meta data value. Must be serializable if non-scalar. Default empty.
*
* @return bool True on success, false on failure.
*/
function edd_delete_order_meta( $order_id, $meta_key, $meta_value = '' ) {
return delete_metadata( 'edd_order', $order_id, $meta_key, $meta_value );
}
/**
* Retrieve order meta field for an order.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. Default empty.
* @param bool $single Optional, default is false. If true, return only the first value of the specified meta_key.
* This parameter has no effect if meta_key is not specified.
*
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true.
*/
function edd_get_order_meta( $order_id, $key = '', $single = false ) {
return get_metadata( 'edd_order', $order_id, $key, $single );
}
/**
* Update order meta field based on order ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and order ID.
*
* If the meta field for the order does not exist, it will be added.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @param string $meta_key Meta data key.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before removing. Default empty.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function edd_update_order_meta( $order_id, $meta_key, $meta_value, $prev_value = '' ) {
return update_metadata( 'edd_order', $order_id, $meta_key, $meta_value, $prev_value );
}
/**
* Delete everything from order meta matching meta key.
*
* @since 3.0
*
* @param string $meta_key Key to search for when deleting.
*
* @return bool Whether the order meta key was deleted from the database.
*/
function edd_delete_order_meta_by_key( $meta_key ) {
return delete_metadata( 'edd_order', null, $meta_key, '', true );
}
/** Order Items ***************************************************************/
/**
* Add meta data field to an order item.
*
* @since 3.0
*
* @param int $order_item_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added. Default false.
*
* @return int|false Meta ID on success, false on failure.
*/
function edd_add_order_item_meta( $order_item_id, $meta_key, $meta_value, $unique = false ) {
return add_metadata( 'edd_order_item', $order_item_id, $meta_key, $meta_value, $unique );
}
/**
* Remove meta data matching criteria from an order item.
*
* You can match based on the key, or key and value. Removing based on key and value, will keep from removing duplicate
* meta data with the same key. It also allows removing all meta data matching key, if needed.
*
* @since 3.0
*
* @param int $order_item_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Optional. Meta data value. Must be serializable if non-scalar. Default empty.
*
* @return bool True on success, false on failure.
*/
function edd_delete_order_item_meta( $order_item_id, $meta_key, $meta_value = '' ) {
return delete_metadata( 'edd_order_item', $order_item_id, $meta_key, $meta_value );
}
/**
* Retrieve order_item meta field for an order item.
*
* @since 3.0
*
* @param int $order_item_id Order ID.
* @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. Default empty.
* @param bool $single Optional, default is false. If true, return only the first value of the specified meta_key.
* This parameter has no effect if meta_key is not specified.
*
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true.
*/
function edd_get_order_item_meta( $order_item_id, $key = '', $single = false ) {
return get_metadata( 'edd_order_item', $order_item_id, $key, $single );
}
/**
* Update order_item meta field based on order_item ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and order_item ID.
*
* If the meta field for the order_item does not exist, it will be added.
*
* @since 3.0
*
* @param int $order_item_id Order Item ID.
* @param string $meta_key Meta data key.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before removing. Default empty.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function edd_update_order_item_meta( $order_item_id, $meta_key, $meta_value, $prev_value = '' ) {
return update_metadata( 'edd_order_item', $order_item_id, $meta_key, $meta_value, $prev_value );
}
/**
* Delete everything from order_item meta matching meta key.
*
* @since 3.0
*
* @param string $meta_key Key to search for when deleting.
*
* @return bool Whether the order_item meta key was deleted from the database.
*/
function edd_delete_order_item_meta_by_key( $meta_key ) {
return delete_metadata( 'edd_order_item', null, $meta_key, '', true );
}
/** Order Adjustments *********************************************************/
/**
* Add meta data field to an order item.
*
* @since 3.0
*
* @param int $adjustment_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added. Default false.
*
* @return int|false Meta ID on success, false on failure.
*/
function edd_add_order_adjustment_meta( $adjustment_id, $meta_key, $meta_value, $unique = false ) {
return add_metadata( 'edd_order_adjustment', $adjustment_id, $meta_key, $meta_value, $unique );
}
/**
* Remove meta data matching criteria from an order item.
*
* You can match based on the key, or key and value. Removing based on key and value, will keep from removing duplicate
* meta data with the same key. It also allows removing all meta data matching key, if needed.
*
* @since 3.0
*
* @param int $adjustment_id Order ID.
* @param string $meta_key Meta data name.
* @param mixed $meta_value Optional. Meta data value. Must be serializable if non-scalar. Default empty.
*
* @return bool True on success, false on failure.
*/
function edd_delete_order_adjustment_meta( $adjustment_id, $meta_key, $meta_value = '' ) {
return delete_metadata( 'edd_order_adjustment', $adjustment_id, $meta_key, $meta_value );
}
/**
* Retrieve order_item meta field for an order item.
*
* @since 3.0
*
* @param int $adjustment_id Order ID.
* @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. Default empty.
* @param bool $single Optional, default is false. If true, return only the first value of the specified meta_key.
* This parameter has no effect if meta_key is not specified.
*
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true.
*/
function edd_get_order_adjustment_meta( $adjustment_id, $key = '', $single = false ) {
return get_metadata( 'edd_order_adjustment', $adjustment_id, $key, $single );
}
/**
* Update order_item meta field based on order_item ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and order_item ID.
*
* If the meta field for the order_item does not exist, it will be added.
*
* @since 3.0
*
* @param int $adjustment_id Order Item ID.
* @param string $meta_key Meta data key.
* @param mixed $meta_value Meta data value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before removing. Default empty.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function edd_update_order_adjustment_meta( $adjustment_id, $meta_key, $meta_value, $prev_value = '' ) {
return update_metadata( 'edd_order_adjustment', $adjustment_id, $meta_key, $meta_value, $prev_value );
}
/**
* Delete everything from order_item meta matching meta key.
*
* @since 3.0
*
* @param string $meta_key Key to search for when deleting.
*
* @return bool Whether the order_item meta key was deleted from the database.
*/
function edd_delete_order_adjustment_meta_by_key( $meta_key ) {
return delete_metadata( 'edd_order_adjustment', null, $meta_key, '', true );
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,469 @@
<?php
/**
* Order Refund Functions
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
use EDD\Orders\Refund_Validator;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Check order can be refunded.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @return bool True if refundable, false otherwise.
*/
function edd_is_order_refundable( $order_id = 0 ) {
global $wpdb;
// Bail if no order ID was passed.
if ( empty( $order_id ) ) {
return false;
}
$order = edd_get_order( $order_id );
// Bail if order was not found.
if ( ! $order ) {
return false;
}
// Only orders with a supported status can be refunded.
if ( ! in_array( $order->status, edd_get_refundable_order_statuses(), true ) ) {
return false;
}
// Check order hasn't already been refunded.
$query = "SELECT COUNT(id) FROM {$wpdb->edd_orders} WHERE parent = %d AND status = '%s'";
$prepare = sprintf( $query, $order_id, esc_sql( 'refunded' ) );
$refunded_order = $wpdb->get_var( $prepare ); // WPCS: unprepared SQL ok.
if ( 0 < absint( $refunded_order ) ) {
return false;
}
// Allow overrides.
if ( true === edd_is_order_refundable_by_override( $order->id ) ) {
return true;
}
// Outside of Refund window.
if ( true === edd_is_order_refund_window_passed( $order->id ) ) {
return false;
}
// If we have reached here, every other check holds so the order is refundable.
return true;
}
/**
* Check order is passed its refund window.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @return bool True if in window, false otherwise.
*/
function edd_is_order_refund_window_passed( $order_id = 0 ) {
// Bail if no order ID was passed.
if ( empty( $order_id ) ) {
return false;
}
$order = edd_get_order( $order_id );
// Bail if order was not found.
if ( ! $order ) {
return false;
}
// Refund dates may not have been set retroactively so we need to calculate it manually.
if ( empty( $order->date_refundable ) ) {
$refund_window = absint( edd_get_option( 'refund_window', 30 ) );
// Refund window is infinite.
if ( 0 === $refund_window ) {
return true;
} else {
$date_refundable = \Carbon\Carbon::parse( $order->date_completed, 'UTC' )->setTimezone( edd_get_timezone_id() )->addDays( $refund_window );
}
// Parse date using Carbon.
} else {
$date_refundable = \Carbon\Carbon::parse( $order->date_refundable, 'UTC' )->setTimezone( edd_get_timezone_id() );
}
return true === $date_refundable->isPast();
}
/**
* Check order can be refunded via a capability override.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @return bool True if refundable via capability override, false otherwise.
*/
function edd_is_order_refundable_by_override( $order_id = 0 ) {
// Bail if no order ID was passed.
if ( empty( $order_id ) ) {
return false;
}
$order = edd_get_order( $order_id );
// Bail if order was not found.
if ( ! $order ) {
return false;
}
// Allow certain capabilities to always provide refunds.
$caps = array( 'edit_shop_payments' );
/**
* Filters the user capabilities that are required for overriding
* refundability requirements.
*
* @since 3.0
*
* @param array $caps List of capabilities that can override
* refundability. Default `edit_shop_payments`.
* @param int $order_id ID of current Order being refunded.
*/
$caps = apply_filters( 'edd_is_order_refundable_by_override_caps', $caps, $order_id );
$can_override = false;
foreach ( $caps as $cap ) {
if ( true === current_user_can( $cap ) ) {
$can_override = true;
break;
}
}
/**
* Filters the allowance of refunds on an Order.
*
* @since 3.0
*
* @param bool $can_override If the refundability can be overriden by
* the current user.
* @param int $order_id ID of current Order being refunded.
*/
$can_override = apply_filters( 'edd_is_order_refundable_by_override', $can_override, $order_id );
return $can_override;
}
/**
* Refund entire order.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @param array|string $order_items {
* Optional. Either `all` as a string to refund all order items, or an array of
* order item IDs, amounts, and quantities to refund.
*
* @type int $order_item_id Required. ID of the order item.
* @type int $quantity Required. Quantity being refunded.
* @type float $subtotal Required. Amount to refund, excluding tax.
* @type float $tax Optional. Amount of tax to refund.
* }
*
* @param array|string $adjustments {
* Optional. Either `all` as a string to refund all order adjustments, or an array of
* order adjustment IDs and amounts to refund.
*
* @type int $adjustment_id Required. ID of the order adjustment being refunded.
* @type float $subtotal Required. Amount to refund, excluding tax.
* @type float $tax Required. Amount of tax to refund.
* }
*
* @return int|WP_Error New order ID if successful, WP_Error on failure.
*/
function edd_refund_order( $order_id, $order_items = 'all', $adjustments = 'all' ) {
global $wpdb;
// Ensure the order ID is an integer.
$order_id = absint( $order_id );
// Fetch order.
$order = edd_get_order( $order_id );
if ( ! $order ) {
return new WP_Error( 'invalid_order', __( 'Invalid order.', 'easy-digital-downloads' ) );
}
if ( false === edd_is_order_refundable( $order_id ) ) {
return new WP_Error( 'not_refundable', __( 'Order not refundable.', 'easy-digital-downloads' ) );
}
/**
* Filter whether refunds should be allowed.
*
* @since 3.0
*
* @param int $order_id Order ID.
*/
$should_refund = apply_filters( 'edd_should_process_order_refund', true, $order_id );
// Bail if refund is blocked.
if ( true !== $should_refund ) {
return new WP_Error( 'refund_not_allowed', __( 'Refund not allowed on this order.', 'easy-digital-downloads' ) );
}
/** Generate new order number *********************************************/
$last_order = $wpdb->get_row( $wpdb->prepare( "SELECT id, order_number
FROM {$wpdb->edd_orders}
WHERE parent = %d
ORDER BY id DESC
LIMIT 1", $order_id ) );
/**
* Filter the suffix applied to order numbers for refunds.
*
* @since 3.0
*
* @param string Suffix.
*/
$refund_suffix = apply_filters( 'edd_order_refund_suffix', '-R-' );
if ( $last_order ) {
// Check for order number first.
if ( $last_order->order_number && ! empty( $last_order->order_number ) ) {
// Order has been previously revised.
if ( false !== strpos( $last_order->order_number, $refund_suffix ) ) {
$number = $last_order->order_number;
++$number;
// First revision to order.
} else {
$number = $last_order->id . $refund_suffix . '1';
}
// Append to ID.
} else {
$number = $last_order->id . $refund_suffix . '1';
}
} else {
$number = $order->id . $refund_suffix . '1';
}
/** Validate refund amounts *************************************************/
try {
$validator = new Refund_Validator( $order, $order_items, $adjustments );
$validator->validate_and_calculate_totals();
} catch( \EDD\Utils\Exceptions\Invalid_Argument $e ) {
return new WP_Error( 'refund_validation_error', __( 'Invalid argument. Please check your amounts and try again.', 'easy-digital-downloads' ) );
} catch ( \Exception $e ) {
return new WP_Error( 'refund_validation_error', $e->getMessage() );
}
/** Insert order **********************************************************/
$order_data = array(
'parent' => $order_id,
'order_number' => $number,
'status' => 'complete',
'type' => 'refund',
'user_id' => $order->user_id,
'customer_id' => $order->customer_id,
'email' => $order->email,
'ip' => $order->ip,
'gateway' => $order->gateway,
'mode' => $order->mode,
'currency' => $order->currency,
'payment_key' => strtolower( md5( uniqid() ) ),
'tax_rate_id' => $order->tax_rate_id,
'subtotal' => edd_negate_amount( $validator->subtotal ),
'tax' => edd_negate_amount( $validator->tax ),
'total' => edd_negate_amount( $validator->total ),
);
// Full refund is inserted first to allow for conditional checks to run later
// and update the order, but we need an INSERT to be executed to generate a
// new order ID.
$refund_id = edd_add_order( $order_data );
// If we have tax, but no tax rate, manually save the percentage.
$tax_rate_meta = edd_get_order_meta( $order_id, 'tax_rate', true );
if ( $tax_rate_meta ) {
edd_update_order_meta( $refund_id, 'tax_rate', $tax_rate_meta );
}
/** Insert order items ****************************************************/
// Maintain a mapping of old order item IDs => new for easier lookup when we do fees.
$order_item_id_map = array();
foreach ( $validator->get_refunded_order_items() as $order_item ) {
$order_item['order_id'] = $refund_id;
$new_item_id = edd_add_order_item( $order_item );
if ( ! empty( $order_item['parent'] ) ) {
$order_item_id_map[ $order_item['parent'] ] = $new_item_id;
}
// Update the status on the original order item.
if ( ! empty( $order_item['parent'] ) && ! empty( $order_item['original_item_status'] ) ) {
edd_update_order_item( $order_item['parent'], array( 'status' => $order_item['original_item_status'] ) );
}
}
/** Insert order adjustments **********************************************/
foreach ( $validator->get_refunded_adjustments() as $adjustment ) {
if ( ! empty( $adjustment['object_type'] ) && 'order' === $adjustment['object_type'] ) {
$adjustment['object_id'] = $refund_id;
} elseif ( ! empty( $adjustment['object_type'] ) && 'order_item' === $adjustment['object_type'] ) {
/*
* At this point, `object_id` references an order item which is attached to the
* original order record. We need to try to convert this to a _refund_ order item
* instead.
*
* If we can't (such as, if the order item was never refunded), we'll have to
* convert the adjustment to be an `order` object type instead. That's because we
* _have_ to reference a refund object of some kind.
*/
$order_item_match_found = false;
if ( ! empty( $adjustment['object_id'] ) && ! empty( $order_item_id_map[ $adjustment['object_id'] ] ) ) {
// We don't need to convert to an `order` adjustment if we are also refunding the original order item.
$adjustment['object_id'] = $order_item_id_map[ $adjustment['object_id'] ];
$order_item_match_found = true;
}
if ( ! $order_item_match_found ) {
$adjustment['object_type'] = 'order';
$adjustment['object_id'] = $refund_id;
}
}
/*
* Note: Order item adjustments retain their `object_id` link to the *original* order item -- not the
* refunded order item. This isn't ideal, but it's because you could refund an order item fee without
* refunding the associated item, in which case there would be no refunded order item to reference.
* So we link back to the *original* order item in all cases to be consistent.
*/
edd_add_order_adjustment( $adjustment );
}
// Update order status to `refunded` once refund is complete and if all items are marked as refunded.
$all_refunded = true;
$remaining_items = edd_count_order_items(
array(
'order_id' => $order_id,
'status__not_in' => array( 'refunded' ),
)
);
if ( edd_get_order_total( $order_id ) > 0 || $remaining_items > 0 ) {
$all_refunded = false;
}
$order_status = true === $all_refunded
? 'refunded'
: 'partially_refunded';
edd_update_order( $order_id, array( 'status' => $order_status ) );
edd_update_order( $refund_id, array( 'date_completed' => date( 'Y-m-d H:i:s' ) ) );
/**
* Fires when an order has been refunded.
* This hook will trigger the legacy `edd_pre_refund_payment` and `edd_post_refund_payment`
* hooks for the time being, but any code using either of those should be updated.
*
* @since 3.0
*
* @param int $order_id Order ID of the original order.
* @param int $refund_id ID of the new refund object.
* @param bool $all_refunded Whether or not the entire order was refunded.
*/
do_action( 'edd_refund_order', $order_id, $refund_id, $all_refunded );
return $refund_id;
}
/**
* Queries for order refunds.
*
* @see \EDD\Database\Queries\Order::__construct()
*
* @since 3.0
*
* @param int $order_id Parent Order.
* @return \EDD\Orders\Order[] Array of `Order` objects.
*/
function edd_get_order_refunds( $order_id = 0 ) {
$order_refunds = new \EDD\Database\Queries\Order();
return $order_refunds->query( array(
'type' => 'refund',
'parent' => $order_id,
) );
}
/**
* Calculate order total. This method is used to calculate the total of an order
* by also taking into account any refunds/partial refunds.
*
* @since 3.0
*
* @param int $order_id Order ID.
* @return float $total Order total.
*/
function edd_get_order_total( $order_id ) {
global $wpdb;
$query = "SELECT SUM(total) FROM {$wpdb->edd_orders} WHERE id = %d OR parent = %d";
$prepare = $wpdb->prepare( $query, $order_id, $order_id );
$total = $wpdb->get_var( $prepare ); // WPCS: unprepared SQL ok.
$retval = ( null === $total )
? 0.00
: floatval( $total );
return $retval;
}
/**
* Calculate order item total. This method is used to calculate the total of an
* order item by also taking into account any refunds/partial refunds.
*
* @since 3.0
*
* @param array $order_ids Order IDs.
* @param int $product_id Product ID.
*
* @return float $total Order total.
*/
function edd_get_order_item_total( $order_ids = array(), $product_id = 0 ) {
global $wpdb;
// Bail if no order IDs were passed.
if ( empty( $order_ids ) ) {
return 0;
}
$query = "SELECT SUM(total) FROM {$wpdb->edd_order_items} WHERE order_id IN (%s) AND product_id = %d";
$ids = join( ',', array_map( 'absint', $order_ids ) );
$prepare = sprintf( $query, $ids, $product_id );
$total = $wpdb->get_var( $prepare ); // WPCS: unprepared SQL ok.
$retval = ( null === $total )
? 0.00
: floatval( $total );
return $retval;
}

View File

@ -0,0 +1,174 @@
<?php
/**
* Order Status Functions.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2022, Easy Digital Downloads
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
/**
* Get the order status array keys that can be used to run reporting related to gross reporting.
*
* @since 3.0
*
* @return array An array of order status array keys that can be related to gross reporting.
*/
function edd_get_gross_order_statuses() {
$statuses = array(
'complete',
'refunded',
'partially_refunded',
'revoked',
);
/**
* Statuses that affect gross order statistics.
*
* This filter allows extensions and developers to alter the statuses that can affect the reporting of gross
* sales statistics.
*
* @since 3.0
*
* @param array $statuses {
* An array of order status array keys.
*
*/
return apply_filters( 'edd_gross_order_statuses', $statuses );
}
/**
* Get the order status array keys that can be used to run reporting related to net reporting.
*
* @since 3.0
*
* @return array An array of order status array keys that can be related to net reporting.
*/
function edd_get_net_order_statuses() {
$statuses = array(
'complete',
'partially_refunded',
'revoked',
);
/**
* Statuses that affect net order statistics.
*
* This filter allows extensions and developers to alter the statuses that can affect the reporting of net
* sales statistics.
*
* @since 3.0
*
* @param array $statuses {
* An array of order status array keys.
*
*/
return apply_filters( 'edd_net_order_statuses', $statuses );
}
/**
* Get the order status array keys which are considered recoverable.
*
* @since 3.0
* @param bool $include_labels Whether to return a multidimensional array including status labels.
* @return array An array of order status keys which are considered recoverable.
*/
function edd_recoverable_order_statuses( $include_labels = false ) {
$statuses = array( 'pending', 'abandoned', 'failed' );
/**
* Order statuses which are considered recoverable.
*
* @param $statuses {
* An array of order status array keys.
* }
*/
$statuses = apply_filters( 'edd_recoverable_payment_statuses', $statuses );
return $include_labels ? array_intersect_key( edd_get_payment_statuses(), array_flip( $statuses ) ) : $statuses;
}
/**
* Get the order status keys which are considered complete.
*
* @since 3.0
* @param bool $include_labels Whether to return a multidimensional array including status labels.
* @return array An array of order status keys which are considered completed.
*/
function edd_get_complete_order_statuses( $include_labels = false ) {
$statuses = array( 'publish', 'complete', 'completed', 'partially_refunded', 'revoked', 'refunded' );
/**
* Order statuses which are considered completed or at their final state.
*
* @param $statuses {
* An array of order status array keys.
* }
*/
$statuses = apply_filters( 'edd_complete_order_statuses', $statuses );
return $include_labels ? array_intersect_key( edd_get_payment_statuses(), array_flip( $statuses ) ) : $statuses;
}
/**
* Get the order status array keys which are considered incomplete.
*
* @since 3.0
* @param bool $include_labels Whether to return a multidimensional array including status labels.
* @return array An array of order status keys which are considered incomplete.
*/
function edd_get_incomplete_order_statuses( $include_labels = false ) {
$statuses = array( 'pending', 'abandoned', 'processing', 'failed', 'cancelled' );
/**
* Filters the payment key
* Order statuses which are considered incomplete.
*
* @param $statuses {
* An array of order status array keys.
* }
*/
$statuses = apply_filters( 'edd_incomplete_order_statuses', $statuses );
return $include_labels ? array_intersect_key( edd_get_payment_statuses(), array_flip( $statuses ) ) : $statuses;
}
/**
* Returns an array of order statuses that support refunds.
*
* @since 3.0
* @return array
*/
function edd_get_refundable_order_statuses() {
$refundable_order_statuses = array( 'complete', 'publish', 'partially_refunded' );
/**
* Filters the order statuses that are allowed to be refunded.
*
* @param array $refundable_order_statuses
*
* @since 3.0
*/
return (array) apply_filters( 'edd_refundable_order_statuses', $refundable_order_statuses );
}
/**
* Returns an array of order item statuses that allow assets to be delivvered.
*
* @since 3.0
* @return array
*/
function edd_get_deliverable_order_item_statuses() {
$deliverable_order_item_statuses = array( 'complete', 'partially_refunded' );
/**
* Filters the order item statuses that aallow assets to be delivered.
*
* @param array $refundable_order_statuses
*
* @since 3.0
*/
return (array) apply_filters( 'edd_deliverable_order_item_statuses', $deliverable_order_item_statuses );
}

View File

@ -0,0 +1,227 @@
<?php
/**
* Order Transaction Functions.
*
* @package EDD
* @subpackage Orders\Transactions
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Add an order transaction.
*
* @since 3.0
*
* @param array $data {
* Array of order transaction data. Default empty.
*
* The `date_created` and `date_modified` parameters do not need to be passed.
* They will be automatically populated if empty.
*
* @type int $object_id Object ID that the transaction refers to. This
* would be an ID that corresponds to the object
* type specified. E.g. an object ID of 25 with object
* type of `order` refers to order 25 in the
* `edd_orders` table. Default 0.
* @type string $object_type Object type that the transaction refers to.
* Default empty.
* @type string $transaction_id Transaction ID from the payment gateway.
* Default empty.
* @type string $gateway Payment gateway used for the order. Default
* empty.
* @type string $status Status of the transaction. Default `pending`.
* @type float $total Total amount processed in the transaction.
* Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the transaction was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the transaction was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
* @return int|false ID of newly created order transaction, false on error.
*/
function edd_add_order_transaction( $data ) {
// An object ID and object type must be supplied for every transaction inserted.
if ( empty( $data['object_id'] ) || empty( $data['object_type'] ) ) {
return false;
}
// Instantiate a query object.
$order_transactions = new EDD\Database\Queries\Order_Transaction();
return $order_transactions->add_item( $data );
}
/**
* Delete an order transaction.
*
* @since 3.0
*
* @param int $order_transaction_id Order transaction ID.
* @return int|false `1` if the transaction was deleted successfully, false on error.
*/
function edd_delete_order_transaction( $order_transaction_id = 0 ) {
$order_transactions = new EDD\Database\Queries\Order_Transaction();
return $order_transactions->delete_item( $order_transaction_id );
}
/**
* Update an order address.
*
* @since 3.0
*
* @param int $order_transaction_id Order transaction ID.
* @param array $data {
* Array of order transaction data. Default empty.
*
* @type int $object_id Object ID that the transaction refers to. This
* would be an ID that corresponds to the object
* type specified. E.g. an object ID of 25 with object
* type of `order` refers to order 25 in the
* `edd_orders` table. Default 0.
* @type string $object_type Object type that the transaction refers to.
* Default empty.
* @type string $transaction_id Transaction ID from the payment gateway.
* Default empty.
* @type string $gateway Payment gateway used for the order. Default
* empty.
* @type string $status Status of the transaction. Default `pending`.
* @type float $total Total amount processed in the transaction.
* Default 0.
* @type string $date_created Optional. Automatically calculated on add/edit.
* The date & time the transaction was inserted.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* @type string $date_modified Optional. Automatically calculated on add/edit.
* The date & time the transaction was last modified.
* Format: YYYY-MM-DD HH:MM:SS. Default empty.
* }
*
* @return int|false Number of rows updated if successful, false otherwise.
*/
function edd_update_order_transaction( $order_transaction_id = 0, $data = array() ) {
$order_transactions = new EDD\Database\Queries\Order_Transaction();
return $order_transactions->update_item( $order_transaction_id, $data );
}
/**
* Get an order transaction by ID.
*
* @since 3.0
*
* @param int $order_transaction_id Order transaction ID.
* @return EDD\Orders\Order_Transaction|false Order_Transaction object if successful,
* false otherwise.
*/
function edd_get_order_transaction( $order_transaction_id = 0 ) {
$order_transactions = new EDD\Database\Queries\Order_Transaction();
// Return order transaction.
return $order_transactions->get_item( $order_transaction_id );
}
/**
* Get an order transaction by a specific field value.
*
* @since 3.0
*
* @param string $field Database table field.
* @param string $value Value of the row.
*
* @return EDD\Orders\Order_Transaction|false Order_Transaction object if successful,
* false otherwise.
*/
function edd_get_order_transaction_by( $field = '', $value = '' ) {
$order_transactions = new EDD\Database\Queries\Order_Transaction();
// Return order transaction.
return $order_transactions->get_item_by( $field, $value );
}
/**
* Query for order transactions.
*
* @see \EDD\Database\Queries\Order_Transaction::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Transaction` for
* accepted arguments.
* @return \EDD\Orders\Order_Transaction[] Array of `Order_Transaction` objects.
*/
function edd_get_order_transactions( $args = array() ) {
// Parse args.
$r = wp_parse_args( $args, array(
'number' => 30,
) );
// Instantiate a query object.
$order_transactions = new EDD\Database\Queries\Order_Transaction();
// Return order transactions.
return $order_transactions->query( $r );
}
/**
* Count order transactions.
*
* @see \EDD\Database\Queries\Order_Transaction::__construct()
*
* @since 3.0
*
* @param array $args Arguments. See `EDD\Database\Queries\Order_Transaction` for
* accepted arguments.
* @return int Number of order transactions returned based on query arguments
* passed.
*/
function edd_count_order_transactions( $args = array() ) {
// Parse args.
$r = wp_parse_args( $args, array(
'count' => true,
) );
// Query for count(s).
$order_transactions = new EDD\Database\Queries\Order_Transaction( $r );
// Return count(s).
return absint( $order_transactions->found_items );
}
/**
* Retrieve order ID based on the transaction ID.
*
* @since 3.0
*
* @param string $transaction_id Transaction ID. Default empty.
* @return int $order_id Order ID. Default 0.
*/
function edd_get_order_id_from_transaction_id( $transaction_id = '' ) {
// Default return value.
$retval = 0;
// Continue if transaction ID was passed.
if ( ! empty( $transaction_id ) ) {
// Look for a transaction by gateway transaction ID.
$transaction = edd_get_order_transaction_by( 'transaction_id', $transaction_id );
// Return object ID if found.
if ( ! empty( $transaction->object_id ) ) {
$retval = $transaction->object_id;
}
}
// Return.
return absint( $retval );
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Order Transition Functions.
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2022, Easy Digital Downloads
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
/**
* Record order status change
*
* @since 3.0
* @param string $old_status the status of the order prior to this change.
* @param string $new_status The new order status.
* @param int $order_id the ID number of the order.
* @return void
*/
function edd_record_order_status_change( $old_status, $new_status, $order_id ) {
// Get the list of statuses so that status in the payment note can be translated.
$stati = edd_get_payment_statuses();
$old_status = isset( $stati[ $old_status ] ) ? $stati[ $old_status ] : $old_status;
$new_status = isset( $stati[ $new_status ] ) ? $stati[ $new_status ] : $new_status;
$status_change = sprintf(
/* translators: %1$s Old order status. %2$s New order status. */
__( 'Status changed from %1$s to %2$s', 'easy-digital-downloads' ),
$old_status,
$new_status
);
edd_insert_payment_note( $order_id, $status_change );
}
add_action( 'edd_transition_order_status', 'edd_record_order_status_change', 100, 3 );
/**
* Triggers `edd_update_payment_status` hook when an order status changes
* for backwards compatibility.
*
* @since 3.0
* @param string $old_status the status of the order prior to this change.
* @param string $new_status The new order status.
* @param int $order_id the ID number of the order.
* @return void
*/
add_action( 'edd_transition_order_status', function( $old_status, $new_status, $order_id ) {
// Trigger the payment status action hook for backwards compatibility.
do_action( 'edd_update_payment_status', $order_id, $new_status, $old_status );
if ( 'complete' === $old_status ) {
// Trigger the action again to account for add-ons listening for status changes from "publish".
do_action( 'edd_update_payment_status', $order_id, $new_status, 'publish' );
}
}, 10, 3 );

View File

@ -0,0 +1,106 @@
<?php
/**
* Order Type Functions
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Retrieve the registered order types.
*
* @since 3.0
*
* @return array
*/
function edd_get_order_types() {
// Get the
$component = edd_get_component( 'order' );
// Setup an empty types array
if ( ! isset( $component->types ) ) {
$component->types = array();
}
// Return types
return (array) $component->types;
}
/**
* Register an Order Type.
*
* @since 3.0
*
* @param array $args
*/
function edd_register_order_type( $type = '', $args = array() ) {
// Sanitize the type
$type = sanitize_key( $type );
// Parse args
$r = wp_parse_args( $args, array(
'show_ui' => true,
'labels' => array(
'singular' => '',
'plural' => ''
)
) );
// Get the
$component = edd_get_component( 'order' );
// Setup an empty types array
if ( ! isset( $component->types ) ) {
$component->types = array();
}
// Add the order type to the `types` array
$component->types[ $type ] = $r;
}
/**
* Register the default Order Types.
*
* @since 3.0
*/
function edd_register_default_order_types( $name = '' ) {
// Bail if not the `order` name
if ( 'order' !== $name ) {
return;
}
// Sales
edd_register_order_type( 'sale', array(
'labels' => array(
'singular' => __( 'Order', 'easy-digital-downloads' ),
'plural' => __( 'Orders', 'easy-digital-downloads' )
)
) );
// Refunds
edd_register_order_type( 'refund', array(
'labels' => array(
'singular' => __( 'Refund', 'easy-digital-downloads' ),
'plural' => __( 'Refunds', 'easy-digital-downloads' )
)
) );
// Invoices
edd_register_order_type( 'invoice', array(
'show_ui' => false,
'labels' => array(
'singular' => __( 'Invoice', 'easy-digital-downloads' ),
'plural' => __( 'Invoices', 'easy-digital-downloads' )
)
) );
}
add_action( 'edd_registered_component', 'edd_register_default_order_types' );

View File

@ -0,0 +1,44 @@
<?php
/**
* Order User Interface Functions
*
* @package EDD
* @subpackage Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Add "Order" to the "+ New" admin menu bar.
*
* @since 3.0
*
* @param WP_Admin_Bar $wp_admin_bar Admin bar object.
*/
function edd_add_new_order_to_wp_admin_bar( $wp_admin_bar ) {
// Bail if no admin bar
if ( empty( $wp_admin_bar ) ) {
return;
}
// Bail if incorrect user.
if ( ! current_user_can( 'manage_shop_settings' ) ) {
return;
}
// Add the menu item
$wp_admin_bar->add_menu( array(
'id' => 'new-order',
'title' => __( 'Order', 'easy-digital-downloads' ),
'parent' => 'new-content',
'href' => edd_get_admin_url( array(
'page' => 'edd-payment-history',
'view' => 'add-order',
) ),
) );
}
add_action( 'admin_bar_menu', 'edd_add_new_order_to_wp_admin_bar', 99 );