700 lines
14 KiB
PHP
700 lines
14 KiB
PHP
|
<?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 );
|
||
|
}
|
||
|
}
|