ソースを参照

Merge branch 'feature/3092-CrossBorderGoods' into new-version

kevin_zhangl 2 年 前
コミット
54ab16eb74
33 ファイル変更3083 行追加121 行削除
  1. 3 3
      backendApi/modules/v1/controllers/ShopController.php
  2. 7 7
      backendApi/modules/v1/models/lists/shop/GoodsList.php
  3. 70 13
      backendApi/modules/v1/models/lists/shop/OrderList.php
  4. 5 9
      backendEle/src/views/shop/goods-add.vue
  5. 4 5
      backendEle/src/views/shop/index.vue
  6. 24 11
      common/config/main.php
  7. 21 0
      common/config/params.php
  8. 159 0
      common/helpers/IPay88.php
  9. 379 0
      common/helpers/Logistics.php
  10. 54 0
      common/helpers/Tool.php
  11. 10 0
      common/helpers/user/Info.php
  12. 97 0
      common/models/ApproachDecOrder.php
  13. 141 0
      common/models/ApproachOrder.php
  14. 71 0
      common/models/ApproachOrderCall.php
  15. 75 0
      common/models/ApproachOrderGoods.php
  16. 2 0
      common/models/ReceiveAddress.php
  17. 7 1
      common/models/ShopGoods.php
  18. 101 0
      common/models/WstOrderCall.php
  19. 492 0
      common/models/forms/ApproachOrderForm.php
  20. 7 3
      common/models/forms/ReceiveAddressForm.php
  21. 18 7
      common/models/forms/ShopGoodsForm.php
  22. 22 2
      frontendApi/config/params.php
  23. 6 1
      frontendApi/config/urlManagerRules.php
  24. 416 0
      frontendApi/modules/v1/components/IPay88.php
  25. 1 1
      frontendApi/modules/v1/controllers/ConfigController.php
  26. 323 22
      frontendApi/modules/v1/controllers/ShopController.php
  27. 12 0
      frontendEle/src/router/index.js
  28. 10 0
      frontendEle/src/views/config/receive-address-edit.vue
  29. 2 1
      frontendEle/src/views/config/receive-address-list.vue
  30. 47 10
      frontendEle/src/views/shop/index.vue
  31. 32 25
      frontendEle/src/views/shop/order-list.vue
  32. 462 0
      frontendEle/src/views/shop/order-overseas.vue
  33. 3 0
      frontendEle/src/views/shop/order.vue

+ 3 - 3
backendApi/modules/v1/controllers/ShopController.php

@@ -368,9 +368,9 @@ class ShopController extends BaseController {
             'PERIOD_NUM'=> 'O.PERIOD_NUM',
             'CREATED_AT'=> 'O.CREATED_AT',
         ]);
-        $condition = $filter['condition'];
+        $condition = ' 1=1 ' . $filter['condition'];
         $params = $filter['params'];
-        $condition .= ' AND O.IS_DELETE=0';
+        $condition .= $condition ? ' AND O.IS_DELETE=0' : ' O.IS_DELETE=0';
         $listObj = new OrderList();
         $data = $listObj->getList(['condition'=>$condition, 'params'=>$params]);
         return static::notice($data);
@@ -403,7 +403,7 @@ class ShopController extends BaseController {
             'PERIOD_NUM'=> 'O.PERIOD_NUM',
             'CREATED_AT'=> 'O.CREATED_AT',
         ]);
-        $filter['condition'] .= ' AND O.IS_DELETE=0';
+        $filter['condition'] = !$filter['condition'] ? '1=1 AND O.IS_DELETE=0' : ('O.IS_DELETE=0 ' . $filter['condition']);
         $form = new ShopExportForm();
         $result = $form->run($filter, '订单列表');
         if (!$result) {

+ 7 - 7
backendApi/modules/v1/models/lists/shop/GoodsList.php

@@ -47,13 +47,13 @@ class GoodsList extends \common\libs\dataList\DataList implements DataListInterf
                     'header' => '商品编号',
                     'headerOther' => ['width' => '200'],
                 ],
