Current File : /home/itiffy/www/blog/wp-content/plugins/wp-invoice/lib/class_xmlrpc_api.php
<?php
/**
 * WP-Invoice XML-RPC API Reference
 *
 * @type Array
 * @author korotkov@ud
 */
global $wpi_xml_rpc_api_reference;

$wpi_xml_rpc_api_reference = array(

  //** Namespace */
  'namespace' => new WPI_XMLRPC_API(),

  //** Methods */
  'methods' => array(

    //** Create Invoice */
    'create_invoice' => array(
      'description' => 'Create new invoice based on information passed.',
      'args' => array(
        'custom_id' => array(
          'description' => 'Previously generated Custom Invoice ID.',
          'required' => false,
          'type' => 'Number'
        ),
        'subject' => array(
          'description' => 'The title of invoice post object.',
          'required' => true,
          'type' => 'String'
        ),
        'description' => array(
          'description' => 'The content of invoice post object.',
          'required' => false,
          'type' => 'String'
        ),
        'type' => array(
          'description' => 'The type of invoice object. One of allowed types can be used. (invoice, quote, single_payment, recurring).',
          'required' => true,
          'type' => 'String'
        ),
        'user_data' => array(
          'description' => 'Recipient data. Conditionaly required. Associative array should always contain "user_email".',
          'required' => true,
          'type' => 'Array'
        ),
        'deposit' => array(
          'description' => 'Minimum amount of Partial Payment allowed. Partial Payments disabled if empty.',
          'required' => false,
          'type' => 'Number'
        ),
        'due_date' => array(
          'description' => 'Invoice Due Date. Associative array of 3 permanent elements: month, year, day (mm, yyyy, dd).',
          'required' => false,
          'type' => 'Array'
        ),
        'currency' => array(
          'description' => 'Currency code of the currency you need to use for current invoice. Format AAA.',
          'required' => false,
          'type' => 'String'
        ),
        'tax' => array(
          'description' => 'Global tax value in percents. Will be applied to every item and charge.',
          'required' => false,
          'type' => 'Number'
        ),
        'tax_method' => array(
          'description' => 'Method of tax counting. After or Before discount. (before_discount, after_discount).',
          'required' => false,
          'type' => 'String'
        ),
        'recurring' => array(
          'description' => 'Recurring billing cycles options.',
          'required' => false,
          'type' => 'String'
        ),
        'status' => array(
          'description' => 'The status of invoice. One of registered statuses is allowed.',
          'required' => true,
          'type' => 'String'
        ),
        'discount' => array(
          'description' => 'Discount for current invoice. Associative array should contain 3 fields: name, type, amount.',
          'required' => false,
          'type' => 'Array'
        ),
        'items' => array(
          'description' => 'Line items of current invoice. Set of associative arrays. Conditionaly required.',
          'required' => true,
          'type' => 'Array'
        ),
        'charges' => array(
          'description' => 'Charges of current invoice. Set of associative arrays. Conditionaly required.',
          'required' => true,
          'type' => 'Array'
        )
      ),
      'return' => 'WPI_Invoice|WP_Error'
    ),

    //** Delete Invoice */
    'delete_invoice' => array(
      'description' => 'Delete invoice by ID.',
      'args' => array(
        'ID' => array(
          'description' => 'Invoice ID.',
          'required' => true,
          'type' => 'Number'
        )
      ),
      'return' => 'Boolean|WP_Error'
    ),

    //** Get Invoice */
    'get_invoice' => array(
      'description' => 'Get invoice by ID',
      'args' => array(
        'ID' => array(
          'description' => 'Invoice ID.',
          'required' => true,
          'type' => 'Number'
        )
      ),
      'return' => 'WPI_Invoice|WP_Error'
    ),

    //** Refund invoice */
    'refund_invoice' => array(
      'description' => 'Refund invoice by ID. Note that it does not do refund on merchant side.',
      'args' => array(
        'ID' => array(
          'description' => 'Invoice ID.',
          'required' => true,
          'type' => 'Number'
        )
      ),
      'return' => 'WPI_Invoice|WP_Error'
    ),

    //** Pay invoice by ID */
    'pay_invoice' => array(
      'description' => 'Pay invoice by ID',
      'args' => array(
        'ID' => array(
          'description' => 'Invoice ID.',
          'required' => true,
          'type' => 'Number'
        ),
        'amount' => array(
          'description' => 'Amount to be paid.',
          'required' => true,
          'type' => 'Number'
        )
      ),
      'return' => 'WPI_Invoice|WP_Error'
    ),

    //** Update invoice */
    'update_invoice' => array(
      'description' => 'Update some of the invoice attributes.',
      'args' => array(
        'ID' => array(
          'description' => 'Invoice ID.',
          'required' => true,
          'type' => 'Number'
        ),
        'subject' => array(
          'description' => 'The title of invoice post object.',
          'required' => true,
          'type' => 'String'
        ),
        'description' => array(
          'description' => 'The content of invoice post object.',
          'required' => false,
          'type' => 'String'
        ),
        'type' => array(
          'description' => 'The type of invoice object. One of allowed types can be used. (invoice, quote, single_payment, recurring).',
          'required' => true,
          'type' => 'String'
        ),
        'deposit' => array(
          'description' => 'Minimum amount of Partial Payment allowed. Partial Payments disabled if empty.',
          'required' => false,
          'type' => 'Number'
        ),
        'due_date' => array(
          'description' => 'Invoice Due Date. Associative array of 3 permanent elements: month, year, day (mm, yyyy, dd).',
          'required' => false,
          'type' => 'Array'
        ),
        'tax' => array(
          'description' => 'Global tax value in percents. Will be applied to every item and charge.',
          'required' => false,
          'type' => 'Number'
        ),
        'tax_method' => array(
          'description' => 'Method of tax counting. After or Before discount. (before_discount, after_discount).',
          'required' => false,
          'type' => 'String'
        ),
        'recurring' => array(
          'description' => 'Recurring billing cycles options.',
          'required' => false,
          'type' => 'String'
        ),
        'discount' => array(
          'description' => 'Discount for current invoice. Associative array should contain 3 fields: name, type, amount.',
          'required' => false,
          'type' => 'Array'
        ),
        'items' => array(
          'description' => 'Line items of current invoice. Set of associative arrays. Conditionaly required.',
          'required' => true,
          'type' => 'Array'
        ),
        'charges' => array(
          'description' => 'Charges of current invoice. Set of associative arrays. Conditionaly required.',
          'required' => true,
          'type' => 'Array'
        )
      )
    )
  )
);

