JezK
Edit File: class-wc-stripe-order.php
<?php use Automattic\WooCommerce\Enums\OrderStatus; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class WC_Stripe_Order * * Wrapper for the original WC_Order class to allow custom getters and setter with the extension's specific metadata. * * @deprecated Since 9.6.0. This class will be removed in a later version of the extension. */ class WC_Stripe_Order extends WC_Order { /** * Meta key for the Stripe source ID. * * @var string */ const META_STRIPE_SOURCE_ID = '_stripe_source_id'; /** * Meta key for the Stripe charge ID. * * @var string */ const META_STRIPE_CHARGE_ID = '_transaction_id'; /** * Meta key for the Stripe refund ID. * * @var string */ const META_STRIPE_REFUND_ID = '_stripe_refund_id'; /** * Meta key for the Stripe Payment Intent ID. * * @var string */ const META_STRIPE_INTENT_ID = '_stripe_intent_id'; /** * Meta key for the Stripe Setup Intent ID. * * @var string */ const META_STRIPE_SETUP_INTENT = '_stripe_setup_intent'; /** * Meta key for the Stripe fee amount. */ const META_STRIPE_FEE = '_stripe_fee'; /** * Meta key for the Stripe net amount. */ const META_STRIPE_NET = '_stripe_net'; /** * Meta key for the Stripe currency. * * @var string */ const META_STRIPE_CURRENCY = '_stripe_currency'; /** * Meta key for the payment awaiting action flag. * * @var string */ const META_STRIPE_PAYMENT_AWAITING_ACTION = '_stripe_payment_awaiting_action'; /** * Meta key for the Stripe card brand. * * @var string */ const META_STRIPE_CARD_BRAND = '_stripe_card_brand'; /** * Meta key for the Stripe lock refund. * * @var string */ const META_STRIPE_LOCK_REFUND = '_stripe_lock_refund'; /** * Meta key for the Stripe lock payment. * * @var string */ const META_STRIPE_LOCK_PAYMENT = '_stripe_lock_payment'; /** * Meta key for the redirect processed flag. * * @var string */ const META_STRIPE_UPE_REDIRECT_PROCESSED = '_stripe_upe_redirect_processed'; /** * Meta key for the status before hold. * * @var string */ const META_STRIPE_STATUS_BEFORE_HOLD = '_stripe_status_before_hold'; /** * Meta key for the UPE waiting for redirect flag. * * @var string */ const META_STRIPE_UPE_WAITING_FOR_REDIRECT = '_stripe_upe_waiting_for_redirect'; /** * Meta key for the mandate ID. * * @var string */ const META_STRIPE_MANDATE_ID = '_stripe_mandate_id'; /** * Meta key for the customer ID. * * @var string */ const META_STRIPE_CUSTOMER_ID = '_stripe_customer_id'; /** * Meta key for the charge captured flag. * * @var string */ const META_STRIPE_CHARGE_CAPTURED = '_stripe_charge_captured'; /** * Meta key to identify whether the status is final. * * @var string */ const META_STRIPE_STATUS_FINAL = '_stripe_status_final'; /** * Meta key for the Multibanco data. * * @var string */ const META_STRIPE_MULTIBANCO = '_stripe_multibanco'; /** * Meta key for the UPE payment type. * * @var string */ const META_STRIPE_UPE_PAYMENT_TYPE = '_stripe_upe_payment_type'; /** * The lock refund expiration time. * * @var int */ const REFUND_LOCK_EXPIRATION = 5 * MINUTE_IN_SECONDS; /** * The lock payment expiration time. * * @var int */ const PAYMENT_LOCK_EXPIRATION = 5 * MINUTE_IN_SECONDS; /** * Converts an order into WC_Stripe_Order if it is not already. * * @param $order WC_Stripe_Order|WC_Order Order object. * @return WC_Stripe_Order */ public static function to_instance( $order ) { return $order instanceof WC_Stripe_Order ? $order : new self( $order ); } /** * Wrapper to create an order using the extension's custom WC_Stripe_Order class. * * @param $order_data array Order data. * @return bool|WC_Stripe_Order */ public static function create( $order_data ) { $order = wc_create_order( $order_data ); if ( ! $order ) { return false; } return self::to_instance( $order ); } /** * Wrapper to return an order using the extension's custom WC_Stripe_Order class. * * @param $order_id int Order ID. * @return bool|WC_Stripe_Order */ public static function get_by_id( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return false; } return self::to_instance( $order ); } /** * Wrapper to get orders using the extension's custom WC_Stripe_Order class. * * @param $args array Arguments to pass to wc_get_orders. * @return array|WC_Stripe_Order[] */ public static function query( $args ) { $orders = wc_get_orders( $args ); if ( empty( $orders ) ) { return []; } return array_map( function ( $order ) { return self::to_instance( $order ); }, $orders ); } /** * Gets the order by Stripe source ID. * * @param string $source_id */ public static function get_by_source_id( $source_id ) { return self::get_by_meta( self::META_STRIPE_SOURCE_ID, $source_id ); } /** * Gets the order by Stripe charge ID. * * @param string $charge_id */ public static function get_by_charge_id( $charge_id ) { return self::get_by_meta( self::META_STRIPE_CHARGE_ID, $charge_id ); } /** * Gets the order by Stripe refund ID. * * @param string $refund_id */ public static function get_by_refund_id( $refund_id ) { return self::get_by_meta( self::META_STRIPE_REFUND_ID, $refund_id ); } /** * Gets the order by Stripe PaymentIntent ID. * * @param string $intent_id The ID of the intent. * @return WC_Order|bool Either an order or false when not found. */ public static function get_by_intent_id( $intent_id ) { return self::get_by_meta( self::META_STRIPE_INTENT_ID, $intent_id ); } /** * Gets the order by Stripe SetupIntent ID. * * @param string $intent_id The ID of the intent. * @return WC_Order|bool Either an order or false when not found. */ public static function get_by_setup_intent_id( $intent_id ) { return self::get_by_meta( self::META_STRIPE_SETUP_INTENT, $intent_id ); } /** * Get owner details. * * @return object $details */ public function get_owner_details() { $billing_first_name = $this->get_billing_first_name(); $billing_last_name = $this->get_billing_last_name(); $details = []; $name = $billing_first_name . ' ' . $billing_last_name; $email = $this->get_billing_email(); $phone = $this->get_billing_phone(); if ( ! empty( $phone ) ) { $details['phone'] = $phone; } if ( ! empty( $name ) ) { $details['name'] = $name; } if ( ! empty( $email ) ) { $details['email'] = $email; } $details['address']['line1'] = $this->get_billing_address_1(); $details['address']['line2'] = $this->get_billing_address_2(); $details['address']['state'] = $this->get_billing_state(); $details['address']['city'] = $this->get_billing_city(); $details['address']['postal_code'] = $this->get_billing_postcode(); $details['address']['country'] = $this->get_billing_country(); return (object) apply_filters( 'wc_stripe_owner_details', $details, $this ); } /** * Validates that the order meets the minimum order amount * set by Stripe. * * @throws WC_Stripe_Exception If the order does not meet the minimum amount. */ public function validate_minimum_amount() { if ( $this->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) { /* translators: 1) amount (including currency symbol) */ throw new WC_Stripe_Exception( 'Did not meet minimum amount', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) ); } } /** * Adds payment intent id and order note to order if payment intent is not already saved * * @param $payment_intent_id string The payment intent id to add to the order. */ public function add_payment_intent_to_order( $payment_intent_id ) { $old_intent_id = $this->get_intent_id(); if ( $old_intent_id === $payment_intent_id ) { return; } $this->add_order_note( sprintf( /* translators: $1%s payment intent ID */ __( 'Stripe payment intent created (Payment Intent ID: %1$s)', 'woocommerce-gateway-stripe' ), $payment_intent_id ) ); $this->set_intent_id( $payment_intent_id ); $this->save(); } /** * Gets the Stripe fee for order. With legacy check. * * @return string $amount */ public function get_fee() { $amount = $this->get_meta( self::META_STRIPE_FEE ); // If not found let's check for legacy name. if ( empty( $amount ) ) { $amount = $this->get_meta( 'Stripe Fee' ); // If found update to new name. if ( $amount ) { $this->set_fee( $amount ); } } return $amount; } /** * Updates the Stripe fee for order. * * @param float $amount */ public function set_fee( $amount = 0.0 ) { $this->update_meta_data( self::META_STRIPE_FEE, $amount ); } /** * Deletes the Stripe fee for order. */ public function delete_fee() { $this->delete_meta_data( self::META_STRIPE_FEE ); $this->delete_meta_data( 'Stripe Fee' ); } /** * Gets the Stripe net for order. With legacy check. * * @return string $amount */ public function get_net() { $amount = $this->get_meta( self::META_STRIPE_NET, true ); // If not found let's check for legacy name. if ( empty( $amount ) ) { $amount = $this->get_meta( 'Net Revenue From Stripe', true ); // If found update to new name. if ( $amount ) { $this->set_net( $amount ); } } return $amount; } /** * Updates the Stripe net for order. * * @param float $amount */ public function set_net( $amount = 0.0 ) { $this->update_meta_data( self::META_STRIPE_NET, $amount ); } /** * Deletes the Stripe net for order. */ public function delete_net() { $this->delete_meta_data( self::META_STRIPE_NET ); $this->delete_meta_data( 'Net Revenue From Stripe' ); } /** * Gets the Stripe currency for order. * * @return string $currency */ public function get_stripe_currency() { return $this->get_meta( self::META_STRIPE_CURRENCY ); } /** * Updates the Stripe currency for order. * * @param string $currency */ public function set_stripe_currency( $currency ) { $this->update_meta_data( self::META_STRIPE_CURRENCY, $currency ); } /** * Adds metadata to the order to indicate that the payment is awaiting action. * * This meta is primarily used to prevent orders from being cancelled by WooCommerce's hold stock settings. * * @return void */ public function set_payment_awaiting_action( $save = true ) { $this->update_meta_data( self::META_STRIPE_PAYMENT_AWAITING_ACTION, wc_bool_to_string( true ) ); if ( $save ) { $this->save(); } } /** * Gets the metadata that indicates that the payment is awaiting action. * * @return bool Whether the payment is awaiting action. */ public function is_payment_awaiting_action() { return wc_string_to_bool( $this->get_meta( self::META_STRIPE_PAYMENT_AWAITING_ACTION ) ); } /** * Removes the metadata from the order that was used to indicate that the payment was awaiting action. * * @param bool $save Whether to save the order after removing the metadata. * * @return void */ public function remove_payment_awaiting_action( $save = true ) { $this->delete_meta_data( self::META_STRIPE_PAYMENT_AWAITING_ACTION ); if ( $save ) { $this->save(); } } /** * Set the preferred card brand. * * @param $brand string The brand to set. * @return void */ public function set_card_brand( $brand ) { $this->update_meta_data( self::META_STRIPE_CARD_BRAND, $brand ); } /** * Get the preferred card brand. * * @return string */ public function get_card_brand() { return $this->get_meta( self::META_STRIPE_CARD_BRAND ); } /** * Locks an order for refund processing for 5 minutes. * * @return bool A flag that indicates whether the order is already locked. */ public function lock_refund() { $this->read_meta_data( true ); $existing_lock = $this->get_lock_refund(); if ( $existing_lock ) { $expiration = (int) $existing_lock; // If the lock is still active, return true. if ( time() <= $expiration ) { return true; } } $new_lock = time() + self::REFUND_LOCK_EXPIRATION; $this->set_lock_refund( $new_lock ); $this->save_meta_data(); return false; } /** * Unlocks an order for processing refund. */ public function unlock_refund() { $this->delete_meta_data( self::META_STRIPE_LOCK_REFUND ); $this->save_meta_data(); } /** * Set the lock refund time. * * @param $time int The time to set. * @return void */ public function set_lock_refund( $time ) { $this->update_meta_data( self::META_STRIPE_LOCK_REFUND, $time ); } /** * Get the lock refund time. * * @return int */ public function get_lock_refund() { return $this->get_meta( self::META_STRIPE_LOCK_REFUND ); } /** * Set the setup intent. * * @param $value string The value to set. * @return void */ public function set_setup_intent( $value ) { $this->update_meta_data( self::META_STRIPE_SETUP_INTENT, $value ); } /** * Get the setup intent. * * @return string */ public function get_setup_intent() { return $this->get_meta( self::META_STRIPE_SETUP_INTENT ); } /** * Set the UPE redirect processed flag. * * @param $value bool The value to set. * @return void */ public function set_upe_redirect_processed( $value ) { $this->update_meta_data( self::META_STRIPE_UPE_REDIRECT_PROCESSED, $value ); } /** * Whether the UPE redirect has been processed. * * @return bool The value of the flag. */ public function is_upe_redirect_processed() { return (bool) $this->get_meta( self::META_STRIPE_UPE_REDIRECT_PROCESSED ); } /** * Stores the status of the order before being put on hold in metadata. * * @param string $status The order status to store. Accepts 'default_payment_complete' which will fetch the default status for payment complete orders. * @return void */ public function set_status_before_hold( $status ) { if ( 'default_payment_complete' === $status ) { $payment_complete_status = $this->needs_processing() ? OrderStatus::PROCESSING : OrderStatus::COMPLETED; $status = apply_filters( 'woocommerce_payment_complete_order_status', $payment_complete_status, $this->get_id(), $this ); } $this->update_meta_data( '_stripe_status_before_hold', $status ); } /** * Helper method to retrieve the status of the order before it was put on hold. * * @return string The status of the order before it was put on hold. */ public function get_status_before_hold() { $before_hold_status = $this->get_meta( self::META_STRIPE_STATUS_BEFORE_HOLD ); if ( ! empty( $before_hold_status ) ) { return $before_hold_status; } $default_before_hold_status = $this->needs_processing() ? OrderStatus::PROCESSING : OrderStatus::COMPLETED; return apply_filters( 'woocommerce_payment_complete_order_status', $default_before_hold_status, $this->get_id(), $this ); } /** * Set the UPE waiting for redirect flag. * * @param $value bool The value to set. * @return void */ public function set_upe_waiting_for_redirect( $value ) { $this->update_meta_data( self::META_STRIPE_UPE_WAITING_FOR_REDIRECT, $value ); } /** * Whether the UPE payment is waiting for redirect. * * @return bool */ public function is_upe_waiting_for_redirect() { return (bool) $this->get_meta( self::META_STRIPE_UPE_WAITING_FOR_REDIRECT ); } /** * Set the mandate ID. * * @param $mandate_id string The mandate ID to set. * @return void */ public function set_mandate_id( $mandate_id ) { $this->update_meta_data( self::META_STRIPE_MANDATE_ID, $mandate_id ); } /** * Get the mandate ID. * * @return string */ public function get_mandate_id() { return $this->get_meta( self::META_STRIPE_MANDATE_ID ); } /** * Locks an order for payment intent processing for 5 minutes. * * @param stdClass $intent The intent that is being processed. * @return bool A flag that indicates whether the order is already locked. */ public function lock_payment( $intent = null ) { $this->read_meta_data( true ); $existing_lock = $this->get_lock_payment(); if ( $existing_lock ) { $parts = explode( '|', $existing_lock ); // Format is: "{expiry_timestamp}" or "{expiry_timestamp}|{pi_xxxx}" if an intent is passed. $expiration = (int) $parts[0]; $locked_intent = ! empty( $parts[1] ) ? $parts[1] : ''; // If the lock is still active, return true. if ( time() <= $expiration && ( empty( $intent ) || empty( $locked_intent ) || ( $intent->id ?? '' ) === $locked_intent ) ) { return true; } } $new_lock = ( time() + self::PAYMENT_LOCK_EXPIRATION ) . ( isset( $intent->id ) ? '|' . $intent->id : '' ); $this->set_lock_payment( $new_lock ); $this->save_meta_data(); return false; } /** * Unlocks an order for processing by payment intents. */ public function unlock_payment() { $this->delete_meta_data( self::META_STRIPE_LOCK_PAYMENT ); $this->save_meta_data(); } /** * Set the lock payment time. * * @param $time int The time to set. * @return void */ public function set_lock_payment( $time ) { $this->update_meta_data( self::META_STRIPE_LOCK_PAYMENT, $time ); } /** * Get the lock payment time. * * @return int */ public function get_lock_payment() { return $this->get_meta( self::META_STRIPE_LOCK_PAYMENT ); } /** * Set the refund ID. * * @param $refund_id string The refund ID to set. * @return void */ public function set_refund_id( $refund_id ) { $this->update_meta_data( self::META_STRIPE_REFUND_ID, $refund_id ); } /** * Get the refund ID. * * @return string */ public function get_refund_id() { return $this->get_meta( self::META_STRIPE_REFUND_ID ); } /** * Set the Multibanco data. * * @param $data array The Multibanco data to set. * @return void */ public function set_multibanco_data( $data ) { $this->update_meta_data( self::META_STRIPE_MULTIBANCO, $data ); } /** * Get the Multibanco data. * * @return array */ public function get_multibanco_data() { return $this->get_meta( self::META_STRIPE_MULTIBANCO ); } /** * Set the Stripe intent ID. * * @param $intent_id string The intent ID to set. * @return void */ public function set_intent_id( $intent_id ) { $this->update_meta_data( self::META_STRIPE_INTENT_ID, $intent_id ); } /** * Get the Stripe intent ID. * * @return string */ public function get_intent_id() { return $this->get_meta( self::META_STRIPE_INTENT_ID ); } /** * Set the UPE payment type. * * @param $payment_type string The payment type to set. * @return void */ public function set_upe_payment_type( $payment_type ) { $this->update_meta_data( self::META_STRIPE_UPE_PAYMENT_TYPE, $payment_type ); } /** * Get the UPE payment type. * * @return string */ public function get_upe_payment_type() { return $this->get_meta( self::META_STRIPE_UPE_PAYMENT_TYPE ); } /** * Set the Stripe source ID. * * @param $source_id string The Stripe source ID. * @return void */ public function set_source_id( $source_id ) { $this->update_meta_data( self::META_STRIPE_SOURCE_ID, $source_id ); } /** * Get the Stripe source ID. * * @return string */ public function get_source_id() { return $this->get_meta( self::META_STRIPE_SOURCE_ID ); } /** * Set the Stripe customer ID. * * @param $customer_id string The Stripe customer ID. * @return void */ public function set_stripe_customer_id( $customer_id ) { $this->update_meta_data( self::META_STRIPE_CUSTOMER_ID, $customer_id ); } /** * Get the Stripe customer ID. * * @return string */ public function get_stripe_customer_id() { return $this->get_meta( self::META_STRIPE_CUSTOMER_ID ); } /** * Set the charge captured flag. * * @param $value string The value to set. * @return void */ public function set_charge_captured( $value ) { $this->update_meta_data( self::META_STRIPE_CHARGE_CAPTURED, $value ); } /** * Whether the charge has been captured. * * @return bool */ public function is_charge_captured() { return wc_string_to_bool( $this->get_meta( self::META_STRIPE_CHARGE_CAPTURED ) ); } /** * Set the status final value. * * @param $value bool The value to set. * @return void */ public function set_status_final( $value ) { $this->update_meta_data( self::META_STRIPE_STATUS_FINAL, $value ); } /** * Whether the current order status is final. * * @return bool */ public function is_status_final() { return (bool) $this->get_meta( self::META_STRIPE_STATUS_FINAL ); } /** * Queries for an order by a specific meta key and value. * * @param $meta_key string The meta key to search for. * @param $meta_value string The meta value to search for. * @return bool|WC_Stripe_Order */ private static function get_by_meta( $meta_key, $meta_value ) { global $wpdb; if ( WC_Stripe_Woo_Compat_Utils::is_custom_orders_table_enabled() ) { $params = [ 'limit' => 1 ]; // Check if the meta key is a transaction ID. If so, use the transaction ID to query the order, instead of the meta when HPOS is enabled. if ( self::META_STRIPE_CHARGE_ID === $meta_key ) { $params['transaction_id'] = $meta_value; } else { $params['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query [ 'key' => $meta_key, 'value' => $meta_value, ], ]; } $orders = self::query( $params ); $order_id = current( $orders ) ? current( $orders )->get_id() : false; } else { $order_id = $wpdb->get_var( $wpdb->prepare( "SELECT DISTINCT ID FROM $wpdb->posts as posts LEFT JOIN $wpdb->postmeta as meta ON posts.ID = meta.post_id WHERE meta.meta_value = %s AND meta.meta_key = %s", $meta_value, $meta_key ) ); } if ( ! empty( $order_id ) ) { $order = self::get_by_id( $order_id ); } if ( ! empty( $order ) && $order->get_status() !== OrderStatus::TRASH ) { return $order; } return false; } }