فهرست منبع

Merge branch 'rfill/time-out-transfer' into raccount

stanley-king 2 ماه پیش
والد
کامیت
3332b529dc

+ 7 - 0
ReadMe/time-out-transfer.MD

@@ -0,0 +1,7 @@
+
+## 数据库更新
+
+alter table lrlz_merchant
+add timeout_transfer_cfg varchar(256) null comment '卡单预回调设置';
+
+#

+ 33 - 0
admin/control/merchant.php

@@ -1172,6 +1172,17 @@ class merchantControl extends SystemControl
                     showMessage('预回调设置:转发通道不存在!');
                 }
             }
+
+            if(intval($_POST['timeout_transfer_opened']) === 1) {
+                if($_POST['timeout_transfer_mchid'] == $mchid) {
+                    showMessage('预回调设置:转发通道不可为自身!');
+                }
+                $tm_transfer_merchant = $model_merchant->getMerchantInfo(['mchid' => $_POST['timeout_transfer_mchid']], '*', true);
+                if(empty($tm_transfer_merchant)) {
+                    showMessage('预回调设置:转发通道不存在!');
+                }
+            }
+
             if(!empty($_POST['segment'])) {
                 $segment = explode(',', $_POST['segment']);
                 foreach ($segment as $str) {
@@ -1195,6 +1206,15 @@ class merchantControl extends SystemControl
                 return serialize($transfer_cfg);
             };
 
+            $timeout_transfer_data = function () {
+                $transfer_cfg = [];
+                $transfer_cfg['transfer_opened'] = intval($_POST['timeout_transfer_opened']) ?? 0;
+                $transfer_cfg['transfer_mchid'] = intval($_POST['timeout_transfer_mchid']) ?? 0;
+                $transfer_cfg['transfer_maxtm']  = intval($_POST['timeout_transfer_maxtm']) ?? 0;
+
+                return serialize($transfer_cfg);
+            };
+
             $intercept_data = function (){
                 $intercept_cfg['is_transfer'] = intval($_POST['is_transfer']) === 1;
                 if (!empty($_POST['card_states'])) {
@@ -1247,6 +1267,7 @@ class merchantControl extends SystemControl
             $update['intercept_cfg'] = $intercept_data();
             $update['retry_times_cfg'] = $retry_times_cfg;
             $update['transfer_cfg'] = $transfer_data();
+            $update['timeout_transfer_cfg'] = $timeout_transfer_data();
 
             if(!empty($_POST['ips'])) {
                 $ips = explode(',', trim($_POST['ips']));
@@ -1276,6 +1297,7 @@ class merchantControl extends SystemControl
             Tpl::output('intercept', $this->merchant_intercept($merchant));
             Tpl::output('retry_times', $this->merchant_retry_times($merchant));
             Tpl::output('transfer_cfg', $this->transfer_cfg($merchant));
+            Tpl::output('timeout_transfer_cfg', $this->timeout_transfer_cfg($merchant));
             Tpl::output('merchant', $merchant);
             Tpl::output('quality', $quality);
             Tpl::output('card_state', mtopcard\CardState);
@@ -1345,4 +1367,15 @@ class merchantControl extends SystemControl
         }
         return $transfer_cfg;
     }
+
+    private function timeout_transfer_cfg($merchant)
+    {
+        $transfer_cfg = $merchant['timeout_transfer_cfg'];
+        if (empty($transfer_cfg)) {
+            $transfer_cfg = ['transfer_opened' => 0, 'transfer_mchid' => 0, 'transfer_maxtm' => 0];
+        } else {
+            $transfer_cfg = unserialize($transfer_cfg);
+        }
+        return $transfer_cfg;
+    }
 }

+ 38 - 1
admin/templates/default/merchant.ctl.php

@@ -199,7 +199,7 @@
 
 
                 <tr class="noborder">
-                    <td colspan="2" class="required"><label class="validation">预回调设置:</label></td>
+                    <td colspan="2" class="required"><label class="validation">保成功率预回调设置:</label></td>
                 </tr>
 
                 <tr class="noborder">
@@ -262,6 +262,43 @@
                 </tr>
 
                 <tr class="noborder">
+                    <td colspan="2" class="required"><label class="validation">卡单预回调设置:</label></td>
+                </tr>
+
+                <tr class="noborder">
+                    <td colspan="4" class="required mleft"><label style="display: inline-block;width: 60px;" class="mleft" for="name">是否开启:</label>
+                        <label>
+                            <input type="radio" name="timeout_transfer_opened" value="1" <?php if ($output['timeout_transfer_cfg']['transfer_opened'] === 1) {
+                                echo 'checked';
+                            } ?>>
+                        </label>是
+                        <label>
+                            <input type="radio" name="timeout_transfer_opened" value="0" <?php if ($output['timeout_transfer_cfg']['transfer_opened'] === 0) {
+                                echo 'checked';
+                            } ?>>
+                        </label>否
+                    </td>
+                </tr>
+
+                <tr class="noborder">
+                    <td colspan="2" class="required mleft"><label style="display: inline-block;" class="mleft" for="name">超时订单转发到机构ID:</label>
+                        <input type="text" name="timeout_transfer_mchid" value="<?php echo $output['timeout_transfer_cfg']['transfer_mchid']; ?>" /><span style="color:red;">注:该通道需要单独创建,帐号不能给下游。</span>
+                    </td>
+                </tr>
+
+                <tr class="noborder">
+                    <td colspan="2" class="required mleft"><label style="display: inline-block;" class="mleft" for="name">超时时长:</label>
+                        <input type="text" name="timeout_transfer_maxtm" value="<?php echo $output['timeout_transfer_cfg']['transfer_maxtm']; ?>" /> <span>(秒)(超过后自动回调成功,失败回来的单子继续补充到成功.)</span>
+                    </td>
+                </tr>
+
+                <tr class="noborder">
+                    <td>
+                        <hr>
+                    </td>
+                </tr>
+
+                <tr class="noborder">
                     <td colspan="2" class="required"><label class="validation">充值时间和次数设置:</label>
                         <div style="margin-left:149px;">
                             <label style="display: inline-block;">白天秒数(07-23)</label>

+ 28 - 0
data/logic/queue.logic.php

@@ -1405,6 +1405,20 @@ class queueLogic
         }
     }
 