/**
 * WP-Invoice XML-RPC API Class
 *
 * @since 3.08.7
 * @author korotkov@ud
 */
class WPI_XMLRPC_API {

  /**
   * API Name
   *
   * @var string
   */
  public $name = 'WPI_XMLRPC_API';

  /**
   * API Description
   *
   * @var string
   */
  public $description = 'WP-Invoice XML-RPC API Handler. Can be used by authorized users by calling single method "wp.invoice" with arguments described in API Reference.';

  /**
   * Initialize
   */
  function __construct() {
    //** Extend standard XML-RPC */
    add_filter( 'xmlrpc_methods', array( __CLASS__, 'r__register' ) );
    add_action( 'wpi_settings_before_help', 'wpi_help_api_reference' );
  }

  /**
   * Register method-hook
   *
   * @param array $methods
   *
   * @return array
   */
  static function r__register( $methods ) {
    $methods[ 'wp.invoice' ] = 'wpi_xmlrpc_request';
    return $methods;
  }

  /**
   * Create new invoice
   *
   * @param array $args
   *
   * @return WPI_Invoice
   * @see WPI_Invoice
   * @uses Internal API of plugin
   */
  function create_invoice( $args = array() ) {
    global $wpi_settings;

    //** Default arguments */
    $defaults = array(
      'custom_id' => false,
      'subject' => false,
      'description' => false,
      'type' => false,
      'user_data' => array(
        'user_email' => false,
        'first_name' => false,
        'last_name' => false,
        'phonenumber' => false,
        'streetaddress' => false,
        'city' => false,
        'state' => false,
        'zip' => false,
        'country' => false
      ),
      'deposit' => false,
      'due_date' => array(
        'year' => false,
        'month' => false,
        'day' => false
      ),
      'currency' => false,
      'tax' => false,
      'tax_method' => false,
      'recurring' => array(
        'unit' => false,
        'length' => false,
        'cycles' => false,
        'send_invoice_automatically' => false,
        'start_date' => array(
          'month' => false,
          'day' => false,
          'year' => false
        )
      ),
      'status' => false,
      'discount' => array(
        'name' => false,
        'type' => false,
        'amount' => false
      ),
      'items' => array(),
      'charges' => array()
    );

    //** Parse arguments */
    extract( $args = wp_parse_args( $args, $defaults ) );

    //** If empty subject - return error */
    if ( !$subject ) return new WP_Error( 'wp.invoice', __( 'Method requires "subject" argument to be passed.', ud_get_wp_invoice()->domain ), $args );

    //** If empty user_email - return error */
    if ( !$user_data[ 'user_email' ] ) return new WP_Error( 'wp.invoice', __( 'Method requires "user_email" in "user_data" argument to be passed.', ud_get_wp_invoice()->domain ), $args );
    if ( !filter_var( $user_data[ 'user_email' ], FILTER_VALIDATE_EMAIL ) ) return new WP_Error( 'wp.invoice', __( 'User Email is malformed.', ud_get_wp_invoice()->domain ), $args );

    //** Items/Charges check */
    if ( empty( $items ) && empty( $charges ) ) return new WP_Error( 'wp.invoice', __( 'Method requires "items" or "charges" argument to be passed.', ud_get_wp_invoice()->domain ), $args );

    //** If type is registered */
    if ( !array_key_exists( $type, $wpi_settings[ 'types' ] ) ) return new WP_Error( 'wp.invoice', __( 'Unknown invoice type.', ud_get_wp_invoice()->domain ), $args );

    //** If recurring */
    if ( $type == 'recurring' ) {
      $recurring = array_filter( $recurring );
      if ( empty( $recurring[ 'unit' ] ) || empty( $recurring[ 'cycles' ] ) ) return new WP_Error( 'wp.invoice', __( 'Method requires correct "recurring" argument if "type" is recurring.', ud_get_wp_invoice()->domain ), $args );
      if ( !empty( $deposit ) ) return new WP_Error( 'wp.invoice', __( 'Cannot use "deposit" with "recurring" type.', ud_get_wp_invoice()->domain ), $args );
    }

    //** If quote */
    if ( $type == 'quote' ) {
      if ( !empty( $deposit ) ) return new WP_Error( 'wp.invoice', __( 'Cannot use "deposit" with "quote" type.', ud_get_wp_invoice()->domain ), $args );
    }

    //** Check status */
    if ( !$status ) return new WP_Error( 'wp.invoice', __( 'Method requires "status" argument to be passed.', ud_get_wp_invoice()->domain ), $args );
    if ( !array_key_exists( $status, $wpi_settings[ 'invoice_statuses' ] ) ) return new WP_Error( 'wp.invoice', __( 'Unknown invoice status.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->create_new_invoice( $args );

    //** Set type */
    $invoice->set( array(
      'type' => $type
    ) );

    //** If quote */
    if ( $type == 'quote' ) {
      $invoice->set( array( 'status' => $type ) );
      $invoice->set( array( 'is_quote' => 'true' ) );
    }

    //** Recurring */
    if ( $type == 'recurring' ) {
      $invoice->create_schedule( $recurring );
    }

    //** Try loading user by email */
    $invoice->load_user( array(
      'email' => $user_data[ 'user_email' ]
    ) );

    //** If new user - add data to his object */
    if ( empty( $invoice->data[ 'user_data' ] ) ) {
      $invoice->data[ 'user_data' ] = $user_data;
    }

    //** Create/Update user if need */
    WPI_Functions::update_user( $user_data );

    //** Try loading user by email again */
    $invoice->load_user( array(
      'email' => $user_data[ 'user_email' ]
    ) );

    //** Partial payments */
    if ( $deposit ) {
      $invoice->set( array( 'deposit_amount' => $deposit ) );
    } else {
      $invoice->set( array( 'deposit_amount' => 0 ) );
    }

    //** Due date */
    $invoice->set( array( 'due_date_year' => $due_date[ 'year' ] ) );
    $invoice->set( array( 'due_date_month' => $due_date[ 'month' ] ) );
    $invoice->set( array( 'due_date_day' => $due_date[ 'day' ] ) );

    //** Currency */
    $invoice->set( array( 'default_currency_code' => $currency ) );

    //** Tax */
    $invoice->set( array( 'tax' => $tax ) );

    //** Status */
    $invoice->set( array( 'post_status' => $status ) );

    //** Discount */
    $discount = array_filter( $discount );
    if ( !empty( $discount ) ) {
      if ( empty( $discount[ 'name' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount name is required.', ud_get_wp_invoice()->domain ), $args );
      if ( empty( $discount[ 'type' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount type is required. ("amount" or "percent").', ud_get_wp_invoice()->domain ), $args );
      if ( empty( $discount[ 'amount' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount amount is required.', ud_get_wp_invoice()->domain ), $args );
      $invoice->add_discount( $discount );
    }

    //** Items */
    foreach ( $items as $item ) {
      //** Do not allow to save melformed items */
      if ( empty( $item[ 'name' ] ) ||
        empty( $item[ 'quantity' ] ) ||
        empty( $item[ 'price' ] )
      ) {
        return new WP_Error( 'wp.invoice', __( 'One or more "items" have malformed structure. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      }

      //** Global tax has higher priority */
      if ( !empty( $tax ) ) $item[ 'tax_rate' ] = $tax;

      //** Check types */
      if ( !is_numeric( $item[ 'quantity' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "quantity" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      if ( !is_numeric( $item[ 'price' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "price" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      if ( !empty( $item[ 'tax_rate' ] ) ) {
        if ( !is_numeric( $item[ 'tax_rate' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "tax_rate" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      }

      //** If passed validation - save item */
      $invoice->line_item( $item );
    }

    //** Charges */
    foreach ( $charges as $charge ) {
      //** Do not allow to save melformed items */
      if ( empty( $charge[ 'name' ] ) ||
        empty( $charge[ 'amount' ] )
      ) {
        return new WP_Error( 'wp.invoice', __( 'One or more "charges" have malformed structure. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      }

      //** Global tax has higher priority */
      if ( !empty( $tax ) ) $charge[ 'tax' ] = $tax;

      //** Check types */
      if ( !is_numeric( $charge[ 'amount' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "charges" have wrong "amount" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      if ( !empty( $charge[ 'tax' ] ) ) {
        if ( !is_numeric( $charge[ 'tax' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "charges" have wrong "tax" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
      }

      //** If passed validation - save item */
      $invoice->line_charge( $charge );
    }

    //** Set tax method */
    if ( !empty( $tax_method ) ) {
      if ( $tax_method != 'before_discount' && $tax_method != 'after_discount' ) {
        return new WP_Error( 'wp.invoice', __( 'Unknown "tax_method".', ud_get_wp_invoice()->domain ), $args );
      }
    }
    $invoice->set( array( 'tax_method' => $tax_method ) );

    $invoice->data['new_invoice'] = false;

    //** Save */
    $invoice->save_invoice();

    //** Return saved object */
    return $invoice;
  }

  /**
   * Refund invoice by ID
   *
   * @param type $args
   *
   * @return WP_Error|WPI_Invoice
   */
  function refund_invoice( $args = array() ) {
    //** Defaults  */
    $defaults = array(
      'ID' => false
    );

    //** Parse arguments */
    extract( wp_parse_args( $args, $defaults ) );

    //** Check */
    if ( !$ID ) return new WP_Error( 'wp.invoice', __( 'Argument "ID" is required.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    //** Check */
    if ( !empty( $invoice->error ) ) return new WP_Error( 'wp.invoice', __( 'Invoice not found', ud_get_wp_invoice()->domain ), $args );

    //** Do refund if it has payments */
    if ( empty( $invoice->data[ 'total_payments' ] ) ) return new WP_Error( 'wp.invoice', __( 'Cannot be refunded. No payments found.', ud_get_wp_invoice()->domain ), $args );

    $insert_id = $invoice->add_entry( array(
      'attribute' => 'balance',
      'note' => 'Refunded via XML-RPC',
      'amount' => (float) $invoice->data[ 'total_payments' ],
      'type' => 'refund'
    ) );
    if ( !$insert_id ) return new WP_Error( 'wp.invoice', __( 'Could not refund due to unknown error.', ud_get_wp_invoice()->domain ), $args );

    $invoice->save_invoice();

    //** Load again to get changes */
    $invoice = new WPI_Invoice();
    $invoice->load_invoice( array( 'id' => $ID ) );

    return $invoice;
  }

  /**
   * Pay invoice by ID
   *
   * @param type $args
   *
   * @return WP_Error|WPI_Invoice
   */
  function pay_invoice( $args = array() ) {
    //** Default arguments */
    $defaults = array(
      'ID' => false,
      'amount' => false
    );

    //** Parse arguments */
    extract( wp_parse_args( $args, $defaults ) );

    //** Check */
    if ( !$ID ) return new WP_Error( 'wp.invoice', __( 'Argument "ID" is required.', ud_get_wp_invoice()->domain ), $args );
    if ( !$amount ) return new WP_Error( 'wp.invoice', __( 'Argument "amount" is required.', ud_get_wp_invoice()->domain ), $args );
    if ( !is_numeric( $amount ) ) return new WP_Error( 'wp.invoice', __( 'Argument "amount" is malformed.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    //** Check */
    if ( !empty( $invoice->error ) ) return new WP_Error( 'wp.invoice', __( 'Invoice not found', ud_get_wp_invoice()->domain ), $args );

    //** Pay only if status if not paid */
    if ( $invoice->data[ 'post_status' ] == 'paid' ) return new WP_Error( 'wp.invoice', __( 'Invoice is completely paid. Payments are not acceptable anymore.', ud_get_wp_invoice()->domain ), $args );

    //** Check amount */
    if ( (float) $invoice->data[ 'net' ] < (float) $amount ) return new WP_Error( 'wp.invoice', __( 'Cannot pay more that the balance is. Maximum is ' . $invoice->data[ 'net' ], ud_get_wp_invoice()->domain ), $args );

    //** Handle partial */
    if ( (float) $invoice->data[ 'net' ] > (float) $amount ) {
      if ( empty( $invoice->data[ 'deposit_amount' ] ) ) return new WP_Error( 'wp.invoice', __( 'Partial payments are not allowed. Pay minimum is ' . $invoice->data[ 'net' ], ud_get_wp_invoice()->domain ), $args );
      if ( (float) $amount < (float) $invoice->data[ 'deposit_amount' ] ) {
        return new WP_Error( 'wp.invoice', __( 'Minimum allowed payment is ' . $invoice->data[ 'deposit_amount' ], ud_get_wp_invoice()->domain ), $args );
      }
    }

    //** Add payment item */
    $invoice->add_entry( array(
      'attribute' => 'balance',
      'note' => 'Paid ' . ( (float) $amount ) . ' ' . $invoice->data[ 'default_currency_code' ] . ' via XML-RPC API',
      'amount' => (float) $amount,
      'type' => 'add_payment'
    ) );

    //** Save to be sure totals recalculated */
    $invoice->save_invoice();

    //** Load again to get changes */
    $invoice = new WPI_Invoice();
    $invoice->load_invoice( array( 'id' => $ID ) );

    return $invoice;
  }

  /**
   * Delete Invoice by ID
   *
   * @param array $args
   *
   * @return bool
   */
  function delete_invoice( $args = array() ) {
    //** Default arguments */
    $defaults = array( 'ID' => false );

    //** Parse arguments */
    extract( wp_parse_args( $args, $defaults ) );

    //** Check */
    if ( !$ID ) return new WP_Error( 'wp.invoice', __( 'Argument "ID" is required.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    //** Return result of delete method */
    return $invoice->delete();
  }

  /**
   * Returns invoice object requested by ID
   *
   * @param array $args
   *
   * @return WPI_Invoice
   */
  function get_invoice( $args = array() ) {
    //** Default arguments */
    $defaults = array( 'ID' => false );

    //** Parse arguments */
    extract( wp_parse_args( $args, $defaults ) );

    //** Check */
    if ( !$ID ) return new WP_Error( 'wp.invoice', __( 'Argument "ID" is required.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    //** Return ready object */
    return empty( $invoice->error ) ? $invoice : new WP_Error( 'wp.invoice', __( 'Invoice not found', ud_get_wp_invoice()->domain ), $args );
  }

  /**
   * Update invoice by ID
   *
   * @global Array $wpi_settings
   *
   * @param Array $args
   *
   * @return WP_Error|WPI_Invoice
   */
  function update_invoice( $args = array() ) {
    global $wpi_settings;

    //** Default arguments */
    $defaults = array(
      'ID' => false,
      'subject' => false,
      'description' => false,
      'type' => false,
      'deposit' => false,
      'due_date' => false,
      'tax' => false,
      'tax_method' => false,
      'recurring' => false,
      'discount' => false,
      'items' => array(),
      'charges' => array()
    );

    //** Parse arguments */
    extract( $args = wp_parse_args( $args, $defaults ) );

    //** Check */
    if ( !$ID ) return new WP_Error( 'wp.invoice', __( 'Argument "ID" is required.', ud_get_wp_invoice()->domain ), $args );

    //** New Invoice object */
    $invoice = new WPI_Invoice();

    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    $set = array();

    //** Subject */
    if ( $subject ) {
      $subject = trim( $subject );
      if ( !empty( $subject ) ) {
        $set[ 'subject' ] = $subject;
        $set[ 'post_title' ] = $subject;
      }
    }

    //** Description */
    if ( $description ) {
      $description = trim( $description );
      if ( !empty( $description ) ) {
        $set[ 'description' ] = $description;
      }
    }

    if ( $type ) {
      //** If type is registered */
      if ( !array_key_exists( $type, $wpi_settings[ 'types' ] ) ) return new WP_Error( 'wp.invoice', __( 'Unknown invoice type.', ud_get_wp_invoice()->domain ), $args );

      //** If recurring */
      if ( $type == 'recurring' ) {
        $recurring = array_filter( $recurring );
        if ( empty( $recurring[ 'unit' ] ) || empty( $recurring[ 'cycles' ] ) ) return new WP_Error( 'wp.invoice', __( 'Method requires correct "recurring" argument if "type" is recurring.', ud_get_wp_invoice()->domain ), $args );
        if ( !empty( $deposit ) ) return new WP_Error( 'wp.invoice', __( 'Cannot use "deposit" with "recurring" type.', ud_get_wp_invoice()->domain ), $args );
      }

      //** If quote */
      if ( $type == 'quote' ) {
        if ( !empty( $deposit ) ) return new WP_Error( 'wp.invoice', __( 'Cannot use "deposit" with "quote" type.', ud_get_wp_invoice()->domain ), $args );
      }

      $set[ 'type' ] = $type;

      //** If quote */
      if ( $type == 'quote' ) {
        $set[ 'status' ] = $type;
        $set[ 'is_quote' ] = 'true';
      }

      //** Recurring */
      if ( $type == 'recurring' ) {
        $invoice->create_schedule( $recurring );
      }
    }

    //** Partial payments */
    if ( $deposit ) {
      $set[ 'deposit_amount' ] = (float) $deposit;
    }

    if ( $due_date ) {
      $set[ 'due_date_year' ] = $due_date[ 'year' ];
      $set[ 'due_date_month' ] = $due_date[ 'month' ];
      $set[ 'due_date_day' ] = $due_date[ 'day' ];
    }

    if ( $tax ) {
      $set[ 'tax' ] = $tax;
    }

    if ( $tax_method ) {
      if ( $tax_method != 'before_discount' && $tax_method != 'after_discount' ) {
        return new WP_Error( 'wp.invoice', __( 'Unknown "tax_method".', ud_get_wp_invoice()->domain ), $args );
      }
      $set[ 'tax_method' ] = $tax_method;
    }

    if ( $discount ) {
      if ( empty( $discount[ 'name' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount name is required.', ud_get_wp_invoice()->domain ), $args );
      if ( empty( $discount[ 'type' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount type is required. ("amount" or "percent").', ud_get_wp_invoice()->domain ), $args );
      if ( empty( $discount[ 'amount' ] ) ) return new WP_Error( 'wp.invoice', __( 'Discount amount is required.', ud_get_wp_invoice()->domain ), $args );
      $invoice->data[ 'discount' ] = array();
      $invoice->add_discount( $discount );
    }

    if ( $items ) {
      //** Items */
      foreach ( $items as $item ) {
        //** Do not allow to save melformed items */
        if ( empty( $item[ 'name' ] ) ||
          empty( $item[ 'quantity' ] ) ||
          empty( $item[ 'price' ] )
        ) {
          return new WP_Error( 'wp.invoice', __( 'One or more "items" have malformed structure. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        }

        //** Global tax has higher priority */
        if ( !empty( $tax ) ) $item[ 'tax_rate' ] = $tax;

        //** Check types */
        if ( !is_numeric( $item[ 'quantity' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "quantity" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        if ( !is_numeric( $item[ 'price' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "price" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        if ( !empty( $item[ 'tax_rate' ] ) ) {
          if ( !is_numeric( $item[ 'tax_rate' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "items" have wrong "tax_rate" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        }
      }
    }

    if ( $charges ) {
      //** Charges */
      foreach ( $charges as $charge ) {
        //** Do not allow to save melformed items */
        if ( empty( $charge[ 'name' ] ) ||
          empty( $charge[ 'amount' ] )
        ) {
          return new WP_Error( 'wp.invoice', __( 'One or more "charges" have malformed structure. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        }

        //** Global tax has higher priority */
        if ( !empty( $tax ) ) $charge[ 'tax' ] = $tax;

        //** Check types */
        if ( !is_numeric( $charge[ 'amount' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "charges" have wrong "amount" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        if ( !empty( $charge[ 'tax' ] ) ) {
          if ( !is_numeric( $charge[ 'tax' ] ) ) return new WP_Error( 'wp.invoice', __( 'One or more "charges" have wrong "tax" value. Cannot create Invoice.', ud_get_wp_invoice()->domain ), $args );
        }
      }
    }

    //** If passed validation - save item */
    if ( $charges ) {
      $invoice->data[ 'itemized_charges' ] = array();
      foreach ( $charges as $charge ) {
        $invoice->line_charge( $charge );
      }
    }
    if ( $items ) {
      $invoice->data[ 'itemized_list' ] = array();
      foreach ( $items as $item ) {
        $invoice->line_item( $item );
      }
    }

    $invoice->set( $set );

    $invoice->save_invoice();

    $invoice = new WPI_Invoice();
    //** Load invoice by ID */
    $invoice->load_invoice( array( 'id' => $ID ) );

    return $invoice;
  }

}

/**
 * Fires API methods in order to arguments passed
 *
 * @param array $args
 */
if ( !function_exists( 'wpi_xmlrpc_request' ) ) {
  function wpi_xmlrpc_request( $args ) {
    global $wp_xmlrpc_server, $wpi_xml_rpc_api_reference;

    //** Escape args */
    $wp_xmlrpc_server->escape( $args );

    //** Sort args */
    $method = $args[ 0 ];
    $credentials = $args[ 1 ];
    $args = $args[ 2 ];
    $blog = isset( $args[ 3 ] ) ? $args[ 3 ] : 0;

    //** Check credentials */
    if ( !$user = $wp_xmlrpc_server->login( $credentials[ 0 ], $credentials[ 1 ] ) )
      return $wp_xmlrpc_server->error;

    if ( !current_user_can_for_blog( $blog, 'manage_options' ) ) return new WP_Error( 'wp.invoice', __( 'Access denied. Do not have rights.', ud_get_wp_invoice()->domain ), $args );

    //** Check for reference */
    if ( !array_key_exists( $method, $wpi_xml_rpc_api_reference[ 'methods' ] ) ) return new WP_Error( 'wp.invoice', __( 'Requested method is absent in API Reference', ud_get_wp_invoice()->domain ), $args );

    //** Return result of calling requested method */
    return is_callable( array( $wpi_xml_rpc_api_reference[ 'namespace' ], $method ) )
      ? call_user_func( array( $wpi_xml_rpc_api_reference[ 'namespace' ], $method ), $args )
      : new WP_Error( 'wp.invoice', __( 'Unknown method', ud_get_wp_invoice()->domain ), $method );
  }
}

/**
 * Render API help
 */
if ( !function_exists( 'wpi_help_api_reference' ) ) {
  function wpi_help_api_reference() {
    global $wpi_xml_rpc_api_reference;
    ?>
    <div class="wpi_settings_block">
        <?php _e( 'WP-Invoice XML-RPC API Reference', ud_get_wp_invoice()->domain ); ?>
      <input type="button" class="wpi_settings_view button-primary" value="<?php esc_attr( _e( 'Toggle', ud_get_wp_invoice()->domain ) ); ?>">
        <div class="wpi_settings_row hidden">
          <div class="wpi_scrollable_content">
            <h2>
              <?php _e( 'WP-Invoice XML-RPC API Reference', ud_get_wp_invoice()->domain ); ?>
            </h2>
            <p>
              <?php echo $wpi_xml_rpc_api_reference[ 'namespace' ]->description; ?>
            </p>
            <h2>
              <?php _e( 'Examples', ud_get_wp_invoice()->domain ); ?>
            </h2>
            <p>
              <a target="_blank" href="https://github.com/UsabilityDynamics/wp-invoice/wiki/API#examples">https://github.com/UsabilityDynamics/wp-invoice/wiki/API</a>
            </p>
            <p>
              <?php _e( 'Below is a list of available functions that current API supports.', ud_get_wp_invoice()->domain ); ?>
            </p>

            <ul>
            <?php foreach ( $wpi_xml_rpc_api_reference[ 'methods' ] as $method => $info ): ?>
              <li class="wpi_api_method_wrapper">
                <code><?php echo $method; ?></code>
                <p><?php echo $info[ 'description' ]; ?></p>
                <h4><?php _e( 'Arguments:', ud_get_wp_invoice()->domain ); ?></h4>
                <ol>
                  <?php foreach ( $info[ 'args' ] as $arg => $arg_info ): ?>
                    <li><b><?php echo $arg; ?></b>:<?php echo $arg_info[ 'type' ]; ?><?php echo $arg_info[ 'required' ] ? '<sup>*</sup>' : ''; ?>
                      - <?php echo $arg_info[ 'description' ] ?></li>
                  <?php endforeach; ?>
                </ol>
              </li>
            <?php endforeach; ?>
            </ul>

          </div>
        </div>
      </div>
  <?php
  }
}