-                // 'TYPE' => [
-                //     'header' => '商品来源',
-                //     'value' => function($row){
-                //         return ShopGoods::GOODS_TYPE[$row['TYPE']]['name'];
-                //     },
-                //     'headerOther' => ['width' => '150'],
-                // ],
+                'CATE_ID' => [
+                    'header' => '商品来源',
+                    'value' => function($row){
+                        return ShopGoods::GOODS_TYPE[$row['CATE_ID']]['name'];
+                    },
+                    'headerOther' => ['width' => '150'],
+                ],
                 'SELL_DISCOUNT' => [
                     'header' => '商品折扣',
                     'value' => function($row) {

+ 70 - 13
backendApi/modules/v1/models/lists/shop/OrderList.php

@@ -5,10 +5,15 @@ use common\helpers\Cache;
 use common\helpers\Tool;
 use common\helpers\user\Info;
 use common\libs\dataList\DataListInterface;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
 use common\models\Order;
 use common\models\OrderGoods;
+use common\models\ShopGoods;
 use common\models\User;
 use common\libs\dataList\column\DateTime;
+use yii\data\Pagination;
+use yii\db\Query;
 
 class OrderList extends \common\libs\dataList\DataList implements DataListInterface
 {
@@ -20,27 +25,74 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
         return '订单列表';
     }
 
+//    /**
+//     * 列表筛选到的数据
+//     */
+//    public function dataHandle()
+//    {
+//        $this->listData = Order::lists($this->condition, $this->params, [
+//            'select' => 'O.*,U.REAL_NAME,U.DEC_ID,OG.*',
+//            'orderBy' => 'O.CREATED_AT DESC, O.ID DESC',
+//            'from' => Order::tableName() . ' AS O',
+//            'join' => [
+//                ['LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN'],
+//                ['LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID'],
+//            ],
+//            'page' => $this->page,
+//            'pageSize' => $this->pageSize,
+//        ]);
+//        foreach ($this->listData['list'] as $key => $value) {
+//            $CREATE_USER_ID = Info::getUserIdByUserName($value['CREATE_USER']);
+//            $this->listData['list'][$key]['DEC_USER_NAME'] = Info::getUserNameByUserId($value['DEC_ID']);
+//            $this->listData['list'][$key]['CREATE_USER_NAME'] = Info::getUserRealNameByUserId($CREATE_USER_ID);
+////            $this->listData['list'][$key]['GOODS_NO'] = '';
+//        }
+//    }
+
     /**
      * 列表筛选到的数据
      */
     public function dataHandle()
     {
-        $this->listData = Order::lists($this->condition, $this->params, [
-            'select' => 'O.*,U.REAL_NAME,U.DEC_ID,OG.*',
-            'orderBy' => 'O.CREATED_AT DESC, O.ID DESC',
-            'from' => Order::tableName() . ' AS O',
-            'join' => [
-                ['LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN'],
-                ['LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID'],
-            ],
-            'page' => $this->page,
-            'pageSize' => $this->pageSize,
-        ]);
+        $orderQuery = Order::find()
+            ->alias('O')
+            ->where($this->condition, $this->params)
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->orderBy('O.CREATED_AT DESC');
+
+        // 订单中间表只查询待支付和支付失败的订单
+        $this->params[':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+        $this->params[':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+        $orderStandardQuery = ApproachOrder::find()
+            ->alias('O')
+            ->where($this->condition . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $this->params)
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->orderBy('O.CREATED_AT DESC');
+
+        $queryAll = $orderQuery->union($orderStandardQuery, true);
+        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+
+        $totalCount = $query->count();
+        $pagination = new Pagination(['totalCount' => $totalCount, 'pageSize' => \Yii::$app->request->get('pageSize')]);
+        $lists = $query->offset($pagination->offset)->limit($pagination->limit)->all();
+
+        $this->listData = [
+            'list' => $lists,
+            'currentPage'=>$pagination->page,
+            'totalPages'=>$pagination->pageCount,
+            'startNum' => $pagination->page * $pagination->pageSize + 1,
+            'totalCount' => $pagination->totalCount,
+            'pageSize' => $pagination->pageSize,
+        ];
+
         foreach ($this->listData['list'] as $key => $value) {
             $CREATE_USER_ID = Info::getUserIdByUserName($value['CREATE_USER']);
             $this->listData['list'][$key]['DEC_USER_NAME'] = Info::getUserNameByUserId($value['DEC_ID']);
             $this->listData['list'][$key]['CREATE_USER_NAME'] = Info::getUserRealNameByUserId($CREATE_USER_ID);
-//            $this->listData['list'][$key]['GOODS_NO'] = '';
         }
     }
 
@@ -53,7 +105,6 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
         if(!$this->columns){
             $this->columns = [
                 'ID' => null,
-                'SN' => null,
                 'DEC_SN' => null,
                 'USER_NAME' => [
                     'header' => '会员编号',
@@ -183,6 +234,8 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
                                 return '充值账户';
                             case 'exchange':
                                 return '兑换点数';
+                            case 'online':
+                                return '在线支付';
                             default:
                                 return '复消积分';
                         }
@@ -221,6 +274,10 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
                     },
                     'headerOther' => ['width' => '190'],
                 ],
+                'SEND_AT' => [
+                    'header' => '推送时间',
+                    'headerOther' => ['width' => '190'],
+                ],
                 'DELIVERY_AT' => [
                     'header' => '发货时间',
                     'value' => function ($row) {

+ 5 - 9
backendEle/src/views/shop/goods-add.vue

@@ -5,12 +5,11 @@
         <el-form-item label="商品名称">
           <el-input v-model="form.goodsName"></el-input>
         </el-form-item>
-      <!-- <el-form-item label="商品来源">
-          <el-select v-model="form.type" placeholder="请选择商品来源">
-              <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name"
-                         :value="index"></el-option>
-          </el-select>
-      </el-form-item> -->
+        <el-form-item label="商品来源">
+            <el-select v-model="form.type" placeholder="请选择商品来源">
+                <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name" :value="index"></el-option>
+            </el-select>
+        </el-form-item>
         <el-form-item label="会员折扣">
             <el-input v-model="form.sellDiscount"></el-input>
         </el-form-item>
@@ -189,10 +188,7 @@ export default {
       })
     },
     getData (page, pageSize) {
-      let filterData = this.filterModel
-      let vueObj = this
       network.getPageData(this, 'shop/goods-add', page, pageSize, this.filterModel, response => {
-        console.log(response)
         this.goodsType = response.goodsType
         this.GiftTypeArr = response.giftType
         this.sellType = response.sellType

+ 4 - 5
backendEle/src/views/shop/index.vue

@@ -70,12 +70,11 @@
                     <el-form-item label="商品名称">
                         <el-input v-model="form.goodsName"></el-input>
                     </el-form-item>
-                    <!-- <el-form-item label="商品来源">
+                    <el-form-item label="商品来源">
                         <el-select v-model="form.type">
-                            <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name"
-                                       :value="index"></el-option>
+                            <el-option v-for="(item,index) in goodsType" :key="index" :label="item.name" :value="index"></el-option>
                         </el-select>
-                    </el-form-item> -->
+                    </el-form-item>
                     <el-form-item label="会员折扣">
                         <el-input v-model="form.sellDiscount"></el-input>
                     </el-form-item>
@@ -353,7 +352,7 @@ export default {
         vueObj.form.goodsName = response.goodsInfo.GOODS_NAME
         vueObj.form.sellDiscount = response.goodsInfo.SELL_DISCOUNT
         vueObj.form.goodsNo = response.goodsInfo.GOODS_NO
-        vueObj.form.type = response.goodsInfo.TYPE
+        vueObj.form.type = response.goodsInfo.CATE_ID
         vueObj.form.unit = response.goodsInfo.UNIT
         vueObj.form.marketPrice = response.goodsInfo.MARKET_PRICE
         vueObj.form.sellPrice = response.goodsInfo.SELL_PRICE

+ 24 - 11
common/config/main.php

@@ -10,17 +10,17 @@ return [
     ],
     'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
     'components' => [
-//        'log' => [
-//            'targets' => [
-//                [
-//                    'class' => 'yii\log\FileTarget',
-//                    'levels' => ['error', 'warning', 'info'],
-//                    'categories' => ['yii\db\*', 'app\models\*'],
-//                    'logFile' => '@runtime/logs/sql.log',
-//                ],
-//                'db' => [ 'class' => 'yii\log\FileTarget'],
-//            ],
-//        ],
+        'log' => [
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning', 'info'],
+                    'categories' => ['yii\db\*', 'app\models\*'],
+                    'logFile' => '@runtime/logs/sql.log',
+                ],
+                'db' => [ 'class' => 'yii\log\FileTarget'],
+            ],
+        ],
         'db' => array_merge([
             'class' => 'yii\db\Connection',
             'enableSlaves' => false,
@@ -60,6 +60,19 @@ return [
         'swooleAsyncTimer' => [
             'class' => 'common\components\SwooleAsyncTimer',
         ],
+        // iPay88配置
+        'iPay88' => [
+            'class' => 'frontendApi\modules\v1\components\IPay88',
+            'merchantCode' => 'M08669', // 'M08669_S0002',  // M08669(prod)
+            'merchantKey' => 'SiYF6a0QYy', // '5Mb154IrY8',   // SiYF6a0QYy(prod)
+            'currencyCode' => 'MYR', //'CNY', // MYR(马来币) CNY(人民币)
+            'responseUrl' => 'https://nc-fele-mips.elken.com:8015/#/shop/order-list', //'https://www.ncshop2023.com/#/shop/order-list', // TODO: https://nc-fele-mips.elken.com:8015/#/shop/order-list(test)
+            'backendUrl' => 'https://nc-fapi-mips.elken.com:8013/v1/shop/verify-approach-order', //'https://fapi.ekhkad.com/v1/shop/verify-approach-order',  // https://nc-fapi-mips.elken.com:8013/v1/shop/verify-approach-order(test)
+            'requeryUrl' => 'https://payment.ipay88.com.my/epayment/enquiry.asp',
+            'paymentUrl' => 'https://payment.ipay88.com.my/epayment/entry.asp',
+            'recurringUrlSubscription' => 'https://www.ipay88.com/recurringpayment/webservice/RecurringPayment.asmx/Subscription',
+            'recurringUrlTermination' => 'https://www.ipay88.com/recurringpayment/webservice/RecurringPayment.asmx/Termination'
+        ],
     ],
     'controllerMap' => [
         'swoole_server' => [

+ 21 - 0
common/config/params.php

@@ -306,6 +306,14 @@ return [
             'value' => 6,
             'label' => '订单删除'
         ],
+        '-1' => [
+            'value' => -1,
+            'label' => 'Payment failed',//支付失败
+        ],
+        'failPaid' => [
+            'value' => -1,
+            'label' => 'Payment failed',//支付失败
+        ],
     ],
     'exportModule' => [
         'shop' => ['label' => '商城管理', 'value'=>'shop'],
@@ -332,4 +340,17 @@ return [
         'atUserIds'   => ['rob9muw'],
         'isAtAll'     => false,
     ],
+    // wst配置
+    'wst' => [
+        //  (prod)
+        'baseToken' => 'ZWxrZW5yZXN0OnNmMjM0ZmE3NHNn',
+        'agentId' => 'TP',
+        'userName' => 'elken',
+        'password' => 'elken0088',
+        // (test)
+        // 'baseToken' => 'dnRuX2FwaV90ZXN0Ond0cmV3cjI0MzI0ZmRz',
+        // 'agentId' => 'elg8',
+        // 'userName' => 'vtn',
+        // 'password' => '123456',
+    ],
 ];

+ 159 - 0
common/helpers/IPay88.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace common\helpers;
+
+class IPay88
+{
+    /**
+     * 根据运行环境加载secretKey
+     */
+    public static function getSecretKey(): string
+    {
+         return YII_ENV == YII_ENV_PROD ? \Yii::$app->iPay88->liveSecretKey : \Yii::$app->iPay88->testSecretKey;
+//       return \Yii::$app->iPay88->testSecretKey;
+    }
+
+    /**
+     * 支付.
+     */
+    public static function transactionInit($currency, $amount, $email)
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $url = "https://api.paystack.co/transaction/initialize";
+            $fields = [
+                'email' => $email,
+                'amount' => $amount * 100
+            ];
+            $fields_string = http_build_query($fields);
+            //open connection
+            $curl = curl_init();
+
+            //set the url, number of POST vars, POST data
+            curl_setopt($curl,CURLOPT_URL, $url);
+            curl_setopt($curl,CURLOPT_POST, true);
+            curl_setopt($curl,CURLOPT_POSTFIELDS, $fields_string);
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                "Authorization: Bearer {$secretKey}",
+                "Cache-Control: no-cache",
+            ));
+            //So that curl_exec returns the contents of the cURL; rather than echoing it
+            curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
+            //execute post
+            $result = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($result, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+    /**
+     * 交易结果校验.
+     * @param $ref
+     * @return array
+     */
+    public static function transactionVerify($ref): array
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $curl = curl_init();
+            curl_setopt_array($curl, [
+                CURLOPT_URL => "https://api.paystack.co/transaction/verify/{$ref}",
+                CURLOPT_RETURNTRANSFER => true,
+                CURLOPT_ENCODING => "",
+                CURLOPT_MAXREDIRS => 10,
+                CURLOPT_TIMEOUT => 30,
+                CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+                CURLOPT_CUSTOMREQUEST => "GET",
+                CURLOPT_HTTPHEADER => array(
+                    "Authorization: Bearer {$secretKey}",
+                    "Cache-Control: no-cache",
+                ),
+            ]);
+
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($response, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+
+    /**
+     * 交易退款.
+     * @param string $reference
+     * @param int $amount
+     * @return array
+     */
+    public static function transactionRefund(string $reference, int $amount)
+    {
+        $secretKey = self::getSecretKey();
+
+        try {
+            $url = "https://api.paystack.co/refund";
+            $fields = [
+                'transaction' => $reference,
+                'amount'      => $amount,
+            ];
+            $fields_string = http_build_query($fields);
+            //open connection
+            $curl = curl_init();
+
+            //set the url, number of POST vars, POST data
+            curl_setopt($curl, CURLOPT_URL, $url);
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $fields_string);
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                "Authorization: Bearer {$secretKey}",
+                "Cache-Control: no-cache",
+            ));
+
+            //So that curl_exec returns the contents of the cURL; rather than echoing it
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+
+            //execute post
+            $response = curl_exec($curl);
+            $err = curl_error($curl);
+            curl_close($curl);
+
+            if ($err) {
+                return [
+                    'status'    => false,
+                    'message'   => $err,
+                ];
+            }
+
+            return json_decode($response, true);
+        } catch (\Exception $e) {
+            return [
+                'status'    => false,
+                'message'   => $e->getMessage(),
+            ];
+        }
+    }
+}

+ 379 - 0
common/helpers/Logistics.php

@@ -0,0 +1,379 @@
+<?php
+
+namespace common\helpers;
+
+use common\models\OrderGoods;
+use common\models\Region;
+use Yii;
+
+class Logistics
+{
+    const prodDomain = 'https//warehouse.taoplus.com.my';
+    const testDomain = 'http://warehouse.worldsyntech.com';
+
+    // 1.获取bearer token,此token是其他api调用时的必传参数.
+    const authenticationUrl = 'http://warehouse.taoplus.com.my/index.php?route=rest/admin_security/api_login&grant_type=client_credentials';
+    // 2.创建订单和产品(以前不存在/已提交的产品).
+    const createOrderUrl = 'http://warehouse.taoplus.com.my/index.php?route=rest/warehouse_api/add_order';
+    // 3.获取产品/包裹的重量和状态.
+    const getProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/get_order_product';
+    // 4.获取订单重量,m3,包装的包裹数量,费用跟踪号码和状态.
+    const getOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/get_order';
+    // 5.通知仓库已经付款,包裹可以投递.
+    const notifyDeliveryUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/ warehouse_api/notify_delivery';
+    // 6.创建产品/包裹.
+    const addProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/add_order_product';
+    // 7.创建交付订单并将现有产品绑定到订单.需要先调用接口6创建商品.
+    const createOrderSimpleUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/add_order_simple';
+    // 8.计算订单费用,并将订单状态改为待付款.
+    const stockOutUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/stock_out';
+    // 9.通知物流系统订单不可删除.error包含错误的订单ID.
+    const notifyOrderUndeletableUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/notify_order_undeletable';
+    // 10.取消订单产品/包裹。它将状态更改为无效,并保留订单产品/包裹.
+    const cancelOrderProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/cancel_order_product';
+    // 11.从数据库中删除订购产品/包裹.
+    const deleteOrderProductUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/delete_order_product';
+    // 12.取消订单,状态改为无效,保留订单不删除.
+    const cancelOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/cancel_order';
+    // 13.从数据库中删除订单.
+    const deleteOrderUrl = 'http://warehouse.worldsyntech.com/index.php?route=rest/warehouse_api/delete_order';
+    // 14.运单追踪.
+    const trackOrderUrl = 'https://taoplus.com.my/index.php?route=information/order_tracking/json&order_tracking_search=';
+
+    // accessToken的缓存key
+    const wstLogisticsBearerTokenKey = 'wstLogisticsBearerToken';
+
+    // 国家
+    const country = 'China';
+
+    function __construct()
+    {
+//         if (!\Yii::$app->redis->get(self::wstLogisticsBearerTokenKey)) {
+            $this->getAuthentication();
+//         }
+    }
+
+    /**
+     * 通用curl接口.
+     * @param string $url api接口url
+     * @param array $request_body 参数
+     * @return mixed
+     */
+    function curl($url, $request_body)
+    {
+        $accessToken = \Yii::$app->redis->get(self::wstLogisticsBearerTokenKey);
+
+        $ch = curl_init($url);
+        header('Content-Type: application/json');
+        $authorization = "Authorization: Bearer " . $accessToken;
+        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', $authorization]);
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        $response = curl_exec($ch);
+        curl_close($ch);
+
+        return json_decode($response, true);
+    }
+
+    // 1.获取bearer token,此token是其他api调用时的必传参数.
+    function getAuthentication()
+    {
+        $request = [
+            'user_name' => Yii::$app->params['wst']['userName'],
+            'password' => Yii::$app->params['wst']['password'],
+            'agent_id' => Yii::$app->params['wst']['agentId'],
+        ];
+
+        $ch = curl_init(self::authenticationUrl);
+        header('Content-Type: application/json');
+        $authorization = "Authorization: Basic " . Yii::$app->params['wst']['baseToken'];
+        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', $authorization]);
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request));
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        $response = curl_exec($ch);
+
+        curl_close($ch);
+
+        $result = json_decode($response, true);
+        if ($result['success']) {
+            // token和过期时间写入redis
+            \Yii::$app->redis->set(self::wstLogisticsBearerTokenKey, $result['data']['access_token']);
+            \Yii::$app->redis->expire(self::wstLogisticsBearerTokenKey, $result['data']['expires_in']);
+        }
+    }
+
+    // 2.创建订单和产品(以前不存在/已提交的产品).
+    function createOrder($order)
+    {
+        $request = [
+            'order_no' => $order['SN'],   // 客户系统中的订单号
+            'delivery_method_id' => '60', // 快递方式ID. TODO: 133(test)
+            'warehouse_id' => '1',   // 仓库ID. TODO:
+            'country' => 'China',    // 收件人国家 Malaysia
+            'state' => Region::getCnName($order['PROVINCE']), // ,   // 收件人省 Johor(test)
+            'city' => Region::getCnName($order['CITY']) . Region::getCnName($order['COUNTY']),   // 收件市县
+            'post_code' => $order['ZIP_CODE'], // 收件人邮政编码
+            'address' => $order['ADDRESS'],    // 收件人送货地址
+            'consignee' => $order['CONSIGNEE_REAL_NAME'],  // 收货人姓名,使用订单中的收货人真实姓名
+            'consignee_ic_number' => $order['CONSIGNEE_ID_NO'], // 收件人身份证号
+            'telephone' => $order['MOBILE'],  // 收件人电话号码
+            'sender' => 'Elken',    // 发件人名字
+            'sender_country' => 'Malaysia',    // 发件人国家
+            'sender_state' => 'Selangor', // ,   // 发件人省
+            'sender_city' => 'Kuala Lumpur',   // 发件人市县
+            'sender_post_code' => '47620', // 发件人邮政编码
+            'sender_address' => '11, 2nd Floor, Jalan TP5, Taman Perindustrian UEP, Subang Jaya',    // 发件人地址
+            'sender_telephone' => '0380210088',  // 发件人电话号码
+            'volumetric_details' => '3*4*20',    // 规格
+        ];
+
+        // 查询商品
+        $orderGoods = OrderGoods::find()->where('ORDER_SN=:ORDER_SN', [':ORDER_SN' => $order['SN']])->select('ORDER_SN,REAL_PRICE,BUY_NUMS,SKU_CODE,GOODS_TITLE')->asArray()->all();
+
+        $products = [];
+        foreach ($orderGoods as $item) {
+            $products[] = [
+                'product_no' => $item['SKU_CODE'], // 客户系统中的产品编号
+                'product_name' => $item['GOODS_TITLE'],   // 产品名称
+                'tracking_number' => Date::today('Ymd') . $this->_random(10, 1),
+                'quantity' => $item['BUY_NUMS'],       // 产品数量
+                'total_price' => number_format(100, 2, '.', ''),    // 订单总金额,Decimal 方便通关,固定100马币
+                'currency_code' => 'MYR',  // 产品的货币代码. 如USD(美元),MYR(马来西亚林吉特),SGD(新加坡元),CNY(人民币)
+                'supplier' => 'MRT',    // 品牌
+                'remark' => '300ML', // 备注:规格
+            ];
+        }
+        $request['products'] = $products;
+
+        return $this->curl(self::createOrderUrl, $request);
+    }
+
+    // 3.获取产品/包裹的重量和状态.
+    function getProduct($order)
+    {
+        $request = [
+            'tracking_number' => '027300027300',
+        ];
+
+        $response = $this->curl(self::getProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 4.获取订单重量,m3,包装的包裹数量,费用跟踪号码和状态
+    function getOrder($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+        ];
+
+        $response = $this->curl(self::getOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 5.通知仓库已经付款,包裹可以投递.
+    function notifyDelivery($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+        ];
+
+        $response = $this->curl(self::notifyDeliveryUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 6.创建产品/包裹.
+    function addProduct($orderProducts)
+    {
+        $products = [];
+        foreach ($orderProducts as $item) {
+            $products[] = [
+                "product_no" => $item['SKU_CODE'],
+                "product_name" => $item['GOODS_TITLE'],
+                "tracking_number" => '',
+                "quantity" => $item['BUY_NUMS'],
+                "total_price" => $item['REAL_PV'],
+                "currency_code" => "CNY",
+            ];
+        }
+
+        $request = [
+            'warehouse_id' => '1',
+            "products" => $products
+        ];
+
+        $response = $this->curl(self::addProductUrl, $request);
+        LoggerTool::info($response);
+        return $response;
+    }
+
+    // 7.创建交付订单并将现有产品绑定到订单.需要先调用接口6创建商品.
+    function createOrderSimple($order)
+    {
+        $request = [
+            "order_no" => "T-1000",
+            "delivery_method_id" => "39",
+            "warehouse_id" => "1",
+            "country" => "Malaysia",
+            "state" => "Pulau Pinang",
+            "city" => "Bukit Mertajam",
+            "post_code" => "14000",
+            "address" => "1584,JALAN NANGKA,TAMAN JAMBU MAWAR,14000 BUKIT MERTAJAM",
+            "consignee" => "TSA",
+            "consignee_ic_number" => "111111-11-1111",
+            "telephone" => "017-5423223",
+            "cod" => "CNY",
+            "cod_currency_code" => "100",
+            "address_area" => "",
+            "address_subdivision" => "",
+            "products" => [
+                [
+                    "product_no" => "12345",
+                    "tracking_number" => "2019061112A1",
+                ],
+                [
+                    "product_no" => "23456",
+                    "tracking_number" => "20190604_A12",
+                ]
+            ]
+        ];
+
+        $response = $this->curl(self::createOrderSimpleUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 8.根据货物重量计算订单费用,并更改订单状态为待付款
+    function stockOut($order)
+    {
+        $request = [
+            'order_id' => 'elg8-785',
+            'weight' => '2.5', // 订单重量(KG)
+        ];
+
+        $response = $this->curl(self::stockOutUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 9.通知物流系统订单不可删除. error包含错误的订单ID.
+    function notifyOrderUndeletable($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-785']
+        ];
+
+        $response = $this->curl(self::notifyOrderUndeletableUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 10.取消订单产品/包裹。它将状态更改为无效,并保留订单产品/包裹.
+    function cancelOrderProduct($order)
+    {
+        $request = [
+            'order_product_ids' => ['15895', '15896', '15897']
+        ];
+
+        $response = $this->curl(self::cancelOrderProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 11.从数据库中删除订购产品/包裹.
+    function deleteOrderProduct($order)
+    {
+        $request = [
+            'order_product_ids' => ['15895', '15896', '15897']
+        ];
+
+        $response = $this->curl(self::deleteOrderProductUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 12.取消订单,状态改为无效,保留订单不删除.
+    function cancelOrder($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-787', 'elg8-786']
+        ];
+
+        $response = $this->curl(self::cancelOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 13.从数据库中删除订单.
+    function deleteOrder($order)
+    {
+        $request = [
+            'order_ids' => ['elg8-787', 'elg8-786']
+        ];
+
+        $response = $this->curl(self::deleteOrderUrl, $request);
+        LoggerTool::info($response);
+
+        return $response;
+    }
+
+    // 13.从数据库中删除订单.
+    function trackOrder($trackNo)
+    {
+        // 运单号
+        $trackNo = 'TBS10182495KJ2430';
+
+        $curl = curl_init();
+        curl_setopt($curl,CURLOPT_TIMEOUT,5000);
+        curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
+        curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,false);
+        curl_setopt($curl,CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
+        curl_setopt($curl,CURLOPT_URL, self::trackOrderUrl . $trackNo);
+        curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
+        $response = curl_exec($curl);
+        if (!$response) {
+            $error = curl_errno($curl);
+            curl_close($curl);
+            LoggerTool::error($error);
+            return false;
+        }
+        curl_close($curl);
+
+        $result = json_decode($response, true);
+        LoggerTool::info($result);
+
+        return $result['data'];
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+}

+ 54 - 0
common/helpers/Tool.php

@@ -9,6 +9,8 @@
 namespace common\helpers;
 
 
+use common\models\ApproachOrderCall;
+use common\models\WstOrderCall;
 use Faker\Provider\Uuid;
 use yii\base\Exception;
 use yii\helpers\Url;
@@ -471,4 +473,56 @@ class Tool {
         $uuid = !$upper ? Uuid::uuid() : strtoupper(Uuid::uuid());
         return str_replace('-', $symbol, $uuid);
     }
+
+    /**
+     * iPay88订单写入MongoDB.
+     * @param $call
+     * @return void
+     * @throws \Exception
+     */
+    public static function approachOrderCall($call)
+    {
+        try {
+            $model = new ApproachOrderCall();
+            $model->sn = $call['RefNo'];
+            $model->TransId = $call['TransId'];
+            $model->Signature = $call['Signature'];
+            $model->data = $call;
+            $model->insert();
+        } catch (\yii\mongodb\Exception $e) {
+            LoggerTool::info($call);
+            LoggerTool::error(sprintf('[%s] [%s] [%s]', $e->getFile(), $e->getLine(), $e->getMessage()));
+        }
+    }
+
+    /**
+     * 订单推送wst系统回执写入mongo.
+     * @param $call
+     * @return void
+     * @throws \Exception
+     */
+    public static function wstOrderCall($call)
+    {
+        try {
+            $model = new WstOrderCall();
+            $model->order_id = $call['order_id'];
+            $model->order_no = $call['order_no'];
+            $model->warehouse_id = $call['warehouse_id'];
+            $model->delivery_method_name = $call['warehouse_id'];
+            $model->addon_service_name = $call['addon_service_name'];
+            $model->country = $call['country'];
+            $model->state = $call['state'];
+            $model->city = $call['city'];
+            $model->post_code = $call['post_code'];
+            $model->address = $call['address'];
+            $model->consignee = $call['consignee'];
+            $model->telephone = $call['telephone'];
+            $model->comment = $call['comment'];
+            $model->products = $call['products'];
+            $model->insert();
+        } catch (\yii\mongodb\Exception $e) {
+            LoggerTool::info($call);
+            LoggerTool::error(sprintf('[%s] [%s] [%s]', $e->getFile(), $e->getLine(), $e->getMessage()));
+        }
+    }
 }

+ 10 - 0
common/helpers/user/Info.php

@@ -73,6 +73,16 @@ class Info {
         return $user ? $user['MOBILE'] : null;
     }
 
+    /**
+     * 获取会员邮箱通过ID
+     * @param $userId
+     * @return null
+     */
+    public static function getUserEmailByUserId($userId) {
+        $user = User::findOneAsArray('ID=:ID AND DELETED=0', [':ID' => $userId], 'EMAIL');
+        return $user ? $user['EMAIL'] : null;
+    }
+
     /**
      * 通过用户ID获取用户的编号和名称
      * @param $userId

+ 97 - 0
common/models/ApproachDecOrder.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%APPROACH_DEC_ORDER}}".
+ *
+ * @property string $ID
+ * @property string $DEC_SN 报单编号
+ * @property string $ORDER_SN 订单编号
+ * @property string $USER_ID 会员ID
+ * @property string $TO_USER_ID 报单对象ID
+ * @property string $TYPE 报单类型
+ * @property int $IS_ADMIN 是否管理员操作
+ * @property string $DEC_AMOUNT 报单金额
+ * @property string $DEC_PV 报单PV
+ * @property string $PAID_WALLET 支付钱包
+ * @property int $IS_BATCH 是否批量报单
+ * @property string $REMARK 备注
+ * @property string $REC_USER_ID 推荐人ID
+ * @property string $CON_USER_ID 接点人ID
+ * @property string $DEC_ID 报单中心ID
+ * @property int $PERIOD_NUM 报单期数
+ * @property string $P_CALC_MONTH 分区结算月
+ * @property int $CALC_MONTH 结算月
+ * @property int $CREATED_AT 创建时间
+ * @property string $UPDATER 操作人
+ * @property string $UPDATED_AT 更新时间
+ * @property int $IS_DEL 是否删除
+ * @property int $DELETED_AT 删除时间
+ * @property int $DETAIL_TYPE 类型
+ * @property int $UPGRADE_TYPE 升级类型
+ * @property string $ORI_LV 升级前级别
+ * @property string $UPGRADE_LV 升级后级别
+ */
+class ApproachDecOrder extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_DEC_ORDER}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['USER_ID', 'TO_USER_ID','REC_USER_ID',/*'CON_USER_ID','DEC_ID', */'TYPE', 'PAID_WALLET', 'PERIOD_NUM', 'P_CALC_MONTH', 'CREATED_AT'], 'required'],
+            [['IS_ADMIN', 'IS_BATCH', 'PERIOD_NUM', 'CALC_MONTH', 'CREATED_AT', 'IS_DEL', 'DELETED_AT'], 'integer'],
+            [['DEC_AMOUNT', 'DEC_PV'], 'number'],
+            [['ID', 'DEC_SN', 'ORDER_SN', 'USER_ID', 'TO_USER_ID','REC_USER_ID','CON_USER_ID','DEC_ID', 'TYPE', 'UPDATER', 'UPDATED_AT'], 'string', 'max' => 32],
+            [['PAID_WALLET'], 'string', 'max' => 48],
+            [['REMARK'], 'string', 'max' => 4000],
+            [['DEC_SN'], 'unique'],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'DEC_SN' => '报单编号',
+            'ORDER_SN' => '订单编号',
+            'USER_ID' => '会员ID',
+            'TO_USER_ID' => '报单对象ID',
+            'TYPE' => '报单类型',
+            'IS_ADMIN' => '是否管理员操作',
+            'DEC_AMOUNT' => '报单金额',
+            'DEC_PV' => '报单PV',
+            'PAID_WALLET' => '支付钱包',
+            'STATUS' => '支付状态',
+            'IS_BATCH' => '是否批量报单',
+            'REMARK' => '备注',
+            'REC_USER_ID' => '开拓人编号',
+            'CON_USER_ID' => '上级编号',
+            'DEC_ID' => '上级编号',
+            'PERIOD_NUM' => '报单期数',
+            'P_CALC_MONTH' => '分区结算月',
+            'CALC_MONTH' => '结算月',
+            'CREATED_AT' => '创建时间',
+            'UPDATER' => '操作人',
+            'UPDATED_AT' => '更新时间',
+            'IS_DEL' => '是否删除',
+            'DELETED_AT' => '删除时间',
+        ];
+    }
+}

+ 141 - 0
common/models/ApproachOrder.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%ORDER}}".
+ *
+ * @property string $ID
+ * @property string $SN 订单号
+ * @property string $DEC_SN 报单编号
+ * @property string $USER_ID 用户ID
+ * @property string $USER_NAME 会员编号
+ * @property string $ORDER_TYPE 订货类型
+ * @property string $ORDER_AMOUNT 订单总价格
+ * @property string $PV 订货PV
+ * @property string $PAY_AMOUNT 支付价格
+ * @property string $PAY_PV 实付PV
+ * @property int $PAY_AT 支付时间
+ * @property string $REMAIN_PV
+ * @property string $PAY_TYPE 支付方式
+ * @property string $FREIGHT 运费
+ * @property string $PAY_FREIGHT 实付运费金额
+ * @property int $DELIVERY_STATUS 发货状态
+ * @property int $DELIVERY_PERIOD 发货期数
+ * @property int $DELIVERY_AT 发货时间
+ * @property string $EXPRESS_COMPANY 快递公司
+ * @property string $ORDER_TRACK_NO 快递单号
+ * @property int $EXPRESS_TYPE 发货方式
+ * @property string $FRONT_REMARK 前台备注
+ * @property string $REMARK 后台备注
+ * @property int $PERIOD_NUM 期数
+ * @property int $STATUS 订单状态
+ * @property string $CONSIGNEE 收货人
+ * @property string $MOBILE 收货人手机
+ * @property string $TEL 固定电话
+ * @property int $PROVINCE 省份名称
+ * @property int $CITY 城市名称
+ * @property int $COUNTY 县区
+ * @property string $ADDRESS 详细地址
+ * @property string $P_CALC_MONTH 分区日期
+ * @property int $CREATED_AT 订单创建时间
+ * @property string $CREATE_USER 订单创建人
+ * @property int $UPDATED_AT 修改时间
+ * @property string $UPDATER 修改人
+ * @property int $IS_DELETE 是否删除
+ * @property int $DELETED_AT 删除时间
+ * @property int $WAREHOUSE 发货仓
+ * @property string $EMAIL 邮箱
+ * @property string $NOTE 备注说明
+ * @property string $CONSIGNEE_REAL_NAME 收件人真实姓名
+ * @property string $CONSIGNEE_ID_NO 收件人身份证号码
+ * @property string $ZIP_CODE 收件人邮编
+ */
+class ApproachOrder extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_ORDER}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['USER_ID', 'USER_NAME', 'ORDER_TYPE', 'CREATE_USER'], 'required'],
+            [['ORDER_AMOUNT', 'PV', 'PAY_AMOUNT', 'PAY_PV', 'FREIGHT', 'PAY_FREIGHT'], 'number'],
+            [['PAY_AT', 'DELIVERY_STATUS', 'DELIVERY_PERIOD', 'DELIVERY_AT', 'EXPRESS_TYPE', 'PERIOD_NUM', 'STATUS', 'PROVINCE', 'CITY', 'COUNTY', 'CREATED_AT', 'UPDATED_AT', 'IS_DELETE', 'DELETED_AT'], 'integer'],
+            [['ID','SN', 'DEC_SN', 'USER_ID', 'ORDER_TRACK_NO','PAY_TYPE'], 'string', 'max' => 32],
+            [['USER_NAME', 'TEL', 'CREATE_USER', 'UPDATER', 'WAREHOUSE'], 'string', 'max' => 16],
+            [['ORDER_TYPE'], 'string', 'max' => 12],
+            [['EXPRESS_COMPANY'], 'string', 'max' => 128],
+            [['FRONT_REMARK'], 'string', 'max' => 1000],
+            [['REMARK', 'NOTE'], 'string', 'max' => 4000],
+            [['CONSIGNEE'], 'string', 'max' => 120],
+            [['MOBILE'], 'string', 'max' => 11],
+            [['ADDRESS'], 'string', 'max' => 255],
+            [['SN'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'SN' => '订单号',
+            'DEC_SN' => '报单编号',
+            'USER_ID' => '用户ID',
+            'USER_NAME' => '会员编号',
+            'ORDER_TYPE' => '订货类型',
+            'ORDER_AMOUNT' => '订单总价格',
+            'PV' => '订货PV',
+            'PAY_AMOUNT' => '支付价格',
+            'PAY_PV' => '实付PV',
+            'PAY_AT' => '支付时间',
+            'PAY_TYPE' => '支付方式',
+            'FREIGHT' => '运费',
+            'PAY_FREIGHT' => '实付运费金额',
+            'DELIVERY_STATUS' => '发货状态',
+            'DELIVERY_PERIOD' => '发货期数',
+            'DELIVERY_AT' => '发货时间',
+            'EXPRESS_COMPANY' => '快递公司',
+            'ORDER_TRACK_NO' => '快递单号',
+            'EXPRESS_TYPE' => '发货方式',
+            'FRONT_REMARK' => '前台备注',
+            'REMARK' => '后台备注',
+            'PERIOD_NUM' => '期数',
+            'STATUS' => '订单状态',
+            'CONSIGNEE' => '收货人',
+            'MOBILE' => '收货人手机',
+            'TEL' => '固定电话',
+            'PROVINCE' => '省份名称',
+            'CITY' => '城市名称',
+            'COUNTY' => '县区',
+            'ADDRESS' => '详细地址',
+            'P_CALC_MONTH' => '分区日期',
+            'CREATED_AT' => '订单创建时间',
+            'CREATE_USER' => '订单创建人',
+            'UPDATED_AT' => '修改时间',
+            'UPDATER' => '修改人',
+            'IS_DELETE' => '是否删除',
+            'DELETED_AT' => '删除时间',
+            'WAREHOUSE' => '发货仓',
+            'EMAIL' => 'Email'
+        ];
+    }
+
+    public function getUserByUserId()
+    {
+        return $this->hasOne(User::class, ['ID' => 'USER_ID']);
+    }
+}

+ 71 - 0
common/models/ApproachOrderCall.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace common\models;
+
+use common\components\MongoActiveRecord;
+use Yii;
+
+/**
+ * This is the model class for collection "ar_approach_order_call".
+ *
+ * @property \MongoDB\BSON\ObjectID|string $_id
+ * @property string $sn
+ * @property string $Signature
+ * @property string $TransId
+ * @property string $data
+ */
+class ApproachOrderCall extends MongoActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function collectionName()
+    {
+        return 'ar_approach_order_call';
+    }
+
+    /**
+     * @return \yii\mongodb\Connection the MongoDB connection used by this AR class.
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('dbLog');
+    }
+
+    /**
+     * 获取id
+     * @return string
+     */
+    public function getId() {
+        return (string) $this->_id;
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function attributes()
+    {
+        return [
+            '_id',
+            'sn',
+            'Signature',
+            'TransId',
+            'data',
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            '_id' => 'objectID',
+            'sn' => 'orderSn',
+            'Signature' => 'Signature',
+            'TransId' => 'TransId',
+            'data' => 'data',
+        ];
+    }
+
+}

+ 75 - 0
common/models/ApproachOrderGoods.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%ORDER_GOODS}}".
+ *
+ * @property string $ID
+ * @property string $ORDER_SN 订单ID
+ * @property string $GOODS_ID 商品ID
+ * @property string $GOODS_TITLE 商品名称
+ * @property string $PRICE 价格
+ * @property string $REAL_PRICE 实际价格
+ * @property string $PV 订货PV
+ * @property string $REAL_PV 实际PV
+ * @property string $TAX_RATE 税率
+ * @property string $POINT 兑换积分
+ * @property string $SKU_CODE 商品编码
+ * @property int $BUY_NUMS 购买数量
+ * @property string $P_CALC_MONTH 分区日期
+ * @property int CATEGORY_TYPE 商品分类
+ * @property int PAY_TYPE 支付方式
+ */
+class ApproachOrderGoods extends \common\components\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return '{{%APPROACH_ORDER_GOODS}}';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['ORDER_SN', 'GOODS_ID', 'GOODS_TITLE', 'SKU_CODE', 'CATEGORY_TYPE', 'PAY_TYPE'], 'required'],
+            [['PRICE', 'REAL_PRICE', 'PV', 'REAL_PV', 'POINT', 'CATEGORY_TYPE', 'PAY_TYPE', 'TAX_RATE'], 'number'],
+            [['BUY_NUMS'], 'integer'],
+            [['ID', 'ORDER_SN', 'GOODS_ID'], 'string', 'max' => 32],
+            [['GOODS_TITLE'], 'string', 'max' => 255],
+            [['SKU_CODE'], 'string', 'max' => 16],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'ORDER_SN' => '订单ID',
+            'GOODS_ID' => '商品ID',
+            'GOODS_TITLE' => '商品名称',
+            'PRICE' => '价格',
+            'REAL_PRICE' => '实际价格',
+            'PV' => '订货BV',
+            'REAL_PV' => '实际BV',
+            'TAX_RATE' => '税率',
+            'POINT' => '兑换积分',
+            'SKU_CODE' => '商品编码',
+            'BUY_NUMS' => '购买数量',
+            'P_CALC_MONTH' => '分区日期',
+            'CATEGORY_TYPE' => '商品分类',
+            'PAY_TYPE' => '支付方式',
+        ];
+    }
+}

+ 2 - 0
common/models/ReceiveAddress.php

@@ -16,6 +16,7 @@ use Yii;
  * @property int $CITY 城市名称
  * @property int $COUNTY 县区名称
  * @property string $ADDRESS 详细地址
+ * @property string $ZIP_CODE 邮政编码
  * @property int $IS_DEFAULT 是否默认
  * @property int $CREATED_AT 创建时间
  * @property string $UPDATER 修改人
@@ -64,6 +65,7 @@ class ReceiveAddress extends \common\components\ActiveRecord
             'CITY' => '城市名称',
             'COUNTY' => '县区名称',
             'ADDRESS' => '详细地址',
+            'ZIP_CODE' => '邮编',
             'IS_DEFAULT' => '是否默认',
             'CREATED_AT' => '创建时间',
             'UPDATER' => '修改人',

+ 7 - 1
common/models/ShopGoods.php

@@ -39,6 +39,9 @@ class ShopGoods extends \common\components\ActiveRecord
         ],
         3 => [
             'name' => '积分兑换'
+        ],
+        4 => [
+            'name' => '在线支付'
         ]
     ];
     const GOODS_TYPE = [
@@ -168,7 +171,10 @@ class ShopGoods extends \common\components\ActiveRecord
             ],
             'exchange' => [
                 'name' => '无业绩兑换点数'
-            ]
+            ],
+            'online' => [
+                'name' => '在线支付'
+            ],
         ];
     }
 

