Files
woocommerce-subscriptions/includes/api/legacy/class-wc-api-subscriptions.php
Prospress Inc 0691aa36da 2.2.3
2017-04-11 09:25:24 +02:00

706 lines
22 KiB
PHP

<?php
/**
* WooCommerce Subscriptions API Subscriptions Class
*
* Handles requests to the /subscriptions endpoint
*
* @author Prospress
* @category API
* @since 2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class WC_API_Subscriptions extends WC_API_Orders {
/* @var string $base the route base */
protected $base = '/subscriptions';
/**
* Register the routes for this class
*
* GET|POST /subscriptions
* GET /subscriptions/count
* GET|PUT|DELETE /subscriptions/<subscription_id>
* GET /subscriptions/<subscription_id>/notes
* GET /subscriptions/<subscription_id>/notes/<id>
* GET /subscriptions/<subscription_id>/orders
*
* @since 2.0
* @param array $routes
* @return array $routes
*/
public function register_routes( $routes ) {
$this->post_type = 'shop_subscription';
# GET /subscriptions
$routes[ $this->base ] = array(
array( array( $this, 'get_subscriptions' ), WC_API_Server::READABLE ),
array( array( $this, 'create_subscription' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /subscriptions/count
$routes[ $this->base . '/count' ] = array(
array( array( $this, 'get_subscription_count' ), WC_API_Server::READABLE ),
);
# GET /subscriptions/statuses
$routes[ $this->base . '/statuses' ] = array(
array( array( $this, 'get_statuses' ), WC_API_Server::READABLE ),
);
# GET|PUT|DELETE /subscriptions/<subscription_id>
$routes[ $this->base . '/(?P<subscription_id>\d+)' ] = array(
array( array( $this, 'get_subscription' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_subscription' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_subscription' ), WC_API_Server::DELETABLE ),
);
# GET /subscriptions/<subscription_id>/notes
$routes[ $this->base . '/(?P<subscription_id>\d+)/notes' ] = array(
array( array( $this, 'get_subscription_notes' ), WC_API_Server::READABLE ),
array( array( $this, 'create_subscription_note' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /subscriptions/<subscription_id>/notes/<id>
$routes[ $this->base . '/(?P<subscription_id>\d+)/notes/(?P<id>\d+)' ] = array(
array( array( $this, 'get_subscription_note' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_subscription_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_subscription_note' ), WC_API_SERVER::DELETABLE ),
);
# GET /subscriptions/<subscription_id>/orders
$routes[ $this->base . '/(?P<subscription_id>\d+)/orders' ] = array(
array( array( $this, 'get_all_subscription_orders' ), WC_API_Server::READABLE ),
);
return $routes;
}
/**
* Ensures the statuses are in the correct format and are valid subscription statues.
*
* @since 2.0
* @param $status string | array
*/
protected function format_statuses( $status = null ) {
$statuses = 'any';
if ( ! empty( $status ) ) {
// get list of statuses and check each on is in the correct format and is valid
$statuses = explode( ',', $status );
// attach the wc- prefix to those statuses that have not specified it
foreach ( $statuses as &$status ) {
if ( 'wc-' != substr( $status, 0, 3 ) ) {
$status = 'wc-' . $status;
if ( ! array_key_exists( $status, wcs_get_subscription_statuses() ) ) {
return new WP_Error( 'wcs_api_invalid_subscription_status', __( 'Invalid subscription status given.', 'woocommerce-subscriptions' ) );
}
}
}
}
return $statuses;
}
/**
* Gets all subscriptions
*
* @since 2.0
* @param null $fields
* @param array $filter
* @param null $status
* @param null $page
* @return array
*/
public function get_subscriptions( $fields = null, $filter = array(), $status = null, $page = 1 ) {
// check user permissions
if ( ! current_user_can( 'read_private_shop_orders' ) ) {
return new WP_Error( 'wcs_api_user_cannot_read_susbcription_count', __( 'You do not have permission to read the subscriptions count', 'woocommerce-subscriptions' ), array( 'status' => 401 ) );
}
$status = $this->format_statuses( $status );
if ( is_wp_error( $status ) ) {
return $status;
}
$filter['page'] = $page;
$base_args = array(
'post_status' => $status,
'post_type' => 'shop_subscription',
'fields' => 'ids',
);
$subscriptions = array();
$query_args = array_merge( $base_args, $filter );
$query = $this->query_orders( $query_args );
foreach ( $query->posts as $subscription_id ) {
if ( ! $this->is_readable( $subscription_id ) ) {
continue;
}
$subscriptions[] = current( $this->get_subscription( $subscription_id, $fields, $filter ) );
}
$this->server->add_pagination_headers( $query );
return array( 'subscriptions' => apply_filters( 'wcs_api_get_subscriptions_response', $subscriptions, $fields, $filter, $status, $page, $this->server ) );
}
/**
* Creating Subscription.
*
* @since 2.0
* @param array data raw order data
* @return array
*/
public function create_subscription( $data ) {
$data = isset( $data['subscription'] ) ? $data['subscription'] : array();
try {
if ( ! current_user_can( 'publish_shop_orders' ) ) {
throw new WC_API_Exception( 'wcs_api_user_cannot_create_subscription', __( 'You do not have permission to create subscriptions', 'woocommerce-subscriptions' ), 401 );
}
$data['order'] = $data;
remove_filter( 'woocommerce_api_order_response', array( WC()->api->WC_API_Customers, 'add_customer_data' ), 10 );
$subscription = $this->create_order( $data );
add_filter( 'woocommerce_api_order_response', array( WC()->api->WC_API_Customers, 'add_customer_data' ), 10, 2 );
unset( $data['order'] );
if ( is_wp_error( $subscription ) ) {
$data = $subscription->get_error_data();
throw new WC_API_Exception( $subscription->get_error_code(), $subscription->get_error_message(), $data['status'] );
}
$subscription = wcs_get_subscription( $subscription['order']['id'] );
unset( $data['billing_period'] );
unset( $data['billing_interval'] );
$this->update_schedule( $subscription, $data );
// allow order total to be manually set, especially for those cases where there's no line items added to the subscription
if ( isset( $data['order_total'] ) ) {
$subscription->set_total( wc_format_decimal( $data['order_total'], get_option( 'woocommerce_price_num_decimals' ) ) );
}
if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) {
$this->update_payment_method( $subscription, $data['payment_details'], false );
}
$subscription->save();
do_action( 'wcs_api_subscription_created', $subscription->get_id(), $this );
return array( 'creating_subscription' => $this->get_subscription( $subscription->get_id() ) );
} catch ( WC_API_Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
} catch ( Exception $e ) {
$response = array( 'error', array( 'wcs_api_error_create_subscription' => array( 'message' => $e->getMessage(), 'status' => $e->getCode() ) ) );
// show the subscription in response if it was still created but errored.
if ( ! empty( $subscription ) && ! is_wp_error( $subscription ) ) {
$response['creating_subscription'] = $this->get_subscription( $subscription->get_id() );
}
return $response;
}
}
/**
* Edit Subscription
*
* @since 2.0
* @return array
*/
public function edit_subscription( $subscription_id, $data, $fields = null ) {
$data = apply_filters( 'wcs_api_edit_subscription_data', isset( $data['subscription'] ) ? $data['subscription'] : array(), $subscription_id, $fields );
try {
$subscription_id = $this->validate_request( $subscription_id, $this->post_type, 'edit' );
if ( is_wp_error( $subscription_id ) ) {
throw new WC_API_Exception( 'wcs_api_cannot_edit_subscription', __( 'The requested subscription cannot be edited.', 'woocommerce-subscriptions' ), 400 );
}
$subscription = wcs_get_subscription( $subscription_id );
if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) {
if ( empty( $data['payment_details']['method_id'] ) || 'manual' == $data['payment_details']['method_id'] ) {
$subscription->set_payment_method( '' );
} else {
$this->update_payment_method( $subscription, $data['payment_details'], true );
}
}
if ( ! empty( $data['order_id'] ) ) {
$subscription->set_parent_id( $data['order_id'] );
}
// set $data['order'] = $data['subscription'] so that edit_order can read in the request
$data['order'] = $data;
// edit subscription by calling WC_API_Orders::edit_order()
$edited = $this->edit_order( $subscription_id, $data, $fields );
// remove part of the array that isn't being used
unset( $data['order'] );
if ( is_wp_error( $edited ) ) {
$data = $edited->get_error_data();
// translators: placeholder is error message
throw new WC_API_Exception( 'wcs_api_cannot_edit_subscription', sprintf( _x( 'Edit subscription failed with error: %s', 'API error message when editing the order failed', 'woocommerce-subscriptions' ), $edited->get_error_message() ), $data['status'] );
}
$this->update_schedule( $subscription, $data );
$subscription->save();
do_action( 'wcs_api_subscription_updated', $subscription_id, $data, $this );
return $this->get_subscription( $subscription_id );
} catch ( WC_API_Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
} catch ( Exception $e ) {
return new WP_Error( 'wcs_api_cannot_edit_subscription', $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}
/**
* Setup the new payment information to call WC_Subscription::set_payment_method()
*
* @param $subscription WC_Subscription
* @param $payment_details array payment data from api request
* @since 2.0
*/
public function update_payment_method( $subscription, $payment_details, $updating ) {
global $wpdb;
$payment_gateways = WC()->payment_gateways->get_available_payment_gateways();
$payment_method = ( ! empty( $payment_details['method_id'] ) ) ? $payment_details['method_id'] : 'manual';
$payment_gateway = ( isset( $payment_gateways[ $payment_details['method_id'] ] ) ) ? $payment_gateways[ $payment_details['method_id'] ] : '';
try {
$wpdb->query( 'START TRANSACTION' );
if ( $updating && ! array_key_exists( $payment_method, WCS_Change_Payment_Method_Admin::get_valid_payment_methods( $subscription ) ) ) {
throw new Exception( 'wcs_api_edit_subscription_error', __( 'Gateway does not support admin changing the payment method on a Subscription.', 'woocommerce-subscriptions' ) );
}
$payment_method_meta = apply_filters( 'woocommerce_subscription_payment_meta', array(), $subscription );
if ( ! empty( $payment_gateway ) && isset( $payment_method_meta[ $payment_gateway->id ] ) ) {
$payment_method_meta = $payment_method_meta[ $payment_gateway->id ];
if ( ! empty( $payment_method_meta ) ) {
foreach ( $payment_method_meta as $meta_table => $meta ) {
if ( ! is_array( $meta ) ) {
continue;
}
foreach ( $meta as $meta_key => $meta_data ) {
if ( isset( $payment_details[ $meta_table ][ $meta_key ] ) ) {
$payment_method_meta[ $meta_table ][ $meta_key ]['value'] = $payment_details[ $meta_table ][ $meta_key ];
}
}
}
}
}
if ( '' == $subscription->get_payment_method() ) {
$subscription->set_payment_method( $payment_gateway );
}
$subscription->set_payment_method( $payment_gateway, $payment_method_meta );
$wpdb->query( 'COMMIT' );
} catch ( Exception $e ) {
$wpdb->query( 'ROLLBACK' );
// translators: 1$: gateway id, 2$: error message
throw new Exception( sprintf( __( 'Subscription payment method could not be set to %1$s and has been set to manual with error message: %2$s', 'woocommerce-subscriptions' ), ( ! empty( $payment_gateway->id ) ) ? $payment_gateway->id : 'manual', $e->getMessage() ) );
}
}
/**
* Override WC_API_Order::create_base_order() to create a subscription
* instead of a WC_Order when calling WC_API_Order::create_order().
*
* @since 2.0
* @param $array
* @return WC_Subscription
*/
protected function create_base_order( $args, $data ) {
$args['order_id'] = ( ! empty( $data['order_id'] ) ) ? $data['order_id'] : '';
$args['billing_interval'] = ( ! empty( $data['billing_interval'] ) ) ? $data['billing_interval'] : '';
$args['billing_period'] = ( ! empty( $data['billing_period'] ) ) ? $data['billing_period'] : '';
return wcs_create_subscription( $args );
}
/**
* Update all subscription specific meta (i.e. Billing interval/period and date fields )
*
* @since 2.0
* @param $data array
* @param $subscription WC_Subscription
*/
protected function update_schedule( $subscription, $data ) {
if ( isset( $data['billing_interval'] ) ) {
$interval = absint( $data['billing_interval'] );
if ( 0 == $interval ) {
throw new WC_API_Exception( 'wcs_api_invalid_subscription_meta', __( 'Invalid subscription billing interval given. Must be an integer greater than 0.', 'woocommerce-subscriptions' ), 400 );
}
$subscription->set_billing_interval( $interval );
}
if ( ! empty( $data['billing_period'] ) ) {
$period = strtolower( $data['billing_period'] );
if ( ! in_array( $period, array_keys( wcs_get_subscription_period_strings() ) ) ) {
throw new WC_API_Exception( 'wcs_api_invalid_subscription_meta', __( 'Invalid subscription billing period given.', 'woocommerce-subscriptions' ), 400 );
}
$subscription->set_billing_period( $period );
}
$dates_to_update = array();
foreach ( array( 'start', 'trial_end', 'end', 'next_payment' ) as $date_type ) {
if ( isset( $data[ $date_type . '_date' ] ) ) {
$date_type_key = ( 'start' === $date_type ) ? 'date_created' : $date_type;
$dates_to_update[ $date_type_key ] = $data[ $date_type . '_date' ];
}
}
if ( ! empty( $dates_to_update ) ) {
$subscription->update_dates( $dates_to_update );
}
}
/**
* Delete subscription
*
* @since 2.0
*/
public function delete_subscription( $subscription_id, $force = false ) {
$subscription_id = $this->validate_request( $subscription_id, $this->post_type, 'delete' );
if ( is_wp_error( $subscription_id ) ) {
return $subscription_id;
}
wc_delete_shop_order_transients( $subscription_id );
do_action( 'woocommerce_api_delete_subscription', $subscription_id, $this );
return $this->delete( $subscription_id, 'subscription', ( 'true' === $force ) );
}
/**
* Retrieves the subscription by the given id.
*
* Called by: /subscriptions/<subscription_id>
*
* @since 2.0
* @param int $subscription_id
* @param array $fields
* @param array $filter
* @return array
*/
public function get_subscription( $subscription_id, $fields = null, $filter = array() ) {
$subscription_id = $this->validate_request( $subscription_id, $this->post_type, 'read' );
if ( is_wp_error( $subscription_id ) ) {
return $subscription_id;
}
$subscription = wcs_get_subscription( $subscription_id );
$order_data = $this->get_order( $subscription_id );
$subscription_data = $order_data['order'];
// Not all order meta relates to a subscription (a subscription doesn't "complete")
if ( isset( $subscription_data['completed_at'] ) ) {
unset( $subscription_data['completed_at'] );
}
$subscription_data['billing_schedule'] = array(
'period' => $subscription->get_billing_period(),
'interval' => $subscription->get_billing_interval(),
'start_at' => $this->get_formatted_datetime( $subscription, 'date_created' ),
'trial_end_at' => $this->get_formatted_datetime( $subscription, 'trial_end' ),
'next_payment_at' => $this->get_formatted_datetime( $subscription, 'next_payment' ),
'end_at' => $this->get_formatted_datetime( $subscription, 'end' ),
);
if ( $subscription->get_parent_id() ) {
$subscription_data['parent_order_id'] = $subscription->get_parent_id();
} else {
$subscription_data['parent_order_id'] = array();
}
return array( 'subscription' => apply_filters( 'wcs_api_get_subscription_response', $subscription_data, $fields, $filter, $this->server ) );
}
/**
* Returns a list of all the available subscription statuses.
*
* @see wcs_get_subscription_statuses() in wcs-functions.php
* @since 2.0
* @return array
*
*/
public function get_statuses() {
return array( 'subscription_statuses' => wcs_get_subscription_statuses() );
}
/**
* Get the total number of subscriptions
*
* Called by: /subscriptions/count
* @since 2.0
* @param $status string
* @param $filter array
* @return int | WP_Error
*/
public function get_subscription_count( $status = null, $filter = array() ) {
return $this->get_orders_count( $status, $filter );
}
/**
* Returns all the notes tied to the subscription
*
* Called by: subscription/<subscription_id>/notes
* @since 2.0
* @param $subscription_id
* @param $fields
* @return WP_Error|array
*/
public function get_subscription_notes( $subscription_id, $fields = null ) {
$notes = $this->get_order_notes( $subscription_id, $fields );
if ( is_wp_error( $notes ) ) {
return $notes;
}
return array( 'subscription_notes' => apply_filters( 'wcs_api_subscription_notes_response', $notes['order_notes'], $subscription_id, $fields ) );
}
/**
* Get information about a subscription note.
*
* @since 2.0
* @param int $subscription_id
* @param int $id
* @param array $fields
*
* @return array Subscription note
*/
public function get_subscription_note( $subscription_id, $id, $fields = null ) {
$note = $this->get_order_note( $subscription_id, $id, $fields );
if ( is_wp_error( $note ) ) {
return $note;
}
return array( 'subscription_note' => apply_filters( 'wcs_api_subscription_note_response', $note['order_note'], $subscription_id, $id, $fields ) );
}
/**
* Get information about a subscription note.
*
* @param int $subscription_id
* @param int $id
* @param array $fields
*
* @return WP_Error|array Subscription note
*/
public function create_subscription_note( $subscription_id, $data ) {
$note = $this->create_order_note( $subscription_id, $data );
if ( is_wp_error( $note ) ) {
return $note;
}
do_action( 'wcs_api_created_subscription_note', $subscription_id, $note['order_note'], $this );
return array( 'subscription_note' => $note['order_note'] );
}
/**
* Verify and edit subscription note.
*
* @since 2.0
* @param int $subscription_id
* @param int $id
*
* @return WP_Error|array Subscription note edited
*/
public function edit_subscription_note( $subscription_id, $id, $data ) {
$note = $this->edit_order_note( $subscription_id, $id, $data );
if ( is_wp_error( $note ) ) {
return $note;
}
do_action( 'wcs_api_edit_subscription_note', $subscription_id, $id, $note['order_note'], $this );
return array( 'subscription_note' => $note['order_note'] );
}
/**
* Verify and delete subscription note.
*
* @since 2.0
* @param int $subscription_id
* @param int $id
* @return WP_Error|array deleted subscription note status
*/
public function delete_subscription_note( $subscription_id, $id ) {
$deleted_note = $this->delete_order_note( $subscription_id, $id );
if ( is_wp_error( $deleted_note ) ) {
return $deleted_note;
}
do_action( 'wcs_api_subscription_note_status', $subscription_id, $id, $this );
return array( 'message' => _x( 'Permanently deleted subscription note', 'API response confirming order note deleted from a subscription', 'woocommerce-subscriptions' ) );
}
/**
* Get information about the initial order and renewal orders of a subscription.
*
* Called by: /subscriptions/<subscription_id>/orders
* @since 2.0
* @param $subscription_id
* @param $fields
*/
public function get_all_subscription_orders( $subscription_id, $filters = null ) {
$subscription_id = $this->validate_request( $subscription_id, $this->post_type, 'read' );
if ( is_wp_error( $subscription_id ) ) {
return $subscription_id;
}
$subscription = wcs_get_subscription( $subscription_id );
$subscription_orders = $subscription->get_related_orders();
$formatted_orders = array();
if ( ! empty( $subscription_orders ) ) {
// set post_type back to shop order so that get_orders doesn't try return a subscription.
$this->post_type = 'shop_order';
foreach ( $subscription_orders as $order_id ) {
$formatted_orders[] = $this->get_order( $order_id );
}
$this->post_type = 'shop_subscription';
}
return array( 'subscription_orders' => apply_filters( 'wcs_api_subscription_orders_response', $formatted_orders, $subscription_id, $filters, $this->server ) );
}
/**
* Get a certain date for a subscription, if it exists, formatted for return
*
* @since 2.0
* @param $subscription
* @param $date_type
*/
protected function get_formatted_datetime( $subscription, $date_type ) {
$timestamp = $subscription->get_time( $date_type );
if ( $timestamp > 0 ) {
$formatted_datetime = $this->server->format_datetime( $timestamp );
} else {
$formatted_datetime = '';
}
return $formatted_datetime;
}
/**
* Helper method to get order post objects
*
* We need to override WC_API_Orders::query_orders() because it uses wc_get_order_statuses()
* for the query, but subscriptions use the values returned by wcs_get_subscription_statuses().
*
* @since 2.0
* @param array $args request arguments for filtering query
* @return WP_Query
*/
protected function query_orders( $args ) {
// set base query arguments
$query_args = array(
'fields' => 'ids',
'post_type' => $this->post_type,
'post_status' => array_keys( wcs_get_subscription_statuses() ),
);
// add status argument
if ( ! empty( $args['status'] ) ) {
$statuses = 'wc-' . str_replace( ',', ',wc-', $args['status'] );
$statuses = explode( ',', $statuses );
$query_args['post_status'] = $statuses;
unset( $args['status'] );
}
$query_args = $this->merge_query_args( $query_args, $args );
return new WP_Query( $query_args );
}
}