config = $config; } /** * 支付接口 * @param array $order * [ * 'title' => '订单标题' * 'order_no' => '交易流水号' * 'amount' => 金额 * 'mall_id' => 商城ID * 'notify_url' => '回调地址', * 'type' => '汇聚支付方式' * 'open_id' => '微信openid' * ] * @return array|mixed * @throws Exception */ public function pay($order) { $request = $this->getRequest($order); //\Yii::error('request param:'.json_encode($request)); $result = RequestUtil::http_post($this->url, $request); Yii::error('joinpay result:' . $result); $result = json_decode($result, true); if ($result['ra_Code'] == 100 && !empty($result['rc_Result'])) { if (StringHelper::isJson($result['rc_Result'])) { $return = json_decode($result['rc_Result'], true); if (!isset($return['timestamp'])) { $return['timestamp'] = $return['timeStamp'] ?? ''; } return $return; } return ['result' => $result['rc_Result'], 'rd_Pic' => $result['rd_Pic']]; } else { //Yii::error('joinpay pay fail,msg:' . json_encode($result)); throw new Exception('汇聚支付有误'); } } private function getRequest($order) { $request = [ 'p0_Version' => '1.0', //版本号 'p1_MerchantNo' => $this->config['merchant_no'], //商户编号 'p2_OrderNo' => $order['order_no'], 'p3_Amount' => sprintf("%.2f", $order['amount']), 'p4_Cur' => 1, //人民币 'p5_ProductName' => $order['title'], 'p6_ProductDesc' => $order['title'], 'p7_Mp' => json_encode(['mall_id' => $order['mall_id']]),// 公用回传参数 'p8_ReturnUrl' => '', //商户页面通知地址 'p9_NotifyUrl' => $order['notify_url'], // 服务器异步通知地址 'q1_FrpCode' => $order['type'], //支付宝扫码,扫码方式 'q2_MerchantBankCode' => '', //银行商户编码 'q3_SubMerchantNo' => '', //子商户号 'q4_IsShowPic' => '', //是否展示图片 'q5_OpenId' => '', //微信 Openid,微信支付必填 'q6_AuthCode' => '', //付款码数字 'q7_AppId' => '', //APPID 'q8_TerminalNo' => '', //终端号 'q9_TransactionModel' => '', //微信 H5 模式:空,MODEL1,MODEL2 'qa_TradeMerchantNo' => $this->config['trade_merchant_no'], //报备商户号 ]; if (in_array($order['type'], ['WEIXIN_GZH', 'WEIXIN_XCX', 'WEIXIN_APP3'])) { //微信环境获取对应的openid $request['q5_OpenId'] = $order['open_id'] ?? ''; $request['q7_AppId'] = $this->config['wechat_app_id']; } $secret = $this->config['merchant_secret']; $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret)); return $request; } /** * 查询订单支付状态 * @params string $trade_no * @return array * @throws */ public function query($trade_no) { $request = [ 'p1_MerchantNo' => $this->config['merchant_no'], 'p2_OrderNo' => $trade_no, ]; $secret = $this->config['merchant_secret']; $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret)); $result = RequestUtil::http_post($this->order_query_url, $request); return json_decode($result, true); } /** * 退款 * @param array $info * [ * 'refund_order_no' => $refund_order_no, * 'out_refund_no' => $out_trade_no, * 'refund_amount' => 1, * 'refund_reason' => 1 * ] * * @return bool * @throws */ public function refund($info) { try { //异步回调地址 $protocol = Yii::$app->params['protocol']; $url = \Yii::$app->request->hostInfo . \Yii::$app->request->baseUrl . '/pay-notify/' . 'joinpay-refund.php'; if ($protocol) { $url = str_replace('http://', ($protocol . '://'), $url); $url = str_replace('https://', ($protocol . '://'), $url); } $request = [ 'p1_MerchantNo' => $this->config['merchant_no'], //商户编号 'p2_OrderNo' => $info['out_refund_no'], 'p3_RefundOrderNo' => $info['refund_order_no'], 'p4_RefundAmount' => $info['refund_amount'], 'p5_RefundReason' => $info['refund_reason'], 'p6_NotifyUrl' => $url ]; //商户密钥: $secret = $this->config['merchant_secret']; $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret)); $result = RequestUtil::http_post($this->refund_url, $request); \Yii::error('joinpay refund res:' . $result); $result = json_decode($result, true); if ($result['ra_Status'] != 100 || $result['rb_Code'] != 100) { //失败 throw new Exception($result['rc_CodeMsg']); } return true; } catch (Exception $e) { Yii::error('joinpay refund fail,msg:' . $e->getMessage()); return false; } } /** * 验证回调签名 * @param $params * @return bool */ private function checkHmac($params) { //验证签名 $hmac = $params['hmac']; unset($params['hmac']); unset($params['r']); $hmacVal = urlencode(RequestUtil::hmacRequest($params, $this->config['merchant_secret'])); if ($hmacVal !== $hmac) { \Yii::error('签名不合法'); return false; } return true; } /** * 异步回调 * @return array * @throws */ public function notify() { $params = array_merge(Yii::$app->request->post(), Yii::$app->request->get()); $params = array_map('urldecode', $params); //验证签名 if (!$this->checkHmac($params)) { throw new Exception('签名不合法'); } if (intval($params['r6_Status']) !== 100) { //100:支付成功,101:支付失败 throw new Exception('支付状态有误'); } if ($params['r1_MerchantNo'] !== $this->config['merchant_no']) { //商户编号不一致 throw new Exception('商户编号不一致'); } $trade = PaymentTrade::findOne(['out_trade_no' => $params['r2_OrderNo']]); if (!$trade) { throw new Exception('订单不存在'); } $pay_fee = (doubleval($params['r3_Amount']) * 100) . ''; //订单号查询对应订单 $order_amount = (doubleval($trade->getOnlinePayFee()) * 100) . ''; if (intval($pay_fee) !== intval($order_amount)) { throw new Exception('支付金额与订单金额不一致'); } //支付回调验证成功 $trade_type = $params['rc_BankCode'] ?? ''; return [ 'out_trade_no' => $trade->out_trade_no, 'trade_no' => $params['r7_TrxNo'], 'r9_BankTrxNo' => $params['r9_BankTrxNo']??'', 'pay_fee' => $params['r3_Amount'], 'trade_type' => $trade_type, ]; } /** * 退款回调处理 * @param $res * @return bool * @throws */ public function refund_notify($res) { //验证签名 if (!$this->checkHmac($res)) { return false; } //r2_OrderNo : 退款流水号 //r3_RefundOrderNo 退款订单号 return true; } }