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'] ); } }