暫無描述

abstract-wc-order.php 66KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193
  1. <?php
  2. /**
  3. * Abstract Order
  4. *
  5. * Handles generic order data and database interaction which is extended by both
  6. * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
  7. *
  8. * @class WC_Abstract_Order
  9. * @version 3.0.0
  10. * @package WooCommerce\Classes
  11. */
  12. use Automattic\WooCommerce\Utilities\NumberUtil;
  13. defined( 'ABSPATH' ) || exit;
  14. require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
  15. /**
  16. * WC_Abstract_Order class.
  17. */
  18. abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
  19. use WC_Item_Totals;
  20. /**
  21. * Order Data array. This is the core order data exposed in APIs since 3.0.0.
  22. *
  23. * Notes: cart_tax = cart_tax is the new name for the legacy 'order_tax'
  24. * which is the tax for items only, not shipping.
  25. *
  26. * @since 3.0.0
  27. * @var array
  28. */
  29. protected $data = array(
  30. 'parent_id' => 0,
  31. 'status' => '',
  32. 'currency' => '',
  33. 'version' => '',
  34. 'prices_include_tax' => false,
  35. 'date_created' => null,
  36. 'date_modified' => null,
  37. 'discount_total' => 0,
  38. 'discount_tax' => 0,
  39. 'shipping_total' => 0,
  40. 'shipping_tax' => 0,
  41. 'cart_tax' => 0,
  42. 'total' => 0,
  43. 'total_tax' => 0,
  44. );
  45. /**
  46. * Order items will be stored here, sometimes before they persist in the DB.
  47. *
  48. * @since 3.0.0
  49. * @var array
  50. */
  51. protected $items = array();
  52. /**
  53. * Order items that need deleting are stored here.
  54. *
  55. * @since 3.0.0
  56. * @var array
  57. */
  58. protected $items_to_delete = array();
  59. /**
  60. * Stores meta in cache for future reads.
  61. *
  62. * A group must be set to to enable caching.
  63. *
  64. * @var string
  65. */
  66. protected $cache_group = 'orders';
  67. /**
  68. * Which data store to load.
  69. *
  70. * @var string
  71. */
  72. protected $data_store_name = 'order';
  73. /**
  74. * This is the name of this object type.
  75. *
  76. * @var string
  77. */
  78. protected $object_type = 'order';
  79. /**
  80. * Get the order if ID is passed, otherwise the order is new and empty.
  81. * This class should NOT be instantiated, but the wc_get_order function or new WC_Order_Factory
  82. * should be used. It is possible, but the aforementioned are preferred and are the only
  83. * methods that will be maintained going forward.
  84. *
  85. * @param int|object|WC_Order $order Order to read.
  86. */
  87. public function __construct( $order = 0 ) {
  88. parent::__construct( $order );
  89. if ( is_numeric( $order ) && $order > 0 ) {
  90. $this->set_id( $order );
  91. } elseif ( $order instanceof self ) {
  92. $this->set_id( $order->get_id() );
  93. } elseif ( ! empty( $order->ID ) ) {
  94. $this->set_id( $order->ID );
  95. } else {
  96. $this->set_object_read( true );
  97. }
  98. $this->data_store = WC_Data_Store::load( $this->data_store_name );
  99. if ( $this->get_id() > 0 ) {
  100. $this->data_store->read( $this );
  101. }
  102. }
  103. /**
  104. * Get internal type.
  105. *
  106. * @return string
  107. */
  108. public function get_type() {
  109. return 'shop_order';
  110. }
  111. /**
  112. * Get all class data in array format.
  113. *
  114. * @since 3.0.0
  115. * @return array
  116. */
  117. public function get_data() {
  118. return array_merge(
  119. array(
  120. 'id' => $this->get_id(),
  121. ),
  122. $this->data,
  123. array(
  124. 'meta_data' => $this->get_meta_data(),
  125. 'line_items' => $this->get_items( 'line_item' ),
  126. 'tax_lines' => $this->get_items( 'tax' ),
  127. 'shipping_lines' => $this->get_items( 'shipping' ),
  128. 'fee_lines' => $this->get_items( 'fee' ),
  129. 'coupon_lines' => $this->get_items( 'coupon' ),
  130. )
  131. );
  132. }
  133. /*
  134. |--------------------------------------------------------------------------
  135. | CRUD methods
  136. |--------------------------------------------------------------------------
  137. |
  138. | Methods which create, read, update and delete orders from the database.
  139. | Written in abstract fashion so that the way orders are stored can be
  140. | changed more easily in the future.
  141. |
  142. | A save method is included for convenience (chooses update or create based
  143. | on if the order exists yet).
  144. |
  145. */
  146. /**
  147. * Save data to the database.
  148. *
  149. * @since 3.0.0
  150. * @return int order ID
  151. */
  152. public function save() {
  153. if ( ! $this->data_store ) {
  154. return $this->get_id();
  155. }
  156. try {
  157. /**
  158. * Trigger action before saving to the DB. Allows you to adjust object props before save.
  159. *
  160. * @param WC_Data $this The object being saved.
  161. * @param WC_Data_Store_WP $data_store THe data store persisting the data.
  162. */
  163. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
  164. if ( $this->get_id() ) {
  165. $this->data_store->update( $this );
  166. } else {
  167. $this->data_store->create( $this );
  168. }
  169. $this->save_items();
  170. /**
  171. * Trigger action after saving to the DB.
  172. *
  173. * @param WC_Data $this The object being saved.
  174. * @param WC_Data_Store_WP $data_store THe data store persisting the data.
  175. */
  176. do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
  177. } catch ( Exception $e ) {
  178. $this->handle_exception( $e, __( 'Error saving order.', 'woocommerce' ) );
  179. }
  180. return $this->get_id();
  181. }
  182. /**
  183. * Log an error about this order is exception is encountered.
  184. *
  185. * @param Exception $e Exception object.
  186. * @param string $message Message regarding exception thrown.
  187. * @since 3.7.0
  188. */
  189. protected function handle_exception( $e, $message = 'Error' ) {
  190. wc_get_logger()->error(
  191. $message,
  192. array(
  193. 'order' => $this,
  194. 'error' => $e,
  195. )
  196. );
  197. }
  198. /**
  199. * Save all order items which are part of this order.
  200. */
  201. protected function save_items() {
  202. $items_changed = false;
  203. foreach ( $this->items_to_delete as $item ) {
  204. $item->delete();
  205. $items_changed = true;
  206. }
  207. $this->items_to_delete = array();
  208. // Add/save items.
  209. foreach ( $this->items as $item_group => $items ) {
  210. if ( is_array( $items ) ) {
  211. $items = array_filter( $items );
  212. foreach ( $items as $item_key => $item ) {
  213. $item->set_order_id( $this->get_id() );
  214. $item_id = $item->save();
  215. // If ID changed (new item saved to DB)...
  216. if ( $item_id !== $item_key ) {
  217. $this->items[ $item_group ][ $item_id ] = $item;
  218. unset( $this->items[ $item_group ][ $item_key ] );
  219. $items_changed = true;
  220. }
  221. }
  222. }
  223. }
  224. if ( $items_changed ) {
  225. delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
  226. }
  227. }
  228. /*
  229. |--------------------------------------------------------------------------
  230. | Getters
  231. |--------------------------------------------------------------------------
  232. */
  233. /**
  234. * Get parent order ID.
  235. *
  236. * @since 3.0.0
  237. * @param string $context View or edit context.
  238. * @return integer
  239. */
  240. public function get_parent_id( $context = 'view' ) {
  241. return $this->get_prop( 'parent_id', $context );
  242. }
  243. /**
  244. * Gets order currency.
  245. *
  246. * @param string $context View or edit context.
  247. * @return string
  248. */
  249. public function get_currency( $context = 'view' ) {
  250. return $this->get_prop( 'currency', $context );
  251. }
  252. /**
  253. * Get order_version.
  254. *
  255. * @param string $context View or edit context.
  256. * @return string
  257. */
  258. public function get_version( $context = 'view' ) {
  259. return $this->get_prop( 'version', $context );
  260. }
  261. /**
  262. * Get prices_include_tax.
  263. *
  264. * @param string $context View or edit context.
  265. * @return bool
  266. */
  267. public function get_prices_include_tax( $context = 'view' ) {
  268. return $this->get_prop( 'prices_include_tax', $context );
  269. }
  270. /**
  271. * Get date_created.
  272. *
  273. * @param string $context View or edit context.
  274. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  275. */
  276. public function get_date_created( $context = 'view' ) {
  277. return $this->get_prop( 'date_created', $context );
  278. }
  279. /**
  280. * Get date_modified.
  281. *
  282. * @param string $context View or edit context.
  283. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  284. */
  285. public function get_date_modified( $context = 'view' ) {
  286. return $this->get_prop( 'date_modified', $context );
  287. }
  288. /**
  289. * Return the order statuses without wc- internal prefix.
  290. *
  291. * @param string $context View or edit context.
  292. * @return string
  293. */
  294. public function get_status( $context = 'view' ) {
  295. $status = $this->get_prop( 'status', $context );
  296. if ( empty( $status ) && 'view' === $context ) {
  297. // In view context, return the default status if no status has been set.
  298. $status = apply_filters( 'woocommerce_default_order_status', 'pending' );
  299. }
  300. return $status;
  301. }
  302. /**
  303. * Get discount_total.
  304. *
  305. * @param string $context View or edit context.
  306. * @return string
  307. */
  308. public function get_discount_total( $context = 'view' ) {
  309. return $this->get_prop( 'discount_total', $context );
  310. }
  311. /**
  312. * Get discount_tax.
  313. *
  314. * @param string $context View or edit context.
  315. * @return string
  316. */
  317. public function get_discount_tax( $context = 'view' ) {
  318. return $this->get_prop( 'discount_tax', $context );
  319. }
  320. /**
  321. * Get shipping_total.
  322. *
  323. * @param string $context View or edit context.
  324. * @return string
  325. */
  326. public function get_shipping_total( $context = 'view' ) {
  327. return $this->get_prop( 'shipping_total', $context );
  328. }
  329. /**
  330. * Get shipping_tax.
  331. *
  332. * @param string $context View or edit context.
  333. * @return string
  334. */
  335. public function get_shipping_tax( $context = 'view' ) {
  336. return $this->get_prop( 'shipping_tax', $context );
  337. }
  338. /**
  339. * Gets cart tax amount.
  340. *
  341. * @param string $context View or edit context.
  342. * @return float
  343. */
  344. public function get_cart_tax( $context = 'view' ) {
  345. return $this->get_prop( 'cart_tax', $context );
  346. }
  347. /**
  348. * Gets order grand total. incl. taxes. Used in gateways.
  349. *
  350. * @param string $context View or edit context.
  351. * @return float
  352. */
  353. public function get_total( $context = 'view' ) {
  354. return $this->get_prop( 'total', $context );
  355. }
  356. /**
  357. * Get total tax amount. Alias for get_order_tax().
  358. *
  359. * @param string $context View or edit context.
  360. * @return float
  361. */
  362. public function get_total_tax( $context = 'view' ) {
  363. return $this->get_prop( 'total_tax', $context );
  364. }
  365. /*
  366. |--------------------------------------------------------------------------
  367. | Non-CRUD Getters
  368. |--------------------------------------------------------------------------
  369. */
  370. /**
  371. * Gets the total discount amount.
  372. *
  373. * @param bool $ex_tax Show discount excl any tax.
  374. * @return float
  375. */
  376. public function get_total_discount( $ex_tax = true ) {
  377. if ( $ex_tax ) {
  378. $total_discount = $this->get_discount_total();
  379. } else {
  380. $total_discount = $this->get_discount_total() + $this->get_discount_tax();
  381. }
  382. return apply_filters( 'woocommerce_order_get_total_discount', NumberUtil::round( $total_discount, WC_ROUNDING_PRECISION ), $this );
  383. }
  384. /**
  385. * Gets order subtotal.
  386. *
  387. * @return float
  388. */
  389. public function get_subtotal() {
  390. $subtotal = NumberUtil::round( $this->get_cart_subtotal_for_order(), wc_get_price_decimals() );
  391. return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this );
  392. }
  393. /**
  394. * Get taxes, merged by code, formatted ready for output.
  395. *
  396. * @return array
  397. */
  398. public function get_tax_totals() {
  399. $tax_totals = array();
  400. foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
  401. $code = $tax->get_rate_code();
  402. if ( ! isset( $tax_totals[ $code ] ) ) {
  403. $tax_totals[ $code ] = new stdClass();
  404. $tax_totals[ $code ]->amount = 0;
  405. }
  406. $tax_totals[ $code ]->id = $key;
  407. $tax_totals[ $code ]->rate_id = $tax->get_rate_id();
  408. $tax_totals[ $code ]->is_compound = $tax->is_compound();
  409. $tax_totals[ $code ]->label = $tax->get_label();
  410. $tax_totals[ $code ]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
  411. $tax_totals[ $code ]->formatted_amount = wc_price( $tax_totals[ $code ]->amount, array( 'currency' => $this->get_currency() ) );
  412. }
  413. if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
  414. $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
  415. $tax_totals = array_intersect_key( $tax_totals, $amounts );
  416. }
  417. return apply_filters( 'woocommerce_order_get_tax_totals', $tax_totals, $this );
  418. }
  419. /**
  420. * Get all valid statuses for this order
  421. *
  422. * @since 3.0.0
  423. * @return array Internal status keys e.g. 'wc-processing'
  424. */
  425. protected function get_valid_statuses() {
  426. return array_keys( wc_get_order_statuses() );
  427. }
  428. /**
  429. * Get user ID. Used by orders, not other order types like refunds.
  430. *
  431. * @param string $context View or edit context.
  432. * @return int
  433. */
  434. public function get_user_id( $context = 'view' ) {
  435. return 0;
  436. }
  437. /**
  438. * Get user. Used by orders, not other order types like refunds.
  439. *
  440. * @return WP_User|false
  441. */
  442. public function get_user() {
  443. return false;
  444. }
  445. /*
  446. |--------------------------------------------------------------------------
  447. | Setters
  448. |--------------------------------------------------------------------------
  449. |
  450. | Functions for setting order data. These should not update anything in the
  451. | database itself and should only change what is stored in the class
  452. | object. However, for backwards compatibility pre 3.0.0 some of these
  453. | setters may handle both.
  454. */
  455. /**
  456. * Set parent order ID.
  457. *
  458. * @since 3.0.0
  459. * @param int $value Value to set.
  460. * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
  461. */
  462. public function set_parent_id( $value ) {
  463. if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
  464. $this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
  465. }
  466. $this->set_prop( 'parent_id', absint( $value ) );
  467. }
  468. /**
  469. * Set order status.
  470. *
  471. * @since 3.0.0
  472. * @param string $new_status Status to change the order to. No internal wc- prefix is required.
  473. * @return array details of change
  474. */
  475. public function set_status( $new_status ) {
  476. $old_status = $this->get_status();
  477. $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
  478. // If setting the status, ensure it's set to a valid status.
  479. if ( true === $this->object_read ) {
  480. // Only allow valid new status.
  481. if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status ) {
  482. $new_status = 'pending';
  483. }
  484. // If the old status is set but unknown (e.g. draft) assume its pending for action usage.
  485. if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) {
  486. $old_status = 'pending';
  487. }
  488. }
  489. $this->set_prop( 'status', $new_status );
  490. return array(
  491. 'from' => $old_status,
  492. 'to' => $new_status,
  493. );
  494. }
  495. /**
  496. * Set order_version.
  497. *
  498. * @param string $value Value to set.
  499. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  500. */
  501. public function set_version( $value ) {
  502. $this->set_prop( 'version', $value );
  503. }
  504. /**
  505. * Set order_currency.
  506. *
  507. * @param string $value Value to set.
  508. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  509. */
  510. public function set_currency( $value ) {
  511. if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) {
  512. $this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
  513. }
  514. $this->set_prop( 'currency', $value ? $value : get_woocommerce_currency() );
  515. }
  516. /**
  517. * Set prices_include_tax.
  518. *
  519. * @param bool $value Value to set.
  520. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  521. */
  522. public function set_prices_include_tax( $value ) {
  523. $this->set_prop( 'prices_include_tax', (bool) $value );
  524. }
  525. /**
  526. * Set date_created.
  527. *
  528. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
  529. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  530. */
  531. public function set_date_created( $date = null ) {
  532. $this->set_date_prop( 'date_created', $date );
  533. }
  534. /**
  535. * Set date_modified.
  536. *
  537. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
  538. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  539. */
  540. public function set_date_modified( $date = null ) {
  541. $this->set_date_prop( 'date_modified', $date );
  542. }
  543. /**
  544. * Set discount_total.
  545. *
  546. * @param string $value Value to set.
  547. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  548. */
  549. public function set_discount_total( $value ) {
  550. $this->set_prop( 'discount_total', wc_format_decimal( $value ) );
  551. }
  552. /**
  553. * Set discount_tax.
  554. *
  555. * @param string $value Value to set.
  556. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  557. */
  558. public function set_discount_tax( $value ) {
  559. $this->set_prop( 'discount_tax', wc_format_decimal( $value ) );
  560. }
  561. /**
  562. * Set shipping_total.
  563. *
  564. * @param string $value Value to set.
  565. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  566. */
  567. public function set_shipping_total( $value ) {
  568. $this->set_prop( 'shipping_total', wc_format_decimal( $value ) );
  569. }
  570. /**
  571. * Set shipping_tax.
  572. *
  573. * @param string $value Value to set.
  574. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  575. */
  576. public function set_shipping_tax( $value ) {
  577. $this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
  578. $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
  579. }
  580. /**
  581. * Set cart tax.
  582. *
  583. * @param string $value Value to set.
  584. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  585. */
  586. public function set_cart_tax( $value ) {
  587. $this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
  588. $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
  589. }
  590. /**
  591. * Sets order tax (sum of cart and shipping tax). Used internally only.
  592. *
  593. * @param string $value Value to set.
  594. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  595. */
  596. protected function set_total_tax( $value ) {
  597. // We round here because this is a total entry, as opposed to line items in other setters.
  598. $this->set_prop( 'total_tax', wc_format_decimal( NumberUtil::round( $value, wc_get_price_decimals() ) ) );
  599. }
  600. /**
  601. * Set total.
  602. *
  603. * @param string $value Value to set.
  604. * @param string $deprecated Function used to set different totals based on this.
  605. *
  606. * @return bool|void
  607. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  608. */
  609. public function set_total( $value, $deprecated = '' ) {
  610. if ( $deprecated ) {
  611. wc_deprecated_argument( 'total_type', '3.0', 'Use dedicated total setter methods instead.' );
  612. return $this->legacy_set_total( $value, $deprecated );
  613. }
  614. $this->set_prop( 'total', wc_format_decimal( $value, wc_get_price_decimals() ) );
  615. }
  616. /*
  617. |--------------------------------------------------------------------------
  618. | Order Item Handling
  619. |--------------------------------------------------------------------------
  620. |
  621. | Order items are used for products, taxes, shipping, and fees within
  622. | each order.
  623. */
  624. /**
  625. * Remove all line items (products, coupons, shipping, taxes) from the order.
  626. *
  627. * @param string $type Order item type. Default null.
  628. */
  629. public function remove_order_items( $type = null ) {
  630. if ( ! empty( $type ) ) {
  631. $this->data_store->delete_items( $this, $type );
  632. $group = $this->type_to_group( $type );
  633. if ( $group ) {
  634. unset( $this->items[ $group ] );
  635. }
  636. } else {
  637. $this->data_store->delete_items( $this );
  638. $this->items = array();
  639. }
  640. }
  641. /**
  642. * Convert a type to a types group.
  643. *
  644. * @param string $type type to lookup.
  645. * @return string
  646. */
  647. protected function type_to_group( $type ) {
  648. $type_to_group = apply_filters(
  649. 'woocommerce_order_type_to_group',
  650. array(
  651. 'line_item' => 'line_items',
  652. 'tax' => 'tax_lines',
  653. 'shipping' => 'shipping_lines',
  654. 'fee' => 'fee_lines',
  655. 'coupon' => 'coupon_lines',
  656. )
  657. );
  658. return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
  659. }
  660. /**
  661. * Return an array of items/products within this order.
  662. *
  663. * @param string|array $types Types of line items to get (array or string).
  664. * @return WC_Order_Item[]
  665. */
  666. public function get_items( $types = 'line_item' ) {
  667. $items = array();
  668. $types = array_filter( (array) $types );
  669. foreach ( $types as $type ) {
  670. $group = $this->type_to_group( $type );
  671. if ( $group ) {
  672. if ( ! isset( $this->items[ $group ] ) ) {
  673. $this->items[ $group ] = array_filter( $this->data_store->read_items( $this, $type ) );
  674. }
  675. // Don't use array_merge here because keys are numeric.
  676. $items = $items + $this->items[ $group ];
  677. }
  678. }
  679. return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
  680. }
  681. /**
  682. * Return array of values for calculations.
  683. *
  684. * @param string $field Field name to return.
  685. *
  686. * @return array Array of values.
  687. */
  688. protected function get_values_for_total( $field ) {
  689. $items = array_map(
  690. function ( $item ) use ( $field ) {
  691. return wc_add_number_precision( $item[ $field ], false );
  692. },
  693. array_values( $this->get_items() )
  694. );
  695. return $items;
  696. }
  697. /**
  698. * Return an array of coupons within this order.
  699. *
  700. * @since 3.7.0
  701. * @return WC_Order_Item_Coupon[]
  702. */
  703. public function get_coupons() {
  704. return $this->get_items( 'coupon' );
  705. }
  706. /**
  707. * Return an array of fees within this order.
  708. *
  709. * @return WC_Order_item_Fee[]
  710. */
  711. public function get_fees() {
  712. return $this->get_items( 'fee' );
  713. }
  714. /**
  715. * Return an array of taxes within this order.
  716. *
  717. * @return WC_Order_Item_Tax[]
  718. */
  719. public function get_taxes() {
  720. return $this->get_items( 'tax' );
  721. }
  722. /**
  723. * Return an array of shipping costs within this order.
  724. *
  725. * @return WC_Order_Item_Shipping[]
  726. */
  727. public function get_shipping_methods() {
  728. return $this->get_items( 'shipping' );
  729. }
  730. /**
  731. * Gets formatted shipping method title.
  732. *
  733. * @return string
  734. */
  735. public function get_shipping_method() {
  736. $names = array();
  737. foreach ( $this->get_shipping_methods() as $shipping_method ) {
  738. $names[] = $shipping_method->get_name();
  739. }
  740. return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
  741. }
  742. /**
  743. * Get used coupon codes only.
  744. *
  745. * @since 3.7.0
  746. * @return array
  747. */
  748. public function get_coupon_codes() {
  749. $coupon_codes = array();
  750. $coupons = $this->get_items( 'coupon' );
  751. if ( $coupons ) {
  752. foreach ( $coupons as $coupon ) {
  753. $coupon_codes[] = $coupon->get_code();
  754. }
  755. }
  756. return $coupon_codes;
  757. }
  758. /**
  759. * Gets the count of order items of a certain type.
  760. *
  761. * @param string $item_type Item type to lookup.
  762. * @return int|string
  763. */
  764. public function get_item_count( $item_type = '' ) {
  765. $items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
  766. $count = 0;
  767. foreach ( $items as $item ) {
  768. $count += $item->get_quantity();
  769. }
  770. return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
  771. }
  772. /**
  773. * Get an order item object, based on its type.
  774. *
  775. * @since 3.0.0
  776. * @param int $item_id ID of item to get.
  777. * @param bool $load_from_db Prior to 3.2 this item was loaded direct from WC_Order_Factory, not this object. This param is here for backwards compatility with that. If false, uses the local items variable instead.
  778. * @return WC_Order_Item|false
  779. */
  780. public function get_item( $item_id, $load_from_db = true ) {
  781. if ( $load_from_db ) {
  782. return WC_Order_Factory::get_order_item( $item_id );
  783. }
  784. // Search for item id.
  785. if ( $this->items ) {
  786. foreach ( $this->items as $group => $items ) {
  787. if ( isset( $items[ $item_id ] ) ) {
  788. return $items[ $item_id ];
  789. }
  790. }
  791. }
  792. // Load all items of type and cache.
  793. $type = $this->data_store->get_order_item_type( $this, $item_id );
  794. if ( ! $type ) {
  795. return false;
  796. }
  797. $items = $this->get_items( $type );
  798. return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
  799. }
  800. /**
  801. * Get key for where a certain item type is stored in _items.
  802. *
  803. * @since 3.0.0
  804. * @param string $item object Order item (product, shipping, fee, coupon, tax).
  805. * @return string
  806. */
  807. protected function get_items_key( $item ) {
  808. if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
  809. return 'line_items';
  810. } elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
  811. return 'fee_lines';
  812. } elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
  813. return 'shipping_lines';
  814. } elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
  815. return 'tax_lines';
  816. } elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
  817. return 'coupon_lines';
  818. }
  819. return apply_filters( 'woocommerce_get_items_key', '', $item );
  820. }
  821. /**
  822. * Remove item from the order.
  823. *
  824. * @param int $item_id Item ID to delete.
  825. * @return false|void
  826. */
  827. public function remove_item( $item_id ) {
  828. $item = $this->get_item( $item_id, false );
  829. $items_key = $item ? $this->get_items_key( $item ) : false;
  830. if ( ! $items_key ) {
  831. return false;
  832. }
  833. // Unset and remove later.
  834. $this->items_to_delete[] = $item;
  835. unset( $this->items[ $items_key ][ $item->get_id() ] );
  836. }
  837. /**
  838. * Adds an order item to this order. The order item will not persist until save.
  839. *
  840. * @since 3.0.0
  841. * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax).
  842. * @return false|void
  843. */
  844. public function add_item( $item ) {
  845. $items_key = $this->get_items_key( $item );
  846. if ( ! $items_key ) {
  847. return false;
  848. }
  849. // Make sure existing items are loaded so we can append this new one.
  850. if ( ! isset( $this->items[ $items_key ] ) ) {
  851. $this->items[ $items_key ] = $this->get_items( $item->get_type() );
  852. }
  853. // Set parent.
  854. $item->set_order_id( $this->get_id() );
  855. // Append new row with generated temporary ID.
  856. $item_id = $item->get_id();
  857. if ( $item_id ) {
  858. $this->items[ $items_key ][ $item_id ] = $item;
  859. } else {
  860. $this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item;
  861. }
  862. }
  863. /**
  864. * Check and records coupon usage tentatively so that counts validation is correct. Display an error if coupon usage limit has been reached.
  865. *
  866. * If you are using this method, make sure to `release_held_coupons` in case an Exception is thrown.
  867. *
  868. * @throws Exception When not able to apply coupon.
  869. *
  870. * @param string $billing_email Billing email of order.
  871. */
  872. public function hold_applied_coupons( $billing_email ) {
  873. $held_keys = array();
  874. $held_keys_for_user = array();
  875. $error = null;
  876. try {
  877. foreach ( WC()->cart->get_applied_coupons() as $code ) {
  878. $coupon = new WC_Coupon( $code );
  879. if ( ! $coupon->get_data_store() ) {
  880. continue;
  881. }
  882. // Hold coupon for when global coupon usage limit is present.
  883. if ( 0 < $coupon->get_usage_limit() ) {
  884. $held_key = $this->hold_coupon( $coupon );
  885. if ( $held_key ) {
  886. $held_keys[ $coupon->get_id() ] = $held_key;
  887. }
  888. }
  889. // Hold coupon for when usage limit per customer is enabled.
  890. if ( 0 < $coupon->get_usage_limit_per_user() ) {
  891. if ( ! isset( $user_ids_and_emails ) ) {
  892. $user_alias = get_current_user_id() ? wp_get_current_user()->ID : sanitize_email( $billing_email );
  893. $user_ids_and_emails = $this->get_billing_and_current_user_aliases( $billing_email );
  894. }
  895. $held_key_for_user = $this->hold_coupon_for_users( $coupon, $user_ids_and_emails, $user_alias );
  896. if ( $held_key_for_user ) {
  897. $held_keys_for_user[ $coupon->get_id() ] = $held_key_for_user;
  898. }
  899. }
  900. }
  901. } catch ( Exception $e ) {
  902. $error = $e;
  903. } finally {
  904. // Even in case of error, we will save keys for whatever coupons that were held so our data remains accurate.
  905. // We save them in bulk instead of one by one for performance reasons.
  906. if ( 0 < count( $held_keys_for_user ) || 0 < count( $held_keys ) ) {
  907. $this->get_data_store()->set_coupon_held_keys( $this, $held_keys, $held_keys_for_user );
  908. }
  909. if ( $error instanceof Exception ) {
  910. throw $error;
  911. }
  912. }
  913. }
  914. /**
  915. * Hold coupon if a global usage limit is defined.
  916. *
  917. * @param WC_Coupon $coupon Coupon object.
  918. *
  919. * @return string Meta key which indicates held coupon.
  920. * @throws Exception When can't be held.
  921. */
  922. private function hold_coupon( $coupon ) {
  923. $result = $coupon->get_data_store()->check_and_hold_coupon( $coupon );
  924. if ( false === $result ) {
  925. // translators: Actual coupon code.
  926. throw new Exception( sprintf( __( 'An unexpected error happened while applying the Coupon %s.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) );
  927. } elseif ( 0 === $result ) {
  928. // translators: Actual coupon code.
  929. throw new Exception( sprintf( __( 'Coupon %s was used in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) );
  930. }
  931. return $result;
  932. }
  933. /**
  934. * Hold coupon if usage limit per customer is defined.
  935. *
  936. * @param WC_Coupon $coupon Coupon object.
  937. * @param array $user_ids_and_emails Array of user Id and emails to check for usage limit.
  938. * @param string $user_alias User ID or email to use to record current usage.
  939. *
  940. * @return string Meta key which indicates held coupon.
  941. * @throws Exception When coupon can't be held.
  942. */
  943. private function hold_coupon_for_users( $coupon, $user_ids_and_emails, $user_alias ) {
  944. $result = $coupon->get_data_store()->check_and_hold_coupon_for_user( $coupon, $user_ids_and_emails, $user_alias );
  945. if ( false === $result ) {
  946. // translators: Actual coupon code.
  947. throw new Exception( sprintf( __( 'An unexpected error happened while applying the Coupon %s.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) );
  948. } elseif ( 0 === $result ) {
  949. // translators: Actual coupon code.
  950. throw new Exception( sprintf( __( 'You have used this coupon %s in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) );
  951. }
  952. return $result;
  953. }
  954. /**
  955. * Helper method to get all aliases for current user and provide billing email.
  956. *
  957. * @param string $billing_email Billing email provided in form.
  958. *
  959. * @return array Array of all aliases.
  960. * @throws Exception When validation fails.
  961. */
  962. private function get_billing_and_current_user_aliases( $billing_email ) {
  963. $emails = array( $billing_email );
  964. if ( get_current_user_id() ) {
  965. $emails[] = wp_get_current_user()->user_email;
  966. }
  967. $emails = array_unique(
  968. array_map( 'strtolower', array_map( 'sanitize_email', $emails ) )
  969. );
  970. $customer_data_store = WC_Data_Store::load( 'customer' );
  971. $user_ids = $customer_data_store->get_user_ids_for_billing_email( $emails );
  972. return array_merge( $user_ids, $emails );
  973. }
  974. /**
  975. * Apply a coupon to the order and recalculate totals.
  976. *
  977. * @since 3.2.0
  978. * @param string|WC_Coupon $raw_coupon Coupon code or object.
  979. * @return true|WP_Error True if applied, error if not.
  980. */
  981. public function apply_coupon( $raw_coupon ) {
  982. if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
  983. $coupon = $raw_coupon;
  984. } elseif ( is_string( $raw_coupon ) ) {
  985. $code = wc_format_coupon_code( $raw_coupon );
  986. $coupon = new WC_Coupon( $code );
  987. if ( $coupon->get_code() !== $code ) {
  988. return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
  989. }
  990. } else {
  991. return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
  992. }
  993. // Check to make sure coupon is not already applied.
  994. $applied_coupons = $this->get_items( 'coupon' );
  995. foreach ( $applied_coupons as $applied_coupon ) {
  996. if ( $applied_coupon->get_code() === $coupon->get_code() ) {
  997. return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
  998. }
  999. }
  1000. $discounts = new WC_Discounts( $this );
  1001. $applied = $discounts->apply_coupon( $coupon );
  1002. if ( is_wp_error( $applied ) ) {
  1003. return $applied;
  1004. }
  1005. $data_store = $coupon->get_data_store();
  1006. // Check specific for guest checkouts here as well since WC_Cart handles that seperately in check_customer_coupons.
  1007. if ( $data_store && 0 === $this->get_customer_id() ) {
  1008. $usage_count = $data_store->get_usage_by_email( $coupon, $this->get_billing_email() );
  1009. if ( 0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user() ) {
  1010. return new WP_Error(
  1011. 'invalid_coupon',
  1012. $coupon->get_coupon_error( 106 ),
  1013. array(
  1014. 'status' => 400,
  1015. )
  1016. );
  1017. }
  1018. }
  1019. $this->set_coupon_discount_amounts( $discounts );
  1020. $this->save();
  1021. // Recalculate totals and taxes.
  1022. $this->recalculate_coupons();
  1023. // Record usage so counts and validation is correct.
  1024. $used_by = $this->get_user_id();
  1025. if ( ! $used_by ) {
  1026. $used_by = $this->get_billing_email();
  1027. }
  1028. $coupon->increase_usage_count( $used_by );
  1029. return true;
  1030. }
  1031. /**
  1032. * Remove a coupon from the order and recalculate totals.
  1033. *
  1034. * Coupons affect line item totals, but there is no relationship between
  1035. * coupon and line total, so to remove a coupon we need to work from the
  1036. * line subtotal (price before discount) and re-apply all coupons in this
  1037. * order.
  1038. *
  1039. * Manual discounts are not affected; those are separate and do not affect
  1040. * stored line totals.
  1041. *
  1042. * @since 3.2.0
  1043. * @param string $code Coupon code.
  1044. * @return void
  1045. */
  1046. public function remove_coupon( $code ) {
  1047. $coupons = $this->get_items( 'coupon' );
  1048. // Remove the coupon line.
  1049. foreach ( $coupons as $item_id => $coupon ) {
  1050. if ( $coupon->get_code() === $code ) {
  1051. $this->remove_item( $item_id );
  1052. $coupon_object = new WC_Coupon( $code );
  1053. $coupon_object->decrease_usage_count( $this->get_user_id() );
  1054. $this->recalculate_coupons();
  1055. break;
  1056. }
  1057. }
  1058. }
  1059. /**
  1060. * Apply all coupons in this order again to all line items.
  1061. * This method is public since WooCommerce 3.8.0.
  1062. *
  1063. * @since 3.2.0
  1064. */
  1065. public function recalculate_coupons() {
  1066. // Reset line item totals.
  1067. foreach ( $this->get_items() as $item ) {
  1068. $item->set_total( $item->get_subtotal() );
  1069. $item->set_total_tax( $item->get_subtotal_tax() );
  1070. }
  1071. $discounts = new WC_Discounts( $this );
  1072. foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
  1073. $coupon_code = $coupon_item->get_code();
  1074. $coupon_id = wc_get_coupon_id_by_code( $coupon_code );
  1075. // If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID.
  1076. if ( $coupon_id ) {
  1077. $coupon_object = new WC_Coupon( $coupon_id );
  1078. } else {
  1079. // If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
  1080. $coupon_object = new WC_Coupon();
  1081. $coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
  1082. $coupon_object->set_code( $coupon_code );
  1083. $coupon_object->set_virtual( true );
  1084. // If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
  1085. if ( ! $coupon_object->get_amount() ) {
  1086. // If the order originally had prices including tax, remove the discount + discount tax.
  1087. if ( $this->get_prices_include_tax() ) {
  1088. $coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
  1089. } else {
  1090. $coupon_object->set_amount( $coupon_item->get_discount() );
  1091. }
  1092. $coupon_object->set_discount_type( 'fixed_cart' );
  1093. }
  1094. }
  1095. /**
  1096. * Allow developers to filter this coupon before it get's re-applied to the order.
  1097. *
  1098. * @since 3.2.0
  1099. */
  1100. $coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
  1101. if ( $coupon_object ) {
  1102. $discounts->apply_coupon( $coupon_object, false );
  1103. }
  1104. }
  1105. $this->set_coupon_discount_amounts( $discounts );
  1106. $this->set_item_discount_amounts( $discounts );
  1107. // Recalculate totals and taxes.
  1108. $this->calculate_totals( true );
  1109. }
  1110. /**
  1111. * After applying coupons via the WC_Discounts class, update line items.
  1112. *
  1113. * @since 3.2.0
  1114. * @param WC_Discounts $discounts Discounts class.
  1115. */
  1116. protected function set_item_discount_amounts( $discounts ) {
  1117. $item_discounts = $discounts->get_discounts_by_item();
  1118. $tax_location = $this->get_tax_location();
  1119. $tax_location = array( $tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city'] );
  1120. if ( $item_discounts ) {
  1121. foreach ( $item_discounts as $item_id => $amount ) {
  1122. $item = $this->get_item( $item_id, false );
  1123. // If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
  1124. if ( $this->get_prices_include_tax() && wc_tax_enabled() && 'taxable' === $item->get_tax_status() ) {
  1125. $taxes = WC_Tax::calc_tax( $amount, $this->get_tax_rates( $item->get_tax_class(), $tax_location ), true );
  1126. // Use unrounded taxes so totals will be re-calculated accurately, like in cart.
  1127. $amount = $amount - array_sum( $taxes );
  1128. }
  1129. $item->set_total( max( 0, $item->get_total() - $amount ) );
  1130. }
  1131. }
  1132. }
  1133. /**
  1134. * After applying coupons via the WC_Discounts class, update or create coupon items.
  1135. *
  1136. * @since 3.2.0
  1137. * @param WC_Discounts $discounts Discounts class.
  1138. */
  1139. protected function set_coupon_discount_amounts( $discounts ) {
  1140. $coupons = $this->get_items( 'coupon' );
  1141. $coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
  1142. $all_discounts = $discounts->get_discounts();
  1143. $coupon_discounts = $discounts->get_discounts_by_coupon();
  1144. $tax_location = $this->get_tax_location();
  1145. $tax_location = array(
  1146. $tax_location['country'],
  1147. $tax_location['state'],
  1148. $tax_location['postcode'],
  1149. $tax_location['city'],
  1150. );
  1151. if ( $coupon_discounts ) {
  1152. foreach ( $coupon_discounts as $coupon_code => $amount ) {
  1153. $item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
  1154. if ( ! $item_id ) {
  1155. $coupon_item = new WC_Order_Item_Coupon();
  1156. $coupon_item->set_code( $coupon_code );
  1157. } else {
  1158. $coupon_item = $this->get_item( $item_id, false );
  1159. }
  1160. $discount_tax = 0;
  1161. // Work out how much tax has been removed as a result of the discount from this coupon.
  1162. foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
  1163. $item = $this->get_item( $item_id, false );
  1164. if ( 'taxable' !== $item->get_tax_status() || ! wc_tax_enabled() ) {
  1165. continue;
  1166. }
  1167. $taxes = array_sum( WC_Tax::calc_tax( $item_discount_amount, $this->get_tax_rates( $item->get_tax_class(), $tax_location ), $this->get_prices_include_tax() ) );
  1168. if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
  1169. $taxes = wc_round_tax_total( $taxes );
  1170. }
  1171. $discount_tax += $taxes;
  1172. if ( $this->get_prices_include_tax() ) {
  1173. $amount = $amount - $taxes;
  1174. }
  1175. }
  1176. $coupon_item->set_discount( $amount );
  1177. $coupon_item->set_discount_tax( $discount_tax );
  1178. $this->add_item( $coupon_item );
  1179. }
  1180. }
  1181. }
  1182. /**
  1183. * Add a product line item to the order. This is the only line item type with
  1184. * its own method because it saves looking up order amounts (costs are added up for you).
  1185. *
  1186. * @param WC_Product $product Product object.
  1187. * @param int $qty Quantity to add.
  1188. * @param array $args Args for the added product.
  1189. * @return int
  1190. */
  1191. public function add_product( $product, $qty = 1, $args = array() ) {
  1192. if ( $product ) {
  1193. $default_args = array(
  1194. 'name' => $product->get_name(),
  1195. 'tax_class' => $product->get_tax_class(),
  1196. 'product_id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
  1197. 'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
  1198. 'variation' => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
  1199. 'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
  1200. 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
  1201. 'quantity' => $qty,
  1202. );
  1203. } else {
  1204. $default_args = array(
  1205. 'quantity' => $qty,
  1206. );
  1207. }
  1208. $args = wp_parse_args( $args, $default_args );
  1209. // BW compatibility with old args.
  1210. if ( isset( $args['totals'] ) ) {
  1211. foreach ( $args['totals'] as $key => $value ) {
  1212. if ( 'tax' === $key ) {
  1213. $args['total_tax'] = $value;
  1214. } elseif ( 'tax_data' === $key ) {
  1215. $args['taxes'] = $value;
  1216. } else {
  1217. $args[ $key ] = $value;
  1218. }
  1219. }
  1220. }
  1221. $item = new WC_Order_Item_Product();
  1222. $item->set_props( $args );
  1223. $item->set_backorder_meta();
  1224. $item->set_order_id( $this->get_id() );
  1225. $item->save();
  1226. $this->add_item( $item );
  1227. wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' );
  1228. delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
  1229. return $item->get_id();
  1230. }
  1231. /*
  1232. |--------------------------------------------------------------------------
  1233. | Payment Token Handling
  1234. |--------------------------------------------------------------------------
  1235. |
  1236. | Payment tokens are hashes used to take payments by certain gateways.
  1237. |
  1238. */
  1239. /**
  1240. * Add a payment token to an order
  1241. *
  1242. * @since 2.6
  1243. * @param WC_Payment_Token $token Payment token object.
  1244. * @return boolean|int The new token ID or false if it failed.
  1245. */
  1246. public function add_payment_token( $token ) {
  1247. if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
  1248. return false;
  1249. }
  1250. $token_ids = $this->data_store->get_payment_token_ids( $this );
  1251. $token_ids[] = $token->get_id();
  1252. $this->data_store->update_payment_token_ids( $this, $token_ids );
  1253. do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
  1254. return $token->get_id();
  1255. }
  1256. /**
  1257. * Returns a list of all payment tokens associated with the current order
  1258. *
  1259. * @since 2.6
  1260. * @return array An array of payment token objects
  1261. */
  1262. public function get_payment_tokens() {
  1263. return $this->data_store->get_payment_token_ids( $this );
  1264. }
  1265. /*
  1266. |--------------------------------------------------------------------------
  1267. | Calculations.
  1268. |--------------------------------------------------------------------------
  1269. |
  1270. | These methods calculate order totals and taxes based on the current data.
  1271. |
  1272. */
  1273. /**
  1274. * Calculate shipping total.
  1275. *
  1276. * @since 2.2
  1277. * @return float
  1278. */
  1279. public function calculate_shipping() {
  1280. $shipping_total = 0;
  1281. foreach ( $this->get_shipping_methods() as $shipping ) {
  1282. $shipping_total += $shipping->get_total();
  1283. }
  1284. $this->set_shipping_total( $shipping_total );
  1285. $this->save();
  1286. return $this->get_shipping_total();
  1287. }
  1288. /**
  1289. * Get all tax classes for items in the order.
  1290. *
  1291. * @since 2.6.3
  1292. * @return array
  1293. */
  1294. public function get_items_tax_classes() {
  1295. $found_tax_classes = array();
  1296. foreach ( $this->get_items() as $item ) {
  1297. if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
  1298. $found_tax_classes[] = $item->get_tax_class();
  1299. }
  1300. }
  1301. return array_unique( $found_tax_classes );
  1302. }
  1303. /**
  1304. * Get tax location for this order.
  1305. *
  1306. * @since 3.2.0
  1307. * @param array $args array Override the location.
  1308. * @return array
  1309. */
  1310. protected function get_tax_location( $args = array() ) {
  1311. $tax_based_on = get_option( 'woocommerce_tax_based_on' );
  1312. if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
  1313. $tax_based_on = 'billing';
  1314. }
  1315. $args = wp_parse_args(
  1316. $args,
  1317. array(
  1318. 'country' => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
  1319. 'state' => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
  1320. 'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
  1321. 'city' => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
  1322. )
  1323. );
  1324. // Default to base.
  1325. if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
  1326. $args['country'] = WC()->countries->get_base_country();
  1327. $args['state'] = WC()->countries->get_base_state();
  1328. $args['postcode'] = WC()->countries->get_base_postcode();
  1329. $args['city'] = WC()->countries->get_base_city();
  1330. }
  1331. return apply_filters( 'woocommerce_order_get_tax_location', $args, $this );
  1332. }
  1333. /**
  1334. * Get tax rates for an order. Use order's shipping or billing address, defaults to base location.
  1335. *
  1336. * @param string $tax_class Tax class to get rates for.
  1337. * @param array $location_args Location to compute rates for. Should be in form: array( country, state, postcode, city).
  1338. * @param object $customer Only used to maintain backward compatibility for filter `woocommerce-matched_rates`.
  1339. *
  1340. * @return mixed|void Tax rates.
  1341. */
  1342. protected function get_tax_rates( $tax_class, $location_args = array(), $customer = null ) {
  1343. $tax_location = $this->get_tax_location( $location_args );
  1344. $tax_location = array( $tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city'] );
  1345. return WC_Tax::get_rates_from_location( $tax_class, $tax_location, $customer );
  1346. }
  1347. /**
  1348. * Calculate taxes for all line items and shipping, and store the totals and tax rows.
  1349. *
  1350. * If by default the taxes are based on the shipping address and the current order doesn't
  1351. * have any, it would use the billing address rather than using the Shopping base location.
  1352. *
  1353. * Will use the base country unless customer addresses are set.
  1354. *
  1355. * @param array $args Added in 3.0.0 to pass things like location.
  1356. */
  1357. public function calculate_taxes( $args = array() ) {
  1358. do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
  1359. $calculate_tax_for = $this->get_tax_location( $args );
  1360. $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
  1361. if ( 'inherit' === $shipping_tax_class ) {
  1362. $found_classes = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
  1363. $shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
  1364. }
  1365. $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
  1366. // Trigger tax recalculation for all items.
  1367. foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
  1368. if ( ! $is_vat_exempt ) {
  1369. $item->calculate_taxes( $calculate_tax_for );
  1370. } else {
  1371. $item->set_taxes( false );
  1372. }
  1373. }
  1374. foreach ( $this->get_shipping_methods() as $item_id => $item ) {
  1375. if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
  1376. $item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
  1377. } else {
  1378. $item->set_taxes( false );
  1379. }
  1380. }
  1381. $this->update_taxes();
  1382. }
  1383. /**
  1384. * Calculate fees for all line items.
  1385. *
  1386. * @return float Fee total.
  1387. */
  1388. public function get_total_fees() {
  1389. return array_reduce(
  1390. $this->get_fees(),
  1391. function( $carry, $item ) {
  1392. return $carry + $item->get_total();
  1393. }
  1394. );
  1395. }
  1396. /**
  1397. * Update tax lines for the order based on the line item taxes themselves.
  1398. */
  1399. public function update_taxes() {
  1400. $cart_taxes = array();
  1401. $shipping_taxes = array();
  1402. $existing_taxes = $this->get_taxes();
  1403. $saved_rate_ids = array();
  1404. foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
  1405. $taxes = $item->get_taxes();
  1406. foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
  1407. $tax_amount = (float) $this->round_line_tax( $tax, false );
  1408. $cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? (float) $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
  1409. }
  1410. }
  1411. foreach ( $this->get_shipping_methods() as $item_id => $item ) {
  1412. $taxes = $item->get_taxes();
  1413. foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
  1414. $tax_amount = (float) $tax;
  1415. if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
  1416. $tax_amount = wc_round_tax_total( $tax_amount );
  1417. }
  1418. $shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
  1419. }
  1420. }
  1421. foreach ( $existing_taxes as $tax ) {
  1422. // Remove taxes which no longer exist for cart/shipping.
  1423. if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
  1424. $this->remove_item( $tax->get_id() );
  1425. continue;
  1426. }
  1427. $saved_rate_ids[] = $tax->get_rate_id();
  1428. $tax->set_rate( $tax->get_rate_id() );
  1429. $tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
  1430. $tax->set_label( WC_Tax::get_rate_label( $tax->get_rate_id() ) );
  1431. $tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
  1432. $tax->save();
  1433. }
  1434. $new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
  1435. // New taxes.
  1436. foreach ( $new_rate_ids as $tax_rate_id ) {
  1437. $item = new WC_Order_Item_Tax();
  1438. $item->set_rate( $tax_rate_id );
  1439. $item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
  1440. $item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
  1441. $this->add_item( $item );
  1442. }
  1443. $this->set_shipping_tax( array_sum( $shipping_taxes ) );
  1444. $this->set_cart_tax( array_sum( $cart_taxes ) );
  1445. $this->save();
  1446. }
  1447. /**
  1448. * Helper function.
  1449. * If you add all items in this order in cart again, this would be the cart subtotal (assuming all other settings are same).
  1450. *
  1451. * @return float Cart subtotal.
  1452. */
  1453. protected function get_cart_subtotal_for_order() {
  1454. return wc_remove_number_precision(
  1455. $this->get_rounded_items_total(
  1456. $this->get_values_for_total( 'subtotal' )
  1457. )
  1458. );
  1459. }
  1460. /**
  1461. * Helper function.
  1462. * If you add all items in this order in cart again, this would be the cart total (assuming all other settings are same).
  1463. *
  1464. * @return float Cart total.
  1465. */
  1466. protected function get_cart_total_for_order() {
  1467. return wc_remove_number_precision(
  1468. $this->get_rounded_items_total(
  1469. $this->get_values_for_total( 'total' )
  1470. )
  1471. );
  1472. }
  1473. /**
  1474. * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
  1475. *
  1476. * @since 2.2
  1477. * @param bool $and_taxes Calc taxes if true.
  1478. * @return float calculated grand total.
  1479. */
  1480. public function calculate_totals( $and_taxes = true ) {
  1481. do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
  1482. $fees_total = 0;
  1483. $shipping_total = 0;
  1484. $cart_subtotal_tax = 0;
  1485. $cart_total_tax = 0;
  1486. $cart_subtotal = $this->get_cart_subtotal_for_order();
  1487. $cart_total = $this->get_cart_total_for_order();
  1488. // Sum shipping costs.
  1489. foreach ( $this->get_shipping_methods() as $shipping ) {
  1490. $shipping_total += NumberUtil::round( $shipping->get_total(), wc_get_price_decimals() );
  1491. }
  1492. $this->set_shipping_total( $shipping_total );
  1493. // Sum fee costs.
  1494. foreach ( $this->get_fees() as $item ) {
  1495. $fee_total = $item->get_total();
  1496. if ( 0 > $fee_total ) {
  1497. $max_discount = NumberUtil::round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1;
  1498. if ( $fee_total < $max_discount && 0 > $max_discount ) {
  1499. $item->set_total( $max_discount );
  1500. }
  1501. }
  1502. $fees_total += $item->get_total();
  1503. }
  1504. // Calculate taxes for items, shipping, discounts. Note; this also triggers save().
  1505. if ( $and_taxes ) {
  1506. $this->calculate_taxes();
  1507. }
  1508. // Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
  1509. foreach ( $this->get_items() as $item ) {
  1510. $taxes = $item->get_taxes();
  1511. foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
  1512. $cart_total_tax += (float) $tax;
  1513. }
  1514. foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) {
  1515. $cart_subtotal_tax += (float) $tax;
  1516. }
  1517. }
  1518. $this->set_discount_total( NumberUtil::round( $cart_subtotal - $cart_total, wc_get_price_decimals() ) );
  1519. $this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
  1520. $this->set_total( NumberUtil::round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
  1521. do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
  1522. $this->save();
  1523. return $this->get_total();
  1524. }
  1525. /**
  1526. * Get item subtotal - this is the cost before discount.
  1527. *
  1528. * @param object $item Item to get total from.
  1529. * @param bool $inc_tax (default: false).
  1530. * @param bool $round (default: true).
  1531. * @return float
  1532. */
  1533. public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
  1534. $subtotal = 0;
  1535. if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
  1536. if ( $inc_tax ) {
  1537. $subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
  1538. } else {
  1539. $subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
  1540. }
  1541. $subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
  1542. }
  1543. return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
  1544. }
  1545. /**
  1546. * Get line subtotal - this is the cost before discount.
  1547. *
  1548. * @param object $item Item to get total from.
  1549. * @param bool $inc_tax (default: false).
  1550. * @param bool $round (default: true).
  1551. * @return float
  1552. */
  1553. public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
  1554. $subtotal = 0;
  1555. if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
  1556. if ( $inc_tax ) {
  1557. $subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
  1558. } else {
  1559. $subtotal = $item->get_subtotal();
  1560. }
  1561. $subtotal = $round ? NumberUtil::round( $subtotal, wc_get_price_decimals() ) : $subtotal;
  1562. }
  1563. return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
  1564. }
  1565. /**
  1566. * Calculate item cost - useful for gateways.
  1567. *
  1568. * @param object $item Item to get total from.
  1569. * @param bool $inc_tax (default: false).
  1570. * @param bool $round (default: true).
  1571. * @return float
  1572. */
  1573. public function get_item_total( $item, $inc_tax = false, $round = true ) {
  1574. $total = 0;
  1575. if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
  1576. if ( $inc_tax ) {
  1577. $total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
  1578. } else {
  1579. $total = floatval( $item->get_total() ) / $item->get_quantity();
  1580. }
  1581. $total = $round ? NumberUtil::round( $total, wc_get_price_decimals() ) : $total;
  1582. }
  1583. return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
  1584. }
  1585. /**
  1586. * Calculate line total - useful for gateways.
  1587. *
  1588. * @param object $item Item to get total from.
  1589. * @param bool $inc_tax (default: false).
  1590. * @param bool $round (default: true).
  1591. * @return float
  1592. */
  1593. public function get_line_total( $item, $inc_tax = false, $round = true ) {
  1594. $total = 0;
  1595. if ( is_callable( array( $item, 'get_total' ) ) ) {
  1596. // Check if we need to add line tax to the line total.
  1597. $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
  1598. // Check if we need to round.
  1599. $total = $round ? NumberUtil::round( $total, wc_get_price_decimals() ) : $total;
  1600. }
  1601. return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
  1602. }
  1603. /**
  1604. * Get item tax - useful for gateways.
  1605. *
  1606. * @param mixed $item Item to get total from.
  1607. * @param bool $round (default: true).
  1608. * @return float
  1609. */
  1610. public function get_item_tax( $item, $round = true ) {
  1611. $tax = 0;
  1612. if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
  1613. $tax = $item->get_total_tax() / $item->get_quantity();
  1614. $tax = $round ? wc_round_tax_total( $tax ) : $tax;
  1615. }
  1616. return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
  1617. }
  1618. /**
  1619. * Get line tax - useful for gateways.
  1620. *
  1621. * @param mixed $item Item to get total from.
  1622. * @return float
  1623. */
  1624. public function get_line_tax( $item ) {
  1625. return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this );
  1626. }
  1627. /**
  1628. * Gets line subtotal - formatted for display.
  1629. *
  1630. * @param object $item Item to get total from.
  1631. * @param string $tax_display Incl or excl tax display mode.
  1632. * @return string
  1633. */
  1634. public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
  1635. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1636. if ( 'excl' === $tax_display ) {
  1637. $ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
  1638. $subtotal = wc_price(
  1639. $this->get_line_subtotal( $item ),
  1640. array(
  1641. 'ex_tax_label' => $ex_tax_label,
  1642. 'currency' => $this->get_currency(),
  1643. )
  1644. );
  1645. } else {
  1646. $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
  1647. }
  1648. return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
  1649. }
  1650. /**
  1651. * Gets order total - formatted for display.
  1652. *
  1653. * @return string
  1654. */
  1655. public function get_formatted_order_total() {
  1656. $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
  1657. return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
  1658. }
  1659. /**
  1660. * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
  1661. *
  1662. * @param bool $compound (default: false).
  1663. * @param string $tax_display (default: the tax_display_cart value).
  1664. * @return string
  1665. */
  1666. public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
  1667. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1668. $subtotal = $this->get_cart_subtotal_for_order();
  1669. if ( ! $compound ) {
  1670. if ( 'incl' === $tax_display ) {
  1671. $subtotal_taxes = 0;
  1672. foreach ( $this->get_items() as $item ) {
  1673. $subtotal_taxes += self::round_line_tax( $item->get_subtotal_tax(), false );
  1674. }
  1675. $subtotal += wc_round_tax_total( $subtotal_taxes );
  1676. }
  1677. $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
  1678. if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
  1679. $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
  1680. }
  1681. } else {
  1682. if ( 'incl' === $tax_display ) {
  1683. return '';
  1684. }
  1685. // Add Shipping Costs.
  1686. $subtotal += $this->get_shipping_total();
  1687. // Remove non-compound taxes.
  1688. foreach ( $this->get_taxes() as $tax ) {
  1689. if ( $tax->is_compound() ) {
  1690. continue;
  1691. }
  1692. $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
  1693. }
  1694. // Remove discounts.
  1695. $subtotal = $subtotal - $this->get_total_discount();
  1696. $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
  1697. }
  1698. return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
  1699. }
  1700. /**
  1701. * Gets shipping (formatted).
  1702. *
  1703. * @param string $tax_display Excl or incl tax display mode.
  1704. * @return string
  1705. */
  1706. public function get_shipping_to_display( $tax_display = '' ) {
  1707. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1708. if ( 0 < abs( (float) $this->get_shipping_total() ) ) {
  1709. if ( 'excl' === $tax_display ) {
  1710. // Show shipping excluding tax.
  1711. $shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
  1712. if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
  1713. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
  1714. }
  1715. } else {
  1716. // Show shipping including tax.
  1717. $shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
  1718. if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
  1719. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
  1720. }
  1721. }
  1722. /* translators: %s: method */
  1723. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this );
  1724. } elseif ( $this->get_shipping_method() ) {
  1725. $shipping = $this->get_shipping_method();
  1726. } else {
  1727. $shipping = __( 'Free!', 'woocommerce' );
  1728. }
  1729. return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this, $tax_display );
  1730. }
  1731. /**
  1732. * Get the discount amount (formatted).
  1733. *
  1734. * @since 2.3.0
  1735. * @param string $tax_display Excl or incl tax display mode.
  1736. * @return string
  1737. */
  1738. public function get_discount_to_display( $tax_display = '' ) {
  1739. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1740. return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this );
  1741. }
  1742. /**
  1743. * Add total row for subtotal.
  1744. *
  1745. * @param array $total_rows Reference to total rows array.
  1746. * @param string $tax_display Excl or incl tax display mode.
  1747. */
  1748. protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
  1749. $subtotal = $this->get_subtotal_to_display( false, $tax_display );
  1750. if ( $subtotal ) {
  1751. $total_rows['cart_subtotal'] = array(
  1752. 'label' => __( 'Subtotal:', 'woocommerce' ),
  1753. 'value' => $subtotal,
  1754. );
  1755. }
  1756. }
  1757. /**
  1758. * Add total row for discounts.
  1759. *
  1760. * @param array $total_rows Reference to total rows array.
  1761. * @param string $tax_display Excl or incl tax display mode.
  1762. */
  1763. protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
  1764. if ( $this->get_total_discount() > 0 ) {
  1765. $total_rows['discount'] = array(
  1766. 'label' => __( 'Discount:', 'woocommerce' ),
  1767. 'value' => '-' . $this->get_discount_to_display( $tax_display ),
  1768. );
  1769. }
  1770. }
  1771. /**
  1772. * Add total row for shipping.
  1773. *
  1774. * @param array $total_rows Reference to total rows array.
  1775. * @param string $tax_display Excl or incl tax display mode.
  1776. */
  1777. protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
  1778. if ( $this->get_shipping_method() ) {
  1779. $total_rows['shipping'] = array(
  1780. 'label' => __( 'Shipping:', 'woocommerce' ),
  1781. 'value' => $this->get_shipping_to_display( $tax_display ),
  1782. );
  1783. }
  1784. }
  1785. /**
  1786. * Add total row for fees.
  1787. *
  1788. * @param array $total_rows Reference to total rows array.
  1789. * @param string $tax_display Excl or incl tax display mode.
  1790. */
  1791. protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
  1792. $fees = $this->get_fees();
  1793. if ( $fees ) {
  1794. foreach ( $fees as $id => $fee ) {
  1795. if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
  1796. continue;
  1797. }
  1798. $total_rows[ 'fee_' . $fee->get_id() ] = array(
  1799. 'label' => $fee->get_name() . ':',
  1800. 'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
  1801. );
  1802. }
  1803. }
  1804. }
  1805. /**
  1806. * Add total row for taxes.
  1807. *
  1808. * @param array $total_rows Reference to total rows array.
  1809. * @param string $tax_display Excl or incl tax display mode.
  1810. */
  1811. protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) {
  1812. // Tax for tax exclusive prices.
  1813. if ( 'excl' === $tax_display && wc_tax_enabled() ) {
  1814. if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
  1815. foreach ( $this->get_tax_totals() as $code => $tax ) {
  1816. $total_rows[ sanitize_title( $code ) ] = array(
  1817. 'label' => $tax->label . ':',
  1818. 'value' => $tax->formatted_amount,
  1819. );
  1820. }
  1821. } else {
  1822. $total_rows['tax'] = array(
  1823. 'label' => WC()->countries->tax_or_vat() . ':',
  1824. 'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
  1825. );
  1826. }
  1827. }
  1828. }
  1829. /**
  1830. * Add total row for grand total.
  1831. *
  1832. * @param array $total_rows Reference to total rows array.
  1833. * @param string $tax_display Excl or incl tax display mode.
  1834. */
  1835. protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
  1836. $total_rows['order_total'] = array(
  1837. 'label' => __( 'Total:', 'woocommerce' ),
  1838. 'value' => $this->get_formatted_order_total( $tax_display ),
  1839. );
  1840. }
  1841. /**
  1842. * Get totals for display on pages and in emails.
  1843. *
  1844. * @param mixed $tax_display Excl or incl tax display mode.
  1845. * @return array
  1846. */
  1847. public function get_order_item_totals( $tax_display = '' ) {
  1848. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1849. $total_rows = array();
  1850. $this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
  1851. $this->add_order_item_totals_discount_row( $total_rows, $tax_display );
  1852. $this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
  1853. $this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
  1854. $this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
  1855. $this->add_order_item_totals_total_row( $total_rows, $tax_display );
  1856. return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
  1857. }
  1858. /*
  1859. |--------------------------------------------------------------------------
  1860. | Conditionals
  1861. |--------------------------------------------------------------------------
  1862. |
  1863. | Checks if a condition is true or false.
  1864. |
  1865. */
  1866. /**
  1867. * Checks the order status against a passed in status.
  1868. *
  1869. * @param array|string $status Status to check.
  1870. * @return bool
  1871. */
  1872. public function has_status( $status ) {
  1873. return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
  1874. }
  1875. /**
  1876. * Check whether this order has a specific shipping method or not.
  1877. *
  1878. * @param string $method_id Method ID to check.
  1879. * @return bool
  1880. */
  1881. public function has_shipping_method( $method_id ) {
  1882. foreach ( $this->get_shipping_methods() as $shipping_method ) {
  1883. if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
  1884. return true;
  1885. }
  1886. }
  1887. return false;
  1888. }
  1889. /**
  1890. * Returns true if the order contains a free product.
  1891. *
  1892. * @since 2.5.0
  1893. * @return bool
  1894. */
  1895. public function has_free_item() {
  1896. foreach ( $this->get_items() as $item ) {
  1897. if ( ! $item->get_total() ) {
  1898. return true;
  1899. }
  1900. }
  1901. return false;
  1902. }
  1903. }