+ 101 - 0
common/models/WstOrderCall.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace common\models;
+
+use common\components\MongoActiveRecord;
+use Yii;
+
+/**
+ * This is the model class for collection "ar_wst_order_call".
+ *
+ * @property \MongoDB\BSON\ObjectID|string $_id
+ * @property string $order_id
+ * @property string $order_no
+ * @property string $warehouse_id
+ * @property string $delivery_method_name
+ * @property string $addon_service_name
+ * @property string $country
+ * @property string $state
+ * @property string $city
+ * @property string $post_code
+ * @property string $address
+ * @property string $consignee
+ * @property string $telephone
+ * @property string $comment
+ * @property string $products
+ */
+class WstOrderCall extends MongoActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function collectionName()
+    {
+        return 'ar_wst_order_call';
+    }
+
+    /**
+     * @return \yii\mongodb\Connection the MongoDB connection used by this AR class.
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('dbLog');
+    }
+
+    /**
+     * 获取id
+     * @return string
+     */
+    public function getId() {
+        return (string) $this->_id;
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function attributes()
+    {
+        return [
+            '_id',
+            'order_id',
+            'order_no',
+            'warehouse_id',
+            'delivery_method_name',
+            'addon_service_name',
+            'country',
+            'state',
+            'city',
+            'post_code',
+            'address',
+            'consignee',
+            'telephone',
+            'comment',
+            'products',
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            '_id' => 'objectID',
+            'order_id',
+            'order_no',
+            'warehouse_id',
+            'delivery_method_name',
+            'addon_service_name',
+            'country',
+            'state',
+            'city',
+            'post_code',
+            'address',
+            'consignee',
+            'telephone',
+            'comment',
+            'products',
+        ];
+    }
+
+}

