/**
* Setup the cart for paying for a delayed initial payment for a subscription.
*
* @since 2.0
*/
public function maybe_setup_cart()
{
global $wp;
if (isset($_GET['pay_for_order']) && isset($_GET['key']) && isset($wp->query_vars['order-pay'])) {
// Pay for existing order
$order_key = $_GET['key'];
$order_id = isset($wp->query_vars['order-pay']) ? $wp->query_vars['order-pay'] : absint($_GET['order_id']);
$order = wc_get_order($wp->query_vars['order-pay']);
if ($order->order_key == $order_key && $order->has_status(array('pending', 'failed')) && !wcs_order_contains_subscription($order, array('renewal', 'resubscribe'))) {
$subscriptions = wcs_get_subscriptions_for_order($order, array('order_type' => 'parent'));
if (get_current_user_id() !== $order->get_user_id()) {
wc_add_notice(__('That doesn\'t appear to be your order.', 'woocommerce-subscriptions'), 'error');
wp_safe_redirect(get_permalink(wc_get_page_id('myaccount')));
exit;
} elseif (!empty($subscriptions)) {
// Setup cart with all the original order's line items
$this->setup_cart($order, array('order_id' => $order_id));
WC()->session->set('order_awaiting_payment', $order_id);
// Set cart hash for orders paid in WC >= 2.6
$this->set_cart_hash($order_id);
wp_safe_redirect(WC()->cart->get_checkout_url());
exit;
}
}
}
}
/**
* Create subscriptions purchased on checkout.
*
* @param int $order_id The post_id of a shop_order post/WC_Order object
* @param array $posted_data The data posted on checkout
* @since 2.0
*/
public static function process_checkout($order_id, $posted_data)
{
if (!WC_Subscriptions_Cart::cart_contains_subscription()) {
return;
}
$order = new WC_Order($order_id);
$subscriptions = array();
// First clear out any subscriptions created for a failed payment to give us a clean slate for creating new subscriptions
$subscriptions = wcs_get_subscriptions_for_order($order->id, array('order_type' => 'parent'));
if (!empty($subscriptions)) {
remove_action('before_delete_post', 'WC_Subscriptions_Manager::maybe_cancel_subscription');
foreach ($subscriptions as $subscription) {
wp_delete_post($subscription->id);
}
add_action('before_delete_post', 'WC_Subscriptions_Manager::maybe_cancel_subscription');
}
WC_Subscriptions_Cart::set_global_recurring_shipping_packages();
// Create new subscriptions for each group of subscription products in the cart (that is not a renewal)
foreach (WC()->cart->recurring_carts as $recurring_cart) {
$subscription = self::create_subscription($order, $recurring_cart);
// Exceptions are caught by WooCommerce
if (is_wp_error($subscription)) {
throw new Exception($subscription->get_error_message());
}
do_action('woocommerce_checkout_subscription_created', $subscription, $order, $recurring_cart);
}
do_action('subscriptions_created_for_order', $order);
// Backward compatibility
}
/**
* Updates other subscription sources.
*/
protected function save_source($order, $source)
{
parent::save_source($order, $source);
// Also store it on the subscriptions being purchased or paid for in the order
if (wcs_order_contains_subscription($order->id)) {
$subscriptions = wcs_get_subscriptions_for_order($order->id);
} elseif (wcs_order_contains_renewal($order->id)) {
$subscriptions = wcs_get_subscriptions_for_renewal_order($order->id);
} else {
$subscriptions = array();
}
foreach ($subscriptions as $subscription) {
update_post_meta($subscription->id, '_stripe_customer_id', $source->customer);
update_post_meta($subscription->id, '_stripe_card_id', $source->source);
}
}
/**
* Clearing for orders / subscriptions with sanitizing bits
*
* @param $post_id integer the ID of an order / subscription
*/
public function purge_subscription_cache_on_update($post_id)
{
$post_type = get_post_type($post_id);
if ('shop_subscription' !== $post_type && 'shop_order' !== $post_type) {
return;
}
if ('shop_subscription' === $post_type) {
$this->log('ID is subscription, calling wcs_clear_related_order_cache for ' . $post_id);
$this->wcs_clear_related_order_cache($post_id);
} else {
$this->log('ID is order, getting subscription.');
$subscription = wcs_get_subscriptions_for_order($post_id);
if (empty($subscription)) {
$this->log('No sub for this ID: ' . $post_id);
return;
}
$subscription = array_shift($subscription);
$this->log('Got subscription, calling wcs_clear_related_order_cache for ' . $subscription->id);
$this->wcs_clear_related_order_cache($subscription->id);
}
}
/**
* Displays the renewal orders in the Related Orders meta box.
*
* @param object $post A WordPress post
* @since 2.0
*/
public static function output_rows($post)
{
$subscriptions = array();
$orders = array();
// On the subscription page, just show related orders
if (wcs_is_subscription($post->ID)) {
$subscriptions[] = wcs_get_subscription($post->ID);
} elseif (wcs_order_contains_subscription($post->ID, array('parent', 'renewal'))) {
$subscriptions = wcs_get_subscriptions_for_order($post->ID, array('order_type' => array('parent', 'renewal')));
}
// First, display all the subscriptions
foreach ($subscriptions as $subscription) {
$subscription->relationship = _x('Subscription', 'relation to order', 'woocommerce-subscriptions');
$orders[] = $subscription;
}
// Now, if we're on a single subscription or renewal order's page, display the parent orders
if (1 == count($subscriptions)) {
foreach ($subscriptions as $subscription) {
if (false !== $subscription->order) {
$subscription->order->relationship = _x('Parent Order', 'relation to order', 'woocommerce-subscriptions');
$orders[] = $subscription->order;
}
}
}
// Finally, display the renewal orders
foreach ($subscriptions as $subscription) {
foreach ($subscription->get_related_orders('all', 'renewal') as $order) {
$order->relationship = _x('Renewal Order', 'relation to order', 'woocommerce-subscriptions');
$orders[] = $order;
}
}
foreach ($orders as $order) {
if ($order->id == $post->ID) {
continue;
}
include 'views/html-related-orders-row.php';
}
}
/**
* Save payment meta to the Subscription object after a successful transaction,
* this is primarily used for the payment token and customer ID which are then
* copied over to a renewal order prior to payment processing.
*
* @since 4.1.0
* @param \WC_Order $order order
*/
public function save_payment_meta($order)
{
// a single order can contain multiple subscriptions
foreach (wcs_get_subscriptions_for_order($order->id) as $subscription) {
// payment token
if (!empty($order->payment->token)) {
update_post_meta($subscription->id, $this->get_gateway()->get_order_meta_prefix() . 'payment_token', $order->payment->token);
}
// customer ID
if (!empty($order->customer_id)) {
update_post_meta($subscription->id, $this->get_gateway()->get_order_meta_prefix() . 'customer_id', $order->customer_id);
}
}
}
/**
* Handle WC API requests where we need to run a reference transaction API operation
*
* @since 2.0
*/
public static function handle_wc_api()
{
if (!isset($_GET['action'])) {
return;
}
switch ($_GET['action']) {
// called when the customer is returned from PayPal after authorizing their payment, used for retrieving the customer's checkout details
case 'create_billing_agreement':
// bail if no token
if (!isset($_GET['token'])) {
return;
}
// get token to retrieve checkout details with
$token = esc_attr($_GET['token']);
try {
$express_checkout_details_response = self::get_api()->get_express_checkout_details($token);
// Make sure the billing agreement was accepted
if (1 == $express_checkout_details_response->get_billing_agreement_status()) {
$order = $express_checkout_details_response->get_order();
if (is_null($order)) {
throw new Exception(__('Unable to find order for PayPal billing agreement.', 'woocommerce-subscriptions'));
}
// we need to process an initial payment
if ($order->get_total() > 0 && !wcs_is_subscription($order)) {
$billing_agreement_response = self::get_api()->do_express_checkout($token, $order, array('payment_action' => 'Sale', 'payer_id' => $express_checkout_details_response->get_payer_id()));
} else {
$billing_agreement_response = self::get_api()->create_billing_agreement($token);
}
if ($billing_agreement_response->has_api_error()) {
throw new Exception($billing_agreement_response->get_api_error_message(), $billing_agreement_response->get_api_error_code());
}
// We're changing the payment method for a subscription, make sure we update it before updating the billing agreement ID so that an old PayPal subscription can be cancelled if the existing payment method is also PayPal
if (wcs_is_subscription($order)) {
WC_Subscriptions_Change_Payment_Gateway::update_payment_method($order, 'paypal');
$redirect_url = add_query_arg('utm_nooverride', '1', $order->get_view_order_url());
}
// Make sure PayPal is set as the payment method on the order and subscription
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$payment_method = isset($available_gateways[self::instance()->get_id()]) ? $available_gateways[self::instance()->get_id()] : false;
$order->set_payment_method($payment_method);
// Store the billing agreement ID on the order and subscriptions
wcs_set_paypal_id($order, $billing_agreement_response->get_billing_agreement_id());
foreach (wcs_get_subscriptions_for_order($order, array('order_type' => 'any')) as $subscription) {
$subscription->set_payment_method($payment_method);
wcs_set_paypal_id($subscription, $billing_agreement_response->get_billing_agreement_id());
}
if (!wcs_is_subscription($order)) {
if (0 == $order->get_total()) {
$order->payment_complete();
} else {
self::process_subscription_payment_response($order, $billing_agreement_response);
}
$redirect_url = add_query_arg('utm_nooverride', '1', $order->get_checkout_order_received_url());
}
// redirect customer to order received page
wp_safe_redirect(esc_url_raw($redirect_url));
} else {
wp_safe_redirect(WC()->cart->get_cart_url());
}
} catch (Exception $e) {
wc_add_notice(__('An error occurred, please try again or try an alternate form of payment.', 'woocommerce-subscriptions'), 'error');
wp_redirect(WC()->cart->get_cart_url());
}
exit;
case 'reference_transaction_account_check':
exit;
}
}
/**
* Checks an order to see if it contains a subscription.
*
* @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in.
* @param array|string $order_type Can include 'parent', 'renewal', 'resubscribe' and/or 'switch'. Defaults to 'parent'.
* @return bool True if the order contains a subscription that belongs to any of the given order types, otherwise false.
* @since 2.0
*/
function wcs_order_contains_subscription($order, $order_type = array('parent', 'resubscribe', 'switch'))
{
// Accept either an array or string (to make it more convenient for singular types, like 'parent' or 'any')
if (!is_array($order_type)) {
$order_type = array($order_type);
}
if (!is_object($order)) {
$order = new WC_Order($order);
}
$contains_subscription = false;
$get_all = in_array('any', $order_type) ? true : false;
if ((in_array('parent', $order_type) || $get_all) && count(wcs_get_subscriptions_for_order($order->id, array('order_type' => 'parent'))) > 0) {
$contains_subscription = true;
} elseif ((in_array('renewal', $order_type) || $get_all) && wcs_order_contains_renewal($order)) {
$contains_subscription = true;
} elseif ((in_array('resubscribe', $order_type) || $get_all) && wcs_order_contains_resubscribe($order)) {
$contains_subscription = true;
} elseif ((in_array('switch', $order_type) || $get_all) && wcs_order_contains_switch($order)) {
$contains_subscription = true;
}
return $contains_subscription;
}
/**
* Returns the renewal orders for a given parent order
*
* @param int $order_id The ID of a WC_Order object.
* @param string $output (optional) How you'd like the result. Can be 'ID' for IDs only or 'WC_Order' for order objects.
* @since 1.2
* @deprecated 2.0
*/
public static function get_renewal_orders($order_id, $output = 'ID')
{
_deprecated_function(__METHOD__, '2.0', 'WC_Subscription::get_related_orders()');
$subscriptions = wcs_get_subscriptions_for_order($order_id, array('order_type' => 'parent'));
$subscription = array_shift($subscriptions);
if ('WC_Order' == $output) {
$renewal_orders = $subscription->get_related_orders('all', 'renewal');
} else {
$renewal_orders = $subscription->get_related_orders('ids', 'renewal');
}
return apply_filters('woocommerce_subscriptions_renewal_orders', $renewal_orders, $order_id);
}
/**
* Creates a 2.0 updated version of the "subscriptions_switched" callback for developers to hook onto.
*
* The subscription passed to the new `woocommerce_subscriptions_switched_item` callback is strictly the subscription
* to which the `$new_order_item` belongs to; this may be a new or the original subscription.
*
* @since 2.0.5
* @param WC_Order $order
*/
public static function maybe_add_switched_callback($order)
{
if (wcs_order_contains_switch($order)) {
$subscriptions = wcs_get_subscriptions_for_order($order);
foreach ($subscriptions as $subscription) {
foreach ($subscription->get_items() as $new_order_item) {
if (isset($new_order_item['switched_subscription_item_id'])) {
$product_id = wcs_get_canonical_product_id($new_order_item);
// we need to check if the switch order contains the line item that has just been switched so that we don't call the hook on items that were previously switched in another order
foreach ($order->get_items() as $order_item) {
if (wcs_get_canonical_product_id($order_item) == $product_id) {
do_action('woocommerce_subscriptions_switched_item', $subscription, $new_order_item, WC_Subscriptions_Order::get_item_by_id($new_order_item['switched_subscription_item_id']));
break;
}
}
}
}
}
}
}
/**
* Override quantities used to lower stock levels by when using synced subscriptions. If it's a synced product
* that does not have proration enabled and the payment date is not today, do not lower stock levels.
*
* @param integer $qty the original quantity that would be taken out of the stock level
* @param array $order order data
* @param array $item item data for each item in the order
*
* @return int
*/
public static function maybe_do_not_reduce_stock($qty, $order, $order_item)
{
if (wcs_order_contains_subscription($order, array('parent', 'resubscribe')) && 0 == $order_item['line_total']) {
$subscriptions = wcs_get_subscriptions_for_order($order);
$product_id = wcs_get_canonical_product_id($order_item);
foreach ($subscriptions as $subscription) {
if (self::subscription_contains_synced_product($subscription) && $subscription->has_product($product_id)) {
foreach ($subscription->get_items() as $subscription_item) {
if (wcs_get_canonical_product_id($subscription_item) == $product_id && 0 < $subscription_item['line_total']) {
$qty = 0;
}
}
}
}
}
return $qty;
}
/**
* Store the Payeezy card data on the order and subscriptions in the order
*
* @param int $order_id
* @param array $card
*/
protected function save_subscription_meta($order_id, $card)
{
update_post_meta($order_id, '_payeezy_token', $card['token']);
update_post_meta($order_id, '_payeezy_expiry', $card['expiry']);
update_post_meta($order_id, '_payeezy_cardtype', $card['cardtype']);
// Also store it on the subscriptions being purchased in the order
foreach (wcs_get_subscriptions_for_order($order_id) as $subscription) {
update_post_meta($subscription->id, '_payeezy_token', $card['token']);
update_post_meta($subscription->id, '_payeezy_expiry', $card['expiry']);
update_post_meta($subscription->id, '_payeezy_cardtype', $card['cardtype']);
}
}
/**
* Returns an array of order IDs for valid orders that grant group
* membership for the given group to the user related to the order.
*
* @param int $user_id
* @param int $group_id
* @return array of int, order IDs
*/
public static function get_valid_order_ids_granting_group_membership_from_order_items($user_id, $group_id)
{
$order_ids = array();
if (!empty($user_id)) {
$base_statuses = array('processing', 'completed');
$statuses = array('completed');
$options = get_option('groups-woocommerce', array());
$order_status = isset($options[GROUPS_WS_MEMBERSHIP_ORDER_STATUS]) ? $options[GROUPS_WS_MEMBERSHIP_ORDER_STATUS] : GROUPS_WS_DEFAULT_MEMBERSHIP_ORDER_STATUS;
if ($order_status == 'processing') {
$statuses[] = 'processing';
}
// DO NOT use groups_ws_order_status( $statuses ) for $statuses or $base_statuses here,
// $order->status doesn't provide the wc- prefix.
$groups_product_groups = get_user_meta($user_id, '_groups_product_groups', true);
if (empty($groups_product_groups)) {
$groups_product_groups = array();
}
foreach ($groups_product_groups as $order_id => $product_ids) {
if ($order = Groups_WS_Helper::get_order($order_id)) {
// If this is a completed/processing order, consider group assignments.
// We check the order status for non-subscription products below,
// for subscriptions the subscription status is checked.
if (in_array($order->status, $base_statuses)) {
// Note that for orders placed with versions up to 1.4.1, the following won't give the results we might expect if the product group-related information has changed since the order was placed.
// As we don't store that information (WC doesn't store the whole lot of the product when purchased, nor does GW) checking the duration based on the product is the best effort at
// finding out about the group membership duration we can make.
// Use the order items (only existing order items are taken into account).
if ($items = $order->get_items()) {
foreach ($items as $item) {
if ($product = $order->get_product_from_item($item)) {
// Use the groups that were stored for the product when it was ordered,
// this avoids hickups when the product's groups were changed since.
if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['groups'])) {
$product_groups = $product_ids[$product->id]['groups'];
if (in_array($group_id, $product_groups)) {
// non-subscriptions
if (!class_exists('WC_Subscriptions_Product') || !WC_Subscriptions_Product::is_subscription($product->id)) {
if (in_array($order->status, $statuses)) {
if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['version'])) {
$has_duration = isset($product_ids[$product->id]['duration']) && $product_ids[$product->id]['duration'] && isset($product_ids[$product->id]['duration_uom']);
} else {
$has_duration = Groups_WS_Product::has_duration($product);
}
// unlimited membership
if (!$has_duration) {
if (!in_array($order_id, $order_ids)) {
$order_ids[] = $order_id;
}
} else {
if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['version'])) {
$duration = Groups_WS_Product::calculate_duration($product_ids[$product->id]['duration'], $product_ids[$product->id]['duration_uom']);
} else {
// <= 1.4.1
$duration = Groups_WS_Product::get_duration($product);
}
// time-limited membership
if ($duration) {
$start_date = $order->order_date;
if ($paid_date = get_post_meta($order_id, '_paid_date', true)) {
$start_date = $paid_date;
}
$end = strtotime($start_date) + $duration;
if (time() < $end) {
if (!in_array($order_id, $order_ids)) {
$order_ids[] = $order_id;
}
}
}
}
}
} else {
// include active subscriptions ( subscriptions >= 2.x )
if (function_exists('wcs_get_subscriptions_for_order')) {
if ($subscriptions = wcs_get_subscriptions_for_order($order_id)) {
if (is_array($subscriptions)) {
foreach ($subscriptions as $subscription) {
if ($subscription->has_product($product->id)) {
$valid = false;
if ($subscription->get_status() == 'active') {
$valid = true;
} else {
if ($subscription->get_status() == 'cancelled') {
$hook_args = array('subscription_id' => $subscription->id);
$end_timestamp = wp_next_scheduled('scheduled_subscription_end_of_prepaid_term', $hook_args);
if ($end_timestamp !== false && $end_timestamp > time()) {
$valid = true;
}
}
}
if ($valid) {
if (!in_array($order_id, $order_ids)) {
$order_ids[] = $order_id;
//.........这里部分代码省略.........
/**
* Store the customer and card IDs on the order and subscriptions in the order
*
* @param int $order_id
* @param string $customer_id
*/
protected function save_subscription_meta( $order_id, $customer_id ) {
$customer_id = wc_clean( $customer_id );
update_post_meta( $order_id, '_nab_crn', $customer_id );
// Also store it on the subscriptions being purchased in the order
if (function_exists('wcs_get_subscriptions_for_order')) {
foreach( wcs_get_subscriptions_for_order( $order_id ) as $subscription ) {
update_post_meta( $subscription->id, '_nab_crn', $customer_id );
}
}
}
/**
* WooCommerce's function receives the original order ID, the item and the list of files. This does not work for
* download permissions stored on the subscription rather than the original order as the URL would have the wrong order
* key. This function takes the same parameters, but queries the database again for download ids belonging to all the
* subscriptions that were in the original order. Then for all subscriptions, it checks all items, and if the item
* passed in here is in that subscription, it creates the correct download link to be passsed to the email.
*
* @param array $files List of files already included in the list
* @param array $item An item (you get it by doing $order->get_items())
* @param WC_Order $order The original order
* @return array List of files with correct download urls
*/
public static function get_item_downloads($files, $item, $order)
{
global $wpdb;
if (wcs_order_contains_subscription($order, 'any')) {
$subscriptions = wcs_get_subscriptions_for_order($order, array('order_type' => array('any')));
} else {
return $files;
}
$product_id = wcs_get_canonical_product_id($item);
foreach ($subscriptions as $subscription) {
foreach ($subscription->get_items() as $subscription_item) {
if (wcs_get_canonical_product_id($subscription_item) === $product_id) {
$files = $subscription->get_item_downloads($subscription_item);
}
}
}
return $files;
}
/**
* Automatically set the order's status to complete if all the subscriptions in an order
* are synced and the order total is zero.
*
* @since 1.5.17
*/
public static function order_autocomplete($new_order_status, $order_id)
{
$order = wc_get_order($order_id);
if ('processing' == $new_order_status && $order->get_total() == 0 && wcs_order_contains_subscription($order)) {
$subscriptions = wcs_get_subscriptions_for_order($order_id);
$all_synced = true;
foreach ($subscriptions as $subscription_id => $subscription) {
if (!self::subscription_contains_synced_product($subscription_id)) {
$all_synced = false;
break;
}
}
if ($all_synced) {
$new_order_status = 'completed';
}
}
return $new_order_status;
}
请发表评论