+    public function NotifyMerchantSuccess($params)
+    {
+        $mchid = intval($params['mchid']);
+        $mch_order = $params['mch_order'] ?? false;
+
+        if($mchid <= 0 || empty($mch_order)) {
+            return callback(false, "NotifyMerchantSuccess 参数 $mchid : $mch_order 错误");
+        }
+        else {
+            refill\util::push_notify_merchant_success($mchid, $mch_order);
+            return callback(true, '成功放入通知队列', ['mchid' => $mchid, 'mch_order' => $mch_order]);
+        }
+    }
+
     public function QueryRefillState($params)
     {
         $order_id = intval($params['order_id']);
@@ -1444,6 +1458,20 @@ class queueLogic
         }
     }
 
+    public function QueryMchOrderTimeout($params)
+    {
+        $mchid = intval($params['mchid']);
+        $mch_order = $params['mch_order'];
+
+        if($mchid <= 0 or empty($mch_order)) {
+            return callback(false, "QueryMchOrderTimeout 参数错误 mchid=$mchid mch_order=$mch_order");
+        }
+        else {
+            refill\util::push_query_timeout($mchid, $mch_order);
+            return callback(true, '成功放入通知队列', ['mchid' => $mchid, 'mch_order' => $mch_order]);
+        }
+    }
+
     public function AysncAddDispatcher($params)
     {
         $method = $params['method'];

+ 2 - 0
global.php

@@ -127,6 +127,8 @@ define('ORDER_STATE_NOEXIST', 60);
 
 define('ORDER_STATE_HANDLED', 70);
 
+define('ORDER_STATE_TIMEOUT', 80);
+
 //未付款订单,自动取消的分钟
 define('VRORDER_AUTO_CANCEL_MINUTE', 10); //
 //未付款订单,自动取消的天数

+ 69 - 0
helper/refill/RefillBase.php

@@ -268,6 +268,7 @@ class RefillBase
             util::monitor_callback($mchid, $spec, $card_type, $refill_info['mch_amount'], $refill_info['channel_amount'], $success, $order_time);
             util::onEventComplete($refill_info, $order_info, $success);
             util::pop_queue_order($mchid,$mch_order,$order_time);
+
             QueueClient::push("NotifyMerchantComplete", ['order_id' => $order_id,'manual' => false]);
 
             return true;
@@ -797,6 +798,49 @@ class RefillBase
         }
     }
 