+ 492 - 0
common/models/forms/ApproachOrderForm.php

@@ -0,0 +1,492 @@
+<?php
+namespace common\models\forms;
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\components\Model;
+use common\helpers\Form;
+use common\helpers\IPay88;
+use common\helpers\LoggerTool;
+use common\helpers\PayStack;
+use common\helpers\user\Balance;
+use common\helpers\user\Cash;
+use common\helpers\user\Info;
+use common\libs\logging\operate\AdminOperate;
+use common\models\ApproachDecOrder;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
+use common\models\BaUser;
+use common\models\DealType;
+use common\models\DecLevelLog;
+use common\models\DecOrder;
+use common\models\Order;
+use common\models\OrderGoods;
+use common\models\Period;
+use common\models\ReceiveAddress;
+use common\models\Region;
+use common\models\ShopGoods;
+use common\models\User;
+use common\models\UserNetwork;
+use common\models\Instalment;
+use Yii;
+use yii\base\Exception;
+
+/**
+ * Login form
+ */
+class ApproachOrderForm extends Model
+{
+    public $sn;
+    public $expressCompany;
+    public $orderTrackNo;
+    public $status;
+    public $remark;
+    public $note;
+
+    public $type;
+    public $addressId;
+    public $payType;
+    public $goodsId;
+    public $goodsNum;
+    public $payPassword;
+
+    public $userName;
+    public $consignee;
+    public $acceptMobile;
+    public $province;
+    public $city;
+    public $county;
+    public $lgaName;
+    public $detailaddress;
+
+    public $consigneeIdNo;
+    public $consigneeRealName;
+
+    private $_address;
+    private $_decAmount;
+    private $_decPv;
+    private $_freight;
+    private $_payAmount;
+    private $_orderGoods;
+    private $_remainPv;
+    private $_realPv;
+
+    /**
+     * @var ApproachOrder
+     */
+    private $_model;
+
+    public function init() {
+        parent::init();
+        $this->adminOperateLogger = new AdminOperate([
+            'fetchClass' => ApproachOrder::class,
+        ]);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function rules()
+    {
+        return [
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','cityName','detailaddress', 'consigneeIdNo', 'consigneeRealName'], 'trim'],
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress', 'consigneeIdNo', 'consigneeRealName'], 'required'],
+            [['status'], 'isStatus'],
+            [['addressId'], 'isAddress'],
+            [['payType'], 'isPayType'],
+        ];
+    }
+
+    public function attributeLabels()
+    {
+        return [
+            'sn' => '订单号',
+            'expressCompany' => '快递公司',
+            'orderTrackNo' => '快递单号',
+            'status' => '状态',
+            'remark' => '备注',
+            'type' => '订单类型',
+            'addressId' => '收货地址',
+            'payType' => '支付方式',
+            'goodsId' => '商品ID',
+            'goodsNum' => '商品数量',
+            'userName' => '复消会员编号',
+            'consignee' => '收货人',
+            'acceptMobile' => '收货电话',
+            'province' => '省',
+            'city' => '市',
+            'county' => '区',
+            'detailaddress' => '收货详细地址',
+        ];
+    }
+
+    /**
+     * 指定校验场景
+     * @return array
+     */
+    public function scenarios()
+    {
+        $parentScenarios =  parent::scenarios();
+        $customScenarios = [
+            // 管理员修改订单状态
+            'adminStatus' => ['sn', 'status'],
+            // 校验订单支付
+            'verifyPay' => ['sn', 'status', 'note'],
+            // 会员下单
+            'userOrder' => ['type','addressId', 'payType','goodsId','goodsNum', 'note', 'consigneeIdNo', 'consigneeRealName'],
+        ];
+        return array_merge($parentScenarios, $customScenarios);
+    }
+
+    /**
+     * 校验之前
+     * @return bool
+     */
+    public function beforeValidate()
+    {
+        $parentValidate = parent::beforeValidate();
+        if ($this->sn) {
+            $this->_model = ApproachOrder::findOne(['SN' => $this->sn]);
+            if (!$this->_model){
+                $this->addError('sn', '订单不存在');
+                return false;
+            }
+        }
+
+        if ($this->scenario == 'verifyIPay88'){
+            if ($this->_model->STATUS != \Yii::$app->params['orderStatus']['notPaid']['value']) {
+                $this->addError('sn', '支付方式错误');
+                return false;
+            }
+        }
+
+        return $parentValidate;
+    }
+
+    /**
+     * 判断收货地址是否存在
+     * @param $attribute
+     */
+    public function isAddress($attribute){
+        if (!$receiveAddress = ReceiveAddress::find()->where(' ID=:ID', [':ID' => $this->addressId])->asArray()->one()) {
+            $this->addError($attribute, '收货地址不存在');
+        } else {
+            $this->_address = $receiveAddress;
+        }
+    }
+
+    /**
+     * 判断支付方式
+     * @param $attribute
+     * @throws Exception
+     */
+    public function isPayType($attribute)
+    {
+        if ($this->payType != 'online'){
+            $this->addError('支付方式错误');
+        }
+
+        // 一个订单只能包含一类商品
+        $goods = ShopGoods::find()->select('ID,CATE_ID')->where(['in', 'ID', $this->goodsId])->andWhere(['STATUS' => 1])->asArray()->all();
+        if (!$goods) {
+            throw new Exception('商品已下架');
+        }
+        $goodsCategoryType = array_unique(array_column($goods, 'CATE_ID'));
+        if (count($goodsCategoryType) > 1) {
+            $this->addError($attribute, '订单不能包含多种商品分类');
+        }
+    }
+
+    /**
+     * 校验类型
+     * @param $attribute
+     */
+    public function isStatus($attribute){
+        if($this->type && !in_array($this->type, \Yii::$app->params['orderStatus'])){
+            $this->addError($attribute, '订单状态类型错误');
+            return ;
+        }
+    }
+
+    /**
+     * 校验iPay88支付,更新订单状态.同步到正式订单.
+     * @throws Exception
+     */
+    public function verifyPayOnline(): ?ApproachOrder
+    {
+        if (!$this->validate()) {
+            return null;
+        }
+
+        LoggerTool::info([$this->sn, $this->note]);
+
+        // 调用iPay88支付校验
+//        LoggerTool::info([$this->note['reference'], $this->note]);
+//        $payload = IPay88::transactionVerify($this->note['reference']);
+//        LoggerTool::info($payload);
+//        if ($payload['status'] !== true) {
+//            throw new Exception(Form::formatErrorsForApi($payload['message']));
+//        }
+//        if ($payload['data']['amount'] != $this->_model->PAY_AMOUNT * 100) {
+//            throw new Exception(Form::formatErrorsForApi('支付金额与订单金额不符'));
+//        }
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            // 更新准订单状态为已支付
+            $this->_model->STATUS = $this->status;
+            $this->_model->NOTE = json_encode($this->note);
+            $this->_model->PAY_AT = time();
+//            $this->_model->PAY_AT = Date::utcToTime($this->note['TranDate']);
+            if (!$this->_model->save()) {
+                throw new Exception(Form::formatErrorsForApi($this->_model->getErrors()));
+            }
+
+            // 同步准订单到正式订单
+            Order::insertOne($this->_model->toArray());
+            // 同步准订单商品到正式订单商品
+            $approachOrderGoods = ApproachOrderGoods::findAllAsArray('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $this->sn]);
+            OrderGoods::batchInsert($approachOrderGoods);
+
+            // 删除中间表
+            ApproachOrder::deleteAll('SN = :SN', [':SN' => $this->sn]);
+            ApproachOrderGoods::deleteAll('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $this->sn]);
+
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('edit', $e->getFile() . '  ' . $e->getMessage());
+            return null;
+        }
+
+        return $this->_model;
+    }
+
+    /**
+     * BV分期
+     *
+     *
+     */
+    private function _pvSplit($oPv){
+        $sysConfig = Cache::getSystemConfig();
+        $mesureUpCondition = $sysConfig['monthPcsPvFxCondition']['VALUE'];
+        if ($oPv > $mesureUpCondition) {
+            $currentPv = $oPv % $mesureUpCondition + $mesureUpCondition;
+            $remainPv = $oPv - $currentPv;
+        } else {
+            $currentPv = $oPv;
+            $remainPv = 0;
+        }
+        return [
+            'current' => $currentPv,
+            'remain' => $remainPv
+        ];
+    }
+
+
+    /**
+     * 复销
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    public function add() {
+        if(!$this->validate()){
+            return null;
+        }
+
+        $ids = $this->goodsId;
+        $totalAmount = 0;
+        $totalPv = 0;
+        $totalRealPv = 0;
+        $this->_remainPv = 0;
+        foreach ($this->goodsNum as $k => $v) {
+            if ($v) {
+                $goods = ShopGoods::findOneAsArray('ID=:ID AND STATUS=1',[':ID'=> $ids[$k]]);
+                if (!$goods) {
+                    throw new Exception('商品已下架');
+                }
+                if ($goods['STORE_NUMS'] > 0) {
+                    $discount = $goods['SELL_DISCOUNT'];
+                    $realPrice = $goods['SELL_PRICE'] * $discount;
+                    $realPv = $goods['PRICE_PV'] * $discount;
+                    if ($goods['PV_SPLIT'] == 1) { // 当商品为PV分期时
+                        $pvSplit = $this->_pvSplit($realPv);
+                        $currentPv = $pvSplit['current'];
+                        $remainPv = $pvSplit['remain'];
+                        $totalPv += $currentPv * intval($v);
+                        $totalRealPv += $realPv * intval($v);
+                        $this->_remainPv += $remainPv * intval($v);
+                    } else {
+                        $currentPv = $goods['PRICE_PV'];
+                        $totalPv += $realPv * intval($v);
+                        $totalRealPv += $realPv * intval($v);
+                        $remainPv = 0;
+                        $this->_remainPv += 0;
+                    }
+                    $totalAmount += $realPrice * intval($v);
+
+                    $this->_orderGoods[] = [
+                        'GOODS_ID' => $goods['ID'],
+                        'PRICE' => $goods['SELL_PRICE'],
+                        'PV' => $currentPv,
+                        'REAL_PRICE' => $realPrice,
+                        'REAL_PV' => $realPv,
+                        'REMAIN_PV' => $remainPv,
+                        'POINT' => $goods['POINT'],
+                        'BUY_NUMS' => intval($v),
+                        'SKU_CODE' => $goods['GOODS_NO'],
+                        'GOODS_TITLE' => $goods['GOODS_NAME']
+                    ];
+                }
+            }
+        }
+
+        $this->_decAmount = $totalAmount;
+        $this->_decPv = $totalPv;
+        $this->_realPv = $totalRealPv;
+        $this->_freight = ($totalAmount>=300) ? 0 : 15;
+        $this->_payAmount = $this->_decAmount + $this->_freight;
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+
+        // 支付减库存
+        foreach ($this->goodsNum as $k => $v) {
+            if ($v) {
+                $goods = ShopGoods::findOneAsArray('ID=:ID AND STATUS=1', [':ID'=> $ids[$k]]);
+                if (!$goods) {
+                    throw new Exception('商品已下架');
+                }
+                if ($goods['STORE_NUMS'] >= $this->goodsNum[$k]) {
+                    $data = ShopGoods::find()->where(['ID' => $ids[$k]])->one();
+                    $goods_store_nums = $data->STORE_NUMS - $this->goodsNum[$k];
+                    $data->STORE_NUMS = $goods_store_nums;
+                    $data->update();
+                    //下单后库存小于等于0 商品下架
+                    if ($goods_store_nums <= 0) {
+                        $data->STATUS = 0;
+                        $data->UPDATED_AT = Date::nowTime();
+                        $data->update();
+                    }
+                } else {
+                    throw new Exception($goods['GOODS_NAME'].'库存不足,无法购买商品');
+                }
+            }
+        }
+
+        try {
+            // 写入订单
+            if (!$orderResult = $this->addOrder()) {
+                throw new Exception(Form::formatErrorsForApi($orderResult->getErrors()));
+            }
+
+            // TODO: 获取iPay88所需参数
+//            $orderSn = $orderResult->SN;
+
+            $transaction->commit();
+
+            return $orderResult;
+        }catch (\Exception $e){
+            $transaction->rollBack();
+            $this->addError('add', $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 复销订单
+     * @throws Exception
+     */
+    public function addOrder()
+    {
+        $periodObj = Period::instance();
+        $nowPeriodNum = $periodObj->getNowPeriodNum();
+        $nowCalcMonth = $periodObj->getYearMonth($nowPeriodNum);
+
+        $userId = \Yii::$app->user->id;
+
+        $userName = Info::getUserNameByUserId($userId);
+        $userRealName = Info::getUserRealNameByUserId($userId);
+        $userMobile = Info::getUserMobileByUserId($userId);
+        $userEmail = Info::getUserEmailByUserId($userId);
+
+        // 加入订单信息
+        $warehouse = Region::getWarehouseByCode($this->_address['PROVINCE']);//仓库
+        if(!$warehouse){
+            throw new Exception('地区暂时不支持配送,具体联系客服');
+        }
+
+        $ordNo = $this->_generateSn();
+        $orderModel = new ApproachOrder();
+        $orderModel->SN = 'OS' . $ordNo;
+        $orderModel->DEC_SN = 'DS' . $ordNo;
+        $orderModel->ORDER_TYPE = $this->type;
+        $orderModel->USER_ID = $userId;
+        $orderModel->USER_NAME = $userName;
+        $orderModel->ORDER_AMOUNT = $this->_decAmount;
+        $orderModel->PV = $this->_decPv;
+        $orderModel->PAY_AMOUNT = $this->_payAmount;
+        $orderModel->PAY_PV = $this->_decPv;
+        $orderModel->REMAIN_PV = $this->_remainPv;
+        $orderModel->PAY_AT = 0;
+        $orderModel->PAY_TYPE = $this->payType;
+        $orderModel->PERIOD_NUM = $nowPeriodNum;
+        $orderModel->P_CALC_MONTH = Date::ociToDate($nowCalcMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH);
+        $orderModel->FREIGHT = $this->_freight;
+        $orderModel->PAY_FREIGHT = $this->_freight;
+        $orderModel->CONSIGNEE = $this->_address['CONSIGNEE'];
+        $orderModel->MOBILE = $this->_address['MOBILE'];
+        $orderModel->PROVINCE = $this->_address['PROVINCE'];
+        $orderModel->CITY = $this->_address['CITY'];
+        $orderModel->COUNTY = $this->_address['COUNTY'];
+        $orderModel->ADDRESS = $this->_address['ADDRESS'];
+        $orderModel->FRONT_REMARK = $this->remark;
+        $orderModel->WAREHOUSE = $warehouse;
+        $orderModel->STATUS = \Yii::$app->params['orderStatus']['notPaid']['value'];
+        $orderModel->CREATED_AT = Date::nowTime();
+        $orderModel->CREATE_USER = $userName;
+        $orderModel->EMAIL = $userEmail ?: $userName.'@elken.net';
+        $orderModel->CONSIGNEE_ID_NO = $this->consigneeIdNo;
+        $orderModel->CONSIGNEE_REAL_NAME = $this->consigneeRealName;
+        $orderModel->ZIP_CODE = $this->_address['ZIP_CODE'];
+        if(!$orderModel->save()){
+            $this->addErrors($orderModel->getErrors());
+            return false;
+        }
+
+        // 加入商品到订单商品表
+        foreach($this->_orderGoods as $key=>$value) {
+            $this->_orderGoods[$key]['ORDER_SN'] = $orderModel->SN;
+            $this->_orderGoods[$key]['P_CALC_MONTH'] = Date::ociToDate($nowCalcMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH);
+        }
+        ApproachOrderGoods::batchInsert($this->_orderGoods);
+
+        return $orderModel;
+    }
+
+    /**
+     * 生成流水号
+     * @return string
+     */
+    private function _generateSn() {
+        return Date::today('Ymd') . $this->_random(10, 1);
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+}

+ 7 - 3
common/models/forms/ReceiveAddressForm.php

@@ -25,6 +25,7 @@ class ReceiveAddressForm extends Model
     public $city;
     public $county;
     public $address;
+    public $zipCode;
     public $isDefault;
 
     /**
@@ -46,7 +47,7 @@ class ReceiveAddressForm extends Model
     public function rules()
     {
         return [
-            [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'], 'trim'],
+            [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'], 'trim'],
             [['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address'], 'required'],
             [['mobile'], 'mobile'],
             [['province', 'city', 'county'], 'exist', 'targetClass' => Region::class, 'targetAttribute' => 'REGION_CODE'],
@@ -62,6 +63,7 @@ class ReceiveAddressForm extends Model
             'city' => '市/区',
             'county' => '区/县',
             'address' => '详细地址',
+            'zipCode' => '邮政编码',
         ];
     }
 
@@ -73,8 +75,8 @@ class ReceiveAddressForm extends Model
     {
         $parentScenarios =  parent::scenarios();
         $customScenarios = [
-            'userAdd' => ['consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'],
-            'userEdit' => ['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'isDefault'],
+            'userAdd' => ['consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'],
+            'userEdit' => ['id', 'consignee', 'mobile', 'province', 'city', 'county', 'address', 'zipCode', 'isDefault'],
             'userIsDefault' => ['id', 'isDefault'],
         ];
         return array_merge($parentScenarios, $customScenarios);
@@ -138,6 +140,7 @@ class ReceiveAddressForm extends Model
                 $this->_model->CITY = $this->city;
                 $this->_model->COUNTY = $this->county;
                 $this->_model->ADDRESS = $this->address;
+                $this->_model->ZIP_CODE = $this->zipCode;
                 $this->_model->IS_DEFAULT = $this->isDefault ? 1 : 0;
                 $this->_model->CREATED_AT = Date::nowTime();
             } elseif($this->scenario == 'userEdit') {
@@ -147,6 +150,7 @@ class ReceiveAddressForm extends Model
                 $this->_model->CITY = $this->city;
                 $this->_model->COUNTY = $this->county;
                 $this->_model->ADDRESS = $this->address;
+                $this->_model->ZIP_CODE = $this->zipCode;
                 $this->_model->IS_DEFAULT = $this->isDefault ? 1 : 0;
                 $this->_model->UPDATED_AT = Date::nowTime();
             } elseif($this->scenario == 'userIsDefault') {

+ 18 - 7
common/models/forms/ShopGoodsForm.php

@@ -51,7 +51,7 @@ class ShopGoodsForm extends Model
     {
         return [
             [['id','sellDiscount','giftType','sellType','goodsNo', 'goodsName', 'unit', 'sellPrice', 'marketPrice', 'pricePv', 'storeNums', 'content', 'sort','status','cover'], 'trim'],
-            [['goodsName','sellDiscount','giftType','goodsNo', 'storeNums','sellPrice','marketPrice','pricePv', 'sort','status','cover'], 'required'],
+            [['goodsName','sellDiscount','giftType','goodsNo', 'storeNums','sellPrice','marketPrice','pricePv', 'sort','status', 'type'], 'required'],
             [['id'], 'required', 'on'=>'edit'],
             [['id'], 'exist', 'targetClass'=>ShopGoods::class, 'targetAttribute'=>'ID'],
             [['sellPrice','marketPrice','pricePv'], 'price'],
@@ -59,7 +59,8 @@ class ShopGoodsForm extends Model
             [['selectedIds'], 'isSelected'],
             [['sort'], 'isSort'],
             [['sellDiscount'], 'isDiscount'],
-            [['statusdate','goodsstatusdate'],'match','pattern'=>'/^[0-1]{1,1}$/']
+            [['statusdate','goodsstatusdate'],'match','pattern'=>'/^[0-1]{1,1}$/'],
+            [['type'], 'isType'],
         ];
     }
 
@@ -82,6 +83,7 @@ class ShopGoodsForm extends Model
             'storeNums' => '库存',
             'content' => '产品详情',
             'listOrder' => '排序',
+            'type' => '商品来源',
         ];
     }
 
@@ -93,8 +95,8 @@ class ShopGoodsForm extends Model
     {
         $parentScenarios =  parent::scenarios();
         $customScenarios = [
-            'add' => ['goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv','storeNums', 'content','sort','cover'],
-            'edit' => ['id','goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv', 'storeNums', 'content','sort','cover','statusdate','goodsstatusdate','goodsdate'],
+            'add' => ['goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv','storeNums', 'content','sort','cover', 'type'],
+            'edit' => ['id','goodsName','sellDiscount','giftType', 'sellType','goodsNo','unit','sellPrice','marketPrice','pricePv', 'storeNums', 'content','sort','cover','statusdate','goodsstatusdate','goodsdate', 'type'],
             'changeStatus' => ['selectedIds', 'status'],
         ];
         return array_merge($parentScenarios, $customScenarios);
@@ -139,6 +141,14 @@ class ShopGoodsForm extends Model
         }
     }
 
+    /**
+     * 根据商品类型处理支付类型
+     * @param $attributes
+     */
+    public function isType() {
+        $this->sellType = $this->type == '1' ? '1,2,3' : '4';
+    }
+
     /**
      * 添加
      * @return ShopGoods|null
@@ -157,7 +167,7 @@ class ShopGoodsForm extends Model
             $shopGoods->SELL_DISCOUNT = $this->sellDiscount;
             $shopGoods->GIFT_TYPE = implode(',',$this->giftType);
             // $shopGoods->SELL_TYPE = implode(',',$this->sellType);
-            $shopGoods->SELL_TYPE = '1,2,3';
+            $shopGoods->SELL_TYPE = $this->sellType;
             $shopGoods->GOODS_NO = $this->goodsNo;
             $shopGoods->UNIT = $this->unit ? $this->unit : '个';
             $shopGoods->COVER = $this->cover ? $this->cover : '';
@@ -168,7 +178,7 @@ class ShopGoodsForm extends Model
             $shopGoods->CONTENT = $this->content;
             $shopGoods->STORE_NUMS = $this->storeNums;
             $shopGoods->SORT = $this->sort;
-            $shopGoods->CATE_ID = '1';
+            $shopGoods->CATE_ID = $this->type;
             $shopGoods->CREATED_AT = Date::nowTime();
             if (!$shopGoods->save()) {
                 throw new Exception(Form::formatErrorsForApi($shopGoods->getErrors()));
@@ -198,10 +208,11 @@ class ShopGoodsForm extends Model
             $model = $this->_model;
             $model->GOODS_NAME = $this->goodsName;
             $model->TYPE = 0;
+            $model->CATE_ID = $this->type;
             $model->SELL_DISCOUNT = $this->sellDiscount;
             $model->GIFT_TYPE = implode(',',$this->giftType);
             // $model->SELL_TYPE = implode(',',$this->sellType);
-            $model->SELL_TYPE = '1,2,3';
+            $model->SELL_TYPE = $this->sellType;
             $model->GOODS_NO = $this->goodsNo;
             $model->UNIT = $this->unit ? $this->unit : '个';
             $model->COVER = $this->cover ? $this->cover : '';

+ 22 - 2
frontendApi/config/params.php

@@ -1,6 +1,26 @@
 <?php
 return [
     'adminEmail' => 'admin@example.com',
-    'noCheckTokenActions' => ['v1/oauth/login', 'v1/oauth/refresh-access-token', 'v1/oauth/refresh-refresh-token', 'v1/oauth/refresh-token', 'v1/site/days-diff', 'v1/site/page-data', 'v1/site/captcha', 'v1/oauth/is-login-verify','v1/oauth/login-by-backend', 'v1/oauth/no-login-modify-password', 'v1/site/doc', 'v1/site/config', 'v1/site/send-notice',],
-    'noCheckPermissionActions' => [],
+    'noCheckTokenActions' => [
+        'v1/oauth/login',
+        'v1/oauth/refresh-access-token',
+        'v1/oauth/refresh-refresh-token',
+        'v1/oauth/refresh-token',
+        'v1/site/days-diff',
+        'v1/site/page-data',
+        'v1/site/captcha',
+        'v1/oauth/is-login-verify',
+        'v1/oauth/login-by-backend',
+        'v1/oauth/no-login-modify-password',
+        'v1/site/doc',
+        'v1/site/config',
+        'v1/site/send-notice',
+        'v1/shop/verify-approach-order',
+        'v1/shop/logistics',
+        'v1/shop/logistics-auto',
+    ],
+    'noCheckPermissionActions' => [
+        'shop/logistics',
+        'shop/logistics-auto',
+    ],
 ];

+ 6 - 1
frontendApi/config/urlManagerRules.php

@@ -73,7 +73,12 @@ return [
             'GET pay-success' => 'pay-success',
             'GET order-list' => 'order-list',
             'GET dec-order-list' => 'dec-order-list',
-            'GET order-list' => 'order-list',
+            'POST sure-approach-order' => 'sure-approach-order',
+            'POST verify-approach-order' => 'verify-approach-order',
+            'POST delete-approach-order' => 'delete-approach-order',
+            'POST i-pay88' => 'i-pay88',
+            'GET logistics' => 'logistics',
+            'GET logistics-auto' => 'logistics-auto',
         ],
     ],
     [

+ 416 - 0
frontendApi/modules/v1/components/IPay88.php

@@ -0,0 +1,416 @@
+<?php
+
+namespace frontendApi\modules\v1\components;
+
+use common\helpers\LoggerTool;
+use Exception;
+use Yii;
+
+class IPay88 {
+
+    /**
+     * Normal iPay88 payment method
+     */
+    const TRANSACTION_TYPE_PAYMENT = 'payment';
+
+    /**
+     * Normal iPay88 recurring payment subscription
+     */
+    const TRANSACTION_TYPE_RECURRING_SUBSCRIPTION = 'recurring_subscription';
+
+    /**
+     * Normal iPay88 recurring payment termination
+     */
+    const TRANSACTION_TYPE_RECURRING_TERMINATION = 'recurring_termination';
+
+    /**
+     * Merchant code assigned by iPay88
+     */
+    public $merchantCode;
+
+    /**
+     * Merchant Key assigned by iPay88
+     */
+    public $merchantKey;
+
+    /**
+     * Currency Code max length 5
+     */
+    public $currencyCode;
+
+    /**
+     * Merchant code assigned by iPay88
+     */
+    public $responseUrl;
+
+    /*
+     * Response Url or Return Url after payment
+     */
+    public $paymentUrl;
+
+    /*
+     * Backend Url or Notify Url after payment (Send response by iPay88 server)
+     */
+    public $backendUrl;
+
+    /*
+     * Requery from iPay88 server regarding bill details
+     */
+    public $requeryUrl;
+
+    /*
+    * ipay88 Recurring Payment Url
+    */
+    public $recurringUrlSubscription;
+
+    /*
+    * ipay88 Recurring Payment Termination Url
+    */
+    public $recurringUrlTermination;
+
+
+    /*
+     * Details to be sent to IPay88 for payment request.
+     */
+    private $paymentRequest = array(
+        'MerchantCode', // Merchant code assigned by iPay88. (length 20)
+        'PaymentId', // (Optional) (int)
+        'RefNo', // Unique merchant transaction number / Order ID (Retry for same RefNo only valid for 30 mins). (length 20)
+        'Amount', // Payment amount with two decimals.
+        'Currency', // (length 5)
+        'ProdDesc', // Product description. (length 100)
+        'UserName', // Customer name. (length 100)
+        'UserEmail', // Customer email.  (length 100)
+        'UserContact', // Customer contact.  (length 20)
+        'Remark', // (Optional) Merchant remarks. (length 100)
+        'Lang', // (Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+        'Signature',
+        'ResponseURL',
+        'BackendURL',
+    );
+
+
+    /*
+     * Details to be sent to iPay88 for recurring subscription payment request.
+     */
+    private $recurringSubscriptionRequest = array(
+        'MerchantCode', // Merchant code assigned by iPay88. (length 20)
+        'RefNo', // Unique merchant transaction number / Order ID. (length 20)
+        'FirstPaymentDate', // (ddmmyyyy)
+        'Currency', // MYR only. (length 5)
+        'Amount', // Payment amount with two decimals.
+        'NumberOfPayments', // (int)
+        'Frequency', // Frequency type; 1 - Monthly, 2 - Quarterly, 3 - Half-Yearly, 4 - Yearly. (int)
+        'Desc', // Product description. (length 100)
+        'CC_Name', // Name printed on credit card. (length 100)
+        'CC_PAN', // 16-digit credit card number (Visa/Mastercard). (length 16)
+        'CC_CVC', // 3-digit verification code behind credit card. (length 3)
+        'CC_ExpiryDate', // Credit card expiry date. (mmyyyy)
+        'CC_Country', // Credit card issuing country. (length 100)
+        'CC_Bank', // Credit card issuing bank. (length 100)
+        'CC_Ic', // Credit card holder IC / Passport number. (length 50)
+        'CC_Email', // Credit card holder email address. (length 255)
+        'CC_Phone', // Credit card phone number. (length 100)
+        'CC_Remark', // (Optional) Remarks. (varchar 100)
+        'P_Name', // Subscriber name as printed in IC / Passport. (length 100)
+        'P_Email', // Subscriber email address. (length 255)
+        'P_Phone', // Subscriber phone number. (length 100)
+        'P_Addrl1', // Subscriber address line 1. (length 100)
+        'P_Addrl2', // (Optional) Subscriber address line 2. (length 100)
+        'P_City', // Subscriber city. (length 100)
+        'P_State', // Subscriber state. (length 100)
+        'P_Zip', // Subscriber zip code. (length 100)
+        'P_Country', // Subscriber country. (varchar 100)
+        'BackendURL', // Payment backend response page. (length 255)
+        'Signature', // SHA1 signature. (length 100)
+    );
+
+
+    /*
+     * Get required payment fields
+     */
+    public function getPaymentFields($reqParams = null, $paymentType) {
+        $retnParams = array();
+        try {
+            if (isset($reqParams) && (count($reqParams) > 0)) {
+
+                if (isset($paymentType) && $paymentType != "") {
+                    $paymentType = strtolower(trim($paymentType));
+                    switch ($paymentType) {
+                        case 'payment':
+                            $retnParams = $this->__getPaymentField($reqParams, $paymentType);
+                            break;
+                        case 'recurring_subscription':
+                            $retnParams = $this->__getRecurringSubscriptionField($reqParams, $paymentType);
+                            break;
+                        case 'recurring_termination':
+                            $retnParams = $this->__getRecurringTerminationField($reqParams, $paymentType);
+                            break;
+                    }
+                } else {
+                    throw new Exception("Ipay: Payment method missing");
+                }
+            } else {
+                throw new Exception("Ipay: Required Parameters missing");
+            }
+        } catch (Exception $e) {
+            LoggerTool::error(['iPay88-getPaymentFields', $e->getLine(), $e->getMessage()]);
+        }
+        return $retnParams;
+    }
+
+    /*
+     * Code for hex2bin
+     */
+    public function _hex2bin($hexSource) {
+        $bin = '';
+        for ($i = 0; $i < strlen($hexSource); $i = $i + 2) {
+            $bin .= chr(hexdec(substr($hexSource, $i, 2)));
+        }
+        return $bin;
+    }
+
+    /*
+     * Get payment fields for normal payment fields
+     */
+    public function __getPaymentField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->paymentRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                    case 'Currency':
+                        $retnParams[$pymtKey] = $this->currencyCode;
+                        break;
+                    case 'Lang':
+                        $retnParams[$pymtKey] = 'UTF-8'; //(Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+                        break;
+                    case 'Signature':
+                        $retnParams[$pymtKey] = $this->__createSignature($retnParams, $paymentType); // SHA1 signature.
+                        break;
+                    case 'ResponseURL':
+                        $retnParams[$pymtKey] = $this->responseUrl; // (Optional) Payment response page.
+                        break;
+                    case 'BackendURL':
+                        $retnParams[$pymtKey] = $this->backendUrl; // (Optional) BackendURL but should security purpose
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+    /*
+     * Get payment fields for recurring payment
+     */
+    public function __getRecurringSubscriptionField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->recurringSubscriptionRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                    case 'Currency':
+                        $retnParams[$pymtKey] = $this->currencyCode;
+                        break;
+                    case 'Lang':
+                        $retnParams[$pymtKey] = 'UTF-8'; //(Optional) Encoding type:- ISO-8859-1 (English), UTF-8 (Unicode), GB2312 (Chinese Simplified), GD18030 (Chinese Simplified), BIG5 (Chinese Traditional)
+                        break;
+                    case 'Signature':
+                        $retnParams[$pymtKey] = $this->__createSignature($retnParams, $paymentType); // SHA1 signature.
+                        break;
+                    case 'ResponseURL':
+                        $retnParams[$pymtKey] = $this->responseUrl; // (Optional) Payment response page.
+                        break;
+                    case 'BackendURL':
+                        $retnParams[$pymtKey] = $this->backendUrl; // (Optional) BackendURL but should security purpose
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+
+
+    /*
+     * Get payment fields for recurring payment termination
+     */
+    public function __getRecurringTerminationField($reqParams, $paymentType) {
+        $retnParams = array();
+        foreach ($this->recurringSubscriptionRequest as $pymtKey) {
+            if (isset($reqParams[$pymtKey])) {
+                $retnParams[$pymtKey] = $reqParams[$pymtKey];
+            } else {
+
+                switch ($pymtKey) {
+                    case 'MerchantCode':
+                        $retnParams[$pymtKey] = $this->merchantCode;
+                        break;
+                }
+            }
+        }
+
+        return $retnParams;
+    }
+
+    /*
+     * Create signature for payment
+     */
+    public function __createSignature($signatureParams, $paymentType) {
+        $signature = '';
+        if (isset($signatureParams)) {
+            $_signatureParams = array();
+            if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+                $_signatureParams = array('MerchantCode', 'RefNo', 'Amount', 'Currency');
+            } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+                $_signatureParams = array('MerchantCode', 'RefNo', 'FirstPaymentDate', 'Currency', 'Amount', 'NumberOfPayments', 'Frequency', 'CC_PAN');
+            } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+                $_signatureParams = array('MerchantCode', 'RefNo');
+            }
+
+
+            foreach ($_signatureParams as $val) {
+                if (!isset($signatureParams[$val])) {
+                    throw new Exception("Ipay: Missing required parameters for signature.");
+                    return false;
+                }
+            }
+        }
+
+        // Make sure the order is correct.
+        if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['MerchantCode'];
+            //$signature .= $signatureParams['PaymentId'];
+            $signature .= $signatureParams['RefNo'];
+            $signature .= preg_replace("/[^\d]+/", "", $signatureParams['Amount']);
+            $signature .= $signatureParams['Currency'];
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+            $signature .= $signatureParams['MerchantCode'];
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['RefNo'];
+            $signature .= $signatureParams['FirstPaymentDate'];
+            $signature .= $signatureParams['Currency'];
+            $signature .= $signatureParams['Amount'];
+            $signature .= $signatureParams['NumberOfPayments'];
+            $signature .= $signatureParams['Frequency'];
+            $signature .= $signatureParams['CC_PAN'];
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+            $signature .= $signatureParams['MerchantCode'];
+            $signature .= $this->merchantKey;
+            $signature .= $signatureParams['RefNo'];
+        }
+        // Hash the signature.
+        //return $signature = base64_encode($this->_hex2bin(sha1($signature)));
+        return $signature = hash('sha256', $signature);
+    }
+
+    /*
+     * Get url for respective payment redirection url
+     */
+    public function getTransactionUrl($paymentType) {
+        if ($paymentType == self::TRANSACTION_TYPE_PAYMENT) {
+            return $this->paymentUrl;
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_SUBSCRIPTION) {
+            return $this->recurringUrlSubscription;
+        } else if ($paymentType == self::TRANSACTION_TYPE_RECURRING_TERMINATION) {
+            return $this->recurringUrlTermination;
+        }
+    }
+
+
+    /*
+     * iPay88 payment signature validation
+     */
+    public function checkiPay88Signature($reqParams) {
+        $status = 'fail';
+        try {
+            if (isset($reqParams) && count($reqParams) > 0) {
+                $orginalKey = $this->merchantKey . $this->merchantCode;
+                if (isset($reqParams['RefNo'])) {
+                    $orginalKey .=$reqParams['RefNo'];
+                }
+
+                if (isset($reqParams['Amount'])) {
+                    $orginalKey .=preg_replace("/[^\d]+/", "", $reqParams['Amount']);
+                }
+                $orginalKey .= $this->currencyCode;
+                if (isset($reqParams['Status'])) {
+                    $orginalKey .=$reqParams['Status'];
+                }
+
+                $orginalKeyGen = base64_encode($this->_hex2bin(sha1($orginalKey)));
+                $returnKey = $this->merchantKey;
+                if (isset($reqParams['MerchantCode'])) {
+                    $returnKey .=$reqParams['MerchantCode'];
+                }
+
+
+                if (isset($reqParams['RefNo'])) {
+                    $returnKey .=$reqParams['RefNo'];
+                }
+                if (isset($reqParams['Amount'])) {
+                    $returnKey .=preg_replace("/[^\d]+/", "", $reqParams['Amount']);
+                }
+                if (isset($reqParams['Currency'])) {
+                    $returnKey .=$reqParams['Currency'];
+                }
+                if (isset($reqParams['Status'])) {
+                    $returnKey .=$reqParams['Status'];
+                }
+
+
+                $returnKeyGen = base64_encode($this->_hex2bin(sha1($returnKey)));
+                if ($orginalKeyGen === $returnKeyGen) {
+                    $status = 'success';
+                }
+            } else {
+                throw new Exception("Ipay::checkiPay88Signature: Params missing");
+            }
+        } catch (exception $e) {
+            LoggerTool::error(['iPay88-checkiPay88Signature', $e->getLine(), $e->getMessage()]);
+        }
+
+        return $status;
+    }
+
+    /*
+     * Curl hit to get bill deyails
+     */
+    public function requeryPayment($rawPostData) {
+        try {
+            $result = '';
+            if (is_callable('curl_init')) {
+                if (isset($rawPostData) && $rawPostData != "") {
+                    $ch = curl_init();
+                    $url = $this->requeryUrl . '?' . $rawPostData;
+                    curl_setopt($ch, CURLOPT_URL, $url);
+                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+                    $result = curl_exec($ch);
+                    curl_close($ch);
+                } else {
+                    throw new Exception("Ipay::requeryPayment: No request string");
+                }
+            } else {
+                throw new Exception("Ipay::requeryPayment: Curl not enabled");
+            }
+        } catch (exception $e) {
+            LoggerTool::error(['iPay88-requeryPayment', $e->getLine(), $e->getMessage()]);
+        }
+
+        return $result;
+    }
+
+}

