JoinPay.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. namespace common\components\payment;
  3. use addons\JoinPay\common\enums\JoinPayTypeEnum;
  4. use common\components\payment\joinpay\util\RequestUtil;
  5. use common\enums\PayTypeEnum;
  6. use common\helpers\StringHelper;
  7. use common\helpers\Url;
  8. use common\models\common\PaymentTradeOrder;
  9. use common\models\common\PaymentTrade;
  10. use common\traits\ErrorTrait;
  11. use Yii;
  12. use Exception;
  13. /**
  14. * 汇聚聚合支付类
  15. *
  16. * Class JoinPay
  17. * @package common\components\payment
  18. */
  19. class JoinPay
  20. {
  21. use ErrorTrait;
  22. protected $config;
  23. public $url = 'https://www.joinpay.com/trade/uniPayApi.action';
  24. public $order_query_url = 'https://www.joinpay.com/trade/queryOrder.action';
  25. public $refund_url = 'https://www.joinpay.com/trade/refund.action';
  26. public function __construct($config)
  27. {
  28. $this->config = $config;
  29. }
  30. /**
  31. * 支付接口
  32. * @param array $order
  33. * [
  34. * 'title' => '订单标题'
  35. * 'order_no' => '交易流水号'
  36. * 'amount' => 金额
  37. * 'mall_id' => 商城ID
  38. * 'notify_url' => '回调地址',
  39. * 'type' => '汇聚支付方式'
  40. * 'open_id' => '微信openid'
  41. * ]
  42. * @return array|mixed
  43. * @throws Exception
  44. */
  45. public function pay($order)
  46. {
  47. $request = $this->getRequest($order);
  48. \Yii::error(' joinpay request params => '.json_encode($request));
  49. $result = RequestUtil::http_post($this->url, $request);
  50. \Yii::error(' joinpay response data => ' . $result);\Yii::getLogger()->flush(true);
  51. $result = json_decode($result, true);
  52. if(isset($order['type'])&&$order['type']==JoinPayTypeEnum::WEIXIN_CJXCX){
  53. if ($result['ra_Code'] == 100 ) {
  54. return $result;
  55. } else {
  56. return ['return_code'=>'FAIL','return_msg'=>$result['rb_CodeMsg']];
  57. }
  58. }
  59. if ($result['ra_Code'] == 100 && !empty($result['rc_Result'])) {
  60. if (StringHelper::isJson($result['rc_Result'])) {
  61. $return = json_decode($result['rc_Result'], true);
  62. if (!isset($return['timestamp'])) {
  63. $return['timestamp'] = $return['timeStamp'] ?? '';
  64. }
  65. return $return;
  66. }
  67. return ['result' => $result['rc_Result'], 'rd_Pic' => $result['rd_Pic']];
  68. } else {
  69. return ['return_code'=>'FAIL','return_msg'=>$result['rb_CodeMsg']];
  70. }
  71. }
  72. private function getRequest($order)
  73. {
  74. $request = [
  75. 'p0_Version' => '1.0', //版本号
  76. 'p1_MerchantNo' => $this->config['merchant_no'], //商户编号
  77. 'p2_OrderNo' => $order['order_no'],
  78. 'p3_Amount' => sprintf("%.2f", $order['amount']),
  79. 'p4_Cur' => 1, //人民币
  80. 'p5_ProductName' => str_replace('+', '', trim($order['title'])),
  81. 'p6_ProductDesc' => str_replace('+', '', trim($order['title'])),
  82. 'p7_Mp' => json_encode(['mall_id' => $order['mall_id']]),// 公用回传参数
  83. 'p8_ReturnUrl' => '', //商户页面通知地址
  84. 'p9_NotifyUrl' => $order['notify_url'], // 服务器异步通知地址
  85. 'q1_FrpCode' => $order['type'], //支付宝扫码,扫码方式
  86. 'q2_MerchantBankCode' => '', //银行商户编码
  87. 'q3_SubMerchantNo' => '', //子商户号
  88. 'q4_IsShowPic' => '', //是否展示图片
  89. 'q5_OpenId' => '', //微信 Openid,微信支付必填
  90. 'q6_AuthCode' => '', //付款码数字
  91. 'q7_AppId' => '', //APPID
  92. 'q8_TerminalNo' => '', //终端号
  93. 'q9_TransactionModel' => '', //微信 H5 模式:空,MODEL1,MODEL2
  94. // 'qa_TradeMerchantNo' => $this->config['trade_merchant_no'], //报备商户号
  95. ];
  96. if (in_array($order['type'], ['WEIXIN_GZH', 'WEIXIN_XCX', 'WEIXIN_APP3',JoinPayTypeEnum::WEIXIN_CJXCX])) {
  97. //微信环境获取对应的openid
  98. $request['q5_OpenId'] = $order['open_id'] ?? '';
  99. $appid = '';
  100. $trade_merchant_no = '';
  101. switch ($order['type']) {
  102. case 'WEIXIN_GZH':
  103. $appid = $this->config['wechat_gzh_appid'] ?? '';
  104. $trade_merchant_no = $this->config['wechat_gzh_trade_merchant_no'] ?? '';
  105. break;
  106. case 'WEIXIN_XCX':
  107. $appid = $this->config['wechat_xcx_appid'] ?? '';
  108. $trade_merchant_no = $this->config['wechat_xcx_trade_merchant_no'] ?? '';
  109. break;
  110. case JoinPayTypeEnum::WEIXIN_CJXCX:
  111. $appid = '';
  112. $request['q5_OpenId'] = '';
  113. $trade_merchant_no = $this->config['wechat_xcx_trade_merchant_no'] ?? '';
  114. break;
  115. case 'WEIXIN_APP3':
  116. $appid = $this->config['wechat_xcx_appid'] ?? '';
  117. $trade_merchant_no = $this->config['wechat_app3_trade_merchant_no'] ?? '';
  118. break;
  119. case 'WEIXIN_H5':
  120. $appid = $this->config['wechat_gzh_appid'] ?? '';
  121. $trade_merchant_no = $this->config['wechat_gzh_trade_merchant_no'] ?? '';
  122. break;
  123. }
  124. } elseif (in_array($order['type'], ['ALIPAY_H5'])) {
  125. $appid = '';
  126. $trade_merchant_no = $this->config['alipay_trade_merchant_no'] ?? '';
  127. } elseif (in_array($order['type'], [JoinPayTypeEnum::UNIONPAY_APP, JoinPayTypeEnum::UNIONPAY_H5])) {
  128. $appid = '';
  129. $trade_merchant_no = $this->config['unionpay_trade_merchant_no'] ?? '';
  130. }
  131. $request['q7_AppId'] = $appid;
  132. $request['qa_TradeMerchantNo'] = $trade_merchant_no;
  133. // dd($this->config, $request);
  134. $secret = $this->config['merchant_secret'];
  135. $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret));
  136. return $request;
  137. }
  138. /**
  139. * 查询订单支付状态
  140. * @params string $trade_no
  141. * @return array
  142. * @throws
  143. */
  144. public function query($trade_no)
  145. {
  146. $request = [
  147. 'p0_Version' => 2.5,
  148. 'p1_MerchantNo' => $this->config['merchant_no'],
  149. 'p2_OrderNo' => $trade_no,
  150. ];
  151. $secret = $this->config['merchant_secret'];
  152. $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret));
  153. $result = RequestUtil::http_post($this->order_query_url, $request);
  154. return json_decode($result, true);
  155. }
  156. /**
  157. * 退款
  158. * @param array $info
  159. * [
  160. * 'refund_order_no' => $refund_order_no,
  161. * 'out_refund_no' => $out_trade_no,
  162. * 'refund_amount' => 1,
  163. * 'refund_reason' => 1
  164. * ]
  165. *
  166. * @return bool
  167. * @throws
  168. */
  169. public function refund($info)
  170. {
  171. try {
  172. //异步回调地址
  173. // $url = Url::toApi(['notify/joinpay-refund'], TRUE);
  174. $request = [
  175. 'p1_MerchantNo' => $this->config['merchant_no'], //商户编号
  176. 'p2_OrderNo' => $info['out_refund_no'],
  177. 'p3_RefundOrderNo' => $info['refund_order_no'],
  178. 'p4_RefundAmount' => $info['refund_amount'],
  179. 'p5_RefundReason' => $info['refund_reason'],
  180. 'p6_NotifyUrl' => \Yii::getAlias('@apiUrl').'/notify/joinpay-refund',
  181. 'q1_version' => '2.1'
  182. ];
  183. //商户密钥:
  184. $secret = $this->config['merchant_secret'];
  185. $request['hmac'] = urlencode(RequestUtil::hmacRequest($request, $secret));
  186. $result = RequestUtil::http_post($this->refund_url, $request);
  187. \Yii::error('joinpay refund res:' . $result);
  188. $result = json_decode($result, true);
  189. if ($result['ra_Status'] != 100 || $result['rb_Code'] != 100) {
  190. //失败
  191. throw new Exception($result['rc_CodeMsg']);
  192. }
  193. return true;
  194. } catch (Exception $e) {
  195. $this->setError($e->getMessage());
  196. Yii::error('joinpay refund fail,msg:' . $e->getMessage());
  197. return false;
  198. }
  199. }
  200. /**
  201. * 验证回调签名
  202. * @param $params
  203. * @return bool
  204. */
  205. private function checkHmac($params)
  206. {
  207. //验证签名
  208. $hmac = $params['hmac'];
  209. unset($params['hmac']);
  210. unset($params['r']);
  211. $hmacVal = urlencode(RequestUtil::hmacRequest($params, $this->config['merchant_secret']));
  212. if ($hmacVal !== $hmac) {
  213. \Yii::error('签名不合法');
  214. return false;
  215. }
  216. return true;
  217. }
  218. /**
  219. * 异步回调
  220. * @return array
  221. * @throws
  222. */
  223. public function notify()
  224. {
  225. $params = array_merge(Yii::$app->request->post(), Yii::$app->request->get());
  226. $params = array_map('urldecode', $params);
  227. //验证签名
  228. if (!$this->checkHmac($params)) {
  229. throw new Exception('签名不合法');
  230. }
  231. if (intval($params['r6_Status']) !== 100) {
  232. //100:支付成功,101:支付失败
  233. throw new Exception('支付状态有误');
  234. }
  235. if ($params['r1_MerchantNo'] !== $this->config['merchant_no']) {
  236. //商户编号不一致
  237. throw new Exception('商户编号不一致');
  238. }
  239. $trade = PaymentTrade::findOne(['out_trade_no' => $params['r2_OrderNo']]);
  240. if (!$trade) {
  241. throw new Exception('订单不存在');
  242. }
  243. $pay_fee = (doubleval($params['r3_Amount']) * 100) . '';
  244. //订单号查询对应订单
  245. $order_amount = (doubleval($trade->pay_fee) * 100) . '';
  246. if (intval($pay_fee) !== intval($order_amount)) {
  247. throw new Exception('支付金额与订单金额不一致');
  248. }
  249. //支付回调验证成功
  250. $trade_type = $params['rc_BankCode'] ?? '';
  251. return [
  252. 'out_trade_no' => $trade->out_trade_no,
  253. 'trade_no' => $params['r7_TrxNo'],
  254. 'pay_fee' => $params['r3_Amount'],
  255. 'trade_type' => $trade_type,
  256. ];
  257. }
  258. /**
  259. * 退款回调处理
  260. * @param $res
  261. * @return bool
  262. * @throws
  263. */
  264. public function refund_notify($res)
  265. {
  266. //验证签名
  267. if (!$this->checkHmac($res)) {
  268. return false;
  269. }
  270. //r2_OrderNo : 退款流水号
  271. //r3_RefundOrderNo 退款订单号
  272. return true;
  273. }
  274. }