+    //发预回调通知消息
+    public function notify_merchant_success($mchid, $mch_order)
+    {
+        $part = util::part_notify();
+        $mod_refill = Model('refill_order');
+        $refill_info = $mod_refill->partition($part)->getOrderInfo(['mch_order' => $mch_order,'mchid' => $mchid,'inner_status' => 0]);
+        if(empty($refill_info)) {
+            return [false, "refill_order table cannot find order when mchid=$mchid mch_order=$mch_order"];
+        }
+
+        $order_sn = $refill_info['order_sn'];
+        $order_id = $refill_info['order_id'];
+
+        $vr_order = Model('vr_order');
+        $order_info = $vr_order->partition($part)->getOrderInfo(['order_sn' => $order_sn]);
+
+        if (empty($order_info)) {
+            return [false, "refill_order table cannot find order when order_sn=$order_sn."];
+        }
+
+        $resp = $this->mPolicy->notify_success($order_info,$refill_info);
+        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        if ($resp) {
+            $mod_refill->partition($part)->edit($order_id, ['mch_notify_state' => 1, 'mch_notify_times' => ['exp', 'mch_notify_times+1']]);
+            return [true, ""];
+        }
+        else
+        {
+            $mod_refill->partition($part)->edit($order_id, ['mch_notify_times' => ['exp', 'mch_notify_times+1']]);
+            $times = $refill_info['mch_notify_times'] + 1;
+
+            if ($times > 100) {
+                $mod_refill->partition($part)->edit($order_id, ['mch_notify_state' => 2]);
+            } else {
+                $period = 5;
+                QueueClient::async_push("NotifyMerchantSuccess", ['mchid' => $mchid, 'mch_order' => $mch_order], $period);
+
+            }
+
+            return [false, "通知{$times}次,失败."];
+        }
+    }
+
     public function query($order_id)
     {
         $mod_order = Model('vr_order');
@@ -946,6 +990,31 @@ class RefillBase
         return $ret;
     }
 
+    public function query_timeout($mchid, $mch_order)
+    {
+        $has_tmout = transfer_timeout::instance()->has_tmout($mchid, $mch_order);
+        if($has_tmout)
+        {
+            //标记为超时,并且预回调成功。
+            util::push_queue_order($mchid, $mch_order, ORDER_STATE_TIMEOUT);
+
+            $part = util::part_notify();
+            $mod_refill = Model('refill_order');
+            $refill_info = $mod_refill->partition($part)->getOrderInfo(['mch_order' => $mch_order,'mchid' => $mchid,'inner_status' => 0]);
+
+            if(empty($refill_info)) {
+                return false;
+            }
+
+            $order_id = $refill_info['order_id'];
+            $mod_refill->partition($part)->edit($order_id, ['notify_time' => time()]);
+
+            QueueClient::push("NotifyMerchantSuccess", ['mchid' => $mchid, 'mch_order' => $mch_order]);
+        }
+
+        return true;
+    }
+
     public function query_auto($order_id,$query_times)
     {
         $perioder = function ($times)

+ 1 - 0
helper/refill/XYZRefillFactory.php

@@ -35,6 +35,7 @@ require_once(BASE_HELPER_PATH . '/refill/policy/match/type_spec_match.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/overload_assigner.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/interceptor.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/transfer.php');
+require_once(BASE_HELPER_PATH . '/refill/policy/transfer_timeout.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/mchannel.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/third_helper.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/ch_times.php');

+ 0 - 10
helper/refill/policy/transfer.php

@@ -111,14 +111,4 @@ class transfer
     {
         return $this->mMchid2Infos;
     }
-
-    public function transfer_ids()
-    {
-        $result = [];
-        foreach ($this->mMchid2Infos as $item) {
-            $result[] = $item['transfer_mchid'];
-        }
-
-        return $result;
-    }
 }

