RefillFactory.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. namespace refill;
  3. require_once(BASE_HELPER_PATH . '/refill/IRefill.php');
  4. require_once(BASE_HELPER_PATH . '/refill/IRefillOil.php');
  5. require_once(BASE_HELPER_PATH . '/refill/IRefillPhone.php');
  6. require_once(BASE_HELPER_PATH . '/refill/IRefillCallBack.php');
  7. require_once(BASE_HELPER_PATH . '/mtopcard/mtopcard.php');
  8. require_once(BASE_HELPER_PATH . '/refill/suhc/RefillOil.php');
  9. require_once(BASE_HELPER_PATH . '/refill/suhc/RefillPhone.php');
  10. require_once(BASE_HELPER_PATH . '/refill/suhc/RefillCallBack.php');
  11. require_once(BASE_HELPER_PATH . '/refill/CalcMerchantPrice.php');
  12. require_once(BASE_HELPER_PATH . '/refill/beixt/RefillPhone.php');
  13. require_once(BASE_HELPER_PATH . '/refill/beixt/RefillCallBack.php');
  14. require_once(BASE_HELPER_PATH . '/refill/beixts/RefillPhone.php');
  15. require_once(BASE_HELPER_PATH . '/refill/beixts/RefillCallBack.php');
  16. use Log;
  17. use mtopcard;
  18. use QueueClient;
  19. use member_info;
  20. class RefillFactory
  21. {
  22. static private $stInstance = null;
  23. static public function instance()
  24. {
  25. if (self::$stInstance == null) {
  26. self::$stInstance = new RefillFactory();
  27. }
  28. return self::$stInstance;
  29. }
  30. private $mOilProvider = [];
  31. private $mPhoneProvider = [];
  32. private $mProviderNames = [];
  33. private function __construct()
  34. {
  35. $this->load();
  36. }
  37. public function goods()
  38. {
  39. global $config;
  40. $oil = $this->combine_goods($config['oil_providers']);
  41. $phone = $this->combine_goods($config['phone_providers']);
  42. return array_merge($oil,$phone);
  43. }
  44. private function combine_goods($configs)
  45. {
  46. $result = [];
  47. foreach ($configs as $cfg)
  48. {
  49. $card_types = $cfg['card_type'];
  50. $amounts = $cfg['amount'];
  51. foreach ($card_types as $type)
  52. {
  53. if(array_key_exists($type,$result)) {
  54. $item = $result[$type];
  55. }
  56. else {
  57. $item = [];
  58. }
  59. foreach ($amounts as $amount => $val) {
  60. $item[] = $amount;
  61. }
  62. $item = array_unique($item);
  63. $result[$type] = $item;
  64. }
  65. }
  66. return $result;
  67. }
  68. private function load()
  69. {
  70. global $config;
  71. $pro_configs = $config['oil_providers'];
  72. $names = [];
  73. foreach ($pro_configs as $cfg) {
  74. $name = $cfg['name'];
  75. $names[] = $name;
  76. if($name == 'suhc') {
  77. $provider = new suhc\RefillOil($cfg);
  78. $this->mOilProvider[] = $provider;
  79. }
  80. else {
  81. Log::record("Oil Provider has no name",Log::ERR);
  82. }
  83. }
  84. $pro_configs = $config['phone_providers'];
  85. foreach ($pro_configs as $cfg) {
  86. $name = $cfg['name'];
  87. $names[] = $name;
  88. if($name == 'beixt') {
  89. $provider = new beixt\RefillPhone($cfg);
  90. $this->mPhoneProvider[] = $provider;
  91. }
  92. else {
  93. Log::record("Phone Provider has no name",Log::ERR);
  94. }
  95. }
  96. $this->mProviderNames = array_unique($names);
  97. }
  98. public function find_providers(int $amount,int $card_type) : array
  99. {
  100. if($card_type == mtopcard\SinopecCard || $card_type == mtopcard\PetroChinaCard){
  101. return $this->find_oil($amount,$card_type);
  102. }
  103. elseif($card_type == mtopcard\ChinaMobileCard || $card_type == mtopcard\ChinaUnicomCard || $card_type == mtopcard\ChinaTelecomCard) {
  104. return $this->find_phone($amount,$card_type);
  105. }
  106. else {
  107. return [];
  108. }
  109. }
  110. private function find_oil(int $amount,int $card_type) : array
  111. {
  112. $providers = [];
  113. foreach ($this->mOilProvider as $provider)
  114. {
  115. $name = $provider->name();
  116. [$success,$err] = $provider->check($amount,$card_type);
  117. if($success) {
  118. $providers[] = $provider;
  119. }
  120. else {
  121. Log::record("{$name} provider cannot match check,err:{$err}",Log::DEBUG);
  122. }
  123. }
  124. return $providers;
  125. }
  126. private function find_phone(int $amount,int $card_type) : array
  127. {
  128. $providers = [];
  129. foreach ($this->mPhoneProvider as $provider)
  130. {
  131. $name = $provider->name();
  132. [$success,$err] = $provider->check($amount,$card_type);
  133. if($success) {
  134. $providers[] = $provider;
  135. }
  136. else {
  137. Log::record("{$name} provider cannot match check,err:{$err}",Log::DEBUG);
  138. }
  139. }
  140. return $providers;
  141. }
  142. public function notify($chname,$input)
  143. {
  144. if(!in_array($chname,$this->mProviderNames)) {
  145. return false;
  146. }
  147. $class_name = "{$chname}\\RefillCallBack";
  148. $caller = new $class_name();
  149. if($caller->verify($input))
  150. {
  151. [$order_id,$success,$can_try] = $caller->notify($input);
  152. if($order_id !== false)
  153. {
  154. $mod_order = Model('vr_order');
  155. $order_info = $mod_order->getOrderInfo(['order_id' => $order_id]);
  156. $order_state = intval($order_info['order_state']);
  157. if($order_state != ORDER_STATE_SEND) {
  158. return false;
  159. }
  160. $mod_refill = Model('refill_order');
  161. $refill_info = $mod_refill->getOrderInfo(['order_id' => $order_id]);
  162. $logic_vr_order = Logic("vr_order");
  163. if($success) {
  164. $logic_vr_order->changeOrderStateSuccess($order_id);
  165. }
  166. elseif ($can_try)
  167. {
  168. $logic_vr_order->changeOrderStateCancel($order_id);
  169. if($this->retry($refill_info) === false) {
  170. }
  171. else {
  172. }
  173. }
  174. else {
  175. $logic_vr_order->changeOrderStateCancel($order_id);
  176. }
  177. $mod_refill->edit($order_id,['notify_time' => time(),'notify_state' => 1]);
  178. QueueClient::push("NotifyMerchantComplete",['order_id' => $order_id]);
  179. }
  180. else {
  181. Log::record("系统无此订单ID:{$order_id}",Log::ERR);
  182. }
  183. }
  184. else {
  185. Log::record("{$chname} 签名失败.");
  186. }
  187. return true;
  188. }
  189. private function retry(array $refill_info)
  190. {
  191. return false;
  192. }
  193. public function add($mchid,$buyer_id,$amount,$card_no,$mch_order,$notify_url,$try_times = 0)
  194. {
  195. $card_type = mtopcard\card_type($card_no);
  196. $providers = $this->find_providers($amount,$card_type);
  197. if(empty($providers)) {
  198. return [202,"找不到合适的充值通道"];
  199. }
  200. $minfo = new member_info($buyer_id);
  201. $calc = new CalcMerchantPrice($mchid,$amount,$card_type);
  202. $mch_amount = $calc->calc_vgoods_price([]);
  203. if($mch_amount > $minfo->available_predeposit()) {
  204. return [203,"余额不足"];
  205. }
  206. $refill_state = false;
  207. foreach ($providers as $provider)
  208. {
  209. $channel_name = $provider->name();
  210. [$goods_id, $price] = $provider->goods($amount);
  211. $input['goods_id'] = $goods_id;
  212. $input['quantity'] = 1;
  213. $input['price'] = $price;
  214. $input['buyer_phone'] = $minfo->mobile();
  215. $input['buyer_name'] = $minfo->truename();
  216. $input['buyer_msg'] = $_POST['buyer_msg'] ?? '';
  217. $input['order_from'] = 1;
  218. $input['pd_pay'] = true;
  219. $logic_buy_virtual = Logic('buy_virtual');
  220. $result = $logic_buy_virtual->buyStep3($input, $buyer_id,[$calc,'calc_vorder_amount'],true);
  221. $mod_refill = Model('refill_order');
  222. if($result['state'] === true)
  223. {
  224. $order_sn = $result['data']['order_sn'];
  225. $order_id = $result['data']['order_id'];
  226. //虚拟订单表信息扩展
  227. $orderext = ['order_id' => $order_id,'order_sn' => $order_sn,'mchid' => $mchid,
  228. 'refill_amount' => $amount,'mch_order' => $mch_order,
  229. 'notify_url' => $notify_url,'channel_name' => $channel_name,
  230. 'mch_amount' => $mch_amount,'channel_amount' => $price,'order_time' => time(),
  231. 'card_type' => $card_type,'card_no' => $card_no];
  232. $mod_refill->add_refill($orderext);
  233. }
  234. else {
  235. continue;
  236. }
  237. $params = ['order_sn' => $order_sn];
  238. [$state,$err] = $provider->add($card_no,$card_type,$amount,$params);
  239. if($state) {
  240. $trade_no = $err;
  241. $logic_vr_order = Logic("vr_order");
  242. $logic_vr_order->changeOrderStateSend($order_id);
  243. $data = ['commit_time' => time(),'ch_trade_no' => $trade_no];
  244. $mod_refill->edit($order_id,$data);
  245. $refill_state = true;
  246. break;
  247. }
  248. else {
  249. Log::record("channel:{$channel_name} err:{$err}");
  250. $logic_vr_order = Logic("vr_order");
  251. $order_info = Model('vr_order')->getOrderInfo(['order_id' => $order_id]);
  252. $logic_vr_order->changeOrderStateCancel($order_info,'',"调用{$channel_name}接口失败");
  253. }
  254. }
  255. if($refill_state) {
  256. return [true,$order_sn];
  257. } else {
  258. return [204,"充值失败."];
  259. }
  260. }
  261. public function notify_merchant($order_id)
  262. {
  263. if($order_id <= 0) {
  264. return [false,"订单ID小于0"];
  265. }
  266. $vr_order = Model('vr_order');
  267. $refill_order = Model('refill_order');
  268. $order_info = $vr_order->getOrderInfo(['order_id' => $order_id]);
  269. $refill_info = $refill_order->getOrderInfo(['order_id' => $order_id]);
  270. if(empty($order_info) || empty($refill_info)) {
  271. return [false,"无此订单"];
  272. }
  273. if ($refill_info['mch_notify_state'] != 0) {
  274. return [false,"已经通知客户方"];
  275. }
  276. $notify_url = $refill_info['notify_url'];
  277. if(empty($notify_url)) {
  278. return [false,"回调地址为空"];
  279. }
  280. $order_state = $order_info['order_state'];
  281. if($order_state == ORDER_STATE_CANCEL) {
  282. $state = "CANCEL";
  283. }
  284. elseif($order_state == ORDER_STATE_SUCCESS) {
  285. $state = "SUCCESS";
  286. }
  287. else {
  288. return [false,"错误的订单状态,不能通知."];
  289. }
  290. $mchid = $refill_info['mchid'];
  291. $mch_info = Model('merchant')->getMerchantInfo($mchid);
  292. [$params,$sign] = $this->body($state,$refill_info,$mch_info);
  293. $params['sign'] = $sign;
  294. $resp = http_request($notify_url,$params,'POST');
  295. if($resp == "SUCCESS") {
  296. $refill_order->edit(['order_id' => $order_id],['mch_notify_state' => 1,'mch_notify_times' => ['exp','mch_notify_times+1']]);
  297. return [true,""];
  298. }
  299. else {
  300. $refill_order->edit(['order_id' => $order_id],['mch_notify_times' => ['exp','mch_notify_times+1']]);
  301. $times = $refill_info['mch_notify_times'] + 1;
  302. if($times > 80) {
  303. $refill_order->edit(['order_id' => $order_id],['mch_notify_state' => 2]);
  304. }
  305. else {
  306. $N = intval($times / 5);
  307. $period = intval(pow(2,$N));
  308. QueueClient::async_push("NotifyMerchantComplete",['order_id' => $order_id],$period);
  309. }
  310. return [false,"通知{$times}次,失败."];
  311. }
  312. }
  313. public function OrderQuery($channel_name,$params){
  314. if($channel_name == "suhc") {
  315. $query_cls = new suhc\RefillOil([]);
  316. [$state,$err] = $query_cls->OrderQuery($params['ch_trade_no'],$params['card_no'],$params['order_sn']);
  317. }elseif ($channel_name == 'beixt'){
  318. $query_cls = new beixt\RefillPhone([]);
  319. [$state,$err] = $query_cls->OrderQuery($params['ch_trade_no'],$params['order_sn']);
  320. }
  321. else {
  322. return false;
  323. }
  324. }
  325. private function body($state,$refill_info,$mch_info)
  326. {
  327. $params = [
  328. "mchid" => $refill_info['mchid'],
  329. "order_sn" => $refill_info['mch_order'],
  330. "amount" => $refill_info['refill_amount'],
  331. "cardno" => $refill_info['card_no'],
  332. "trade_no" => $refill_info['order_sn'],
  333. "idcard" => $refill_info['idcard'] ?? "",
  334. "card_name" => $refill_info['card_name'] ?? "",
  335. "state" => $state ];
  336. $secure_key = $mch_info['secure_key'];
  337. $sign = $this->sign($params,$secure_key);
  338. return[$params,$sign];
  339. }
  340. private function sign($params,$key)
  341. {
  342. ksort($params);
  343. $body = "";
  344. $i = 0;
  345. foreach ($params as $k => $v)
  346. {
  347. if (false === $this->check_empty($v) && "@" != substr($v, 0, 1))
  348. {
  349. if ($i == 0) {
  350. $body .= "{$k}" . "=" . urlencode($v);
  351. } else {
  352. $body .= "&" . "{$k}" . "=" . urlencode($v);
  353. }
  354. $i++;
  355. }
  356. }
  357. $body .= "&key={$key}";
  358. return md5($body);
  359. }
  360. private function check_empty($value)
  361. {
  362. if (!isset($value))
  363. return true;
  364. if ($value === null)
  365. return true;
  366. if (trim($value) === "")
  367. return true;
  368. return false;
  369. }
  370. }