+ 1 - 1
frontendApi/modules/v1/controllers/ConfigController.php

@@ -86,7 +86,7 @@ class ConfigController extends BaseController
         $condition = ' AND USER_ID=:USER_ID';
         $params[':USER_ID'] = \Yii::$app->user->id;
         $data = ReceiveAddress::lists($condition, $params, [
-            'SELECT' => 'ID,CONSIGNEE,MOBILE,PROVINCE,CITY,COUNTY,ADDRESS,IS_DEFAULT',
+            'SELECT' => 'ID,CONSIGNEE,MOBILE,PROVINCE,CITY,COUNTY,ADDRESS,ZIP_CODE,IS_DEFAULT',
             'orderBy' => 'IS_DEFAULT DESC,CREATED_AT DESC',
             'useSlaves' => true,
         ]);

+ 323 - 22
frontendApi/modules/v1/controllers/ShopController.php

@@ -8,10 +8,18 @@
 
 namespace frontendApi\modules\v1\controllers;
 
+use common\helpers\Cache;
 use common\helpers\Date;
+use common\helpers\DingTalk;
 use common\helpers\Form;
+use common\helpers\LoggerTool;
+use common\helpers\Logistics;
+use common\helpers\Tool;
 use common\helpers\user\Info;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
 use common\models\DecOrder;