+ 148 - 0
helper/refill/policy/transfer_timeout.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace refill;
+
+use QueueClient;
+
+class transfer_timeout
+{
+    private $mMchid2Infos;
+    public function __construct()
+    {
+        $this->load();
+    }
+
+    private static $stInstance = null;
+    public static function instance()
+    {
+        if (self::$stInstance == null) {
+            self::$stInstance = new transfer_timeout();
+        }
+
+        return self::$stInstance;
+    }
+
+    public function load()
+    {
+        $mch_checker = function ($mchid) {
+            $item = Model()->table('merchant')->field('mchid,admin_id')->where(['mchid' => $mchid])->find();
+            return intval($item['admin_id']);
+        };
+
+        $this->mMchid2Infos = [];
+        $i = 0;
+        while (true)
+        {
+            $start = $i * 1000;
+            $items = Model()->table('merchant')->field('mchid,timeout_transfer_cfg')->order('mchid asc')->limit("{$start},1000")->select();
+            if(empty($items)) {
+                return;
+            }
+            $i++;
+
+            foreach ($items as $item)
+            {
+                $cfg = $item['timeout_transfer_cfg'];
+
+                if(empty($cfg)) {
+                    continue;
+                }
+
+                $cfg = unserialize($cfg);
+                if($cfg === false) {
+                    continue;
+                }
+
+                $mchid = intval($item['mchid']);
+                $opened = intval($cfg['transfer_opened']);
+                $tmchid = intval($cfg['transfer_mchid']);
+                $maxtm = intval($cfg['transfer_maxtm']);
+
+                if ($opened == 1 && $tmchid > 0 && $tmchid != $mchid && $maxtm > 0)
+                {
+                    $admin_id = $mch_checker($tmchid);
+                    if ($admin_id > 0) {
+                        $this->mMchid2Infos[$mchid] = ['transfer_mchid' => $tmchid, 'admin_id' => $admin_id, 'transfer_maxtm' => $maxtm];
+                    }
+                }
+            }
+        }
+    }
+
+    private function time_out($mchid)
+    {
+        return $this->mMchid2Infos[$mchid]['transfer_maxtm'];
+    }
+
+    public function need_monitor($mchid)
+    {
+        if(array_key_exists($mchid,$this->mMchid2Infos)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function has_tmout($mchid,$mch_order)
+    {
+        $need_monitor = $this->need_monitor($mchid);
+        if(!$need_monitor) {
+            return false;
+        }
+
+        $order_state = util::query_queue_order($mchid,$mch_order);
+        if($order_state === false || $order_state == ORDER_STATE_TIMEOUT) {
+            return false;
+        }
+
+        $mod_refill = Model('refill_order');
+        $detail = $mod_refill->get_detail($mchid, $mch_order);
+        if(empty($detail)) {
+            return false;
+        }
+
+        $order_state = intval($detail['order_state']);
+        if($order_state == ORDER_STATE_HANDLED) {
+            return false;
+        }
+
+        $order_time = intval($detail['order_time']);
+        $tmout = $this->time_out($mchid);
+
+        if (time() >= $order_time + $tmout) {
+            return true;
+        } else {
+            $delta = $order_time + $tmout - time();
+            QueueClient::async_push("QueryMchOrderTimeout", ['mchid' => $mchid, 'mch_order' => $mch_order], $delta);
+            return false;
+        }
+    }
+
+    public function monitor(order $order)
+    {
+        $mchid = $order->mchid();
+        $mch_order = $order->mch_order();
+        $tmout = $this->time_out($mchid);
+
+        QueueClient::async_push("QueryMchOrderTimeout", ['mchid' => $mchid, 'mch_order' => $mch_order], $tmout);
+    }
+
+    public function transfer_info($mchid)
+    {
+        if(array_key_exists($mchid,$this->mMchid2Infos))
+        {
+            $transfer_cfg = $this->mMchid2Infos[$mchid];
+            $transfer_mchid = $transfer_cfg['transfer_mchid'];
+            $admin_id = $transfer_cfg['admin_id'];
+
+            return [$transfer_mchid,$admin_id];
+        }
+
+        return [0,0];
+    }
+
+    public function transfers()
+    {
+        return $this->mMchid2Infos;
+    }
+}

+ 45 - 0
helper/refill/policy/xyz/policy.php

@@ -570,6 +570,51 @@ class policy extends ProviderManager implements IPolicy
         return [$params, $sign];
     }
 
+
+    public function notify_success($order_info, $refill_info) : bool
+    {
+        $gen_body = function ($state, $refill_info, $mch_info)
+        {
+
+            $err_msg = '';
+            $osn = "";
+            $params = [
+                "mchid" => $refill_info['mchid'],
+                "order_sn" => $refill_info['mch_order'],
+                "amount" => $refill_info['refill_amount'],
+                "sale_price" => ncPriceFormat($refill_info['mch_amount']),
+                "cardno" => $refill_info['card_no'],
+                "trade_no" => $refill_info['order_sn'],
+                "idcard" => $refill_info['idcard'] ?? "",
+                "card_name" => $refill_info['card_name'] ?? "",
+                'official_sn' => $osn,
+                'message' => $err_msg,
+                "state" => $state];
+
+            $secure_key = $mch_info['secure_key'];
+            $sign = $this->sign($params, $secure_key);
+
+            return [$params, $sign];
+        };
+
+        $state = "SUCCESS";
+        $mchid = $refill_info['mchid'];
+        $mch_info = Model('merchant')->getMerchantInfo(['mchid' => $mchid]);
+
+        [$params, $sign] = $gen_body($state, $refill_info, $mch_info);
+        $params['sign'] = $sign;
+
+        $notify_url = $refill_info['notify_url'];
+        //如果http请求内部,又发出回调自己的请求,在处理进程非动态扩容的情况下,容易造成阻塞.
+        if ($this->is_url($notify_url)) {
+            $resp = http_request($notify_url, $params, 'POST');
+        } else {
+            $resp = RBridgeFactory::instance()->notify($notify_url, $params);
+        }
+
+        return $resp == "SUCCESS" || $resp == 'FAILED';
+    }
+
     private function sms($refill_info)
     {
         $official_sn = $refill_info['official_sn'] ?? "";

+ 23 - 0
helper/refill/util.php

@@ -322,6 +322,18 @@ class util
         }
     }
 
