mall_id = $mall_id; $this->config = \Yii::$app->services->setting->addons->get($mall_id, UnionPayEnum::ADDONS_NAME, 'basic'); } } // 下单 public function create(PaymentTrade $trade, $trade_type, $openid) { $config = $this->config; $trade_type = trim($trade_type); new WechatForm(['mall_id' => $trade->mall_id]); if (empty($openid) && in_array($trade_type,['W06','W06-CASHIER'])) { //h5下,openid,生成一个中间页打开微信小程序 $token = \Yii::$app->wechat->getMiniProgram()->access_token->getToken(); \Yii::error('token==========:' . json_encode($token)); $url = 'https://api.weixin.qq.com/wxa/generatescheme?access_token=' . $token['access_token']; $out_trade_no = $trade->out_trade_no; $trade_id = $trade->id; $user_id = $trade->user_id; $token_model = AccessToken::findOne(['user_id'=>$user_id,'group'=>'api']); $token = $token_model->access_token; $pay_money = $trade->pay_fee; $user_info = UserInfo::findOne(['user_id'=>$user_id,'platform'=>User::PLATFORM_MP_WX,'status'=>StatusEnum::ENABLED]); $is_openid = 0; if(!empty($user_info['openid'])){ $is_openid = 1; } $post = [ 'jump_wxa' => [ 'path' => '/pages/public/tlpay', 'query' => "trade_no={$out_trade_no}&trade_id={$trade_id}&token={$token}&pay_money={$pay_money}&is_openid={$is_openid}&trade_type={$trade_type}" ] ]; Yii::error('post_data'.json_encode($post)); $res = CurlHelper::post($url, $post);//请求微信服务端 if (!empty($res['errcode'])) { throw new Exception($res['errmsg']); } return ['openlink' => $res['openlink'],]; } $sub_appid = ''; switch ($trade_type) { case 'W06': case 'W06-CASHIER': $sub_appid = \Yii::$app->params['wechatMiniProgramConfig']['app_id']; break; case 'W02': $sub_appid = \Yii::$app->params['wechatConfig']['app_id']; break; } $order_setting = \Yii::$app->services->setting->mall->get($trade->mall_id, 'order'); $order_auto_handle_time = 30; if (!empty($order_setting)) $order_auto_handle_time = $order_setting['payment_expire_time']; $params = []; $params["cusid"] = $config['cusid']; $params["appid"] = $config['appid']; $params["orgid"] = $config['orgid']; $params["version"] = $this->version; $params["trxamt"] = $trade->pay_fee * 100; $params["reqsn"] = $trade->out_trade_no; $params["paytype"] = $trade_type; $params["body"] = $trade->title; $params["validtime"] = $order_auto_handle_time + 5; $params["acct"] = $openid; $params["notify_url"] = \Yii::$app->services->setting->platform->get('system.api_url') . "/union-pay/notify/notify"; $params["sub_appid"] = $sub_appid; $params["randomstr"] = md5(microtime(true)); $params["signtype"] = $this->sign_type; if($trade_type=='W06-CASHIER'){ unset($params['sub_appid']); unset($params['acct']); $params["paytype"] = 'W06'; $params["version"] = "12"; $params["trxamt"] = (string)$params["trxamt"]; $params["validtime"] = (string)($params["validtime"]); } $sign = $this->sign($params, $config['private_key']); if($trade_type!='W06-CASHIER'){ $params["sign"] = urlencode($sign);//签名 }else{ //微信小程序收银台 $params["sign"] = $sign;//签名 $trade->trade_type = $trade_type; $trade->save(); return ['appId'=>$sub_appid,'extraData'=>$params]; } $params_str = $this->toUrlParams($params); $url = $this->api_url . "/pay"; $rsp = $this->request($url, $params_str); $rsp = json_decode($rsp, true); $add_data = [ 'type' => 'pay', 'out_trade_no' => $trade->out_trade_no, 'oldtrxid' => $rsp['trxid'] ?? '', 'trade_type' => $trade_type, 'randomstr' => $params["randomstr"], 'request_data' => $params_str, 'response_data' => json_encode($rsp) ]; UnionpayRequestLog::setData($trade->mall_id, $add_data); if ($rsp['retcode'] != 'SUCCESS') { $msg = $rsp['retmsg'] ?? '系统内部错误'; throw new Exception($msg); } if (!$this->validSign($rsp, $config['union_pay_public_key'])) { throw new Exception('验签失败'); } if (!empty($rsp['errmsg'])) { throw new Exception($rsp['errmsg']); } $trade->third_trade_no = $rsp['trxid']; $trade->trade_type = $trade_type; $trade->save(); if (in_array($trade_type, ['W06', 'W02'])) { $rsp['payinfo'] = json_decode($rsp['payinfo'], true); $rsp['payinfo']['timestamp'] = $rsp['payinfo']['timeStamp']; return $rsp['payinfo']; } else { return ['result' => $rsp['payinfo']]; } } public function notify($trade, $params) { PaymentTradeLog::updateAll(['notify_result' => 1], ['trade_id' => $trade->id]); try { $config = $this->config; if (!$this->validSign($params, $config['union_pay_public_key'])) { throw new Exception('验签失败'); } // 支付完成 if ($trade->is_pay == 1) return true; PaymentTrade::updateAll(['is_pay' => 1, 'third_trade_no' => $params['trxid'] ?? '', 'second_trade_no' => $params['termrefnum'] ?? '', 'pay_time' => strtotime($params['paytime']), 'pay_type' => PayTypeEnum::UNIONPAY], ['id' => $trade->id]); // 业务回调 // 增加组合支付判断 $trade_id = $trade->id; $is_pay = true; if(!empty($trade->is_pay_group)){ $paymentOrderObj = PaymentTradeGroupOrder::class; if(empty($trade->parent_trade_id)){ $trade_id = $trade->id; }else{ $trade_id = $trade->parent_trade_id; } $exist = PaymentTrade::find()->where(['parent_trade_id'=>$trade_id,'is_pay'=>0])->one(); if(!empty($exist)){ $is_pay = false; } }else{ $paymentOrderObj = PaymentTradeOrder::class; } $paymentOrderObj::updateAll(['is_pay' => 1, 'pay_type' => PayTypeEnum::UNIONPAY], ['trade_id' => $trade->id]); if($is_pay){ $orders = PaymentTradeOrder::findAll(['trade_id' => $trade_id]); Yii::$app->services->pay->notify($orders); } return true; } catch (\Exception $e) { return false; } } public function refund($params) { try { $mall_id = $this->mall_id; $config = $this->config; if(empty($config['is_enable'])){ return false; } if($params['refund_price']<=0) { throw new \Exception('退款金额必须大于0'); } if(empty($params['order_no'])) { throw new \Exception('订单号不能为空'); } if(empty($params['refund_order_no'])) { throw new \Exception('退款单号不能为空'); } $order_no = $params['order_no']; $refund_order_no = $params['refund_order_no']; $refund_price = $params['refund_price']; if(!empty($params['payment_trade_id'])){ $payment_trade = PaymentTrade::findOne(['id' => $params['payment_trade_id'],'pay_type'=>PayTypeEnum::UNIONPAY]); }else{ $payment_trade_order = PaymentTradeOrder::findOne(['order_no' => $order_no,'pay_type'=>PayTypeEnum::UNIONPAY]); $payment_trade = PaymentTrade::findOne(['id' => $payment_trade_order['trade_id'],'pay_type'=>PayTypeEnum::UNIONPAY]); } if(empty($payment_trade)){ return false; } $third_trade_no = $payment_trade['third_trade_no']; $post = []; $randomstr = md5(microtime(true)); $post["cusid"] = $config['cusid']; $post["appid"] = $config['appid']; $post["orgid"] = $config['orgid']; $post["version"] = $this->version; $post["randomstr"] = $randomstr; $post["signtype"] = $this->sign_type; $post["trxamt"] = bcmul($refund_price, 100, 0); $post["reqsn"] = $refund_order_no; $post["oldtrxid"] = $third_trade_no;//原交易的收银宝平台流水 $post["sign"] = urlencode($this->sign($post, $config['private_key']));//签名 $params_str = $this->toUrlParams($post); $url = $this->api_url . "/refund"; switch ($payment_trade->trade_type) { case 'fast_pay': $url = 'https://vsp.allinpay.com/apiweb/tranx/refund'; break; default: } \Yii::error('addons\UnionPay\common\global_func\Payment::refund->post'.$params_str); $rsp = $this->request($url, $params_str); \Yii::error('addons\UnionPay\common\global_func\Payment::refund->resp'.$rsp); $add_data = [ 'type' => 'refund', 'out_trade_no' => $payment_trade->out_trade_no, 'oldtrxid' => $third_trade_no, 'trade_type' => $payment_trade->trade_type, 'randomstr' => $randomstr, 'request_data' => $params_str, 'response_data' => $rsp ]; UnionpayRequestLog::setData($mall_id, $add_data); $rspArray = json_decode($rsp, true); if ($rspArray['retcode'] == 'SUCCESS' && $rspArray['trxstatus'] == '0000') { if (!$this->validSign($rspArray, $config['union_pay_public_key'])) { throw new Exception('验签失败'); } return [true, 'ok']; } else { throw new Exception($rspArray['errmsg'] ?? ''); } } catch (\Exception $e) { return [false, $e->getMessage()]; } } public function query($out_trade_no) { $config = $this->config; $params = []; $params["version"] = $this->version; $params["cusid"] = $config['cusid']; $params["appid"] = $config['appid']; $params["orgid"] = $config['orgid']; $params["reqsn"] = $out_trade_no; $params["randomstr"] = md5(microtime(true)); $params["signtype"] = 'RSA'; $params["sign"] = urlencode($this->sign($params, $config['private_key']));//签名 $params_str = $this->toUrlParams($params); $url = $this->api_url . "/query"; $rsp = $this->request($url, $params_str); $rspArray = json_decode($rsp, true); if (!$this->validSign($rspArray, $config['union_pay_public_key'])) { throw new Exception('验签失败'); } return $rspArray; } //RSA签名 public function sign($array, $private_key) { ksort($array); $bufSignSrc = $this->toUrlParams($array); $private_key = chunk_split($private_key, 64, "\n"); $key = "-----BEGIN RSA PRIVATE KEY-----\n" . wordwrap($private_key) . "-----END RSA PRIVATE KEY-----"; if (openssl_sign($bufSignSrc, $signature, $key)) { } else { throw new Exception('生成签名失败'); } return base64_encode($signature);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的 } public function validSign($array, $public_key) { $sign = $array['sign']; unset($array['sign']); ksort($array); $bufSignSrc = $this->ToUrlParams($array); $public_key = chunk_split($public_key, 64, "\n"); $key = "-----BEGIN PUBLIC KEY-----\n$public_key-----END PUBLIC KEY-----\n"; return openssl_verify($bufSignSrc, base64_decode($sign), $key); } public function toUrlParams(array $array) { $buff = ""; foreach ($array as $k => $v) { if ($v != "" && !is_array($v)) { $buff .= $k . "=" . $v . "&"; } } return trim($buff, "&"); } public function request($url, $params) { $ch = curl_init(); $this_header = array("content-type: application/x-www-form-urlencoded;charset=UTF-8"); curl_setopt($ch, CURLOPT_HTTPHEADER, $this_header); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)'); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);//如果不加验证,就设false,商户自行处理 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $output = curl_exec($ch); curl_close($ch); return $output; } }