+use common\models\forms\ApproachOrderForm;
 use common\models\forms\DeclarationForm;
 use common\models\forms\OrderForm;
 use common\models\Order;
@@ -22,10 +30,17 @@ use common\models\ShopGoods;
 use common\models\User;
 use common\models\UserBonus;
 use common\models\UserWallet;
+use Yii;
+use yii\data\Pagination;
+use yii\db\Query;
+use yii\web\HttpException;
 
 class ShopController extends BaseController {
+
     public $modelClass = DecOrder::class;
 
+    const TRANSACTION_TYPE_PAYMENT = 'payment';
+
     /**
      * 商品列表
      * @return mixed
@@ -45,6 +60,7 @@ class ShopController extends BaseController {
         ]);
         foreach ($data['list'] as $key => $value) {
             $data['list'][$key]['DISCOUNT'] = $value['SELL_DISCOUNT']*100;
+            $data['list'][$key]['CATE'] = ShopGoods::GOODS_TYPE[$value['CATE_ID']]['name'] ?? '';
         }
         return static::notice($data);
     }
@@ -146,6 +162,40 @@ class ShopController extends BaseController {
         return static::notice($data);
     }
 
+//    /**
+//     * 我的订单
+//     * @return mixed
+//     * @throws \yii\web\HttpException
+//     */
+//    public function actionOrderList() {
+//        $uname = Info::getUserNameByUserId(\Yii::$app->user->id);
+//        $condition = " AND IS_DELETE=0 AND ORDER_TYPE='FX' AND (USER_ID=:USER_ID OR CREATE_USER='$uname')";
+//        $params[':USER_ID'] = \Yii::$app->user->id;
+//        $data = Order::lists($condition, $params, [
+//            'select' => 'O.*,U.REAL_NAME,OG.*',
+//            'orderBy' => 'O.CREATED_AT DESC',
+//            'from' => Order::tableName() . ' AS O',
+//            'join' => [
+//                ['LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID'],
+//                ['LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN'],
+//            ],
+//        ]);
+//        foreach ($data['list'] as $key => $value) {
+//            if($value['ORDER_TYPE']=='ZC'){
+//                $data['list'][$key]['ORDER_TYPE'] = '首单';
+//            }else{
+////                $data['list'][$key]['ORDER_TYPE'] = in_array($value['PAY_TYPE'], ['cash', 'paystack']) ? '复消': '积分';
+//                $data['list'][$key]['ORDER_TYPE'] = '复消';
+//            }
+//            //$data['list'][$key]['PROVINCE_NAME'] = $value['PROVINCE'] ? Region::getCnName($value['PROVINCE']) : '';
+//           //$data['list'][$key]['CITY_NAME'] = $value['CITY'] ? Region::getCnName($value['CITY']) : '';
+//            //$data['list'][$key]['COUNTY_NAME'] = $value['COUNTY'] ? Region::getCnName($value['COUNTY']) : '';
+//            $data['list'][$key]['PAY_AT'] = Date::convert($value['PAY_AT'],'Y-m-d H:i:s');
+//            $data['list'][$key]['PAY_TYPE'] = $value['PAY_TYPE'] == 'cash' ? '消费点数' : ($value['PAY_TYPE'] == 'exchange' ? '兑换点数' : '复消点数');
+//        }
+//        return static::notice($data);
+//    }
+
     /**
      * 我的订单
      * @return mixed
@@ -153,31 +203,51 @@ class ShopController extends BaseController {
      */
     public function actionOrderList() {
         $uname = Info::getUserNameByUserId(\Yii::$app->user->id);
-        $condition = " AND IS_DELETE=0 AND ORDER_TYPE='FX' AND (USER_ID=:USER_ID OR CREATE_USER='$uname')";
+        $condition = " O.IS_DELETE = 0 AND O.ORDER_TYPE='FX' AND (O.USER_ID=:USER_ID OR O.CREATE_USER='$uname')";
         $params[':USER_ID'] = \Yii::$app->user->id;
-        $data = Order::lists($condition, $params, [
-            'select' => 'O.*,U.REAL_NAME,OG.*',
-            'orderBy' => 'O.CREATED_AT DESC',
-            'from' => Order::tableName() . ' AS O',
-            'join' => [
-                ['LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID'],
-                ['LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN'],
-            ],
-        ]);
+        $orderQuery = Order::find()
+            ->alias('O')
+            ->where($condition, $params)
+            ->select('O.*,U.REAL_NAME,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV,OG.ORDER_SN,OG.GOODS_ID')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->orderBy('O.CREATED_AT DESC');
+
+        // 订单中间表只查询待支付和支付失败的订单
+        $params[':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+        $params[':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+        $orderStandardQuery = ApproachOrder::find()
+            ->alias('O')
+            ->where($condition . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $params)
+            ->select('O.*,U.REAL_NAME,OG.REAL_PRICE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV,OG.ORDER_SN,OG.GOODS_ID')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->orderBy('O.CREATED_AT DESC');
+
+        $queryAll = $orderQuery->union($orderStandardQuery, true);
+        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+
+        $totalCount = $query->count();
+        $pagination = new Pagination(['totalCount' => $totalCount, 'pageSize' => \Yii::$app->request->get('pageSize')]);
+        $lists = $query->offset($pagination->offset)->limit($pagination->limit)->all();
+
+        $data = [
+            'list' => $lists,
+            'currentPage'=>$pagination->page,
+            'totalPages'=>$pagination->pageCount,
+            'startNum' => $pagination->page * $pagination->pageSize + 1,
+            'totalCount' => $pagination->totalCount,
+            'pageSize' => $pagination->pageSize,
+        ];
+
         foreach ($data['list'] as $key => $value) {
-            if($value['ORDER_TYPE']=='ZC'){
-                $data['list'][$key]['ORDER_TYPE'] = '首单';
-            }else{
-//                $data['list'][$key]['ORDER_TYPE'] = in_array($value['PAY_TYPE'], ['cash', 'paystack']) ? '复消': '积分';
-                $data['list'][$key]['ORDER_TYPE'] = '复消';
-            }
-            //$data['list'][$key]['PROVINCE_NAME'] = $value['PROVINCE'] ? Region::getCnName($value['PROVINCE']) : '';
-           //$data['list'][$key]['CITY_NAME'] = $value['CITY'] ? Region::getCnName($value['CITY']) : '';
-            //$data['list'][$key]['COUNTY_NAME'] = $value['COUNTY'] ? Region::getCnName($value['COUNTY']) : '';
-            $data['list'][$key]['PAY_AT'] = Date::convert($value['PAY_AT'],'Y-m-d H:i:s');
-            $data['list'][$key]['PAY_TYPE'] = $value['PAY_TYPE'] == 'cash' ? '消费点数' : ($value['PAY_TYPE'] == 'exchange' ? '兑换点数' : '复消点数');
+            $data['list'][$key]['ORDER_TYPE'] = $value['ORDER_TYPE'] == 'ZC' ? '首单' : '复消';
+            $data['list'][$key]['PAY_AT'] = $value['PAY_AT'] ? Date::convert($value['PAY_AT'],'Y-m-d H:i:s') : '';
+            $data['list'][$key]['PAY_TYPE'] = ShopGoods::payTypes()[$value['PAY_TYPE']]['name'] ?? '';
+            $data['list'][$key]['STATUS'] = \Yii::$app->params['orderStatus'][$value['STATUS']]['label'] ?? '';
         }
-        return static::notice($data);
+
+        return $data;
     }
 
     /**
@@ -237,6 +307,237 @@ class ShopController extends BaseController {
                 return static::notice(Form::formatErrorsForApi($formModel->getErrors()),400);
             }
         }
+
+        return static::notice('无效请求');
+    }
+
+    /**
+     * 确认订单
+     */
+    public function actionSureApproachOrder(){
+        if (\Yii::$app->request->isPost) {
+            $formModel = new ApproachOrderForm();
+            $formModel->scenario = 'userOrder';
+            $formModel->remark = '复销备注';
+            $post = \Yii::$app->request->post();
+            $post['type'] = DeclarationForm::TYPE_FX;
+            if ($formModel->load($post, '') && $order = $formModel->add()) {
+                return static::notice($order);
+            } else {
+                return static::notice(Form::formatErrorsForApi($formModel->getErrors()),400);
+            }
+        }
+
+        return static::notice('无效请求');
+    }
+
+    /**
+     * iPay88支付成功的webhook.
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionVerifyApproachOrder() {
+        // iPay88支付成功的webhook.
+        $rawPostData = file_get_contents('php://input');
+        LoggerTool::notice(['actionVerifyApproachOrder', $rawPostData]);
+        $data = [];
+        if (strlen($rawPostData) > 0) {
+            $rawPostArray = explode('&', $rawPostData);
+            foreach ($rawPostArray as $raw) {
+                $raw = explode('=', $raw);
+                if (count($raw) == 2)
+                    $data[$raw[0]] = urldecode($raw[1]);
+            }
+        }
+
+        // 支付webhook回调日志
+        Tool::approachOrderCall($data);
+
+        try {
+            // 订单状态
+            $orderStatus = ($data['Status'] == '1') ? \Yii::$app->params['orderStatus']['paid']['value'] : \Yii::$app->params['orderStatus']['failPaid']['value'];
+
+            $oderSn = $data['RefNo'] ?? '';
+
+            $formModel =  new ApproachOrderForm();
+            $formModel->scenario = 'verifyPay';
+            $load = [
+                'sn' => $oderSn,
+                'scenario' => 'verifyPay',
+                'status' => $orderStatus,
+                'note' => [
+                    'MerchantCode' => $data['MerchantCode'],
+                    'PaymentId' => $data['PaymentId'],
+                    'status' => $data['Status'],
+                    'Signature' => $data['Signature'],
+                    'Currency' => $data['Currency'],
+                    'Amount' => $data['Amount'],
+                    'TransId' => $data['TransId'],
+                    'TranDate' => $data['TranDate'],
+                    'BankMID' => $data['BankMID'],
+                    'CCNo' => $data['CCNo'],
+
+                ],
+            ];
+
+            if ($formModel->load($load, '') && $result = $formModel->verifyPayOnline()) {
+                LoggerTool::info($result);
+
+                return http_response_code(200);
+            } else {
+                LoggerTool::error(Form::formatErrorsForApi($formModel->getErrors()));
+                return http_response_code(500);
+            }
+        } catch (\Exception $e) {
+            LoggerTool::error(sprintf('actionVerifyApproachOrderError: File[%s], Line:[%s], Message[%s]', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return http_response_code(500);
+        }
+    }
+
+    /**
+     * 删除准订单
+     */
+    public function actionDeleteApproachOrder()
+    {
+        $orderSn = \Yii::$app->request->post('orderSn');
+        // 删除订单中间表
+        ApproachOrder::deleteAll('SN = :SN', [':SN' => $orderSn]);
+        // 删除订单商品中间表
+        ApproachOrderGoods::deleteAll('ORDER_SN = :ORDER_SN', [':ORDER_SN' => $orderSn]);
+
+        return static::notice('');
+    }
+
+    /**
+     * iPay88支付
+     * @return mixed
+     * @throws HttpException
+     */
+    public function actionIPay88()
+    {
+        // 订单ID
+        $paymentParams['RefNo'] = \Yii::$app->request->post('RefNo');
+        // 订单金额,元=>分
+        $money = \Yii::$app->request->post('Amount');
+        // 马来币汇率
+        $exchangeRateMYR = floatval(Cache::getSystemConfig()['exchangeRateMYR']['VALUE'] ?? 0);
+        // 计算马来币
+        $amount = number_format(round($money * $exchangeRateMYR), 2, '.', '');
+//        $amount = number_format(1, 2, '.', ''); // TODO: 测试
+        $paymentParams['Amount'] = str_replace('.', '', $amount);
+        // (Optional) (int)
+        $paymentParams['PaymentId'] = '182';   // 2=信用卡 182=银联
+        // Product description. (length 100)
+        $paymentParams['ProdDesc'] = 'Pay for sales';
+        // Customer name. (length 100)
+        $paymentParams['UserName'] = 'MY32';
+        $paymentParams['SignatureType'] = 'SHA256';
+        // Customer email.  (length 100)
+        $paymentParams['UserEmail'] = 'ek_dummy25@elken.com';
+        // Customer contact.  (length 20)
+        $paymentParams['UserContact'] = '60172249692';
+
+        // (Optional) Merchant remarks. (length 100)
+        //$paymentParams['Remark'] = 'Here is the description';
+        //merchantkey + merchantcode+ reference Number + amount in cent + currency_code
+        $paymentFields = \Yii::$app->iPay88->getPaymentFields($paymentParams, self::TRANSACTION_TYPE_PAYMENT);
+
+        $transactionUrl = \Yii::$app->iPay88->getTransactionUrl(self::TRANSACTION_TYPE_PAYMENT);
+        $paymentFields['Amount'] = $amount;
+        $res = [
+            'paymentFields' => $paymentFields,
+            'transactionUrl' => $transactionUrl,
+        ];
+
+        return static::notice($res);
+    }
+
+    /**
+     * 推送订单到wst仓储系统
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionLogistics()
+    {
+        $orderSn = \Yii::$app->request->get('sn');
+        $order = Order::find()
+            ->where('SN=:ORDER_SN', [':ORDER_SN' => $orderSn])
+            ->asArray()
+            ->one();
+
+        if (!$order) {
+            return static::notice('订单【' . $orderSn . '】不存在');
+        }
+        if ($order['SEND_AT'] > 0) {
+            return static::notice('订单【' . $orderSn . '】不可重复推送');
+        }
+
+        $logistics = new Logistics();
+        $response = $logistics->createOrder($order);
+        LoggerTool::info(['actionLogistics', $response]);
+        if ($response['success'] == 1) {
+            // 更新db中订单推送成功状态
+            if (Order::updateAll(['SEND_AT' => time()], 'SN=:SN', [':SN' => $orderSn])) {
+                return static::notice($response);
+            } else {
+                return static::notice($orderSn . ' 推送wst系统成功, 更新状态失败');
+            }
+        }
+
+        return static::notice($orderSn . ' 推送wst系统失败');
     }
 
+    /**
+     * @throws HttpException
+     * @throws \Exception
+     */
+    public function actionLogisticsAuto()
+    {
+        $createdAtStart = strtotime('yesterday');
+        $createdAtEnd = strtotime(date('Y-m-d')) - 1;
+        // 早5点推送,前一天0-24点的订单
+        $orderList = Order::find()
+            ->where(
+                '(CREATED_AT BETWEEN :CREATED_AT_START AND :CREATED_AT_END) AND STATUS=:STATUS AND SEND_AT=:SEND_AT AND PAY_TYPE=:PAY_TYPE',
+                [
+                    ':CREATED_AT_START' => $createdAtStart,
+                    ':CREATED_AT_END' => $createdAtEnd,
+                    ':STATUS' => \Yii::$app->params['orderStatus']['paid']['value'],
+                    ':SEND_AT' => 0,
+                    'PAY_TYPE' => 'online',
+                ]
+            )
+            ->asArray()
+            ->all();
+
+        $orderSnSuccess = [];
+        $orderSnFailed = [];
+        $logistics = new Logistics();
+        foreach ($orderList as $order) {
+            // 发送wst仓库系统
+            $response = $logistics->createOrder($order);
+            LoggerTool::warning($response);
+            if ($response['success'] == 1) {
+                // 写入mongo
+                Tool::wstOrderCall($response['data']);
+                $orderSnSuccess[] = $order['SN'];
+            } else {
+                $orderSnFailed[] = $order['SN'];
+            }
+
+            $orderSnSuccess[] = $response;
+        }
+
+
+        // 更新db中订单推送成功状态
+        if (count($orderSnSuccess) > 0) {
+            Order::updateAll(['SEND_AT' => time()], 'SN IN (:SN)', [':SN' => implode("', '", $orderSnSuccess)]);
+        }
+        if (count($orderSnFailed) > 0) {
+            // 发送预警通知
+
+        }
+
+        return static::notice(sprintf('wstLogisticsAutoSend success order count{%d}, orderSN[%s]; failed count{%d}, orderSN[%s]', count($orderSnSuccess), implode(', ', $orderSnSuccess), count($orderSnFailed), implode(', ', $orderSnFailed)));
+    }
 }

+ 12 - 0
frontendEle/src/router/index.js

@@ -90,6 +90,18 @@ export const constantRouterMap = [
                     ],
                 },
             },