+    public static function push_notify_merchant_success($mchid, $mch_order)
+    {
+        try
+        {
+            $ret = self::push_queue('notify_merchant_success', ['mchid' => $mchid, 'mch_order' => $mch_order]);
+            return $ret !== false;
+        }
+        catch (Exception $ex) {
+            return false;
+        }
+    }
+
     public static function push_query($order_id)
     {
         try
@@ -356,6 +368,17 @@ class util
         }
     }
 
+    public static function push_query_timeout($mchid,$mch_order)
+    {
+        try {
+            $ret = self::push_queue('query_timeout',  ['mchid' => $mchid, 'mch_order' => $mch_order]);
+            return $ret !== false;
+        }
+        catch (Exception $ex) {
+            return false;
+        }
+    }
+
     public static function manual_success($order_id)
     {
         try

+ 28 - 0
mobile/control/refill.php

@@ -507,6 +507,24 @@ class refillControl extends merchantControl
         return $result;
     }
 
+    private function format_success($order_info,$refill_info)
+    {
+        $result = [];
+        $result['mchid'] = $refill_info['mchid'];
+        $result['trade_no'] = $refill_info['order_sn'];
+        $result['order_sn'] = $refill_info['mch_order'];
+        $result['card_no'] = $refill_info['card_no'];
+        $result['card_type'] = $refill_info['card_type'];
+        $result['refill_amount'] = $refill_info['refill_amount'];
+        $result['order_amount'] = $refill_info['mch_amount'];
+        $result['order_time'] = $refill_info['order_time'];
+        $result['success_time'] = $refill_info['notify_time'];
+        $result['official_sn'] = "";
+        $result['order_state'] = ORDER_STATE_SUCCESS;
+
+        return $result;
+    }
+
     public function queryOp()
     {
         $detail_checker = function($mchid, $mch_order)
@@ -534,6 +552,16 @@ class refillControl extends merchantControl
             Log::record("query_state in queue mchid=$mchid mch_order=$mch_order order_state=$order_state" ,Log::DEBUG);
             return self::outsuccess($send);
         }
