laipower/wp-content/plugins/easy-digital-downloads/includes/compat/class-edd-payment-compat.php

973 lines
22 KiB
PHP

<?php
/**
* This class is used to build an EDD_Payment object
* from a WP_Post object.
*
* This is intended for internal use only, to ensure that existing
* payments can be accessed before the migration is complete.
*
* @package EDD
* @subpackage Compat
* @copyright Copyright (c) 2022, Easy Digital Downloads
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* EDD_Payment_Compat Class
*
* @since 3.0
*
* @property int $ID
* @property string $number
* @property string $mode
* @property string $key
* @property float $total
* @property float $subtotal
* @property float $tax
* @property float $discounted_amount
* @property float $tax_rate
* @property array $fees
* @property float $fees_total
* @property string $discounts
* @property string $completed_date
* @property string $status
* @property int $customer_id
* @property int $user_id
* @property string $first_name
* @property string $last_name
* @property string $email
* @property array $user_info
* @property array $payment_meta
* @property array $address
* @property string $transaction_id
* @property array $downloads
* @property string $ip
* @property string $gateway
* @property string $currency
* @property array $cart_details
* @property bool $has_unlimited_downloads
* @property int $parent_payment
*/
class EDD_Payment_Compat {
/**
* The Payment ID
*
* @since 3.0
* @var integer
*/
public $ID = 0;
/**
* The Payment number (for use with sequential payments)
*
* @since 3.0
* @var string
*/
public $number = '';
/**
* The Gateway mode the payment was made in
*
* @since 3.0
* @var string
*/
public $mode = 'live';
/**
* The Unique Payment Key
*
* @since 3.0
* @var string
*/
public $key = '';
/**
* The total amount the payment is for
* Includes items, taxes, fees, and discounts
*
* @since 3.0
* @var float
*/
public $total = 0.00;
/**
* The Subtotal fo the payment before taxes
*
* @since 3.0
* @var float
*/
public $subtotal = 0;
/**
* The amount of tax for this payment
*
* @since 3.0
* @var float
*/
public $tax = 0;
/**
* The amount the payment has been discounted through discount codes
*
* @since 3.0
* @var int
*/
public $discounted_amount = 0;
/**
* The tax rate charged on this payment
*
* @since 3.0
* @var float
*/
public $tax_rate = '';
/**
* Array of global fees for this payment
*
* @since 3.0
* @var array
*/
public $fees = array();
/**
* The sum of the fee amounts
*
* @since 3.0
* @var float
*/
public $fees_total = 0;
/**
* Any discounts applied to the payment
*
* @since 3.0
* @var string
*/
public $discounts = 'none';
/**
* The date the payment was created
*
* @since 3.0
* @var string
*/
public $date = '';
/**
* The date the payment was marked as 'complete'
*
* @since 3.0
* @var string
*/
public $completed_date = '';
/**
* The status of the payment
*
* @since 3.0
* @var string
*/
public $status = 'pending';
/**
* The customer ID that made the payment
*
* @since 3.0
* @var integer
*/
public $customer_id = null;
/**
* The User ID (if logged in) that made the payment
*
* @since 3.0
* @var integer
*/
public $user_id = 0;
/**
* The first name of the payee
*
* @since 3.0
* @var string
*/
public $first_name = '';
/**
* The last name of the payee
*
* @since 3.0
* @var string
*/
public $last_name = '';
/**
* The email used for the payment
*
* @since 3.0
* @var string
*/
public $email = '';
/**
* Legacy (not to be accessed) array of user information
*
* @since 3.0
* @var array
*/
public $user_info = array();
/**
* Legacy (not to be accessed) payment meta array
*
* @since 3.0
* @var array
*/
public $payment_meta = array();
/**
* The physical address used for the payment if provided
*
* @since 3.0
* @var array
*/
public $address = array();
/**
* The transaction ID returned by the gateway
*
* @since 3.0
* @var string
*/
public $transaction_id = '';
/**
* Array of downloads for this payment
*
* @since 3.0
* @var array
*/
public $downloads = array();
/**
* IP Address payment was made from
*
* @since 3.0
* @var string
*/
public $ip = '';
/**
* The gateway used to process the payment
*
* @since 3.0
* @var string
*/
public $gateway = '';
/**
* The the payment was made with
*
* @since 3.0
* @var string
*/
public $currency = '';
/**
* The cart details array
*
* @since 3.0
* @var array
*/
public $cart_details = array();
/**
* Allows the files for this payment to be downloaded unlimited times (when download limits are enabled)
*
* @since 3.0
* @var boolean
*/
public $has_unlimited_downloads = false;
/**
* Order object.
*
* @since 3.0
* @var EDD\Orders\Order
*/
public $order;
/**
* The parent payment (if applicable)
*
* @since 3.0
* @var integer
*/
public $parent_payment = 0;
/**
* Post object.
*
* @since 3.0
* @var WP_Post
*/
private $payment;
/**
* Setup the EDD Payments class
*
* @since 3.0
* @param int $payment_id A given payment
* @return mixed void|false
*/
public function __construct( $payment_id = false ) {
if ( empty( $payment_id ) ) {
return false;
}
$this->ID = absint( $payment_id );
$this->payment = get_post( $this->ID );
if ( ! $this->payment instanceof WP_Post ) {
return false;
}
$this->setup();
}
/**
* Sets up the payment object.
*
* @since 3.0
* @return void
*/
private function setup() {
$this->payment_meta = $this->get_meta();
$this->cart_details = $this->setup_cart_details();
$this->status = $this->setup_status();
$this->completed_date = $this->setup_completed_date();
$this->mode = $this->setup_mode();
$this->total = $this->setup_total();
$this->tax = $this->setup_tax();
$this->tax_rate = $this->setup_tax_rate();
$this->fees_total = $this->setup_fees_total();
$this->subtotal = $this->setup_subtotal();
$this->discounts = $this->setup_discounts();
$this->currency = $this->setup_currency();
$this->fees = $this->setup_fees();
$this->gateway = $this->setup_gateway();
$this->transaction_id = $this->setup_transaction_id();
$this->ip = $this->setup_ip();
$this->customer_id = $this->setup_customer_id();
$this->user_id = $this->setup_user_id();
$this->email = $this->setup_email();
$this->user_info = $this->setup_user_info();
$this->address = $this->setup_address();
$this->key = $this->setup_payment_key();
$this->number = $this->setup_payment_number();
$this->downloads = $this->setup_downloads();
$this->has_unlimited_downloads = $this->setup_has_unlimited();
$this->order = $this->_shim_order();
}
/**
* Get a post meta item for the payment
*
* @since 3.0
* @param string $meta_key The Meta Key
* @param boolean $single Return single item or array
* @return mixed The value from the post meta
*/
private function get_meta( $meta_key = '_edd_payment_meta', $single = true ) {
$meta = get_post_meta( $this->ID, $meta_key, $single );
if ( '_edd_payment_meta' === $meta_key ) {
if ( empty( $meta ) ) {
$meta = array();
}
// #5228 Fix possible data issue introduced in 2.6.12
if ( is_array( $meta ) && isset( $meta[0] ) ) {
$bad_meta = $meta[0];
unset( $meta[0] );
if ( is_array( $bad_meta ) ) {
$meta = array_merge( $meta, $bad_meta );
}
update_post_meta( $this->ID, '_edd_payment_meta', $meta );
}
// Payment meta was simplified in EDD v1.5, so these are here for backwards compatibility
if ( empty( $meta['key'] ) ) {
$meta['key'] = $this->setup_payment_key();
}
if ( empty( $meta['email'] ) ) {
$meta['email'] = $this->setup_email();
}
if ( empty( $meta['date'] ) ) {
$meta['date'] = get_post_field( 'post_date', $this->ID );
}
}
$meta = apply_filters( 'edd_get_payment_meta_' . $meta_key, $meta, $this->ID );
if ( is_serialized( $meta ) ) {
preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $meta, $matches );
if ( ! empty( $matches ) ) {
$meta = array();
}
}
return apply_filters( 'edd_get_payment_meta', $meta, $this->ID, $meta_key );
}
/**
* Setup the payment completed date
*
* @since 3.0
* @return string The date the payment was completed
*/
private function setup_completed_date() {
if ( in_array( $this->payment->post_status, array( 'pending', 'preapproved', 'processing' ), true ) ) {
return false; // This payment was never completed
}
$date = $this->get_meta( '_edd_completed_date', true );
// phpcs:ignore WordPress.PHP.DisallowShortTernary
return $date ?: $this->payment->date;
}
/**
* Setup the payment mode
*
* @since 3.0
* @return string The payment mode
*/
private function setup_mode() {
return $this->get_meta( '_edd_payment_mode' );
}
/**
* Setup the payment total
*
* @since 3.0
* @return float The payment total
*/
private function setup_total() {
$amount = $this->get_meta( '_edd_payment_total', true );
if ( empty( $amount ) && '0.00' != $amount ) {
$meta = $this->get_meta( '_edd_payment_meta', true );
$meta = maybe_unserialize( $meta );
if ( isset( $meta['amount'] ) ) {
$amount = $meta['amount'];
}
}
return $amount;
}
/**
* Setup the payment tax
*
* @since 3.0
* @return float The tax for the payment
*/
private function setup_tax() {
$tax = $this->get_meta( '_edd_payment_tax', true );
// We don't have tax as its own meta and no meta was passed
if ( '' === $tax ) {
$tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
}
return $tax;
}
/**
* Setup the payment tax rate
*
* @since 3.0
* @return float The tax rate for the payment
*/
private function setup_tax_rate() {
return $this->get_meta( '_edd_payment_tax_rate', true );
}
/**
* Setup the payment fees
*
* @since 3.0
* @return float The fees total for the payment
*/
private function setup_fees_total() {
$fees_total = (float) 0.00;
$payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
if ( ! empty( $payment_fees ) ) {
foreach ( $payment_fees as $fee ) {
$fees_total += (float) $fee['amount'];
}
}
return $fees_total;
}
/**
* Setup the payment subtotal
*
* @since 3.0
* @return float The subtotal of the payment
*/
private function setup_subtotal() {
$subtotal = 0;
$cart_details = $this->cart_details;
if ( is_array( $cart_details ) ) {
foreach ( $cart_details as $item ) {
if ( isset( $item['subtotal'] ) ) {
$subtotal += $item['subtotal'];
}
}
} else {
$subtotal = $this->total;
$tax = edd_use_taxes() ? $this->tax : 0;
$subtotal -= $tax;
}
return $subtotal;
}
/**
* Setup the payments discount codes
*
* @since 3.0
* @return array Array of discount codes on this payment
*/
private function setup_discounts() {
return ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
}
/**
* Setup the currency code
*
* @since 3.0
* @return string The currency for the payment
*/
private function setup_currency() {
return isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'edd_payment_currency_default', edd_get_currency(), $this );
}
/**
* Setup any fees associated with the payment
*
* @since 3.0
* @return array The Fees
*/
private function setup_fees() {
return isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
}
/**
* Setup the gateway used for the payment
*
* @since 3.0
* @return string The gateway
*/
private function setup_gateway() {
return $this->get_meta( '_edd_payment_gateway', true );
}
/**
* Setup the transaction ID
*
* @since 3.0
* @return string The transaction ID for the payment
*/
private function setup_transaction_id() {
$transaction_id = $this->get_meta( '_edd_payment_transaction_id', true );
if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
$gateway = $this->gateway;
$transaction_id = apply_filters( 'edd_get_payment_transaction_id-' . $gateway, $this->ID );
}
return $transaction_id;
}
/**
* Setup the IP Address for the payment
*
* @since 3.0
* @return string The IP address for the payment
*/
private function setup_ip() {
return $this->get_meta( '_edd_payment_user_ip', true );
}
/**
* Setup the customer ID
*
* @since 3.0
* @return int The Customer ID
*/
private function setup_customer_id() {
return $this->get_meta( '_edd_payment_customer_id', true );
}
/**
* Setup the User ID associated with the purchase
*
* @since 3.0
* @return int The User ID
*/
private function setup_user_id() {
$user_id = $this->get_meta( '_edd_payment_user_id', true );
$customer = new EDD_Customer( $this->customer_id );
// Make sure it exists, and that it matches that of the associated customer record
if ( ! empty( $customer->user_id ) && ( empty( $user_id ) || (int) $user_id !== (int) $customer->user_id ) ) {
$user_id = $customer->user_id;
// Backfill the user ID, or reset it to be correct in the event of data corruption
update_post_meta( $this->ID, '_edd_payment_user_id', $user_id );
}
return $user_id;
}
/**
* Setup the email address for the purchase
*
* @since 3.0
* @return string The email address for the payment
*/
private function setup_email() {
$email = $this->get_meta( '_edd_payment_user_email', true );
if ( empty( $email ) ) {
$email = EDD()->customers->get_column( 'email', $this->customer_id );
}
return $email;
}
/**
* Setup the user info
*
* @since 3.0
* @return array The user info associated with the payment
*/
private function setup_user_info() {
$defaults = array(
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'discount' => $this->discounts,
);
$user_info = isset( $this->payment_meta['user_info'] ) ? $this->payment_meta['user_info'] : array();
if ( is_serialized( $user_info ) ) {
preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $user_info, $matches );
if ( ! empty( $matches ) ) {
$user_info = array();
}
}
// As per Github issue #4248, we need to run maybe_unserialize here still.
$user_info = wp_parse_args( maybe_unserialize( $user_info ), $defaults );
// Ensure email index is in the old user info array
if ( empty( $user_info['email'] ) ) {
$user_info['email'] = $this->email;
}
if ( empty( $user_info ) ) {
// Get the customer, but only if it's been created
$customer = new EDD_Customer( $this->customer_id );
if ( $customer->id > 0 ) {
$name = explode( ' ', $customer->name, 2 );
$user_info = array(
'first_name' => $name[0],
'last_name' => $name[1],
'email' => $customer->email,
'discount' => 'none',
);
}
} else {
// Get the customer, but only if it's been created
$customer = new EDD_Customer( $this->customer_id );
if ( $customer->id > 0 ) {
foreach ( $user_info as $key => $value ) {
if ( ! empty( $value ) ) {
continue;
}
switch ( $key ) {
case 'first_name':
$name = explode( ' ', $customer->name, 2 );
$user_info[ $key ] = $name[0];
break;
case 'last_name':
$name = explode( ' ', $customer->name, 2 );
$last_name = ! empty( $name[1] ) ? $name[1] : '';
$user_info[ $key ] = $last_name;
break;
case 'email':
$user_info[ $key ] = $customer->email;
break;
}
}
}
}
return $user_info;
}
/**
* Setup the Address for the payment
*
* @since 3.0
* @return array The Address information for the payment
*/
private function setup_address() {
$address = ! empty( $this->payment_meta['user_info']['address'] ) ? $this->payment_meta['user_info']['address'] : array();
$defaults = array(
'line1' => '',
'line2' => '',
'city' => '',
'country' => '',
'state' => '',
'zip' => '',
);
return wp_parse_args( $address, $defaults );
}
/**
* Setup the payment key
*
* @since 3.0
* @return string The Payment Key
*/
private function setup_payment_key() {
return $this->get_meta( '_edd_payment_purchase_key', true );
}
/**
* Setup the payment number
*
* @since 3.0
* @return int|string Integer by default, or string if sequential order numbers is enabled
*/
private function setup_payment_number() {
$number = false;
if ( edd_get_option( 'enable_sequential' ) ) {
$number = $this->get_meta( '_edd_payment_number', true );
}
return $number ?: $this->ID;
}
/**
* Setup the cart details
*
* @since 3.0
* @return array The cart details
*/
private function setup_cart_details() {
return isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
}
/**
* Setup the downloads array
*
* @since 3.0
* @return array Downloads associated with this payment
*/
private function setup_downloads() {
return isset( $this->payment_meta['downloads'] ) ? maybe_unserialize( $this->payment_meta['downloads'] ) : array();
}
/**
* Setup the Unlimited downloads setting
*
* @since 3.0
* @return bool If this payment has unlimited downloads
*/
private function setup_has_unlimited() {
return (bool) $this->get_meta( '_edd_payment_unlimited_downloads', true );
}
/**
* Sets up the payment status.
*
* @since 3.0
* @return string
*/
private function setup_status() {
return 'publish' === $this->payment->post_status ? 'complete' : $this->payment->post_status;
}
/**
* Shims the payment, as much as possible, into an EDD Order object.
* @todo deprecate in 3.1
*
* @return EDD\Orders\Order
*/
public function _shim_order() {
return new \EDD\Orders\Order(
array(
'id' => $this->ID,
'parent' => $this->payment->parent,
'order_number' => $this->number,
'status' => $this->status,
'type' => 'sale',
'user_id' => $this->user_id,
'customer_id' => $this->customer_id,
'email' => $this->email,
'ip' => $this->ip,
'gateway' => $this->gateway,
'mode' => $this->mode,
'currency' => $this->currency,
'payment_key' => $this->key,
'subtotal' => $this->subtotal,
'discount' => $this->discounted_amount,
'tax' => $this->tax,
'total' => $this->total,
'rate' => $this->get_order_tax_rate(),
'date_created' => $this->payment->post_date_gmt,
'date_modified' => $this->payment->post_modified,
'date_completed' => $this->completed_date,
'items' => $this->get_order_items(),
)
);
}
/**
* Updates the payment tax rate to match the expected order tax rate.
*
* @since 3.0
* @return float
*/
private function get_order_tax_rate() {
$tax_rate = (float) $this->tax_rate;
if ( $tax_rate < 1 ) {
$tax_rate = $tax_rate * 100;
}
return $tax_rate;
}
/**
* Gets an array of order item objects from the cart details.
*
* @since 3.0
* @return array
*/
private function get_order_items() {
$order_items = array();
if ( empty( $this->cart_details ) ) {
return $order_items;
}
foreach ( $this->cart_details as $key => $cart_item ) {
$product_name = isset( $cart_item['name'] )
? $cart_item['name']
: '';
$price_id = $this->get_valid_price_id_for_cart_item( $cart_item );
if ( ! empty( $product_name ) ) {
$option_name = edd_get_price_option_name( $cart_item['id'], $price_id );
if ( ! empty( $option_name ) ) {
$product_name .= ' — ' . $option_name;
}
}
$order_item_args = array(
'order_id' => $this->ID,
'product_id' => $cart_item['id'],
'product_name' => $product_name,
'price_id' => $price_id,
'cart_index' => $key,
'type' => 'download',
'status' => $this->status,
'quantity' => ! empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1,
'amount' => ! empty( $cart_item['item_price'] ) ? (float) $cart_item['item_price'] : (float) $cart_item['price'],
'subtotal' => (float) $cart_item['subtotal'],
'discount' => ! empty( $cart_item['discount'] ) ? (float) $cart_item['discount'] : 0.00,
'tax' => $cart_item['tax'],
'total' => (float) $cart_item['price'],
'date_created' => $this->payment->post_date_gmt,
'date_modified' => $this->payment->post_modified_gmt,
);
$order_items[] = new EDD\Orders\Order_Item( $order_item_args );
}
return $order_items;
}
/**
* Retrieves a valid price ID for a given cart item.
* If the product does not have variable prices, then `null` is always returned.
* If the supplied price ID does not match a price ID that actually exists, then the default
* variable price is returned instead of the supplied one.
*
* @since 3.0
*
* @param array $cart_item Array of cart item details.
*
* @return int|null
*/
private function get_valid_price_id_for_cart_item( $cart_item ) {
// If the product doesn't have variable prices, just return `null`.
if ( ! edd_has_variable_prices( $cart_item['id'] ) ) {
return null;
}
$variable_prices = edd_get_variable_prices( $cart_item['id'] );
if ( ! is_array( $variable_prices ) || empty( $variable_prices ) ) {
return null;
}
// Get the price ID that's set to the cart item right now.
$price_id = isset( $cart_item['item_number']['options']['price_id'] ) && is_numeric( $cart_item['item_number']['options']['price_id'] )
? absint( $cart_item['item_number']['options']['price_id'] )
: null;
// Now let's confirm it's actually a valid price ID.
$variable_price_ids = array_map( 'intval', array_column( $variable_prices, 'index' ) );
return in_array( $price_id, $variable_price_ids, true ) ? $price_id : edd_get_default_variable_price( $cart_item['id'] );
}
}