+            {
+              path: '/shop/order-overseas',
+              component: _import('shop/order-overseas'),
+              name: 'order-overseas',
+              meta: {
+                title: '海外商品结算',
+                breadcrumb: [
+                  {title: '首页', path: '/dashboard/index'},
+                  {title: '商城管理', path: '/shop/dec-order-list'}
+                ],
+              },
+            },
             {
                 path: '/shop/goods-detail/:id',
                 component: _import('shop/goods-detail'),

+ 10 - 0
frontendEle/src/views/config/receive-address-edit.vue

@@ -34,6 +34,12 @@
           </template>
           <el-input v-model="form.address"></el-input>
         </el-form-item>
+        <el-form-item>
+          <template slot="label">
+            邮政编码
+          </template>
+          <el-input v-model="form.zipCode"></el-input>
+        </el-form-item>
         <el-form-item>
           <template slot="label">
             是否默认
@@ -73,6 +79,7 @@
           mobile: '',
           areaSelected: [],
           address: '',
+          zipCode: '',
           isDefault: false,
         },
         loading: true,
@@ -90,6 +97,7 @@
           city: this.form.areaSelected[1] ? this.form.areaSelected[1] : '',
           county: this.form.areaSelected[2] ? this.form.areaSelected[2] : '',
           address: this.form.address,
+          zipCode: this.form.zipCode,
           isDefault: this.form.isDefault,
         }
         if (this.$route.name === 'config_receive-address-edit'){
@@ -102,6 +110,7 @@
             city: this.form.areaSelected[1] ? this.form.areaSelected[1] : '',
             county: this.form.areaSelected[2] ? this.form.areaSelected[2] : '',
             address: this.form.address,
+            zipCode: this.form.zipCode,
             isDefault: this.form.isDefault,
           }
         }
@@ -128,6 +137,7 @@
           this.form.areaSelected[1] = response.CITY
           this.form.areaSelected[2] = response.COUNTY
           this.form.address = response.ADDRESS
+          this.form.zipCode = response.ZIP_CODE
           this.form.isDefault = response.IS_DEFAULT === '1' ? true : false
           this.loading = false
         })

+ 2 - 1
frontendEle/src/views/config/receive-address-list.vue

@@ -4,10 +4,11 @@
       <el-table class="table-box" ref="multipleTable" :data="tableData" stripe style="width: 100%;">
         <el-table-column label="收货人" prop="CONSIGNEE" width="150"></el-table-column>
         <el-table-column label="手机号码" prop="MOBILE" width="150"></el-table-column>
+        <el-table-column label="邮编" prop="ZIP_CODE" width="120"></el-table-column>
         <el-table-column label="省/市" prop="PROVINCE_NAME" width="150"></el-table-column>
         <el-table-column label="市/区" prop="CITY_NAME" width="150"></el-table-column>
         <el-table-column label="区/县" prop="COUNTY_NAME" width="150"></el-table-column>
-        <el-table-column label="详细地址" prop="ADDRESS" ></el-table-column>
+        <el-table-column label="详细地址" prop="ADDRESS"></el-table-column>
         <el-table-column label="是否默认" width="100">
           <template slot-scope="scope">
             {{scope.row.IS_DEFAULT === '1' ? '是' : '否'}}

+ 47 - 10
frontendEle/src/views/shop/index.vue

@@ -16,6 +16,9 @@
 
                 </el-table-column>
 
+
+                <el-table-column label="商品来源" prop="CATE"></el-table-column>
+
                 <el-table-column label="图片" >
                      <template slot-scope="scope">
                         <el-image style="width: 100px; height: 100px" :src="tool.getLocaleLink(scope.row.COVER, '/files/')" :preview-src-list="[tool.getLocaleLink(scope.row.COVER, '/files/')]"></el-image>
@@ -45,12 +48,11 @@
 </template>
 
 <script>
-    import network from '@/utils/network'
-    import baseInfo from '@/utils/baseInfo'
-    import store from '@/utils/vuexStore'
-    import tool from '@/utils/tool'
-    import Pagination from '@/components/Pagination'
-    export default {
+import network from '@/utils/network'
+import tool from '@/utils/tool'
+import Pagination from '@/components/Pagination'
+
+export default {
         name: "index",
         components: {Pagination},
         mounted() {
@@ -74,6 +76,7 @@
                 is_go_order:true,
                 numList: [],
                 selectLock:false,
+                cate: [],
             }
         },
         watch: {
@@ -81,11 +84,26 @@
                 this.getData()
             }
         },