+        elseif($order_state == ORDER_STATE_TIMEOUT) //超时预回调的单子
+        {
+            $mod_refill = Model('refill_order');
+            $refill_info = $mod_refill->partition(refill\util::part_query())->getOrderInfo(['mch_order' => $mch_order,'mchid' => $mchid,'inner_status' => 0]);
+            $vr_order = Model('vr_order');
+            $order_info = $vr_order->partition(refill\util::part_query())->getOrderInfo(['order_sn' => $refill_info['order_sn']]);
+
+            $result = $this->format_success($order_info, $refill_info);
+            return self::outsuccess($result);
+        }
         else
         {
             $mod_refill = Model('refill_order');

+ 1 - 0
rdispatcher/codispatcher.php

@@ -80,6 +80,7 @@ function subscribe_message(&$quit, &$redis, $channels)
                     if($type == 'channel' || $type == 'merchant') {
                         refill\RefillFactory::instance()->load();
                         refill\transfer::instance()->load();
+                        refill\transfer_timeout::instance()->load();
                         refill\EventManager::instance()->load();
                     }
                     elseif($type == 'mch_profit_ratio') {

+ 8 - 0
rdispatcher/processor.php

@@ -42,6 +42,10 @@ class processor extends queue\ILooper
                     $order_id = intval($params['order_id']);
                     $manual = $params['manual'] ?? false;
                     $this->mProxy->notify_merchant($order_id, $manual);
+                } elseif ($method == 'notify_merchant_success') { //发预回调通知.
+                    $mchid = intval($params['mchid']);
+                    $mch_order = trim($params['mch_order']);
+                    $this->mProxy->notify_merchant_success($mchid, $mch_order);
                 } elseif ($method == 'query') {
                     $order_id = intval($params['order_id']);
                     $this->mProxy->query($order_id);
@@ -52,6 +56,10 @@ class processor extends queue\ILooper
                 } elseif ($method == 'query_net') {
                     $order_id = intval($params['order_id']);
                     $this->mProxy->query_net($order_id);
+                } elseif ($method == 'query_timeout') {
+                    $mchid = intval($params['mchid']);
+                    $mch_order = trim($params['mch_order']);
+                    $this->mProxy->query_timeout($mchid,$mch_order);
                 } elseif ($method == 'manual_success') {
                     $order_id = intval($params['order_id']);
                     $this->mProxy->manual_success($order_id);

+ 53 - 8
rdispatcher/proxy.php

@@ -7,6 +7,7 @@ use refill\Quality;
 
 require_once(BASE_ROOT_PATH . '/helper/model_helper.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/transfer.php');
+require_once(BASE_HELPER_PATH . '/refill/policy/transfer_timeout.php');
 
 class proxy
 {
@@ -79,7 +80,16 @@ class proxy
         return $ret;
     }
 
-    private function transfer(refill\order $order) : bool
+    private function need_transfer_timeout(refill\order $order)
+    {
+        $mchid = $order->mchid();
+        $mch_order = $order->mch_order();
+        $order_state = util::query_queue_order($mchid,$mch_order);
+
+        return $order_state == ORDER_STATE_TIMEOUT;
+    }
+
+    private function transfer(refill\order $order, $time_out = false): bool
     {
         $order_canceler = function ($order_id,$err_msg) {
             $logic_vr_order = Logic("vr_order");
@@ -87,10 +97,15 @@ class proxy
             $logic_vr_order->changeOrderStateCancel($order_info, '', $err_msg, true, true);
         };
 
-        $transfer_order = function (refill\order $order)
+        $transfer_order = function (refill\order $order,$time_out)
         {
             $mchid = $order->mchid();
-            [$trans_mchid,$adminid] = refill\transfer::instance()->transfer_info($mchid);
+
+            if ($time_out) {
+                [$trans_mchid, $adminid] = refill\transfer_timeout::instance()->transfer_info($mchid);
+            } else {
+                [$trans_mchid, $adminid] = refill\transfer::instance()->transfer_info($mchid);
+            }
 
             if($trans_mchid == 0 || $adminid == 0) {
                 return false;
@@ -123,15 +138,18 @@ class proxy
             return false;
         }
 
-        if(!$transfer_order($order)) {
-            $order_canceler($order_id,$errmsg);
+        if (!$transfer_order($order, $time_out)) {
+            $order_canceler($order_id, $errmsg);
             return false;
         }
 
         $order->finish();
         refill\util::pop_queue_order($mchid, $mch_order, $order->order_time());
-        QueueClient::push("NotifyMerchantComplete", ['order_id' => $order_id, 'manual' => false]);
 
+        if(!$time_out) {
+            QueueClient::push("NotifyMerchantComplete", ['order_id' => $order_id, 'manual' => false]);
+        }
+        
         return true;
     }
 
@@ -158,6 +176,7 @@ class proxy
                 refill\util::push_queue_order($mchid, $mch_order, ORDER_STATE_SEND);
                 $mod_refill->partition(util::part_refill($order_time))->edit_detail($mchid,$mch_order,['order_state' => ORDER_STATE_SEND]);
             }
+
         }
         Log::record("proxy::add times={$order->commit_times()} mch_order=$mch_order card_no={$order->card_no()} regin_no={$order->region_no()} org_quality={$order->org_quality()} quality={$order->cur_quality()}",Log::DEBUG);
 
@@ -216,7 +235,10 @@ class proxy
                 refill\util::onEventSubmit($order);
             }
         }
