config = $config; } public function getError() { // 文件services/common/PayService.php $refund_class->getError() 无此方法飘红 return ''; } /** * 快捷支付-发送签约短信 * @desc 签约第一步 * @param int $card_id UserBankCard->id * @throws Exception * @return bool */ public function sendSignSms($card_id) { $card = JoinPayUserBankcard::findOne(['card_id' => $card_id]); if(!$card){ throw new Exception('银行卡信息不存在'); } $order_id = JoinpayFastPayOrder::createOrder($card->mall_id,$card->user_id,$card_id,0.01); $orderInfo = JoinpayFastPayOrder::findOne(['id' => $order_id]); // $orderInfo = []; $secKey = RandomUtil::randomStr(16); $request_data = $this->getData($orderInfo,$card->toArray(),$secKey); $request = $this->getRequestObject('fastPay.agreement.signSms',$request_data,$secKey); $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->url, $request, $secretKey); if ($response->isSuccess()) { //受理成功 $dataArr = json_decode($response->getData(), true); if ($dataArr["order_status"] == 'P1000' || $dataArr['order_status'] == 'P3000') { //成功 return true; } else { \Yii::error('FAIL OR PROCESSING OR UNKNOWN, Response =' . json_encode($dataArr, JSON_UNESCAPED_UNICODE)); throw new Exception($dataArr['err_msg']); } } else { \Yii::error('受理失败, Response =' . json_encode($response, JSON_UNESCAPED_UNICODE)); throw new Exception('受理失败'); } }catch (SDKException $e){ throw new Exception($e->getMsg()); } catch (Exception $e) { throw new Exception($e->getMessage().'请稍后重新发起交易,或联系汇聚客服'); } } /** * 快捷支付-签约 * @desc 签约第二步 END * @param $card_info * @param $sms_code * @throws Exception * @return bool */ public function sign($card_id,$sms_code) { $card_info = UserBankCard::findOne([ 'id' => $card_id, 'status' => StatusEnum::ENABLED, ]); /** @var JoinPayUserBankcard $card_info */ $joinpay_card_info = JoinPayUserBankcard::findOne([ 'card_id' => $card_id, 'status' => StatusEnum::ENABLED, 'is_sign' => 0, ]); if (empty($card_info) || empty($joinpay_card_info)) throw new \Exception('银行卡不存在'); /** @var JoinpayFastPayOrder $orderInfo */ $orderInfo = JoinpayFastpayOrder::find() ->where(['bank_card_id' => $card_info->id,'user_id'=>$card_info->user_id]) ->orderBy('id desc') ->one(); if (empty($orderInfo)) throw new \Exception('签约订单未找到'); $secKey = RandomUtil::randomStr(16); $request_data = [ 'mch_no_trade' => '', 'mch_order_no' => $orderInfo->order_no, 'sms_code' => $sms_code ]; $request = $this->getRequestObject("fastPay.agreement.smsSign",$request_data,$secKey); $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->url, $request, $secretKey); if ($response->isSuccess()) { //受理成功 $dataArr = json_decode($response->getData(), true); \Yii::error(' Response data => '.json_encode($dataArr, JSON_UNESCAPED_UNICODE)); if($dataArr["order_status"] == "P1000" || $dataArr['order_status'] == 'P3000'){ //成功 //返回签约ID : sign_no , bank_code,jp_order_no $joinpay_card_info->sign_no = $dataArr['sign_no'] ?? ''; $joinpay_card_info->is_sign = !empty($dataArr['sign_no']) ? 1 : 0; $joinpay_card_info->bank_code = $dataArr['bank_code'] ?? ''; $joinpay_card_info->updated_at = time(); if(!$joinpay_card_info->save()){ throw new Exception($joinpay_card_info->getErrorMessage()); } $orderInfo->jp_order_no = $dataArr['jp_order_no'] ?? ''; $orderInfo->updated_at = time(); if(!$orderInfo->save()){ throw new Exception($orderInfo->getErrorMessage()); } return true; }else{ throw new Exception($dataArr['err_msg'] ?? '操作失败'); } }else{ throw new Exception($response->getBizMsg() ?? '受理失败'); } } catch (Exception $e) { throw $e; } } /** * 解约 * @param $card_info * @return bool * @throws Exception */ public function unsign($card_info) { $order_id = JoinpayFastPayOrder::createOrder($card_info->mall_id,$card_info->user_id,$card_info->id,0.01,0); $orderInfo = JoinpayFastPayOrder::findOne(['id' => $order_id]); $secKey = RandomUtil::randomStr(16); $request_data = [ 'mch_order_no' => $orderInfo->order_no, 'mch_req_time' => date('Y-m-d H:i:s', $orderInfo->created_at), 'sign_no' => AESUtil::encryptECB($card_info->sign_no, $secKey) //'bank_card_no' => $card_info->bank_card_no, ]; $request = $this->getRequestObject('fastPay.agreement.unSign',$request_data,$secKey); $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->url, $request, $secretKey); if ($response->isSuccess()) { //受理成功 $dataArr = json_decode($response->getData(), true); \Yii::error(' Response data='.json_encode($dataArr, JSON_UNESCAPED_UNICODE)); if($dataArr["order_status"] == "P1000" || $dataArr['order_status'] == 'P3000'){ //成功 $card_info->sign_no = ''; $card_info->is_sign = 0; $card_info->updated_at = time(); if(!$card_info->save()){ throw new Exception($card_info->getErrorMessage()); } $orderInfo->jp_order_no = $dataArr['jp_order_no'] ?? ''; $orderInfo->updated_at = time(); if(!$orderInfo->save()){ throw new Exception($orderInfo->getErrorMessage()); } return true; }else{ throw new Exception('FAIL OR PROCESSING OR UNKNOWN'); } }else{ throw new Exception($response->getBizMsg() ?? '受理失败'); } } catch (Exception $e) { throw $e; } } /** * 支付短信接口 * @param $trade_id 支付流水id * @param $card_id 银行卡记录id * @throws Exception * @return bool */ public function sendPaySms(int $trade_id, $card_info,$mall_id) { // $card_info = JoinPayUserBankcard::find()->where(['id' => $card_id])->asArray()->one(); $order_info = PaymentTrade::find() ->select('*,out_trade_no as order_no') ->where(['id' => $trade_id]) ->asArray() ->one(); $secKey = RandomUtil::randomStr(16); $request_data = $this->getData($order_info,$card_info,$secKey,true); $request = $this->getRequestObject("fastPay.agreement.paySms",$request_data,$secKey); $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->url, $request, $secretKey); if ($response->isSuccess()) {//受理成功 $dataArr = json_decode($response->getData(), true); if($dataArr["order_status"] == "P1000" || $dataArr['order_status'] == 'P3000'){//订单交易成功 return true; }else{ throw new Exception('FAIL OR PROCESSING OR UNKNOWN'); } }else{ throw new Exception($response->getBizMsg() ?? '受理失败'); } } catch (Exception $e) { throw $e; } } /** * 订单支付 * @param array $order * [ * 'order_no' => '交易流水号' * 'sms_code' => '短信验证码' * 'created_at' => '交易创建时间戳' * ] * * @throws Exception * @return bool */ public function pay($order) { $secKey = RandomUtil::randomStr(16); $request = new Request(); $request_data = [ 'mch_no_trade' => '', 'mch_order_no' => $order['order_no'], 'sms_code' => $order['sms_code'], 'mch_req_time' => date('Y-m-d H:i:s', $order['created_at']) ]; $request->setMethod("fastPay.agreement.smsPay"); $request->setVersion("1.0"); $request->setMchNo($this->config['joinpay_merchant_no']); $request->setSignType("2"); $request->setRandStr(RandomUtil::randomStr(32)); $request->setData($request_data); $request->setSecKey($secKey);//rsa有效 $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->url, $request, $secretKey); \Yii::error(' fastpay response data => ' . $response->getData());\Yii::getLogger()->flush(true); if ($response->isSuccess()) { //受理成功 $dataArr = json_decode($response->getData(), true); if($dataArr["order_status"] == "P1000"){ //订单交易成功 $trade = PaymentTrade::findOne(['out_trade_no' => $order['order_no']]); $trade_order = PaymentTradeOrder::findOne(['trade_id' => $trade->id]); // 返回这两个数据给前端展示支付成功页面使用 return ['amount' => $trade_order->total_fee, 'order_no' => $trade_order->order_no]; }else{ throw new Exception('支付异常,请查询是否扣款'); } }else{ throw new Exception($response->getBizMsg() ?? '受理失败'); } } catch (Exception $e) { throw $e; } } /** * 组装汇聚请求参数data * @param $orderInfo * @param $userBankInfo * @param $secKey * @param bool $is_pay * @return array */ private function getData($orderInfo,$userBankInfo,$secKey,$is_pay = false) { $data = []; $data["mch_no_trade"] = ""; $data["mch_order_no"] = $orderInfo['order_no']; $data["order_amount"] = sprintf("%.2f", $orderInfo['pay_fee']); //$data["mch_req_time"] = date('Y-m-d H:i:s', time()); $data["mch_req_time"] = date('Y-m-d H:i:s', $orderInfo['created_at']); $data["order_desc"] = $orderInfo['title']; $data["id_type"] = "1"; // $data["callback_url"] = Url::toFront( ['notify/' . PayTypeEnum::action(PayTypeEnum::JOINPAY_FAST_PAY)],TRUE); $data["callback_url"] = \Yii::$app->request->hostInfo . '/join-pay/notify/joinpay-fastpay'; $data["callback_param"] = json_encode(['mall_id' => $userBankInfo['mall_id']]);// 公用回传参数; if(!$is_pay){ $data["payer_name"] = AESUtil::encryptECB($userBankInfo['payer_name'], $secKey);//加密 $data["id_no"] = AESUtil::encryptECB($userBankInfo['id_no'], $secKey);//加密 $data["bank_card_no"] = AESUtil::encryptECB($userBankInfo['bank_card_no'], $secKey);//加密 $data["mobile_no"] = AESUtil::encryptECB($userBankInfo['mobile_no'], $secKey);//加密 }else{ //支付 $data['bank_card_no'] = AESUtil::encryptECB($userBankInfo['bank_card_no'], $secKey);//加密 } //信用卡 需要加入:expire_date,cvv //https://www.joinpay.com/open-platform/pages/document.html?apiName=%E5%BF%AB%E6%8D%B7%E5%8D%8F%E8%AE%AE%E6%94%AF%E4%BB%98&id=9 return $data; } /** * 获取Request 对象 * @param $method * @param $request_data * @param $secKey * @return Request */ private function getRequestObject($method,$request_data,$secKey) { $request = new Request(); $request->setMethod($method); $request->setVersion('1.0'); $request->setMchNo($this->config['joinpay_merchant_no']); $request->setSignType('2'); $request->setRandStr(RandomUtil::randomStr(32)); $request->setData($request_data); $request->setSecKey($secKey);//rsa有效 return $request; } /** * 获取SecretKey Object * @return SecretKey */ private function getSecretKeyObject() { $secretKey = new SecretKey(); $secretKey->setReqSignKey($this->config['joinpay_fast_pay_private_key']);//签名:使用商户私钥 $secretKey->setRespVerifyKey($this->config['joinpay_fast_pay_public_key']);//验签:使用平台公钥 $secretKey->setSecKeyEncryptKey($this->config['joinpay_fast_pay_public_key']);//sec_key加密:使用平台公钥 $secretKey->setSecKeyDecryptKey($this->config['joinpay_fast_pay_private_key']);//sec_key解密:使用商户私钥 return $secretKey; } /** * 回调处理 * @return bool */ public function notify() { try{ $post = Yii::$app->request->post(); $data = $this->checkNotifyParams($post); return [ 'out_trade_no' => $data['mch_order_no'], //商户订单号 'trade_no' => $post['jp_order_no'], //汇聚平台流水号 'pay_fee' => $data['order_amount'] ]; }catch(Exception $e){ \Yii::error(__CLASS__ .' method:'.__METHOD__ .' error :'.$e->getMessage()); return false; } } /** * 回调验签 * @param $data * @return bool * @throws Exception */ public function signature_verify($data) { $respJson = json_encode($data,JSON_UNESCAPED_UNICODE); $response = new Response(); try{ ObjectUtil::fillProperties($response, $respJson); }catch (Exception $e){ throw new Exception(SDKException::BIZ_ERROR, "请求完成,但响应信息转换失败: " . $e->getMessage() . ",respJson = " . $respJson); } $isOk = false; $secretKey = $this->getSecretKeyObject(); try{ $signStr = SignUtil::getSortedString($response, false); $isOk = SignUtil::verify($signStr, $response->getSign(), $response->getSignType(), $secretKey->getRespVerifyKey()); }catch (Exception $e){ throw new Exception(SDKException::BIZ_ERROR, "请求完成,但响应信息验签异常: " . $e->getMessage() . ",respJson = " . $respJson); } if($isOk !== true){ throw new Exception(SDKException::BIZ_ERROR, "响应信息验签不通过! respJson = " . $respJson); } if($data['biz_code'] !== 'JS000000'){ throw new Exception('error'); } if($data['mch_no'] != $this->config['joinpay_merchant_no']){ throw new Exception('商户号有误'); } return true; } /** * 校验回调参数 * @param $res * @return bool * @throws Exception */ public function checkNotifyParams($res) { $this->signature_verify($res); $data = json_decode($res['data'],true); if($data['order_status'] !== 'P1000'){ //P100:支付成功 throw new Exception($data['err_msg'] ?? '交易失败'); } $order_no = $data['mch_order_no']; $total_fee = (doubleval($data['order_amount']) * 100) . ''; //订单号查询 $trade = PaymentTrade::findOne(['trade_no' => $order_no]); if (!$trade || $trade->is_pay == 1) { throw new Exception('订单不存在'); } $order_amount = (doubleval($trade->pay_fee) * 100) . ''; //金额是否一致 if (intval($total_fee) !== intval($order_amount)) { throw new Exception('支付金额与订单金额不一致: ' . $order_no); } return $data; } /** * 退款 * @param array $info * [ * 'refund_order_no' => $refund_order_no, * 'out_trade_no' => $out_trade_no, //trade表交易流水号 * 'refund_amount' => 1, * 'refund_reason' => 1 * 'trade_created_at' => 1661400214 * ] * * @return bool * @throws */ public function refund($info) { $secKey = RandomUtil::randomStr(16); $request_data = [ 'refund_order_no' => $info['refund_order_no'], 'refund_amount' => $info['refund_amount'], 'refund_reason' => mb_substr($info['refund_reason'],0,60), 'org_mch_order_no' => $info['out_trade_no'], 'org_mch_req_time' => date('Y-m-d H:i:s',$info['trade_created_at']), 'callback_url' => \Yii::getAlias('@apiUrl').'/notify/joinpay-fast-refund' ]; $request = $this->getRequestObject('fastPay.refund',$request_data,$secKey); $secretKey = $this->getSecretKeyObject(); try { $response = RequestUtil::doRequest($this->refund_url, $request, $secretKey); if ($response->isSuccess()) { \Yii::error('joinpay fast refund res:' . $response->getData()); //受理成功 $dataArr = json_decode($response->getData(), true); if($dataArr["refund_status"] == '101'){ //退款失败 throw new Exception('退款失败'); } return true; } else { \Yii::error('受理失败, Response =' . json_encode($response, JSON_UNESCAPED_UNICODE)); throw new Exception($response->getBizMsg()); } }catch (SDKException $e){ throw new Exception($e->getMsg()); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }