Order.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086
  1. <?php
  2. /*
  3. * FecShop file.
  4. *
  5. * @link http://www.fecshop.com/
  6. * @copyright Copyright (c) 2016 FecShop Software LLC
  7. * @license http://www.fecshop.com/license/
  8. */
  9. namespace fecshop\services;
  10. use Yii;
  11. /**
  12. * Order services.
  13. *
  14. * @property \fecshop\services\order\Item $item
  15. *
  16. * @author Terry Zhao <2358269014@qq.com>
  17. * @since 1.0
  18. */
  19. class Order extends Service
  20. {
  21. public $requiredAddressAttr;
  22. // 必填的订单字段。
  23. // 下面是订单支付状态
  24. // 等待付款状态
  25. public $payment_status_pending = 'payment_pending';
  26. // 付款处理中,(支付处理中,因为信用卡有预售,因此需要等IPN消息来确认是否支付成功)
  27. public $payment_status_processing = 'payment_processing';
  28. // 收款成功(支付状态已确认,代表已经收到钱了)
  29. public $payment_status_confirmed = 'payment_confirmed';
  30. // 欺诈【当paypal的返回金额和网站金额不一致【以及货币类型】的情况,就会判定该状态】
  31. public $payment_status_suspected_fraud = 'payment_suspected_fraud';
  32. // 订单支付已取消【用户进入paypal点击取消订单返回网站,或者payment_pending订单超过xx时间未支付被脚本取消,或者客服后台取消】
  33. public $payment_status_canceled = 'payment_canceled';
  34. // 订单审核中(订单收款成功后,进入erp,需要客服审核,才能开始发货流程,或者可能存在某些问题,被客服暂时挂起)
  35. public $status_holded = 'holded';
  36. // 订单备货处理中,从成功收款进入erp并客服审核成功后,进入备货流程 到 发货前的状态
  37. public $status_processing = 'processing';
  38. // 订单已发货【订单包裹被物流公司收取后】
  39. public $status_dispatched = 'dispatched';
  40. // 订单已退款【已收款订单因为某些原因进行退款,譬如:仓库无货,用户收到货后发现破损退款等】
  41. public $status_refunded = 'refunded';
  42. // 订单已完成,【用户收到货物xx时间后,未发起纠纷争端,订单状态标记为已完成】
  43. public $status_completed = 'completed';
  44. // 订单已取消,【用户付款后,因为纠纷进行取消订单后的状态】
  45. public $status_canceled = 'canceled';
  46. // 订单号格式。
  47. public $increment_id = 1000000000;
  48. public $createdOrder;
  49. // 计算销量的订单时间范围(将最近几个月内的订单中的产品销售个数累加,作为产品的销量值,譬如3代表计算最近3个月的订单产品)
  50. // 0:代表计算订单表中所有的订单。
  51. // 这个值用于console入口(脚本端),通过shell脚本执行,计算产品的销量,将订单中产品个数累加作为产品的销量,然后将这个值更新到产品表字段中,用于产品按照销量排序或者过滤
  52. public $orderProductSaleInMonths = 3;
  53. // 将xx分钟内未支付的pending订单取消掉,并释放产品库存的设置
  54. public $minuteBeforeThatReturnPendingStock = 60;
  55. // 每次处理未支付的pending订单的个数限制。
  56. public $orderCountThatReturnPendingStock = 30;
  57. // 订单备注字符的最大数
  58. public $orderRemarkStrMaxLen = 1500;
  59. // 支付类型,目前只有standard 和 express 两种,express 指的是在购物车点击支付按钮的方式,譬如paypal的express
  60. // standard类型指的是填写完货运地址后生成订单跳转到第三方支付平台的支付类型。
  61. protected $checkout_type;
  62. // 当前的订单信息保存到这个变量中,订单信息是从数据库中取出来订单和产品信息,然后进行了一定的数据处理后,再保存到该变量的。
  63. protected $_currentOrderInfo;
  64. // 支付类型常量
  65. const CHECKOUT_TYPE_STANDARD = 'standard';
  66. const CHECKOUT_TYPE_EXPRESS = 'express';
  67. const CHECKOUT_TYPE_ADMIN_CREATE= 'admin_create';
  68. // 作为保存incrementId到session的key,把当前的order incrementId保存到session的时候,对应的key就是该常量。
  69. const CURRENT_ORDER_INCREAMENT_ID = 'current_order_increament_id';
  70. protected $_orderModelName = '\fecshop\models\mysqldb\Order';
  71. protected $_orderModel;
  72. public function init()
  73. {
  74. parent::init();
  75. list($this->_orderModelName, $this->_orderModel) = \Yii::mapGet($this->_orderModelName);
  76. }
  77. /**
  78. * @return array
  79. * 将订单所有的支付类型,组合成一个数组,进行返回。
  80. */
  81. protected function actionGetCheckoutTypeArr()
  82. {
  83. return [
  84. self::CHECKOUT_TYPE_ADMIN_CREATE => self::CHECKOUT_TYPE_ADMIN_CREATE,
  85. self::CHECKOUT_TYPE_STANDARD => self::CHECKOUT_TYPE_STANDARD,
  86. self::CHECKOUT_TYPE_EXPRESS => self::CHECKOUT_TYPE_EXPRESS,
  87. ];
  88. }
  89. /**
  90. * 付款成功,而且订单付款状态正常的订单状态
  91. *
  92. */
  93. public function getOrderPaymentedStatusArr()
  94. {
  95. return [
  96. $this->payment_status_confirmed,
  97. $this->status_holded,
  98. $this->status_processing,
  99. $this->status_completed,
  100. ];
  101. }
  102. /**
  103. * @return array
  104. * 将订单所有的状态,组合成一个数组,进行返回。
  105. */
  106. protected function actionGetStatusArr()
  107. {
  108. return [
  109. $this->payment_status_pending => $this->payment_status_pending,
  110. $this->payment_status_processing => $this->payment_status_processing,
  111. $this->payment_status_confirmed => $this->payment_status_confirmed,
  112. $this->payment_status_canceled => $this->payment_status_canceled,
  113. $this->payment_status_suspected_fraud => $this->payment_status_suspected_fraud,
  114. $this->status_holded => $this->status_holded,
  115. $this->status_processing => $this->status_processing,
  116. $this->status_dispatched => $this->status_dispatched,
  117. $this->status_refunded => $this->status_refunded,
  118. $this->status_completed => $this->status_completed,
  119. ];
  120. }
  121. /**
  122. * @return array
  123. * 将订单所有的状态,组合成一个数组,进行返回。
  124. */
  125. protected function actionGetSelectStatusArr()
  126. {
  127. return [
  128. $this->payment_status_pending => '等待支付('.$this->payment_status_pending.')',
  129. $this->payment_status_processing => '支付处理中('.$this->payment_status_processing.')',
  130. $this->payment_status_confirmed => '支付成功('.$this->payment_status_confirmed.')',
  131. $this->payment_status_canceled => '支付取消('.$this->payment_status_canceled.')',
  132. $this->payment_status_suspected_fraud => '欺诈订单('.$this->payment_status_suspected_fraud.')',
  133. $this->status_holded => '审核订单('.$this->status_holded.')',
  134. $this->status_processing => '备货中订单('.$this->status_processing.')',
  135. $this->status_dispatched => '已发货订单('.$this->status_dispatched.')',
  136. $this->status_refunded => '已退款订单('.$this->status_refunded.')',
  137. $this->status_completed => '已完成订单('.$this->status_completed.')',
  138. ];
  139. }
  140. /**
  141. * @param $checkout_type | String ,支付类型
  142. * 设置支付类型,其他计算以此设置作为基础,进而获取其他的配置。
  143. */
  144. protected function actionSetCheckoutType($checkout_type)
  145. {
  146. $arr = [self::CHECKOUT_TYPE_STANDARD, self::CHECKOUT_TYPE_EXPRESS];
  147. if (in_array($checkout_type, $arr)) {
  148. $this->checkout_type = $checkout_type;
  149. return true;
  150. }
  151. return false;
  152. }
  153. /**
  154. * 得到支付类型
  155. */
  156. protected function actionGetCheckoutType()
  157. {
  158. return $this->checkout_type;
  159. }
  160. /**
  161. * @param $billing | Array
  162. * @return bool
  163. * 通过$this->requiredAddressAttr,检查地址的必填。
  164. */
  165. protected function actionCheckRequiredAddressAttr($billing)
  166. {
  167. //$this->requiredAddressAttr;
  168. if (is_array($this->requiredAddressAttr) && !empty($this->requiredAddressAttr)) {
  169. foreach ($this->requiredAddressAttr as $attr) {
  170. if (!isset($billing[$attr]) || empty($billing[$attr])) {
  171. Yii::$service->helper->errors->add('{attr} can not empty', ['attr' => $attr]);
  172. return false;
  173. }
  174. }
  175. }
  176. return true;
  177. }
  178. /**
  179. * 得到order 表的id字段。
  180. */
  181. protected function actionGetPrimaryKey()
  182. {
  183. return 'order_id';
  184. }
  185. /**
  186. * @param $primaryKey | Int
  187. * @return Object($this->_orderModel)
  188. * 通过主键值,返回Order Model对象
  189. */
  190. protected function actionGetByPrimaryKey($primaryKey)
  191. {
  192. $one = $this->_orderModel->findOne($primaryKey);
  193. $primaryKey = $this->getPrimaryKey();
  194. if ($one[$primaryKey]) {
  195. return $one;
  196. } else {
  197. return new $this->_orderModelName();
  198. }
  199. }
  200. /**
  201. * @param $increment_id | String , 订单号
  202. * @return object ($this->_orderModel),返回 $this->_orderModel model
  203. * 通过订单号incrementId,得到订单Model对象。
  204. */
  205. protected function actionGetByIncrementId($increment_id)
  206. {
  207. $one = $this->_orderModel->findOne(['increment_id' => $increment_id]);
  208. $primaryKey = $this->getPrimaryKey();
  209. if ($one[$primaryKey]) {
  210. return $one;
  211. } else {
  212. return false;
  213. }
  214. }
  215. /**
  216. * @param $reflush | boolean 是否从数据库中重新获取,如果是,则不会使用类变量中计算的值
  217. * 获取当前的订单信息,原理为:
  218. * 通过从session中取出来订单的increment_id,
  219. * 在通过increment_id(订单编号)取出来订单信息。
  220. */
  221. protected function actionGetCurrentOrderInfo($reflush = false)
  222. {
  223. if (!$this->_currentOrderInfo || $reflush) {
  224. $increment_id = Yii::$service->order->getSessionIncrementId();
  225. $this->_currentOrderInfo = Yii::$service->order->getOrderInfoByIncrementId($increment_id);
  226. }
  227. return $this->_currentOrderInfo;
  228. }
  229. /**
  230. * @param $increment_id | String 订单编号
  231. * @return array
  232. * 通过increment_id 从数据库中取出来订单数据,
  233. * 然后进行一系列的处理,返回订单数组数据。
  234. */
  235. protected function actionGetOrderInfoByIncrementId($increment_id)
  236. {
  237. $one = $this->getByIncrementId($increment_id);
  238. if (!$one) {
  239. return;
  240. }
  241. $primaryKey = $this->getPrimaryKey();
  242. if (!isset($one[$primaryKey]) || empty($one[$primaryKey])) {
  243. return;
  244. }
  245. $order_info = [];
  246. foreach ($one as $k=>$v) {
  247. $order_info[$k] = $v;
  248. }
  249. $order_info['customer_address_state_name'] = Yii::$service->helper->country->getStateByContryCode($order_info['customer_address_country'], $order_info['customer_address_state']);
  250. $order_info['customer_address_country_name'] = Yii::$service->helper->country->getCountryNameByKey($order_info['customer_address_country']);
  251. $order_info['currency_symbol'] = Yii::$service->page->currency->getSymbol($order_info['order_currency_code']);
  252. $order_info['products'] = Yii::$service->order->item->getByOrderId($one[$primaryKey]);
  253. return $order_info;
  254. }
  255. protected function actionGetorderinfocoll($filter = '')
  256. {
  257. $primaryKey = $this->getPrimaryKey();
  258. $query = $this->_orderModel->find();
  259. $query = Yii::$service->helper->ar->getCollByFilter($query, $filter);
  260. $coll = $query->all();
  261. foreach ($coll as $k => $order_info) {
  262. $coll[$k]['customer_address_state_name'] = Yii::$service->helper->country->getStateByContryCode($order_info['customer_address_country'], $order_info['customer_address_state']);
  263. $coll[$k]['customer_address_country_name'] = Yii::$service->helper->country->getCountryNameByKey($order_info['customer_address_country']);
  264. $coll[$k]['currency_symbol'] = Yii::$service->page->currency->getSymbol($order_info['order_currency_code']);
  265. $coll[$k]['products'] = Yii::$service->order->item->getByOrderId($order_info[$primaryKey]);
  266. }
  267. return [
  268. 'coll' => $coll,
  269. 'count'=> $query->limit(null)->offset(null)->count(),
  270. ];
  271. }
  272. /**
  273. * @param $order_id | Int
  274. * @return array
  275. * 通过order_id 从数据库中取出来订单数据,
  276. * 然后进行一系列的处理,返回订单数组数据。
  277. */
  278. protected function actionGetOrderInfoById($order_id)
  279. {
  280. if (!$order_id) {
  281. return;
  282. }
  283. $one = $this->_orderModel->findOne($order_id);
  284. $primaryKey = $this->getPrimaryKey();
  285. if (!isset($one[$primaryKey]) || empty($one[$primaryKey])) {
  286. return;
  287. }
  288. $order_info = [];
  289. foreach ($one as $k=>$v) {
  290. $order_info[$k] = $v;
  291. }
  292. $order_info['customer_address_state_name'] = Yii::$service->helper->country->getStateByContryCode($order_info['customer_address_country'], $order_info['customer_address_state']);
  293. $order_info['customer_address_country_name']= Yii::$service->helper->country->getCountryNameByKey($order_info['customer_address_country']);
  294. $order_info['currency_symbol'] = Yii::$service->page->currency->getSymbol($order_info['order_currency_code']);
  295. $order_info['products'] = Yii::$service->order->item->getByOrderId($order_id);
  296. return $order_info;
  297. }
  298. /**
  299. * @param $filter|array
  300. * @return Array;
  301. * 通过过滤条件,得到coupon的集合。
  302. * example filter:
  303. * [
  304. * 'numPerPage' => 20,
  305. * 'pageNum' => 1,
  306. * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ],
  307. * 'where' => [
  308. * ['>','price',1],
  309. * ['<=','price',10]
  310. * ['sku' => 'uk10001'],
  311. * ],
  312. * 'asArray' => true,
  313. * ]
  314. * 根据$filter 搜索参数数组,返回满足条件的订单数据。
  315. */
  316. protected function actionColl($filter = '')
  317. {
  318. $query = $this->_orderModel->find();
  319. $query = Yii::$service->helper->ar->getCollByFilter($query, $filter);
  320. $coll = $query->all();
  321. return [
  322. 'coll' => $coll,
  323. 'count'=> $query->limit(null)->offset(null)->count(),
  324. ];
  325. }
  326. /**
  327. * @param $one|array , save one data .
  328. * @return int 保存order成功后,返回保存的id。
  329. */
  330. protected function actionSave($one)
  331. {
  332. $time = time();
  333. $primaryKey = $this->getPrimaryKey();
  334. $primaryVal = isset($one[$primaryKey]) ? $one[$primaryKey] : '';
  335. if ($primaryVal) {
  336. $model = $this->_orderModel->findOne($primaryVal);
  337. if (!$model) {
  338. Yii::$service->helper->errors->add('order {primaryKey} is not exist', ['primaryKey' => $this->getPrimaryKey()]);
  339. return;
  340. }
  341. } else {
  342. $model = new $this->_orderModelName();
  343. $model->created_at = time();
  344. }
  345. $model->updated_at = time();
  346. $model = Yii::$service->helper->ar->save($model, $one);
  347. $primaryVal = $model[$this->getPrimaryKey()];
  348. return $primaryVal;
  349. }
  350. /**
  351. * @param $ids | Int or Array
  352. * @return bool
  353. * 如果传入的是id数组,则删除多个
  354. * 如果传入的是Int,则删除一个
  355. */
  356. protected function actionRemove($ids)
  357. {
  358. if (!$ids) {
  359. Yii::$service->helper->errors->add('remove id is empty');
  360. return false;
  361. }
  362. if (is_array($ids) && !empty($ids)) {
  363. foreach ($ids as $id) {
  364. $model = $this->_orderModel->findOne($id);
  365. if (isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()])) {
  366. $model->delete();
  367. } else {
  368. Yii::$service->helper->errors->add('Order Remove Errors:ID {id} is not exist', ['id' => $id]);
  369. return false;
  370. }
  371. }
  372. } else {
  373. $id = $ids;
  374. $model = $this->_orderModel->findOne($id);
  375. if (isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()])) {
  376. $model->delete();
  377. } else {
  378. Yii::$service->helper->errors->add('Coupon Remove Errors:ID:{id} is not exist.', ['id' => $id]);
  379. return false;
  380. }
  381. }
  382. return true;
  383. }
  384. /**
  385. * @param $increment_id | String , 订单号
  386. * @return object ($this->_orderModel),返回 $this->_orderModel model
  387. * 通过订单号,得到订单以及订单产品信息。
  388. */
  389. protected function actionGetInfoByIncrementId($increment_id)
  390. {
  391. $order = $this->getByIncrementId($increment_id);
  392. $orderInfo = [];
  393. if ($order) {
  394. $primaryKey = $this->getPrimaryKey();
  395. $order_id = $order[$primaryKey];
  396. $items = Yii::$service->order->item->getByOrderId($order_id);
  397. foreach ($order as $k=>$v) {
  398. $orderInfo[$k] = $v;
  399. }
  400. $orderInfo['items'] = $items;
  401. return $orderInfo;
  402. } else {
  403. return;
  404. }
  405. }
  406. /**
  407. * @param $token | String , paypal 支付获取的token,订单生成后只有三个字段
  408. * order_id, increment_id , payment_token ,目的就是将token对应到一个increment_id
  409. * 在paypal 点击continue的时候,可以通过token找到对应的订单。
  410. */
  411. protected function actionGeneratePPExpressOrder($token)
  412. {
  413. $myOrder = new $this->_orderModelName();
  414. $myOrder->setGenerateOrderByPaypalToken(true);
  415. $myOrder->payment_token = $token;
  416. $myOrder->save();
  417. $order_id = $myOrder['order_id'];
  418. if ($order_id) {
  419. $increment_id = $this->generateIncrementIdByOrderId($order_id);
  420. $myOrder['increment_id'] = $increment_id;
  421. $myOrder->save();
  422. $this->setSessionIncrementId($increment_id);
  423. return true;
  424. } else {
  425. Yii::$service->helper->errors->add('generate order fail');
  426. return false;
  427. }
  428. }
  429. /**
  430. * @param $token | String , paypal 支付获取的token,
  431. * 通过token 得到订单 Object
  432. */
  433. protected function actionGetByPaymentToken($token)
  434. {
  435. $one = $this->_orderModel->find()->where(['payment_token' => $token])
  436. ->one();
  437. if (isset($one['order_id']) && $one['order_id']) {
  438. return $one;
  439. } else {
  440. return '';
  441. }
  442. }
  443. /**
  444. * @param $reflush | boolean 是否从数据库中重新获取,如果是,则不会使用类变量中计算的值
  445. * 通过从session中取出来订单的increment_id
  446. * 在通过increment_id(订单编号)取出来订单信息。
  447. */
  448. protected function actionGetInfoByPaymentToken($token)
  449. {
  450. $orderModel = $this->getByPaymentToken($token);
  451. $increment_id = isset($orderModel['increment_id']) ? $orderModel['increment_id'] : '';
  452. return Yii::$service->order->getOrderInfoByIncrementId($increment_id);
  453. }
  454. /**
  455. * @param $address | Array 货运地址
  456. * @param $shipping_method | String 货运快递方式
  457. * @param $payment_method | Array 支付方式、
  458. * @param $clearCartAndDeductStock | boolean 是否清空购物车,并扣除库存,这种情况是先 生成订单,在支付的情况下失败的处理方式。
  459. * @param $token | string 代表 通过payment_token得到order,然后更新order信息的方式生成order,这个是paypal购物车express支付对应的功能
  460. * @param $order_remark | string , 订单备注
  461. * @return bool 通过购物车的数据生成订单是否成功
  462. * 通过购物车中的产品信息,以及传递的货运地址,货运快递方式,支付方式生成订单。
  463. */
  464. protected function actionGenerateOrderByCart($address, $shipping_method, $payment_method, $clearCart = true, $token = '', $order_remark = '')
  465. {
  466. $cart = Yii::$service->cart->quote->getCurrentCart();
  467. if (!$cart) {
  468. Yii::$service->helper->errors->add('current cart is empty');
  469. }
  470. $currency_info = Yii::$service->page->currency->getCurrencyInfo();
  471. $currency_code = $currency_info['code'];
  472. $currency_rate = $currency_info['rate'];
  473. $country = $address['country'];
  474. $state = $address['state'];
  475. $cartInfo = Yii::$service->cart->getCartInfo(true, $shipping_method, $country, $state);
  476. // 检查cartInfo中是否存在产品
  477. if (!is_array($cartInfo) && empty($cartInfo)) {
  478. Yii::$service->helper->errors->add('current cart product is empty');
  479. return false;
  480. }
  481. // 扣除库存。(订单生成后,库存产品库存。)
  482. // 备注)需要另起一个脚本,用来处理半个小时后,还没有支付的订单,将订单取消,然后将订单里面的产品库存返还。
  483. // 如果是无限库存(没有库存就去采购的方式),那么不需要跑这个脚本,将库存设置的非常大即可。
  484. $deductStatus = Yii::$service->product->stock->deduct($cartInfo['products']);
  485. if (!$deductStatus) {
  486. // 库存不足则返回
  487. return false;
  488. }
  489. $beforeEventName = 'event_generate_order_before';
  490. $afterEventName = 'event_generate_order_after';
  491. Yii::$service->event->trigger($beforeEventName, $cartInfo);
  492. if ($token) {
  493. // 有token 代表前面已经生成了order,直接通过token查询出来即可。
  494. $myOrder = $this->getByPaymentToken($token);
  495. if (!$myOrder) {
  496. Yii::$service->helper->errors->add('order increment id is not exist.');
  497. return false;
  498. } else {
  499. $increment_id = $myOrder['increment_id'];
  500. }
  501. } else {
  502. $myOrder = new $this->_orderModelName();
  503. }
  504. $myOrder['order_status'] = $this->payment_status_pending;
  505. $currentStore = Yii::$service->store->currentStore;
  506. $currentStore || $currentStore = $cartInfo['store'];
  507. $myOrder['store'] = $currentStore;
  508. $myOrder['created_at'] = time();
  509. $myOrder['updated_at'] = time();
  510. $myOrder['items_count'] = $cartInfo['items_count'];
  511. $myOrder['total_weight'] = $cartInfo['product_weight'];
  512. $myOrder['order_currency_code'] = $currency_code;
  513. $myOrder['order_to_base_rate'] = $currency_rate;
  514. $myOrder['grand_total'] = $cartInfo['grand_total'];
  515. $myOrder['base_grand_total'] = $cartInfo['base_grand_total'];
  516. $myOrder['subtotal'] = $cartInfo['product_total'];
  517. $myOrder['base_subtotal'] = $cartInfo['base_product_total'];
  518. $myOrder['subtotal_with_discount'] = $cartInfo['coupon_cost'];
  519. $myOrder['base_subtotal_with_discount'] = $cartInfo['base_coupon_cost'];
  520. $myOrder['shipping_total'] = $cartInfo['shipping_cost'];
  521. $myOrder['base_shipping_total'] = $cartInfo['base_shipping_cost'];
  522. $myOrder['checkout_method'] = $this->getCheckoutType();
  523. !$order_remark || $myOrder['order_remark'] = \yii\helpers\Html::encode($order_remark);
  524. if ($address['customer_id']) {
  525. $is_guest = 2;
  526. } else {
  527. $is_guest = 1;
  528. }
  529. if (!Yii::$app->user->isGuest) {
  530. $customer_id = Yii::$app->user->identity->id;
  531. } else {
  532. $customer_id = '';
  533. }
  534. $myOrder['customer_id'] = $customer_id;
  535. $myOrder['customer_email'] = $address['email'];
  536. $myOrder['customer_firstname'] = $address['first_name'];
  537. $myOrder['customer_lastname'] = $address['last_name'];
  538. $myOrder['customer_is_guest'] = $is_guest;
  539. $myOrder['customer_telephone'] = $address['telephone'];
  540. $myOrder['customer_address_country']= $address['country'];
  541. $myOrder['customer_address_state'] = $address['state'];
  542. $myOrder['customer_address_city'] = $address['city'];
  543. $myOrder['customer_address_zip'] = $address['zip'];
  544. $myOrder['customer_address_street1']= $address['street1'];
  545. $myOrder['customer_address_street2']= $address['street2'];
  546. $myOrder['coupon_code'] = $cartInfo['coupon_code'];
  547. $myOrder['payment_method'] = $payment_method;
  548. $myOrder['shipping_method'] = $shipping_method;
  549. // 进行model验证。
  550. if (!$myOrder->validate()) {
  551. $errors = $myOrder->errors;
  552. Yii::$service->helper->errors->addByModelErrors($errors);
  553. return false;
  554. }
  555. // 保存订单
  556. $saveOrderStatus = $myOrder->save();
  557. if (!$saveOrderStatus) {
  558. return false;
  559. }
  560. $order_id = $myOrder['order_id'];
  561. if (!$increment_id) {
  562. $increment_id = $this->generateIncrementIdByOrderId($order_id);
  563. $myOrder['increment_id'] = $increment_id;
  564. // 保存订单
  565. $saveOrderStatus = $myOrder->save();
  566. if (!$saveOrderStatus) {
  567. return false;
  568. }
  569. }
  570. Yii::$service->event->trigger($afterEventName, $myOrder);
  571. if ($myOrder[$this->getPrimaryKey()]) {
  572. // 保存订单产品
  573. $saveItemStatus = Yii::$service->order->item->saveOrderItems($cartInfo['products'], $order_id, $cartInfo['store']);
  574. if (!$saveItemStatus) {
  575. return false;
  576. }
  577. // 订单生成成功,通过api传递数据给trace系统
  578. $this->sendTracePaymentPendingOrder($myOrder, $cartInfo['products']);
  579. // 如果是登录用户,那么,在生成订单后,需要清空购物车中的产品和coupon。
  580. if (!Yii::$app->user->isGuest && $clearCart) {
  581. Yii::$service->cart->clearCartProductAndCoupon();
  582. }
  583. $this->createdOrder = $myOrder;
  584. // 执行成功,则在session中设置increment_id
  585. $this->setSessionIncrementId($increment_id);
  586. return true;
  587. } else {
  588. Yii::$service->helper->errors->add('generate order fail');
  589. return false;
  590. }
  591. }
  592. /**
  593. * @param $order_increment_id | string,订单编号 increment_id
  594. * 订单支付成功后,执行的代码,该代码只会在接收到支付成功信息后,才会执行。
  595. * 在调用该函数前,会对IPN支付成功消息做验证,一次,无论发送多少次ipn消息,该函数只会执行一次。
  596. * 您可以把订单支付成功需要做的事情都在这个函数里面完成。
  597. **/
  598. public function orderPaymentCompleteEvent($order_increment_id)
  599. {
  600. if (!$order_increment_id) {
  601. Yii::$service->helper->errors->add('order increment id is empty');
  602. return false;
  603. }
  604. $orderInfo = Yii::$service->order->getOrderInfoByIncrementId($order_increment_id);
  605. if (!$orderInfo['increment_id']) {
  606. Yii::$service->helper->errors->add('get order by increment_id: {increment_id} fail, order is not exist ', ['increment_id' => $order_increment_id]);
  607. return false;
  608. }
  609. // 追踪信息
  610. Yii::$service->order->sendTracePaymentSuccessOrder($orderInfo);
  611. // 发送订单支付成功邮件
  612. Yii::$service->email->order->sendCreateEmail($orderInfo);
  613. }
  614. /**
  615. * @param $orderInfo | Object, 订单对象
  616. * @param $cartInfo | Object,购物车对象
  617. * 根据传递的参数,得出trace系统的要求的order参数格式数组
  618. * 执行page trace services,将支付完成订单的数据传递给trace系统
  619. */
  620. protected function sendTracePaymentSuccessOrder($orderInfo)
  621. {
  622. \Yii::info('sendTracePaymentSuccessOrder', 'fecshop_debug');
  623. if (Yii::$service->page->trace->traceJsEnable) {
  624. $arr = [];
  625. $arr['invoice'] = (string)$orderInfo['increment_id'];
  626. $arr['order_type'] = $orderInfo['checkout_method'];
  627. $arr['payment_status'] = $orderInfo['order_status'];
  628. $arr['payment_type'] = $orderInfo['payment_method'];
  629. $arr['amount'] = (float)$orderInfo['base_grand_total'];
  630. $arr['shipping'] = (float)$orderInfo['base_shipping_total'];
  631. $arr['discount_amount'] = (float)$orderInfo['base_subtotal_with_discount'];
  632. $arr['coupon'] = $orderInfo['coupon_code'];
  633. $arr['city'] = $orderInfo['customer_address_city'];
  634. $arr['email'] = $orderInfo['customer_email'];
  635. $arr['first_name'] = $orderInfo['customer_firstname'];
  636. $arr['last_name'] = $orderInfo['customer_lastname'];
  637. $arr['zip'] = $orderInfo['customer_address_zip'];
  638. $arr['address1'] = $orderInfo['customer_address_street1'];
  639. $arr['address2'] = $orderInfo['customer_address_street2'];
  640. $arr['created_at'] = $orderInfo['created_at'];
  641. $arr['country_code'] = $orderInfo['customer_address_country'];
  642. $arr['state_code'] = $orderInfo['customer_address_state'];
  643. $arr['state_name'] = Yii::$service->helper->country->getStateByContryCode($orderInfo['customer_address_country'], $orderInfo['customer_address_state']);
  644. $arr['country_name'] = Yii::$service->helper->country->getCountryNameByKey($orderInfo['customer_address_country']);
  645. $product_arr = [];
  646. $products = $orderInfo['products'];
  647. if (is_array($products)) {
  648. foreach ($products as $product) {
  649. $product_arr[] = [
  650. 'sku' => $product['sku'],
  651. 'name' => $product['name'],
  652. 'qty' => (int)$product['qty'],
  653. 'price' => (float)$product['base_product_price'],
  654. ];
  655. }
  656. }
  657. $arr['products'] = $product_arr;
  658. \Yii::info('sendTracePaymentSuccessOrderByApi', 'fecshop_debug');
  659. Yii::$service->page->trace->sendTracePaymentSuccessOrderByApi($arr);
  660. return true;
  661. }
  662. return false;
  663. }
  664. /**
  665. * @param $myOrder | Object, 订单对象
  666. * @param $products | Array,购物车产品数组
  667. * 根据传递的参数,得出trace系统的要求的order参数格式数组,
  668. * 执行page trace services,将等待支付订单(刚刚生成的订单)的数据传递给trace系统
  669. */
  670. protected function sendTracePaymentPendingOrder($myOrder, $products)
  671. {
  672. if (Yii::$service->page->trace->traceJsEnable) {
  673. $arr = [];
  674. $arr['invoice'] = (string)$myOrder['increment_id'];
  675. $arr['order_type'] = $myOrder['checkout_method'];
  676. $arr['payment_status'] = $myOrder['order_status'];
  677. $arr['payment_type'] = $myOrder['payment_method'];
  678. $arr['amount'] = (float)$myOrder['base_grand_total'];
  679. $arr['shipping'] = (float)$myOrder['base_shipping_total'];
  680. $arr['discount_amount'] = (float)$myOrder['base_subtotal_with_discount'];
  681. $arr['coupon'] = $myOrder['coupon_code'];
  682. $arr['city'] = $myOrder['customer_address_city'];
  683. $arr['created_at'] = $myOrder['created_at'];
  684. $arr['email'] = $myOrder['customer_email'];
  685. $arr['first_name'] = $myOrder['customer_firstname'];
  686. $arr['last_name'] = $myOrder['customer_lastname'];
  687. $arr['zip'] = $myOrder['customer_address_zip'];
  688. $arr['address1'] = $myOrder['customer_address_street1'];
  689. $arr['address2'] = $myOrder['customer_address_street2'];
  690. $arr['country_code'] = $myOrder['customer_address_country'];
  691. $arr['state_code'] = $myOrder['customer_address_state'];
  692. $arr['state_name'] = Yii::$service->helper->country->getStateByContryCode($myOrder['customer_address_country'], $myOrder['customer_address_state']);
  693. $arr['country_name'] = Yii::$service->helper->country->getCountryNameByKey($myOrder['customer_address_country']);
  694. $product_arr = [];
  695. // $products = $cartInfo['products'];
  696. if (is_array($products)) {
  697. foreach ($products as $product) {
  698. $product_arr[] = [
  699. 'sku' => $product['sku'],
  700. 'name' => $product['name'],
  701. 'qty' => (int)$product['qty'],
  702. 'price' => (float)$product['base_product_price'],
  703. ];
  704. }
  705. }
  706. $arr['products'] = $product_arr;
  707. Yii::$service->page->trace->sendTracePaymentPendingOrderByApi($arr);
  708. return true;
  709. }
  710. return false;
  711. }
  712. /**
  713. * @param $increment_id | String 每执行一次,version都会+1 (version默认为0)
  714. * 执行完,查看version是否为1,如果不为1,则说明已经执行过了,返回false
  715. */
  716. public function checkOrderVersion($increment_id)
  717. {
  718. // 更新订单版本号,防止被多次执行。
  719. $sql = 'update '.$this->_orderModel->tableName().' set version = version + 1 where increment_id = :increment_id';
  720. $data = [
  721. 'increment_id' => $increment_id,
  722. ];
  723. $result = $this->_orderModel->getDb()->createCommand($sql, $data)->execute();
  724. $myOrder = $this->_orderModel->find()->where([
  725. 'increment_id' => $increment_id,
  726. ])->one();
  727. // 如果版本号不等于1,则回滚
  728. if ($myOrder['version'] > 1) {
  729. Yii::$service->helper->errors->add('Your order has been paid');
  730. return false;
  731. } elseif ($myOrder['version'] < 1) {
  732. Yii::$service->helper->errors->add('Your order is error');
  733. return false;
  734. } else {
  735. return true;
  736. }
  737. }
  738. /**
  739. * @param $increment_id | String ,order订单号
  740. * 将生成的订单号写入session
  741. */
  742. protected function actionSetSessionIncrementId($increment_id)
  743. {
  744. Yii::$service->session->set(self::CURRENT_ORDER_INCREAMENT_ID, $increment_id);
  745. }
  746. /**
  747. * 从session中取出来订单号.
  748. */
  749. protected function actionGetSessionIncrementId()
  750. {
  751. return Yii::$service->session->get(self::CURRENT_ORDER_INCREAMENT_ID);
  752. }
  753. /**
  754. * @param $increment_id | String 订单号
  755. * @param $token | String ,通过api支付的token
  756. * 通过订单号,更新订单的支付token
  757. */
  758. protected function actionUpdateTokenByIncrementId($increment_id, $token)
  759. {
  760. $myOrder = Yii::$service->order->getByIncrementId($increment_id);
  761. if ($myOrder) {
  762. $myOrder->payment_token = $token;
  763. $myOrder->save();
  764. }
  765. }
  766. /**
  767. * 从session中销毁订单号.
  768. */
  769. protected function actionRemoveSessionIncrementId()
  770. {
  771. return Yii::$service->session->remove(self::CURRENT_ORDER_INCREAMENT_ID);
  772. }
  773. /**
  774. * @param int $order_id the order id
  775. * @return int $increment_id
  776. * 通过 order_id 生成订单号。
  777. */
  778. protected function generateIncrementIdByOrderId($order_id)
  779. {
  780. $increment_id = (int) $this->increment_id + (int) $order_id;
  781. return $increment_id;
  782. }
  783. /**
  784. * get order list by customer account id.
  785. * @param int $customer_id
  786. * @deprecated
  787. */
  788. protected function actionGetCustomerOrderList($customer_id)
  789. {
  790. }
  791. /**
  792. * @param int $order_id the order id
  793. * 订单支付成功后,更改订单的状态为支付成功状态。
  794. * @deprecated
  795. */
  796. protected function actionOrderPaySuccess($order_id)
  797. {
  798. }
  799. /**
  800. * @param $increment_id | String
  801. * @return bool
  802. * 取消订单,更新订单的状态为cancel。
  803. * 并且释放库存给产品
  804. */
  805. protected function actionCancel($increment_id = '', $customer_id = '')
  806. {
  807. if (!$increment_id) {
  808. $increment_id = $this->getSessionIncrementId();
  809. }
  810. if ($increment_id) {
  811. $order = $this->getByIncrementId($increment_id);
  812. if ($customer_id && $order['customer_id'] != $customer_id) {
  813. Yii::$service->helper->errors->add('do not have role to cancel this order');
  814. return false;
  815. }
  816. if ($order) {
  817. $order->order_status = $this->payment_status_canceled;
  818. $order->updated_at = time();
  819. $order->save();
  820. // 释放库存
  821. $order_primary_key = $this->getPrimaryKey();
  822. $product_items = Yii::$service->order->item->getByOrderId($order[$order_primary_key], true);
  823. Yii::$service->product->stock->returnQty($product_items);
  824. return true;
  825. }
  826. }
  827. return false;
  828. }
  829. // 用户确认收货
  830. public function delivery($incrementId, $customerId)
  831. {
  832. $updateComules = $this->_orderModel->updateAll(
  833. [
  834. 'order_status' => $this->status_completed,
  835. ],
  836. [
  837. 'increment_id' => $incrementId,
  838. 'order_status' => $this->status_dispatched,
  839. 'customer_id' => $customerId,
  840. ]
  841. );
  842. if (empty($updateComules)) {
  843. Yii::$service->helper->errors->add('customer delivery order fail');
  844. return false;
  845. }
  846. return true;
  847. }
  848. /**
  849. * 将xx时间内未支付的pending订单取消掉,并释放产品库存。
  850. * 这个是后台脚本执行的函数。
  851. */
  852. protected function actionReturnPendingStock()
  853. {
  854. $logMessage = [];
  855. $minute = $this->minuteBeforeThatReturnPendingStock;
  856. $begin_time = strtotime(date('Y-m-d H:i:s'). ' -'.$minute.' minutes ');
  857. // 不需要释放库存的支付方式。譬如货到付款,在系统中
  858. // pending订单,如果一段时间未付款,会释放产品库存,但是货到付款类型的订单不会释放,
  859. // 如果需要释放产品库存,客服在后台取消订单即可释放产品库存。
  860. $noRelasePaymentMethod = Yii::$service->payment->noRelasePaymentMethod;
  861. $where = [
  862. ['<', 'updated_at', $begin_time],
  863. ['order_status' => $this->payment_status_pending],
  864. ['if_is_return_stock' => 2],
  865. ];
  866. $logMessage[] = 'order_updated_at: '.$begin_time;
  867. if (is_array($noRelasePaymentMethod) && !empty($noRelasePaymentMethod)) {
  868. $where[] = ['not in', 'payment_method', $noRelasePaymentMethod];
  869. }
  870. $filter = [
  871. 'where' => $where,
  872. 'numPerPage' => $this->orderCountThatReturnPendingStock,
  873. 'pageNum' => 1,
  874. 'asArray' => false,
  875. ];
  876. $data = $this->coll($filter);
  877. $coll = $data['coll'];
  878. $count = $data['count'];
  879. $logMessage[] = 'order count: '.$count;
  880. if ($count > 0) {
  881. foreach ($coll as $one) {
  882. /**
  883. * service严格上是不允许使用事务的,该方法特殊,是命令行执行的操作。
  884. * 每一个循环是一个事务。
  885. */
  886. $innerTransaction = Yii::$app->db->beginTransaction();
  887. try {
  888. $logMessage[] = 'cancel order[begin] increment_id: '.$one['increment_id'];
  889. $order_id = $one['order_id'];
  890. $updateComules = $one->updateAll(
  891. [
  892. 'if_is_return_stock' => 1,
  893. 'order_status' => $this->payment_status_canceled,
  894. ],
  895. [
  896. 'order_id' => $one['order_id'],
  897. 'order_status' => $this->payment_status_pending,
  898. 'if_is_return_stock' => 2
  899. ]
  900. );
  901. /**
  902. * 取消订单,只能操作一次,因此,我们在更新条件里面加上了order_id, order_status,if_is_return_stock
  903. * 因为在上面查询和当前执行的时间之间,订单可能被进行其他操作,
  904. * 如果被其他操作,更改了order_status,那么上面的更新行数就是0行。
  905. * 那么事务直接回滚。
  906. */
  907. if (empty($updateComules)) {
  908. $innerTransaction->rollBack();
  909. continue;
  910. } else {
  911. $product_items = Yii::$service->order->item->getByOrderId($order_id, true);
  912. Yii::$service->product->stock->returnQty($product_items);
  913. }
  914. //$one->if_is_return_stock = 1;
  915. // 将订单取消掉。取消后的订单不能再次支付。
  916. //$one->order_status = $this->payment_status_canceled;
  917. //$one->save();
  918. $innerTransaction->commit();
  919. $logMessage[] = 'cancel order[end] increment_id: '.$one['increment_id'];
  920. } catch (\Exception $e) {
  921. $innerTransaction->rollBack();
  922. }
  923. }
  924. }
  925. return $logMessage;
  926. }
  927. /**
  928. * @param $days | Int 天数
  929. * 得到最近1个月的订单数据,包括:日期,订单支付状态,订单金额
  930. * 下面的数据是为了后台的订单统计
  931. */
  932. public function getPreMonthOrder($days)
  933. {
  934. // 得到一个月前的时间戳
  935. $preMonthTime = strtotime("-$days days");
  936. $filter = [
  937. 'select' => ['created_at', 'increment_id', 'order_status', 'base_grand_total' ],
  938. 'numPerPage' => 10000000,
  939. 'pageNum' => 1,
  940. 'where' => [
  941. ['>=', 'created_at', $preMonthTime]
  942. ],
  943. 'asArray' => true,
  944. ];
  945. $orderPaymentStatusArr = $this->getOrderPaymentedStatusArr();
  946. $data = $this->coll($filter);
  947. $coll = $data['coll'];
  948. $dateArr = Yii::$service->helper->format->getPreDayDateArr($days);
  949. $orderAmountArr = $dateArr;
  950. $paymentOrderAmountArr = $dateArr;
  951. $orderCountArr = $dateArr;
  952. $paymentOrderCountArr = $dateArr;
  953. if (is_array($coll) && !empty($coll)) {
  954. foreach ($coll as $order) {
  955. $created_at = $order['created_at'];
  956. $created_at_str = date("Y-m-d", $created_at);
  957. $order_status = $order['order_status'];
  958. $base_grand_total = $order['base_grand_total'];
  959. if (isset($orderAmountArr[$created_at_str])) {
  960. $orderAmountArr[$created_at_str] += $base_grand_total;
  961. $orderCountArr[$created_at_str] += 1;
  962. if (in_array($order_status, $orderPaymentStatusArr)) {
  963. $paymentOrderAmountArr[$created_at_str] += $base_grand_total;
  964. $paymentOrderCountArr[$created_at_str] += 1;
  965. }
  966. }
  967. }
  968. }
  969. return [
  970. [
  971. Yii::$service->page->translate->__('Order Total') => $orderAmountArr,
  972. Yii::$service->page->translate->__('Payment Order Total') => $paymentOrderAmountArr,
  973. ],
  974. [
  975. Yii::$service->page->translate->__('Order Count') => $orderCountArr,
  976. Yii::$service->page->translate->__('Payment Order Count') => $paymentOrderCountArr,
  977. ],
  978. ];
  979. }
  980. }