-        elseif($this->need_transfer($order) && $this->transfer($order)) {
+        elseif ($this->need_transfer($order) && $this->transfer($order)) {
+            return true;
+        }
+        elseif ($this->need_transfer_timeout($order) && $this->transfer($order, true)) {
             return true;
         }
 
@@ -260,7 +282,7 @@ class proxy
             return $fError;
         };
 
-        [$errcode, $errmsg, $order_id, $neterr,$net_errno] = refill\RefillFactory::instance()->add($order);
+        [$errcode, $errmsg, $order_id, $neterr, $net_errno] = refill\RefillFactory::instance()->add($order);
         if($errcode !== true)
         {
             //遇到网络错误情况,查询处理
@@ -284,11 +306,26 @@ class proxy
             if ($fError) {
                 return $this->onEerror($order,true, $errmsg);
             }
+        } else {
+            $fError = false;
+        }
+
+        //超时预回调,需要在下订单号对订单进行监控。
+        if(!$fError and $order->first_commit() and $this->need_tmout_monitor($order)) {
+            refill\transfer_timeout::instance()->monitor($order);
         }
 
         return true;
     }
 
+    private function need_tmout_monitor(refill\order $order)
+    {
+        $mchid = $order->mchid();
+        $ret = refill\transfer_timeout::instance()->need_monitor($mchid);
+
+        return $ret;
+    }
+
     private function need_intercept(refill\order $order)
     {
         if($order->is_third()) return false;
@@ -360,6 +397,10 @@ class proxy
     {
         return refill\RefillFactory::instance()->notify_merchant($order_id,$manual);
     }
+    public function notify_merchant_success($mchid, $mch_order)
+    {
+        return refill\RefillFactory::instance()->notify_merchant_success($mchid, $mch_order);
+    }
     public function query($order_id)
     {
         return refill\RefillFactory::instance()->query($order_id);
@@ -373,6 +414,10 @@ class proxy
     {
         return refill\RefillFactory::instance()->query_net($order_id);
     }
+    public function query_timeout($mchid, $mch_order)
+    {
+        return refill\RefillFactory::instance()->query_timeout($mchid, $mch_order);
+    }
     public function manual_success($order_id)
     {
         refill\RefillFactory::instance()->manual_success($order_id);

+ 5 - 0
test/TestRefillTransfer.php

@@ -10,6 +10,7 @@ require_once(BASE_ROOT_PATH . '/global.php');
 require_once(BASE_CORE_PATH . '/lrlz.php');
 require_once(BASE_ROOT_PATH . '/fooder.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/transfer.php');
+require_once(BASE_HELPER_PATH . '/refill/policy/transfer_timeout.php');
 
 class TestRefillTransfer extends TestCase
 {
@@ -21,6 +22,10 @@ class TestRefillTransfer extends TestCase
     public function testLoad()
     {
         refill\transfer::instance()->need_transfer(1088,strtotime('1022-04-27 14:00:00'));
+    }
 
+    public function testOutLoad()
+    {
+        refill\transfer_timeout::instance()->load();
     }
 }

+ 3 - 0
test/TestRefillUtil.php

@@ -34,6 +34,9 @@ class TestRefillUtil extends TestCase
     {
         refill\util::push_queue_order(1092,'abcdefg',30);
         $val = refill\util::query_queue_order(1092,'abcdefg');
+        $y = refill\util::query_queue_order(1092,'fdafdasfdas');
+
+        $x = 1;
     }
 
     public function testWriteCard()