+        created() {
+          // network.getData('shop/logistics').then(response => {
+          //   console.log(response);
+          // }).catch(err => {
+          //   console.log(err);
+          // })
+        },
         methods: {
             handleChange(val){
                 console.log(val);
             },
             goToAccounts() {
+                if (this.cate.length > 1) {
+                  this.$message({
+                    message: '海内商品、海外商品只能选择一种',
+                    type: 'warning'
+                  })
+                  return false
+                }
+
                 this.is_go_order=true;
                 // this.multipleSelection.map((item,index)=>{
                 //     item.chose_num = parseInt(item.chose_num);
@@ -97,11 +115,10 @@
                 //         this.is_go_order=false
                 //     }
                 // });
-                console.log('this.multipleSelection',this.multipleSelection);
                 setTimeout(()=>{
                     if(this.is_go_order){
                         sessionStorage.setItem('order_goods',JSON.stringify(this.multipleSelection))
-                        this.$router.push({path: `/shop/order`})
+                        this.$router.push(this.cate[0] == '1' ? {path: `/shop/order`} : {path: `/shop/order-overseas`})
                     }
                 },0)
 
@@ -116,18 +133,38 @@
             handleSelectionChange(val) {
                 if( this.selectLock ) return;
                 let idx = -1,num;
+                this.cate = []
+                let cateList = []
                 for(let i in this.tableData){
                     for(let v in val){
                         if(val[v].ID==this.tableData[i].ID){
                             idx = i;
                             num = this.numList[idx];
                             val[v]["chose_num"] = num;
+                            cateList.push(val[v].CATE_ID)
                             break;
                         }
                     }
                 }
-                this.multipleSelection[this.currentPage] = val;
-                console.log(this.multipleSelection);
+
+                for (let i = 0; i < cateList.length; i++) {
+                    if (cateList.indexOf(cateList[i]) === i) {
+                       this.cate.push(cateList[i])
+                    }
+                }
+
+                if ((this.cate.length > 1)) {
+                    // 海内/海外商品只能选择一种
+                    if (this.cate.length > 1) {
+                        this.$message({
+                          message: '海内商品、海外商品只能选择一种',
+                          type: 'warning'
+                        })
+                        return false
+                    }
+                }
+
+                this.multipleSelection[this.currentPage] = val
             },
             handleInputNumber(val, row){
                 let pageList = this.multipleSelection[this.currentPage];

ファイルの差分が大きいため隠しています
+ 32 - 25
frontendEle/src/views/shop/order-list.vue


+ 462 - 0
frontendEle/src/views/shop/order-overseas.vue

@@ -0,0 +1,462 @@
+<template>
+    <div v-loading="loading">
+        <div class="white-box">
+            <div class="table">
+                <el-table
+                    :data="goods"
+                    style="width: 100%"
+                    show-summary
+                    :summary-method="getSummaries">
+                     <el-table-column label="商品名称" prop="GOODS_NAME">
+                    </el-table-column>
+
+                  <el-table-column label="图片" >
+                    <template slot-scope="scope">
+                      <el-image style="width: 100px; height: 100px" :src="tool.getLocaleLink(scope.row.COVER, '/files/')" :preview-src-list="[tool.getLocaleLink(scope.row.COVER, '/files/')]"></el-image>
+                    </template>
+                  </el-table-column>
+
+                    <el-table-column label="会员价格" prop="member_price">
+                    </el-table-column>
+                    <el-table-column label="会员PV" prop="member_pv">
+                    </el-table-column>
+                    <el-table-column label="数量" prop="chose_num">
+
+                    </el-table-column>
+                    <el-table-column label="合计金额" prop="member_price_plus">
+
+                    </el-table-column>
+                    <el-table-column label="合计PV" prop="member_pv_plus"></el-table-column>
+                </el-table>
+            </div>
+
+            <div class="box address_box">
+                <div style="min-width: 65px;">收件人:</div>
+                <div class="sum" style="margin-top: 5px; margin-bottom: 5px;">
+                  <div class="sum_box">
+                    <div style="min-width: 70px; font-size: 14px; margin-right: 0;">真实姓名<span style="color: red;">*</span></div>
+                    <div><el-input size="small" v-model="consigneeRealName" style="min-width: 150px;"></el-input></div>
+                  </div>
+                  <div class="sum_box" style="margin-top: 5px;">
+                    <div style="min-width: 70px; font-size: 14px; margin-right: 0;">身份证号<span style="color: red;">*</span></div>
+                    <div><el-input size="small" v-model="consigneeIdNo" style="min-width: 150px;"></el-input></div>
+                  </div>
+                </div>
+            </div>
+            <div class="address_box">
+                请选择收货地址:
+                <el-radio-group v-model="addressId" @change='choseAddress'>
+                    <div v-for="(item , index) in all_address" :key='index' class="address">
+                        <el-radio :label="item.ID" >
+                            {{item.PROVINCE_NAME}}省{{item.CITY_NAME}}市{{item.COUNTY_NAME}}县&nbsp;&nbsp;&nbsp;&nbsp;
+                            详细地址:{{item.ADDRESS}}
+                            邮编:{{item.ZIP_CODE}}
+                            手机号码:{{item.MOBILE}}
+                        </el-radio>
+                    </div>
+                </el-radio-group>
+            </div>
+            <div class="address_box">
+                请选择支付方式:
+                <el-radio-group v-model="payType" @change='chosePayType'>
+                    <div v-for="(item , index) in payList" :key='index' class="address">
+                        <el-radio :label="item.type" >
+                            {{item.name}}
+                        </el-radio>
+                    </div>
+                </el-radio-group>
+            </div>
+            <div class="box address_box">
+                合计:
+                <div class="sum">
+                    <div class="sum_box">
+                        <div>实付金额</div>
+                        <div>¥{{ cashSum }}元</div>
+                    </div>
+                </div>
+            </div>
+            <div>
+                <el-button @click="goToAccounts()" :loading="submitButtonStat">去结算</el-button>
+            </div>
+        </div>
+
+      <el-dialog title="支付" v-if="visible" :visible.sync="visible" width="450px" :before-close="handleClose">
+        <div class="app-container">
+          <div v-loading="loading" class="white-box">
+            <form method="post" name="ePayment" :action="actionUrl">
+              <input type="hidden" name="MerchantCode" v-bind:value="form.MerchantCode"></input>
+              <input type="hidden" name="PaymentId" v-bind:value="form.PaymentId"></input>
+              <input type="hidden" name="RefNo" v-bind:value="form.RefNo"></input>
+              <input type="hidden" name="Amount"  v-bind:value="form.Amount"></input>
+              <input type="hidden" name="Currency"  v-bind:value="form.Currency"></input>
+              <input type="hidden" name="ProdDesc"  v-bind:value="form.ProdDesc"></input>
+              <input type="hidden" name="UserName"  v-bind:value="form.UserName"></input>
+              <input type="hidden" name="UserEmail" v-bind:value="form.UserEmail"></input>
+              <input type="hidden" name="UserContact" v-bind:value="form.UserContact"></input>
+              <!-- <input type="hidden" name="Remark"  value="gfdfgd"></input> -->
+              <input type="hidden" name="Lang"  v-bind:value="form.Lang"></input>
+              <input type="hidden" name="SignatureType" value="SHA256"></input>
+              <input type="hidden" name="Signature" v-bind:value="form.Signature"></input>
+              <input type="hidden" name="ResponseURL" v-bind:value="form.ResponseURL"></input>
+              <input type="hidden" name="BackendURL" v-bind:value="form.BackendURL"></input>
+              <span style="display: inline-block; margin: 0 auto;">因汇率波动,实际支付金额可能会有浮动</span><br /><br />
+              <input type="submit" value="支 付" name="Submit" class="payBtn"></input>
+            </form>
+          </div>
+        </div>
+      </el-dialog>
+
+      <el-dialog title="支付成功" :visible.sync="payDialog" :show-close="false" width="320px" :close="handleOrderList">
+        <el-result icon="success" title="支付成功">
+          <template slot="extra">
+            <span style="color: #008efa; font-size: 30px;">{{ countdown }}</span>
+          </template>
+        </el-result>
+      </el-dialog>
+
+      <el-dialog title="提示" :visible.sync="IDCardDialog" width="350px">
+        <el-result icon="warning" title="">
+          <template slot="extra">
+            <span style="font-size: 15px;">购买跨境商品必须上传<span style="color: red;">收件人身份信息</span>,如已上传请忽略</span><br /><br />
+            <el-button type="primary" size="medium" @click="uploadIDCard">去上传</el-button>
+          </template>
+        </el-result>
+      </el-dialog>
+    </div>
+</template>
+
+<script>
+ import network from '@/utils/network'
+ import tool from '@/utils/tool'
+  export default{
+      name: 'order-overseas',
+      data() {
+          return {
+              loading: true,
+              tool: tool,
+              goods:[],
+              payList:[],
+              all_address:[],
+              balance:{},
+              addressId:'',
+              payType:'',
+              pointsSum:'',
+              cashSum:'',
+              freight:'',
+              pointFreight:'',
+              goodsId:'',
+              goodsNum:'',
+              payPassword:'',
+              submitButtonStat: false,
+              visible: false,
+              payLoading: false,
+              payDialog: false,
+              countdown: 5,
+              actionUrl: '',
+              ePaymentShow: false,
+              form: {
+                MerchantCode: '',
+                PaymentId: 2,
+                RefNo: '',
+                Amount: '',
+                Currency: '',
+                ProdDesc: '',
+                UserName: '',
+                UserEmail: '',
+                UserContact: '',
+                Remark: '',
+                Lang: 'UTF-8',
+                SignatureType: 'SHA256',
+                Signature: '',
+                ResponseURL: '',
+                BackendURL: '',
+              },
+              consigneeRealName: '',
+              consigneeIdNo: '',
+              IDCardDialog: false,
+              IDCardWebsite: 'http://t.taoplus.cc/Home/UploadIDCard',
+          }
+      },
+      created() {
+          this.getShowCart();
+          let option= sessionStorage.getItem('order_goods');
+          if(option){
+              let pageGoodsList = JSON.parse(option);
+              let pageList;
+              for( let i in pageGoodsList ) {
+                  pageList =  pageGoodsList[i];
+                  if (!pageList) continue;
+
+                  pageList.map((pageData, index)=>{
+                      if( Number(pageData.chose_num) > 0 ) {
+                        let discount = pageData.DISCOUNT / 100;
+                        pageData.member_price = Math.round(pageData.SELL_PRICE * discount * 100) / 100;
+                        pageData.member_price_plus = Math.round(pageData.SELL_PRICE * Number(pageData.chose_num) * discount * 100) / 100;
+
+                        pageData.member_pv = Math.round(pageData.PRICE_PV * discount * 100) / 100;
+                        pageData.member_pv_plus = Math.round(pageData.PRICE_PV * Number(pageData.chose_num) * discount * 100) / 100;
+
+                        this.goods.push(pageData)
+                      }
+                  })
+              }
+
+              this.getSumMoney();
+          }
+          // 展示上传身份证页面
+          this.IDCardDialog  = true
+      },
+      methods:{
+          getSummaries(param) {
+            const { columns, data } = param;
+            const sums = [];
+            columns.forEach((column, index) => {
+              if (index === 0) {
+                sums[index] = '合计';
+                return;
+              }
+              const values = data.map(item => Number(item[column.property]));
+              if ((!values.every(value => isNaN(value))) && [4, 5, 6].includes(index)) {
+                sums[index] = values.reduce((prev, curr) => {
+                  const value = Number(curr);
+                  if (!isNaN(value)) {
+                    return prev + curr;
+                  } else {
+                    return prev;
+                  }
+                }, 0);
+              }
+            });
+
+            return sums;
+          },
+          goToAccounts(){
+              // 海外商品收货地址中必须包含邮编
+              let address = ''
+              this.all_address.map(item => {
+                if (item.ID === this.addressId) {
+                  address = item
+                }
+              })
+              // if (address.ZIP_CODE.length < 1) {
+              //   this.$message({
+              //     message: '收货地址中没有邮政编码',
+              //     type: 'error'
+              //   })
+              //   return false
+              // }
+
+              // 必须填写真实姓名和身份证号码
+              if (((this.consigneeIdNo.trim()).length <= 0) || ((this.consigneeRealName.trim()).length <= 0)) {
+                  this.$message({
+                    message: '收件人真实姓名和身份证号码必须填写',
+                    type: 'error'
+                  })
+                  return false
+              }
+
+              this.submitButtonStat = true
+              const data = {
+                addressId: this.addressId,
+                payType: this.payType,
+                goodsId: this.goodsId,
+                goodsNum: this.goodsNum,
+                consigneeIdNo: this.consigneeIdNo,
+                consigneeRealName: this.consigneeRealName,
+              }
+              this.$confirm(`确定要购买商品吗?`, '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning',
+                customClass: 'msgbox',
+              }).then(() => {
+                network.postData(`shop/sure-approach-order`, data).then(response => {
+                  this.submitButtonStat = false
+                  // 唤起支付页面
+                  network.postData(`shop/i-pay88`, { Amount: response.ORDER_AMOUNT, RefNo: response.SN }).then(payment => {
+                    this.actionUrl = payment.transactionUrl
+                    this.form = payment.paymentFields
+
+                    this.visible = true
+                    this.payLoading = false
+                  }).catch(err => {
+                    this.$message({
+                      message: err,
+                      type: 'error'
+                    })
+                    this.submitButtonStat = false
+                  })
+                })
+              }).catch(err => {
+                this.$message({
+                  message: err,
+                  type: 'error'
+                })
+                this.submitButtonStat = false
+              })
+          },
+          getSumMoney(){
+              let cash_plus_sum=[];
+              let goodsId=[];
+              let goodsNum=[];
+              let choseNum = 0;
+              this.goods.map((item,index)=>{
+                  choseNum = Number(item.chose_num);
+                  if( choseNum > 0 ) {
+                      cash_plus_sum.push(item.SELL_PRICE * choseNum*(item.DISCOUNT/100));
+                      goodsId.push(item.ID);
+                      goodsNum.push(choseNum);
+                  }
+              })
+              this.goodsNum=goodsNum;
+              this.goodsId=goodsId;
+              //增加运费03-12yuan
+              let payAmount = tool.sum(cash_plus_sum);
+              this.pointFreight = 0
+              // if(payAmount>=300){
+              //   this.pointFreight = this.freight = 0;
+              // }else{
+              //   this.pointFreight = this.freight = 15;
+              // }
+
+            this.pointsSum = this.cashSum = tool.formatPrice(tool.sum(cash_plus_sum) + this.freight) ;
+          },
+          getShowCart(){
+              network.getData('shop/show-cart')
+              .then(response=>{
+                  this.loading=false;
+                  this.all_address=response.allAddress;
+                  this.all_address.map((item,index)=>{
+                      if(item.IS_DEFAULT==1){
+                          this.addressId=item.ID
+                      }
+                  })
+                  // 如果没有默认收货地址,则第一个地址默认选中
+                  if (this.all_address.length > 0 && this.addressId.length < 1) {
+                      this.addressId = this.all_address[0].ID
+                  }
+
+                  for(let key in response.payList){
+                    // 如果是海外商品,则只有在线支付
+                    if (key === 'online') {
+                      this.payList.push({type:key, name:response.payList[key].name})
+                      break;
+                    }
+                  }
+
+                //默认设置为第一项付款方式
+                  this.payType=this.payList[0].type;
+              })
+          },
+          chosePayType(type){
+
+          },
+          choseAddress(addressId){
+          },
+          // 关闭支付回调
+          handleClose () {
+           let _this = this
+           _this.$confirm('确认关闭?').then(() => {
+             return network.postData('shop/delete-approach-order', {orderSn: this.form.RefNo}).then(() => {
+               // 关闭支付模态框
+               _this.visible = false
+               // 关闭购物车页面,返回到订单列表页
+               sessionStorage.setItem('order_goods', null)
+               this.$router.push({path: `/shop/index`})
+             })
+           }).catch(() => {
+             // 关闭支付模态框
+             _this.visible = false
+             // 关闭购物车页面,返回到订单列表页
+             sessionStorage.setItem('order_goods', null)
+             this.$router.push({path: `/shop/index`})
+           })
+          },
+          // 关闭支付回调
+          processClose () {
+           // 关闭支付模态框
+           this.visible = false
+           // 关闭购物车页面,返回到订单列表页
+           sessionStorage.removeItem('order_goods')
+           this.$router.push({path: `/shop/index`})
+          },
+          handleOrderList () {
+           this.$router.push({path: `/shop/order-list`})
+          },
+          // 启动倒计时
+          handleCountdown () {
+           // 创建定时器
+           setInterval(() => {
+             // 每隔1秒把time的值减一,赋值给span标签
+             this.countdown--
+             if (this.countdown === 0) {
+               // 倒计时结束,跳转到订单列表
+               this.$router.push({path: `/shop/order-list`})
+             }
+           }, 1000)
+          },
+          // 上传身份证
+          uploadIDCard() {
+            // window.location.href = this.IDCardWebsite
+            window.open(this.IDCardWebsite, '_blank')
+          },
+      }
+  }
+</script>
+<style scoped>
+.address{
+    /* height: 3rem; */
+    line-height: 3.5rem;
+
+}
+.address_box{
+     border-bottom: 1px solid #e3e3e3;
+}
+.sum{
+    display: inline-block;
+}
+.box{
+    margin: 1rem 0;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    padding-bottom: 1rem;
+}
+.sum_box{
+    display: flex;
+    margin-left: 1rem;
+}
+.sum_box > div{
+    line-height: 2rem;
+}
+.sum_box > div:nth-child(1){
+    margin-right: 1rem;
+}
+.payBtn {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  text-align: center;
+  padding: 10px 10px;
+  font-size: 14px;
+  border-radius: 4px;
+  color: #fff;
+  background-color: #409eff;
+  border: 1px solid #DCDFE6;
+  -webkit-appearance: none;
+  box-sizing: border-box;
+  margin-left: 150px;
+}
+@media (max-width: 720px) {
+  .msgbox{
+    width: 320px !important;
+    margin-top: 15px;
+  }
+}
+</style>
+<style>
+.myclass {
+    width:260px;
+}
+</style>

+ 3 - 0
frontendEle/src/views/shop/order.vue

@@ -245,6 +245,9 @@
                         }
                     })
                     for(let key in response.payList){
+                        if (key === 'online') {
+                          continue
+                        }
                         this.payList.push({type:key,name:response.payList[key].name})
                     }
                     // console.log(this.payList)

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません