type ) { return; } $completed_date = empty( $order->date_completed ) ? null : $order->date_completed; $customer_id = $order->customer_id; $amount = $order->total; $order_items = $order->items; do_action( 'edd_pre_complete_purchase', $order_id ); if ( is_array( $order_items ) ) { // Increase purchase count and earnings. foreach ( $order_items as $item ) { // "bundle" or "default" $download_type = edd_get_download_type( $item->product_id ); // Increase earnings and fire actions once per quantity number. for ( $i = 0; $i < $item->quantity; $i++ ) { // Ensure these actions only run once, ever. if ( empty( $completed_date ) ) { // For backwards compatibility purposes, we need to construct an array and pass it // to edd_complete_download_purchase. $item_fees = array(); foreach ( $item->get_fees() as $key => $item_fee ) { /** @var EDD\Orders\Order_Adjustment $item_fee */ $download_id = $item->product_id; $price_id = $item->price_id; $no_tax = (bool) 0.00 === $item_fee->tax; $id = is_null( $item_fee->type_key ) ? $item_fee->id : $item_fee->type_key; if ( array_key_exists( $id, $item_fees ) ) { $id .= '_2'; } $item_fees[ $id ] = array( 'amount' => $item_fee->amount, 'label' => $item_fee->description, 'no_tax' => $no_tax ? $no_tax : false, 'type' => 'fee', 'download_id' => $download_id, 'price_id' => $price_id ? $price_id : null, ); } $item_options = array( 'quantity' => $item->quantity, 'price_id' => $item->price_id, ); /* * For backwards compatibility from pre-3.0: add in order item meta prefixed with `_option_`. * While saving, we've migrated these values to order item meta, but people may still be looking * for them in this cart details array, so we need to fill them back in. */ $order_item_meta = edd_get_order_item_meta( $item->id ); if ( ! empty( $order_item_meta ) ) { foreach ( $order_item_meta as $item_meta_key => $item_meta_value ) { if ( '_option_' === substr( $item_meta_key, 0, 8 ) && isset( $item_meta_value[0] ) ) { $item_options[ str_replace( '_option_', '', $item_meta_key ) ] = $item_meta_value[0]; } } } $cart_details = array( 'name' => $item->product_name, 'id' => $item->product_id, 'item_number' => array( 'id' => $item->product_id, 'quantity' => $item->quantity, 'options' => $item_options, ), 'item_price' => $item->amount, 'quantity' => $item->quantity, 'discount' => $item->discount, 'subtotal' => $item->subtotal, 'tax' => $item->tax, 'fees' => $item_fees, 'price' => $item->amount, ); do_action( 'edd_complete_download_purchase', $item->product_id, $order_id, $download_type, $cart_details, $item->cart_index ); } } } // Clear the total earnings cache delete_transient( 'edd_earnings_total' ); delete_transient( 'edd_earnings_total_without_tax' ); // Clear the This Month earnings (this_monththis_month is NOT a typo) delete_transient( md5( 'edd_earnings_this_monththis_month' ) ); delete_transient( md5( 'edd_earnings_todaytoday' ) ); } // Increase the customer's purchase stats $customer = new EDD_Customer( $customer_id ); $customer->recalculate_stats(); edd_increase_total_earnings( $amount ); // Check for discount codes and increment their use counts $discounts = $order->get_discounts(); foreach ( $discounts as $adjustment ) { /** @var EDD\Orders\Order_Adjustment $adjustment */ edd_increase_discount_usage( $adjustment->description ); } // Ensure this action only runs once ever if ( empty( $completed_date ) ) { $date = EDD()->utils->date()->format( 'mysql' ); $date_refundable = edd_get_refund_date( $date ); $date_refundable = false === $date_refundable ? '' : $date_refundable; // Save the completed date edd_update_order( $order_id, array( 'date_completed' => $date, 'date_refundable' => $date_refundable, ) ); // Required for backwards compatibility. $payment = edd_get_payment( $order_id ); /** * Runs **when** a purchase is marked as "complete". * * @since 2.8 Added EDD_Payment and EDD_Customer object to action. * * @param int $order_id Payment ID. * @param EDD_Payment $payment EDD_Payment object containing all payment data. * @param EDD_Customer $customer EDD_Customer object containing all customer data. */ do_action( 'edd_complete_purchase', $order_id, $payment, $customer ); // If cron doesn't work on a site, allow the filter to use __return_false and run the events immediately. $use_cron = apply_filters( 'edd_use_after_payment_actions', true, $order_id ); if ( false === $use_cron ) { /** * Runs **after** a purchase is marked as "complete". * * @see edd_process_after_payment_actions() * * @since 2.8 - Added EDD_Payment and EDD_Customer object to action. * * @param int $order_id Payment ID. * @param EDD_Payment $payment EDD_Payment object containing all payment data. * @param EDD_Customer $customer EDD_Customer object containing all customer data. */ do_action( 'edd_after_payment_actions', $order_id, $payment, $customer ); } } // Empty the shopping cart edd_empty_cart(); } add_action( 'edd_update_payment_status', 'edd_complete_purchase', 100, 3 ); /** * Schedules the one time event via WP_Cron to fire after purchase actions. * * Is run on the edd_complete_purchase action. * * @since 2.8 * @param $payment_id */ function edd_schedule_after_payment_action( $payment_id ) { $use_cron = apply_filters( 'edd_use_after_payment_actions', true, $payment_id ); if ( $use_cron ) { $after_payment_delay = apply_filters( 'edd_after_payment_actions_delay', 30, $payment_id ); // Use time() instead of current_time( 'timestamp' ) to avoid scheduling the event in the past when server time // and WordPress timezone are different. wp_schedule_single_event( time() + $after_payment_delay, 'edd_after_payment_scheduled_actions', array( $payment_id, false ) ); } } add_action( 'edd_complete_purchase', 'edd_schedule_after_payment_action', 10, 1 ); /** * Executes the one time event used for after purchase actions. * * @since 2.8 * @since 3.1.0.4 This also verifies that all order items have the synced status as the order. * @param $payment_id * @param $force */ function edd_process_after_payment_actions( $payment_id = 0, $force = false ) { if ( empty( $payment_id ) ) { return; } $payment = new EDD_Payment( $payment_id ); $has_fired = $payment->get_meta( '_edd_complete_actions_run' ); if ( ! empty( $has_fired ) && false === $force ) { return; } /** * In the event that during the order completion process, a timeout happens, * ensure that all the order items have the correct status, to match the order itself. * * @see https://github.com/awesomemotive/easy-digital-downloads-pro/issues/77 */ $order_items = edd_get_order_items( array( 'order_id' => $payment_id, 'status__not_in' => edd_get_deliverable_order_item_statuses(), 'number' => 200, ) ); if ( ! empty( $order_items ) ) { foreach ( $order_items as $order_item ) { edd_update_order_item( $order_item->id, array( 'status' => $payment->status, ) ); } } $payment->add_note( __( 'After payment actions processed.', 'easy-digital-downloads' ) ); $payment->update_meta( '_edd_complete_actions_run', time() ); // This is in GMT do_action( 'edd_after_payment_actions', $payment_id, $payment, new EDD_Customer( $payment->customer_id ) ); } add_action( 'edd_after_payment_scheduled_actions', 'edd_process_after_payment_actions', 10, 1 ); /** * Updates week-old+ 'pending' orders to 'abandoned' * * This function is only intended to be used by WordPress cron. * * @since 1.6 * @return void */ function edd_mark_abandoned_orders() { // Bail if not in WordPress cron if ( ! edd_doing_cron() ) { return; } $args = array( 'status' => 'pending', 'number' => 9999999, 'output' => 'edd_payments', ); add_filter( 'posts_where', 'edd_filter_where_older_than_week' ); $payments = edd_get_payments( $args ); remove_filter( 'posts_where', 'edd_filter_where_older_than_week' ); if( $payments ) { foreach( $payments as $payment ) { if( 'pending' === $payment->post_status ) { $payment->status = 'abandoned'; $payment->save(); } } } } add_action( 'edd_weekly_scheduled_events', 'edd_mark_abandoned_orders' ); /** * Process an attempt to complete a recoverable payment. * * @since 2.7 * @return void */ function edd_recover_payment() { if ( empty( $_GET['payment_id'] ) ) { return; } $payment = new EDD_Payment( $_GET['payment_id'] ); if ( $payment->ID !== (int) $_GET['payment_id'] ) { return; } if ( ! $payment->is_recoverable() ) { return; } if ( // Logged in, but wrong user ID ( is_user_logged_in() && $payment->user_id != get_current_user_id() ) // ...OR... || // Logged out, but payment is for a user ( ! is_user_logged_in() && ! empty( $payment->user_id ) ) ) { $redirect = get_permalink( edd_get_option( 'purchase_history_page' ) ); edd_set_error( 'edd-payment-recovery-user-mismatch', __( 'Error resuming payment.', 'easy-digital-downloads' ) ); edd_redirect( $redirect ); } $payment->add_note( __( 'Payment recovery triggered URL', 'easy-digital-downloads' ) ); // Empty out the cart. EDD()->cart->empty_cart(); // Recover any downloads. foreach ( $payment->cart_details as $download ) { edd_add_to_cart( $download['id'], $download['item_number']['options'] ); // Recover any item specific fees. if ( ! empty( $download['fees'] ) ) { foreach ( $download['fees'] as $key => $fee ) { $fee['id'] = ! empty( $fee['id'] ) ? $fee['id'] : $key; EDD()->fees->add_fee( $fee ); } } } // Recover any global fees. foreach ( $payment->fees as $key => $fee ) { $fee['id'] = ! empty( $fee['id'] ) ? $fee['id'] : $key; EDD()->fees->add_fee( $fee ); } // Recover any discounts. if ( 'none' !== $payment->discounts && ! empty( $payment->discounts ) ){ $discounts = ! is_array( $payment->discounts ) ? explode( ',', $payment->discounts ) : $payment->discounts; foreach ( $discounts as $discount ) { edd_set_cart_discount( $discount ); } } EDD()->session->set( 'edd_resume_payment', $payment->ID ); $redirect_args = array( 'payment-mode' => urlencode( $payment->gateway ) ); $redirect = add_query_arg( $redirect_args, edd_get_checkout_uri() ); edd_redirect( $redirect ); } add_action( 'edd_recover_payment', 'edd_recover_payment' ); /** * If the payment trying to be recovered has a User ID associated with it, be sure it's the same user. * * @since 2.7 * @return void */ function edd_recovery_user_mismatch() { if ( ! edd_is_checkout() ) { return; } $resuming_payment = EDD()->session->get( 'edd_resume_payment' ); if ( $resuming_payment ) { $payment = new EDD_Payment( $resuming_payment ); if ( is_user_logged_in() && $payment->user_id != get_current_user_id() ) { edd_empty_cart(); edd_set_error( 'edd-payment-recovery-user-mismatch', __( 'Error resuming payment.', 'easy-digital-downloads' ) ); edd_redirect( get_permalink( edd_get_option( 'purchase_page' ) ) ); } } } add_action( 'template_redirect', 'edd_recovery_user_mismatch' ); /** * If the payment trying to be recovered has a User ID associated with it, we need them to log in. * * @since 2.7 * @return void */ function edd_recovery_force_login_fields() { $resuming_payment = EDD()->session->get( 'edd_resume_payment' ); if ( $resuming_payment ) { $payment = new EDD_Payment( $resuming_payment ); $requires_login = edd_no_guest_checkout(); if ( ( $requires_login && ! is_user_logged_in() ) && ( $payment->user_id > 0 && ( ! is_user_logged_in() ) ) ) { ?>

session->get( 'edd_resume_payment' ); if ( $resuming_payment ) { $payment = new EDD_Payment( $resuming_payment ); $same_user = ! empty( $payment->user_id ) && ( is_user_logged_in() && $payment->user_id == get_current_user_id() ); $same_email = strtolower( $payment->email ) === strtolower( $post_data['edd_email'] ); if ( ( is_user_logged_in() && ! $same_user ) || ( ! is_user_logged_in() && (int) $payment->user_id > 0 && ! $same_email ) ) { edd_set_error( 'recovery_requires_login', __( 'To complete this payment, please login to your account.', 'easy-digital-downloads' ) ); } } } add_action( 'edd_checkout_error_checks', 'edd_recovery_verify_logged_in', 10, 2 );