MS_Model_Invoice

Invoice model.

Defined (1)

The class is defined in the following location(s).

/app/model/class-ms-model-invoice.php  
  1. class MS_Model_Invoice extends MS_Model_CustomPostType { 
  2.  
  3. /** 
  4. * Model custom post type. 
  5. * Both static and class property are used to handle php 5.2 limitations. 
  6. * @since 1.0.0 
  7. * @var string 
  8. */ 
  9. protected static $POST_TYPE = 'ms_invoice'; 
  10.  
  11. /** 
  12. * Invoice status constants. 
  13. * @since 1.0.0 
  14. * @see $status property. 
  15. * @var string 
  16. */ 
  17. // Invoice was created but user did not yet confirm that he wants to sign up/pay. 
  18. const STATUS_NEW = 'new'; 
  19.  
  20. // Invoice was created but user did not make any attempt to pay. 
  21. const STATUS_BILLED = 'billed'; 
  22.  
  23. // User confirmed payment and it was successful. 
  24. const STATUS_PAID = 'paid'; 
  25.  
  26. // User confirmed payment but gateway returned a "pending" notification. 
  27. const STATUS_PENDING = 'pending'; 
  28.  
  29. // User confirmed payment but gateway returned some error (dispute, wrong amount, etc). 
  30. const STATUS_DENIED = 'denied'; 
  31.  
  32. // Archived invoices are hidden from invoice lists, i.e. "deleted" 
  33. const STATUS_ARCHIVED = 'archived'; 
  34.  
  35. /** 
  36. * External transaction ID. 
  37. * Used to link 3rd party transaction ID to $this->id 
  38. * @since 1.0.0 
  39. * @var string 
  40. */ 
  41. protected $external_id = ''; 
  42.  
  43. /** 
  44. * Gateway ID. 
  45. * Gateway used to pay this invoice. 
  46. * @since 1.0.0 
  47. * @var string 
  48. */ 
  49. protected $gateway_id = ''; 
  50.  
  51. /** 
  52. * Membership ID. 
  53. * Invoice for membership. 
  54. * @since 1.0.0 
  55. * @var int 
  56. */ 
  57. protected $membership_id = 0; 
  58.  
  59. /** 
  60. * User ID. 
  61. * Invoice for this user/member. 
  62. * @since 1.0.0 
  63. * @var int 
  64. */ 
  65. protected $user_id = 0; 
  66.  
  67. /** 
  68. * Log the users IP address once he visits the checkout page. 
  69. * This way we can also see if the user visited the checkout page to pay the 
  70. * invoice. 
  71. * @since 1.0.2.0 
  72. * @var string 
  73. */ 
  74. protected $checkout_ip = ''; 
  75.  
  76. /** 
  77. * Log the timestamp when the user visits the checkout page. 
  78. * @since 1.0.2.0 
  79. * @var string 
  80. */ 
  81. protected $checkout_date = ''; 
  82.  
  83. /** 
  84. * Membership Relationship ID. 
  85. * @since 1.0.0 
  86. * @var int 
  87. */ 
  88. protected $ms_relationship_id = 0; 
  89.  
  90. /** 
  91. * Coupon ID. 
  92. * Used coupon ID. 
  93. * @since 1.0.0 
  94. * @var int 
  95. */ 
  96. protected $coupon_id = 0; 
  97.  
  98. /** 
  99. * Currency of this invoice. 
  100. * @since 1.0.0 
  101. * @var string 
  102. */ 
  103. protected $currency = ''; 
  104.  
  105. /** 
  106. * Amount value not including discounts. 
  107. * @since 1.0.0 
  108. * @var float 
  109. */ 
  110. protected $amount = 0; 
  111.  
  112. /** 
  113. * Discount value. 
  114. * @since 1.0.0 
  115. * @var float 
  116. */ 
  117. protected $discount = 0; 
  118.  
  119. /** 
  120. * Pro rate value. 
  121. * @since 1.0.0 
  122. * @var float 
  123. */ 
  124. protected $pro_rate = 0; 
  125.  
  126. /** 
  127. * READ-ONLY. Invoice amount including all discounts but no taxes. 
  128. * To modify this value change any of these properties: 
  129. * amount, discount, pro_rate 
  130. * @since 1.0.0 
  131. * @var float 
  132. */ 
  133. protected $subtotal = 0; 
  134.  
  135. /** 
  136. * READ-ONLY. Total value (= subtotal + taxes). 
  137. * To modify this value change any of these properties: 
  138. * amount, discount, pro_rate, tax_rate 
  139. * @since 1.0.0 
  140. * @var float 
  141. */ 
  142. protected $total = 0; 
  143.  
  144. /** 
  145. * Inovoice status. 
  146. * @since 1.0.0 
  147. * @var string 
  148. */ 
  149. protected $status = ''; 
  150.  
  151. /** 
  152. * Invoice for trial period. 
  153. * @since 1.0.0 
  154. * @var boolean 
  155. */ 
  156. protected $uses_trial = false; 
  157.  
  158. /** 
  159. * The trial period price. 
  160. * @since 1.0.0 
  161. * @var numeric 
  162. */ 
  163. protected $trial_price = 0; 
  164.  
  165. /** 
  166. * This is the last day of the trial period. The next day is paid. 
  167. * @since 1.0.0 
  168. * @var date 
  169. */ 
  170. protected $trial_ends = ''; 
  171.  
  172. /** 
  173. * Invoice date. 
  174. * This is the date when the INVOICE WAS CREATED. It may be differe than the 
  175. * due date if the subscription uses a trial period. 
  176. * @since 1.0.0 
  177. * @var string 
  178. */ 
  179. protected $invoice_date = ''; 
  180.  
  181. /** 
  182. * Defines date WHEN PAYMENT IS DUE. 
  183. * When invoice uses_trial is true then this is the first day that is paid. 
  184. * @since 1.0.0 
  185. * @var string 
  186. */ 
  187. protected $due_date = ''; 
  188.  
  189. /** 
  190. * Date when the invoice was MARKED AS PAID. 
  191. * Note that free invoices do not have a pay-date! The pay-date is only set 
  192. * when something was actually paid ;) 
  193. * @since 1.0.2.0 
  194. * @var string 
  195. */ 
  196. protected $pay_date = ''; 
  197.  
  198. /** 
  199. * Invoice notes. 
  200. * @since 1.0.0 
  201. * @var string 
  202. */ 
  203. protected $notes = ''; 
  204.  
  205. /** 
  206. * Invoice number. 
  207. * @since 1.0.0 
  208. * @var int 
  209. */ 
  210. protected $invoice_number = 0; 
  211.  
  212. /** 
  213. * Tax rate value. 
  214. * @since 1.0.0 
  215. * @var float 
  216. */ 
  217. protected $tax_rate = 0; 
  218.  
  219. /** 
  220. * Tax name. 
  221. * @since 1.0.0 
  222. * @var string 
  223. */ 
  224. protected $tax_name = ''; 
  225.  
  226. /** 
  227. * Short, compact version of the payment description 
  228. * @since 1.0.0 
  229. * @var string 
  230. */ 
  231. protected $short_description = ''; 
  232.  
  233. /** 
  234. * Where the data came from. Can only be changed by data import tool 
  235. * @since 1.0.0 
  236. * @var string 
  237. */ 
  238. protected $source = ''; 
  239.  
  240. /** 
  241. * Timestamp of price calculation. 
  242. * This information is used when price-options of the memberhsip is changed. 
  243. * @since 1.0.0 
  244. * @var int 
  245. */ 
  246. protected $price_date = 0; 
  247.  
  248. // 
  249. // 
  250. // 
  251. // -------------------------------------------------------------- COLLECTION 
  252.  
  253. /** 
  254. * Returns the post-type of the current object. 
  255. * @since 1.0.0 
  256. * @return string The post-type name. 
  257. */ 
  258. public static function get_post_type() { 
  259. return parent::_post_type( self::$POST_TYPE ); 
  260.  
  261. /** 
  262. * Get custom register post type args for this model. 
  263. * @since 1.0.0 
  264. */ 
  265. public static function get_register_post_type_args() { 
  266. $args = array( 
  267. 'label' => __( 'Membership2 Invoices', 'membership2' ),  
  268. 'description' => __( 'Member Invoices', 'membership2' ),  
  269. 'public' => true,  
  270. 'show_ui' => false,  
  271. 'show_in_menu' => false,  
  272. 'has_archive' => false,  
  273. 'publicly_queryable' => true,  
  274. 'supports' => false,  
  275. 'hierarchical' => false,  
  276. ); 
  277.  
  278. return apply_filters( 
  279. 'ms_customposttype_register_args',  
  280. $args,  
  281. self::get_post_type() 
  282. ); 
  283.  
  284. /** 
  285. * Get invoice status types. 
  286. * @since 1.0.0 
  287. * @param bool $extended Optional. If true, additional details will be 
  288. * returned, not only the status name. 
  289. * @return array A list of status IDs with status name/description. 
  290. */ 
  291. public static function get_status_types( $extended = false ) { 
  292. if ( $extended ) { 
  293. $result = array( 
  294. self::STATUS_NEW => __( 'Draft - Invoice is prepared but user cannot see it yet', 'membership2' ),  
  295. self::STATUS_BILLED => __( 'Billed - User can see the invoice and needs to pay', 'membership2' ),  
  296. self::STATUS_PENDING => __( 'Pending - Waiting for confirmation from payment gateway', 'membership2' ),  
  297. self::STATUS_PAID => __( 'Paid - Payment arrived on our account!', 'membership2' ),  
  298. self::STATUS_DENIED => __( 'Denied - Payment was denied', 'membership2' ),  
  299. ); 
  300. } else { 
  301. $result = array( 
  302. self::STATUS_NEW => __( 'Draft', 'membership2' ),  
  303. self::STATUS_BILLED => __( 'Billed', 'membership2' ),  
  304. self::STATUS_PENDING => __( 'Pending', 'membership2' ),  
  305. self::STATUS_PAID => __( 'Paid', 'membership2' ),  
  306. self::STATUS_DENIED => __( 'Denied', 'membership2' ),  
  307. ); 
  308.  
  309. return apply_filters( 
  310. 'ms_model_invoice_get_status_types',  
  311. $result,  
  312. $extended 
  313. ); 
  314.  
  315. /** 
  316. * Returns the default query-arg array 
  317. * @since 1.0.0 
  318. * @return array 
  319. */ 
  320. public static function get_query_args() { 
  321. $args = array(); 
  322.  
  323. if ( ! empty( $_REQUEST['orderby'] ) && ! empty( $_REQUEST['order'] ) ) { 
  324. $args['orderby'] = $_REQUEST['orderby']; 
  325. $args['order'] = $_REQUEST['order']; 
  326. } else { 
  327. $args['orderby'] = 'ID'; 
  328. $args['order'] = 'DESC'; 
  329.  
  330. // Prepare order by statement. 
  331. $orderby = $args['orderby']; 
  332. if ( ! empty( $orderby ) 
  333. && ! in_array( $orderby, array( 'ID', 'author' ) ) 
  334. && property_exists( 'MS_Model_Invoice', $orderby ) 
  335. ) { 
  336. $args['meta_key'] = $orderby; 
  337. if ( in_array( $orderby, array( 'amount', 'total' ) ) ) { 
  338. $args['orderby'] = 'meta_value_num'; 
  339. } else { 
  340. $args['orderby'] = 'meta_value'; 
  341.  
  342. // Search string. 
  343. if ( ! empty( $_REQUEST['s'] ) ) { 
  344. $user_args = array( 
  345. 'search' => '*' . $_REQUEST['s'] . '*',  
  346. ); 
  347. $user_list = new WP_User_Query( $user_args ); 
  348. $user_ids = array(); 
  349. foreach ( $user_list->results as $user ) { 
  350. $user_ids[] = $user->ID; 
  351. $args['author__in'] = $user_ids; 
  352.  
  353. $args['meta_query'] = array(); 
  354.  
  355. // Gateway filter. 
  356. if ( ! empty( $_REQUEST['gateway_id'] ) ) { 
  357. $args['meta_query']['gateway_id'] = array( 
  358. 'key' => 'gateway_id',  
  359. 'value' => $_REQUEST['gateway_id'],  
  360. ); 
  361.  
  362. // Payment status filter. 
  363. if ( ! empty( $_REQUEST['status'] ) ) { 
  364. if ( 'default' === $_REQUEST['status'] ) { 
  365. $args['meta_query']['status'] = array( 
  366. 'key' => 'status',  
  367. 'value' => array( 
  368. self::STATUS_BILLED,  
  369. self::STATUS_PENDING,  
  370. self::STATUS_PAID,  
  371. self::STATUS_DENIED,  
  372. ),  
  373. 'compare' => 'IN',  
  374. ); 
  375. } elseif ( 'open' === $_REQUEST['status'] ) { 
  376. $args['meta_query']['status'] = array( 
  377. 'key' => 'status',  
  378. 'value' => array( 
  379. self::STATUS_BILLED,  
  380. self::STATUS_PENDING,  
  381. ),  
  382. 'compare' => 'IN',  
  383. ); 
  384. } else { 
  385. $args['meta_query']['status'] = array( 
  386. 'key' => 'status',  
  387. 'value' => $_REQUEST['status'],  
  388. ); 
  389.  
  390. return $args; 
  391.  
  392. /** 
  393. * Get the number of invoices. 
  394. * @since 1.0.0 
  395. * @param array $args The query post args 
  396. * @see http://codex.wordpress.org/Class_Reference/WP_Query 
  397. * @return int 
  398. */ 
  399. public static function get_invoice_count( $args = null ) { 
  400. $defaults = array( 
  401. 'post_type' => self::get_post_type(),  
  402. 'post_status' => 'any',  
  403. ); 
  404. $args = apply_filters( 
  405. 'ms_model_invoice_get_invoice_count_args',  
  406. wp_parse_args( $args, $defaults ) 
  407. ); 
  408.  
  409. MS_Factory::select_blog(); 
  410. $query = new WP_Query( $args ); 
  411. MS_Factory::revert_blog(); 
  412.  
  413. return apply_filters( 
  414. 'ms_model_invoice_get_invoice_count',  
  415. $query->found_posts,  
  416. $args 
  417. ); 
  418.  
  419. /** 
  420. * Count the number of unpaid invoices. Unpaid is any invoice with status 
  421. * BILLED or PENDING. 
  422. * @since 1.0.0 
  423. * @param array $args The query post args 
  424. * @see http://codex.wordpress.org/Class_Reference/WP_Query 
  425. * @param bool $for_badge if true then return value is a string 
  426. * @return int|string 
  427. */ 
  428. public static function get_unpaid_invoice_count( $args = null, $for_badge = false ) { 
  429. $defaults = self::get_query_args(); 
  430.  
  431. $args = apply_filters( 
  432. 'ms_model_invoice_get_unpaid_invoice_count_args',  
  433. wp_parse_args( $args, $defaults ) 
  434. ); 
  435.  
  436. $args['meta_query']['status']['value'] = array( 
  437. self::STATUS_BILLED,  
  438. self::STATUS_PENDING,  
  439. ); 
  440. $args['meta_query']['status']['compare'] = 'IN'; 
  441.  
  442. $bill_count = self::get_invoice_count( $args ); 
  443.  
  444. $res = $bill_count; 
  445. if ( $for_badge ) { 
  446. if ( $bill_count > 99 ) { 
  447. $res = '99+'; 
  448. } elseif ( ! $bill_count ) { 
  449. $res = ''; 
  450.  
  451. return apply_filters( 
  452. 'ms_model_invoice_get_unpaid_invoice_count',  
  453. $res,  
  454. $bill_count,  
  455. $args,  
  456. $for_badge 
  457. ); 
  458.  
  459. /** 
  460. * Get invoices. 
  461. * @since 1.0.0 
  462. * @param mixed $args The arguments to select data. 
  463. * @return array $invoices 
  464. */ 
  465. public static function get_invoices( $args = null ) { 
  466. $defaults = array( 
  467. 'post_type' => self::get_post_type(),  
  468. 'posts_per_page' => 10,  
  469. 'post_status' => 'any',  
  470. 'fields' => 'ids',  
  471. 'order' => 'DESC',  
  472. 'orderby' => 'ID',  
  473. ); 
  474. $args = apply_filters( 
  475. 'ms_model_invoice_get_invoices_args',  
  476. wp_parse_args( $args, $defaults ) 
  477. ); 
  478.  
  479. MS_Factory::select_blog(); 
  480. $query = new WP_Query( $args ); 
  481. $items = $query->posts; 
  482. $invoices = array(); 
  483. MS_Factory::revert_blog(); 
  484.  
  485. foreach ( $items as $item ) { 
  486. $invoices[] = MS_Factory::load( 'MS_Model_Invoice', $item ); 
  487.  
  488. return apply_filters( 
  489. 'ms_model_invoice_get_invoices',  
  490. $invoices,  
  491. $args 
  492. ); 
  493.  
  494. /** 
  495. * Returns all invoices of the specified user that are "public" for the 
  496. * user. This means that some internal invoices will not be displayed: 
  497. * - Invoices with 0.00 total amount are not displayed 
  498. * - Invoices with status New are not displayed 
  499. * @since 1.0.0 
  500. * @param int $user_id 
  501. * @param int $limit 
  502. * @return array List of MS_Model_Invoice objects. 
  503. */ 
  504. public static function get_public_invoices( $user_id, $limit = -1 ) { 
  505. $list = self::get_invoices( 
  506. array( 
  507. 'author' => $user_id,  
  508. 'posts_per_page' => $limit,  
  509. 'meta_query' => array( 
  510. 'relation' => 'AND',  
  511. // Do not display invoices for free memberships. 
  512. array( 
  513. 'key' => 'amount',  
  514. 'value' => '0',  
  515. 'compare' => '!=',  
  516. ),  
  517. // Do not display and Invoice with status "New". 
  518. array( 
  519. 'key' => 'status',  
  520. 'value' => MS_Model_Invoice::STATUS_NEW,  
  521. 'compare' => '!=',  
  522. ),  
  523. ); 
  524.  
  525. return $list; 
  526.  
  527. /** 
  528. * Get specific invoice. 
  529. * Get invoice of a user and membership. 
  530. * @since 1.0.0 
  531. * @param int $subscription_id The membership relationship id. 
  532. * @param int $invoice_number Optional. The invoice number. Get the current number if null. 
  533. * @param string $status Optional. The invoice status. 
  534. * @return MS_Model_Invoice The found invoice or null if not found. 
  535. */ 
  536. public static function get_invoice( $subscription_id, $invoice_number = null, $status = null ) { 
  537. $args = array( 
  538. 'post_type' => self::get_post_type(),  
  539. 'post_status' => 'any',  
  540. 'fields' => 'ids',  
  541. 'order' => 'DESC',  
  542. ); 
  543.  
  544. $args['meta_query']['ms_relationship_id'] = array( 
  545. 'key' => 'ms_relationship_id',  
  546. 'value' => $subscription_id,  
  547. ); 
  548. if ( ! empty( $status ) ) { 
  549. $args['meta_query']['status'] = array( 
  550. 'key' => 'status',  
  551. 'value' => $status,  
  552. ); 
  553. if ( ! empty( $invoice_number ) ) { 
  554. $args['meta_query']['invoice_number'] = array( 
  555. 'key' => 'invoice_number',  
  556. 'value' => $invoice_number,  
  557. ); 
  558.  
  559. MS_Factory::select_blog(); 
  560. $args = apply_filters( 'ms_model_invoice_get_invoice_args', $args ); 
  561. $query = new WP_Query( $args ); 
  562. $item = $query->posts; 
  563. MS_Factory::revert_blog(); 
  564.  
  565. $invoice = null; 
  566. if ( ! empty( $item[0] ) ) { 
  567. $invoice = MS_Factory::load( 'MS_Model_Invoice', $item[0] ); 
  568.  
  569. return apply_filters( 
  570. 'ms_model_invoice_get_invoice',  
  571. $invoice,  
  572. $subscription_id,  
  573. $invoice_number,  
  574. $status 
  575. ); 
  576.  
  577. /** 
  578. * Get current member membership invoice. 
  579. * The current invoice is the not paid one. Every time a invoice is paid,  
  580. * the current invoice number is incremented. 
  581. * @since 1.0.0 
  582. * @param MS_Model_Relationship $subscription The membership relationship. 
  583. * @param bool $create_missing Optional. True to overwrite existing 
  584. * invoice or false to create a new one if doesn't exist. 
  585. * @return MS_Model_Invoice 
  586. */ 
  587. public static function get_current_invoice( $subscription, $create_missing = true ) { 
  588. $invoice = self::get_invoice( 
  589. $subscription->id,  
  590. $subscription->current_invoice_number 
  591. ); 
  592.  
  593. if ( ! $invoice && $create_missing ) { 
  594. // Create a new invoice. 
  595. $invoice = self::create_invoice( 
  596. $subscription,  
  597. $subscription->current_invoice_number 
  598. ); 
  599.  
  600. return apply_filters( 
  601. 'ms_model_invoice_get_current_invoice',  
  602. $invoice,  
  603. $subscription,  
  604. $create_missing 
  605. ); 
  606.  
  607. /** 
  608. * Get next invoice for the membership. 
  609. * @since 1.0.0 
  610. * @param MS_Model_Relationship $subscription The membership relationship. 
  611. * @param bool $create_missing Optional. True to overwrite existing 
  612. * invoice or false to create a new one if doesn't exist. 
  613. * @return MS_Model_Invoice 
  614. */ 
  615. public static function get_next_invoice( $subscription, $create_missing = true ) { 
  616. $invoice = self::get_invoice( 
  617. $subscription->id,  
  618. $subscription->current_invoice_number + 1 
  619. ); 
  620.  
  621. if ( ! $invoice && $create_missing ) { 
  622. // Create a new invoice. 
  623. $invoice = self::create_invoice( 
  624. $subscription,  
  625. $subscription->current_invoice_number + 1 
  626. ); 
  627.  
  628. /** 
  629. * Since only the *first* invoice can have discount/pro-rating we 
  630. * manually set those values to 0. 
  631. */ 
  632. $invoice->discount = 0; 
  633. $invoice->pro_rate = 0; 
  634. $invoice->notes = array(); 
  635.  
  636. return apply_filters( 
  637. 'ms_model_invoice_get_next_invoice',  
  638. $invoice,  
  639. $subscription,  
  640. $create_missing 
  641. ); 
  642.  
  643. /** 
  644. * Get previous invoice for the membership. 
  645. * @since 1.0.0 
  646. * @param MS_Model_Relationship $subscription The membership relationship. 
  647. * @param string $status The invoice status to find. Optional 
  648. * @return MS_Model_Invoice 
  649. */ 
  650. public static function get_previous_invoice( $subscription, $status = null ) { 
  651. $invoice = self::get_invoice( 
  652. $subscription->id,  
  653. $subscription->current_invoice_number - 1,  
  654. $status 
  655. ); 
  656.  
  657. return apply_filters( 
  658. 'ms_model_invoice_get_previous_invoice',  
  659. $invoice,  
  660. $subscription,  
  661. $status 
  662. ); 
  663.  
  664. /** 
  665. * Create invoice. 
  666. * Create a new invoice using the membership information. 
  667. * @since 1.0.0 
  668. * @param MS_Model_Relationship $subscription The membership to create invoice for. 
  669. * @param int $invoice_number Optional. The invoice number. 
  670. * @return object $invoice 
  671. */ 
  672. public static function create_invoice( $subscription, $invoice_number = false ) { 
  673. $membership = $subscription->get_membership(); 
  674.  
  675. if ( ! MS_Model_Membership::is_valid_membership( $membership->id ) ) { 
  676. throw new Exception( 'Invalid Membership.' ); 
  677.  
  678. $invoice = null; 
  679. $member = MS_Factory::load( 'MS_Model_Member', $subscription->user_id ); 
  680. $invoice_status = self::STATUS_NEW; 
  681. $notes = null; 
  682.  
  683. if ( empty( $invoice_number ) ) { 
  684. $invoice_number = $subscription->current_invoice_number; 
  685.  
  686. $invoice = self::get_invoice( $subscription->id, $invoice_number ); 
  687.  
  688. // No existing invoice, create a new one. 
  689. if ( ! $invoice || ! $invoice->id ) { 
  690. $invoice = MS_Factory::create( 'MS_Model_Invoice' ); 
  691. $invoice = apply_filters( 'ms_model_invoice', $invoice ); 
  692.  
  693. // Update invoice info. 
  694. $invoice->ms_relationship_id = $subscription->id; 
  695. $invoice->gateway_id = $subscription->gateway_id; 
  696. $invoice->status = $invoice_status; 
  697. $invoice->invoice_date = MS_Helper_Period::current_date(); 
  698. $invoice->membership_id = $membership->id; 
  699. $invoice->currency = MS_Plugin::instance()->settings->currency; 
  700. $invoice->user_id = $member->id; 
  701. $invoice->name = apply_filters( 
  702. 'ms_model_invoice_name',  
  703. sprintf( 
  704. __( 'Invoice for %s - %s', 'membership2' ),  
  705. $membership->name,  
  706. $member->username 
  707. ); 
  708. $invoice->invoice_number = $invoice_number; 
  709. $invoice->discount = 0; 
  710. $invoice->notes = $notes; 
  711. $invoice->amount = $membership->price; // Without taxes! 
  712.  
  713. // Check for trial period in the first period. 
  714. if ( $subscription->is_trial_eligible() 
  715. && $invoice_number === $subscription->current_invoice_number 
  716. ) { 
  717. $invoice->trial_price = $membership->trial_price; // Without taxes! 
  718. $invoice->uses_trial = true; 
  719. $invoice->trial_ends = $subscription->trial_expire_date; 
  720.  
  721. $invoice->set_due_date(); 
  722.  
  723. $invoice = apply_filters( 
  724. 'ms_model_invoice_create_before_save',  
  725. $invoice,  
  726. $subscription 
  727. ); 
  728.  
  729. $invoice->save(); 
  730.  
  731. // Refresh the tax-rate and payment description. 
  732. $invoice->total_amount_changed(); 
  733.  
  734. $invoice->save(); 
  735.  
  736. return apply_filters( 
  737. 'ms_model_relationship_create_invoice',  
  738. $invoice,  
  739. $subscription,  
  740. $invoice_number 
  741. ); 
  742.  
  743.  
  744. // 
  745. // 
  746. // 
  747. // ------------------------------------------------------------- SINGLE ITEM 
  748.  
  749.  
  750. /** 
  751. * Save model. 
  752. * @since 1.0.0 
  753. */ 
  754. public function save() { 
  755. // Validate the pay_date attribute of the invoice. 
  756. $this->validate_pay_date(); 
  757.  
  758. parent::save(); 
  759. parent::store_singleton(); 
  760.  
  761. /** 
  762. * Move an invoice to tha archive - i.e. hide it from the user. 
  763. * @since 1.0.2.0 
  764. */ 
  765. public function archive() { 
  766. if ( $this->id ) { 
  767. $this->add_notes( '----------' ); 
  768. $this->add_notes( 
  769. sprintf( 
  770. __( 'Archived on: %s', 'membership2' ),  
  771. MS_Helper_Period::current_date() 
  772. ); 
  773. $this->add_notes( 
  774. sprintf( 
  775. __( 'Former status: %s', 'membership2' ),  
  776. $this->status 
  777. ); 
  778.  
  779. $this->status = self::STATUS_ARCHIVED; 
  780. $this->save(); 
  781.  
  782. /** 
  783. * Registers the payment and marks the invoice as paid. 
  784. * This should be the only place that sets an invoice status to PAID. 
  785. * @since 1.0.0 
  786. * @param string $gateway_id The payment gateway. 
  787. * @param string $external_id Payment-ID provided by the gateway 
  788. */ 
  789. public function pay_it( $gateway_id = null, $external_id = null ) { 
  790. if ( $gateway_id ) { 
  791. $this->gateway_id = $gateway_id; 
  792. if ( $external_id ) { 
  793. $this->external_id = $external_id; 
  794. $is_paid = false; 
  795.  
  796. $subscription = $this->get_subscription(); 
  797.  
  798. // Save details on the payment. 
  799. if ( 0 == $this->total || MS_Gateway_Free::ID == $gateway_id ) { 
  800. $is_paid = $subscription->add_payment( 
  801. 0,  
  802. MS_Gateway_Free::ID,  
  803. 'free' 
  804. ); 
  805. } else { 
  806. $is_paid = $subscription->add_payment( 
  807. $this->total,  
  808. $gateway_id,  
  809. $external_id 
  810. ); 
  811.  
  812. if ( $is_paid ) { 
  813. $this->status = self::STATUS_PAID; 
  814. $this->pay_date = MS_Helper_Period::current_date(); 
  815. } else { 
  816. $this->status = self::STATUS_BILLED; 
  817.  
  818. // Manual gateway works differently. This conditon avoids infinite loop. 
  819. if ( MS_Gateway_Manual::ID != $gateway_id ) { 
  820. /** 
  821. * Process the payment and update the subscription. 
  822. * This function will call the config_period() function to calculate 
  823. * the new expire date of the subscription. 
  824. * All changes above are also saved at the end of changed() 
  825. */ 
  826. $this->changed(); 
  827.  
  828. /** 
  829. * Notify Add-ons that an invoice was paid. 
  830. * @since 1.0.0 
  831. */ 
  832. do_action( 'ms_invoice_paid', $this, $subscription ); 
  833.  
  834. /** 
  835. * Returns true if the invoice was paid. 
  836. * @since 1.0.0 
  837. * @return bool Payment status. 
  838. */ 
  839. public function is_paid() { 
  840. return $this->status == self::STATUS_PAID; 
  841.  
  842. /** 
  843. * Makes sure that the pay_date attribtue has a valid value. 
  844. * @since 1.0.2.0 
  845. */ 
  846. protected function validate_pay_date() { 
  847. if ( $this->is_paid() && $this->amount ) { 
  848. if ( ! $this->pay_date ) { 
  849. $subscription = $this->get_subscription(); 
  850. $payments = $subscription->get_payments(); 
  851. $last_payment = end( $payments ); 
  852. $this->pay_date = $last_payment['date']; 
  853. if ( ! $this->pay_date ) { 
  854. $this->pay_date = $this->due_date; 
  855. } elseif ( $this->pay_date ) { 
  856. $this->pay_date = ''; 
  857.  
  858. /** 
  859. * Update the subscription details after the invoice has changed. 
  860. * Process transaction status change related to this membership relationship. 
  861. * Change status accordinly to transaction status. 
  862. * @since 1.0.0 
  863. * @param MS_Model_Invoice $invoice The invoice to process. 
  864. * @return MS_Model_Invoice The processed invoice. 
  865. */ 
  866. public function changed() { 
  867. do_action( 
  868. 'ms_model_invoice_changed_before',  
  869. $this 
  870. ); 
  871.  
  872. if ( ! $this->ms_relationship_id ) { 
  873. MS_Helper_Debug::log( 'Cannot process transaction: No relationship defined (inv #' . $this->id .')' ); 
  874. } else { 
  875. $subscription = $this->get_subscription(); 
  876. $member = MS_Factory::load( 'MS_Model_Member', $this->user_id ); 
  877. $membership = $subscription->get_membership(); 
  878.  
  879. switch ( $this->status ) { 
  880. case self::STATUS_NEW: 
  881. case self::STATUS_BILLED: 
  882. break; 
  883.  
  884. case self::STATUS_PAID: 
  885. if ( $this->total > 0 ) { 
  886. MS_Model_Event::save_event( 
  887. MS_Model_Event::TYPE_PAID,  
  888. $subscription 
  889. ); 
  890.  
  891. do_action( 
  892. 'ms_model_invoice_changed-paid',  
  893. $this,  
  894. $member 
  895. ); 
  896.  
  897. // Check for moving memberships 
  898. if ( $subscription->move_from_id ) { 
  899. $ids = explode( ', ', $subscription->move_from_id ); 
  900. foreach ( $ids as $id ) { 
  901. $move_from = MS_Model_Relationship::get_subscription( 
  902. $subscription->user_id,  
  903. $id 
  904. ); 
  905.  
  906. if ( $move_from->is_valid() ) { 
  907. /** 
  908. * @since 1.0.1.2 The old subscription will be 
  909. * deactivated instantly, and not cancelled. 
  910. * When the subscription is cancelled the user 
  911. * still has full access to the membership 
  912. * contents. When it is deactivated he cannot 
  913. * access protected content anymore (instantly). 
  914. */ 
  915. $move_from->deactivate_membership(); 
  916.  
  917. $subscription->cancelled_memberships = $subscription->move_from_id; 
  918. $subscription->move_from_id = ''; 
  919.  
  920. /** 
  921. * Memberships with those payment types can have multiple 
  922. * invoices for a single subscription. 
  923. */ 
  924. $multi_invoice = array( 
  925. MS_Model_Membership::PAYMENT_TYPE_RECURRING,  
  926. MS_Model_Membership::PAYMENT_TYPE_FINITE,  
  927. ); 
  928.  
  929. if ( in_array( $membership->payment_type, $multi_invoice ) ) { 
  930. // Update the current_invoice_number counter. 
  931. $subscription->current_invoice_number = max( 
  932. $subscription->current_invoice_number,  
  933. $this->invoice_number + 1 
  934. ); 
  935.  
  936. if ( MS_Gateway_Manual::ID == $this->gateway_id ) { 
  937. $this->pay_it( $this->gateway_id ); 
  938. break; 
  939.  
  940. case self::STATUS_DENIED: 
  941. MS_Model_Event::save_event( MS_Model_Event::TYPE_PAYMENT_DENIED, $subscription ); 
  942. break; 
  943.  
  944. case self::STATUS_PENDING: 
  945. MS_Model_Event::save_event( MS_Model_Event::TYPE_PAYMENT_PENDING, $subscription ); 
  946. break; 
  947.  
  948. default: 
  949. do_action( 'ms_model_invoice_changed-unknown', $this ); 
  950. break; 
  951.  
  952. $member->save(); 
  953. $subscription->gateway_id = $this->gateway_id; 
  954. $subscription->save(); 
  955. $this->gateway_id = $this->gateway_id; 
  956. $this->save(); 
  957.  
  958. return apply_filters( 
  959. 'ms_model_invoice_changed',  
  960. $this,  
  961. $this 
  962. ); 
  963.  
  964. /** 
  965. * Add invoice notes. 
  966. * @since 1.0.0 
  967. * @param string $notes 
  968. */ 
  969. public function add_notes( $notes ) { 
  970. $this->notes[] = apply_filters( 
  971. 'ms_model_invoice_add_notes',  
  972. $notes,  
  973. $this 
  974. ); 
  975.  
  976. /** 
  977. * Get notes array as string. 
  978. * @since 1.0.0 
  979. * @return string The notes as text description. 
  980. */ 
  981. public function get_notes_desc() { 
  982. $desc = $this->notes; 
  983. if ( is_array( $desc ) ) { 
  984. $desc = implode( "\n", $desc ); 
  985.  
  986. return apply_filters( 
  987. 'ms_model_invoice_get_notes_desc',  
  988. $desc,  
  989. $this 
  990. ); 
  991.  
  992. /** 
  993. * Returns a translated version of the invoice status 
  994. * @since 1.0.0 
  995. * @return string 
  996. */ 
  997. public function status_text() { 
  998. static $Status = null; 
  999.  
  1000. if ( null === $Status ) { 
  1001. $Status = self::get_status_types(); 
  1002.  
  1003. $result = $this->status; 
  1004.  
  1005. if ( isset( $Status[$this->status] ) ) { 
  1006. $result = $Status[$this->status]; 
  1007.  
  1008. return apply_filters( 
  1009. 'ms_invoice_status_text',  
  1010. $result,  
  1011. $this->status 
  1012. ); 
  1013.  
  1014. /** 
  1015. * Updates various fields that display/depend on the invoice total amount. 
  1016. * @since 1.0.0 
  1017. */ 
  1018. public function total_amount_changed() { 
  1019. $subscription = $this->get_subscription(); 
  1020.  
  1021. // Allow add-ons or other plugins to set the tax infos for this invoice. 
  1022. $this->tax_rate = apply_filters( 
  1023. 'ms_invoice_tax_rate',  
  1024. 0,  
  1025. $this 
  1026. ); 
  1027. $this->tax_name = apply_filters( 
  1028. 'ms_invoice_tax_name',  
  1029. '',  
  1030. $this 
  1031. ); 
  1032.  
  1033. // Update the invoice descriptions that are displayed to the user. 
  1034. $this->description = apply_filters( 
  1035. 'ms_model_invoice_description',  
  1036. $subscription->get_payment_description( $this ) 
  1037. ); 
  1038. $this->short_description = apply_filters( 
  1039. 'ms_model_invoice_short_description',  
  1040. $subscription->get_payment_description( $this, true ) 
  1041. ); 
  1042.  
  1043. /** 
  1044. * Sets the invoice amount to the price defined by the membership settings. 
  1045. * Provides the filter `ms_model_invoice_price_timeout` which can be used to 
  1046. * define the price-timeout value. The price will not be updated before the 
  1047. * timeout is reached. 
  1048. * Only unpaid invoices are updated! 
  1049. * @since 1.0.0 
  1050. */ 
  1051. private function refresh_amount() { 
  1052. // Never change the amount of paid invoices. 
  1053. if ( $this->is_paid() ) { return; } 
  1054.  
  1055. /** 
  1056. * Define a timeout for the price in unpaid invoices. 
  1057. * The price will not change before the timeout expires, after this 
  1058. * it is updated again based on the current membership settings. 
  1059. * @var int 
  1060. */ 
  1061. $timeout = apply_filters( 
  1062. 'ms_model_invoice_price_timeout',  
  1063. 604800, // 604800 = 7 days 
  1064. $this 
  1065. ); 
  1066.  
  1067. $expire_timestamp = absint( $this->price_date ) + absint( $timeout ); 
  1068.  
  1069. // Do not change price before timeout is reached. 
  1070. if ( $expire_timestamp > time() ) { return; } 
  1071.  
  1072. // Store the current timestamp, so we don't refresh the price until 
  1073. // the timeout expires again. 
  1074. $this->price_date = time(); 
  1075. $membership = $this->get_membership(); 
  1076.  
  1077. // The invoice always has the real membership price as amount, never 
  1078. // the trial amount. 
  1079. $this->amount = $membership->price; // Without taxes! 
  1080.  
  1081. // Re-Calculate the subscription dates 
  1082. $this->set_due_date(); 
  1083.  
  1084. /** 
  1085. * Refreshes the due-date of the invoice. 
  1086. * @since 1.0.0 
  1087. */ 
  1088. public function set_due_date() { 
  1089. // Never change due-date of paid invoices. 
  1090. if ( $this->is_paid() ) { return; } 
  1091.  
  1092. $subscription = $this->get_subscription(); 
  1093.  
  1094. $due_date = false; 
  1095.  
  1096. // Handle special cases in due date calculation. 
  1097. switch ( $subscription->status ) { 
  1098. case MS_Model_Relationship::STATUS_TRIAL: 
  1099. $due_date = $subscription->trial_expire_date; 
  1100. break; 
  1101.  
  1102. case MS_Model_Relationship::STATUS_ACTIVE: 
  1103. case MS_Model_Relationship::STATUS_CANCELED: 
  1104. $due_date = $subscription->expire_date; 
  1105. break; 
  1106.  
  1107. // Default due date is today. 
  1108. if ( empty( $due_date ) ) { 
  1109. if ( $subscription->is_trial_eligible() ) { 
  1110. /** 
  1111. * This invoice includes a trial period. 
  1112. * Payment is due on last day of trial 
  1113. */ 
  1114. $due_date = $subscription->trial_expire_date; 
  1115. } else { 
  1116. // No trial period is used for this invoice. Due now. 
  1117. $due_date = MS_Helper_Period::current_date(); 
  1118.  
  1119. // Update the trial expiration date. 
  1120. $this->trial_ends = $subscription->trial_expire_date; 
  1121.  
  1122. $this->due_date = $due_date; 
  1123.  
  1124. /** 
  1125. * Get invoice net amount: Amount excluding taxes. 
  1126. * Discounting coupon and pro-rating. 
  1127. * Add taxes. 
  1128. * @since 1.0.0 
  1129. */ 
  1130. private function get_net_amount() { 
  1131. if ( ! $this->is_paid() ) { 
  1132. $this->refresh_amount(); 
  1133.  
  1134. $net_amount = $this->amount; // Net amount 
  1135. $net_amount -= $this->discount; // Remove discount 
  1136. $net_amount -= $this->pro_rate; // Remove Pro-Rate 
  1137.  
  1138. if ( $net_amount < 0 ) { 
  1139. $net_amount = 0; 
  1140.  
  1141. // Set precission to 2 decimal points. 
  1142. $net_amount = round( $net_amount, 2 ); 
  1143.  
  1144. return apply_filters( 
  1145. 'ms_model_invoice_get_net_amount',  
  1146. $net_amount,  
  1147. $this 
  1148. ); 
  1149.  
  1150. /** 
  1151. * Returns the tax-value in currency (opposed to the percentage value) 
  1152. * @since 1.0.0 
  1153. * @return float Total tax amount 
  1154. */ 
  1155. private function get_tax() { 
  1156. $tax_rate = $this->tax_rate; 
  1157.  
  1158. if ( ! is_numeric( $tax_rate ) ) { 
  1159. $tax_rate = 0; 
  1160.  
  1161. $value = $this->get_net_amount() * ( $tax_rate / 100 ); 
  1162. if ( $value < 0 ) { 
  1163. $value = 0; 
  1164.  
  1165. return $value; 
  1166.  
  1167. /** 
  1168. * Returns the tax-value in currency for the trial membership (opposed to 
  1169. * the percentage value) 
  1170. * @since 1.0.0 
  1171. * @return float Total tax amount (trial membership) 
  1172. */ 
  1173. private function get_trial_tax() { 
  1174. $tax_rate = $this->tax_rate; 
  1175.  
  1176. if ( ! is_numeric( $tax_rate ) ) { 
  1177. $tax_rate = 0; 
  1178.  
  1179. $value = floatval( $this->trial_price ) * ( $tax_rate / 100 ); 
  1180. if ( $value < 0 ) { 
  1181. $value = 0; 
  1182.  
  1183. return $value; 
  1184.  
  1185. /** 
  1186. * Get invoice total. 
  1187. * Discounting coupon and pro-rating. 
  1188. * Add taxes. 
  1189. * @since 1.0.0 
  1190. */ 
  1191. private function get_total() { 
  1192. $total = $this->get_net_amount(); // Net amount 
  1193. $total += $this->get_tax(); // Tax-Rate was defined in `create_invoice()` 
  1194.  
  1195. if ( $total < 0 ) { 
  1196. $total = 0; 
  1197.  
  1198. // Set precission to 2 decimal points. 
  1199. $total = round( $total, 2 ); 
  1200.  
  1201. $this->total = apply_filters( 
  1202. 'ms_model_invoice_get_total',  
  1203. $total,  
  1204. $this 
  1205. ); 
  1206.  
  1207. return $this->total; 
  1208.  
  1209. /** 
  1210. * Get invoice trial price. 
  1211. * @since 1.0.0 
  1212. */ 
  1213. private function get_trial_price() { 
  1214. $membership = $this->get_membership(); 
  1215. $trial_price = $membership->trial_price; // Net amount 
  1216. $trial_price += $this->get_trial_tax(); // Tax-Rate was defined in `create_invoice()` 
  1217.  
  1218. if ( $trial_price < 0 ) { 
  1219. $trial_price = 0; 
  1220.  
  1221. // Set precission to 2 decimal points. 
  1222. $trial_price = round( $trial_price, 2 ); 
  1223.  
  1224. $this->trial_price = apply_filters( 
  1225. 'ms_model_invoice_get_trial_price',  
  1226. $trial_price,  
  1227. $this 
  1228. ); 
  1229.  
  1230. return $this->trial_price; 
  1231.  
  1232. /** 
  1233. * Returns the public invoice number for this invoice. 
  1234. * The public invoice number is the official identifier that is displayed 
  1235. * to the end user that refers to an invoice 
  1236. * @since 1.0.0 
  1237. * @return string The public invoice number. 
  1238. */ 
  1239. public function get_invoice_number() { 
  1240. $identifier = '#' . $this->id . '-' . $this->invoice_number; 
  1241.  
  1242. return apply_filters( 
  1243. 'ms_model_invoice_the_number',  
  1244. $identifier,  
  1245. $this 
  1246. ); 
  1247.  
  1248. /** 
  1249. * Returns the membership model that is linked to this invoice. 
  1250. * @since 1.0.0 
  1251. * @return MS_Model_Membership 
  1252. */ 
  1253. public function get_membership() { 
  1254. return MS_Factory::load( 'MS_Model_Membership', $this->membership_id ); 
  1255.  
  1256. /** 
  1257. * Returns the membership model that is linked to this invoice. 
  1258. * @since 1.0.0 
  1259. * @return MS_Model_Membership 
  1260. */ 
  1261. public function get_member() { 
  1262. return MS_Factory::load( 'MS_Model_Member', $this->user_id ); 
  1263.  
  1264. /** 
  1265. * Returns the subscription model that is linked to this invoice. 
  1266. * @since 1.0.0 
  1267. * @return MS_Model_Relationship 
  1268. */ 
  1269. public function get_subscription() { 
  1270. return MS_Factory::load( 'MS_Model_Relationship', $this->ms_relationship_id ); 
  1271.  
  1272. /** 
  1273. * Returns property associated with the render. 
  1274. * @since 1.0.0 
  1275. * @internal 
  1276. * @param string $property The name of a property. 
  1277. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  1278. */ 
  1279. public function __get( $property ) { 
  1280. $value = null; 
  1281.  
  1282. switch ( $property ) { 
  1283. case 'total': 
  1284. $value = $this->get_total(); 
  1285. break; 
  1286.  
  1287. case 'trial_price': 
  1288. $value = $this->get_trial_price(); 
  1289. break; 
  1290.  
  1291. case 'invoice': 
  1292. $value = $this->id; 
  1293. break; 
  1294.  
  1295. case 'short_description': 
  1296. if ( empty( $this->short_description ) ) { 
  1297. $value = $this->description; 
  1298. } else { 
  1299. $value = $this->short_description; 
  1300. break; 
  1301.  
  1302. case 'invoice_date': 
  1303. $value = $this->invoice_date; 
  1304.  
  1305. if ( empty( $value ) ) { 
  1306. $value = get_the_date( 'Y-m-d', $this->id ); 
  1307. break; 
  1308.  
  1309. case 'pay_date': 
  1310. $this->validate_pay_date(); 
  1311. $value = $this->pay_date; 
  1312. break; 
  1313.  
  1314. case 'tax': 
  1315. $value = $this->get_tax(); 
  1316. break; 
  1317.  
  1318. case 'trial_tax': 
  1319. $value = $this->get_trial_tax(); 
  1320. break; 
  1321.  
  1322. case 'subtotal': 
  1323. $value = $this->get_net_amount(); 
  1324. break; 
  1325.  
  1326. default: 
  1327. if ( property_exists( $this, $property ) ) { 
  1328. $value = $this->$property; 
  1329. break; 
  1330.  
  1331. return apply_filters( 
  1332. 'ms_model_invoice__get',  
  1333. $value,  
  1334. $property,  
  1335. $this 
  1336. ); 
  1337.  
  1338. /** 
  1339. * Set specific property. 
  1340. * @since 1.0.0 
  1341. * @internal 
  1342. * @param string $property The name of a property to associate. 
  1343. * @param mixed $value The value of a property. 
  1344. */ 
  1345. public function __set( $property, $value ) { 
  1346. switch ( $property ) { 
  1347. case 'name': 
  1348. case 'currency': 
  1349. $this->$property = sanitize_text_field( $value ); 
  1350. break; 
  1351.  
  1352. case 'notes': 
  1353. if ( is_array( $value ) ) { 
  1354. $this->notes = array_map( 'sanitize_text_field', $value ); 
  1355. } else { 
  1356. $this->notes = array( sanitize_text_field( $value ) ); 
  1357. break; 
  1358.  
  1359. case 'status': 
  1360. if ( array_key_exists( $value, self::get_status_types() ) ) { 
  1361. $this->$property = $value; 
  1362. break; 
  1363.  
  1364. case 'due_date': 
  1365. $this->$property = $this->validate_date( $value ); 
  1366. break; 
  1367.  
  1368. case 'amount': 
  1369. case 'discount': 
  1370. case 'pro_rate': 
  1371. case 'trial_price': 
  1372. $this->$property = floatval( $value ); 
  1373. $this->total_amount_changed(); 
  1374. $this->get_total(); 
  1375. $this->get_trial_price(); 
  1376. break; 
  1377.  
  1378. default: 
  1379. if ( property_exists( $this, $property ) ) { 
  1380. $this->$property = $value; 
  1381. break; 
  1382.  
  1383. do_action( 
  1384. 'ms_model_invoice__set_after',  
  1385. $property,  
  1386. $value,  
  1387. $this 
  1388. ); 
  1389.