You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
9.4 KiB

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: travis
  5. * Date: 2019/12/12
  6. * Time: 6:20
  7. */
  8. namespace api\logic;
  9. use backend\modules\shop\models\ars\PaymentLog;
  10. use backend\modules\shop\models\ars\WxPayConfig;
  11. use common\models\User;
  12. use Yii;
  13. use backend\modules\shop\models\ars\Config;
  14. use EasyWeChat\Factory;
  15. use backend\modules\shop\models\ars\Order;
  16. use yii\db\Exception;
  17. use yii\web\BadRequestHttpException;
  18. use yii\web\NotFoundHttpException;
  19. use yii\base\BaseObject;
  20. class WxPaymentLogic extends BaseObject
  21. {
  22. /*支付类型*/
  23. const PAY_TYPE_WEB = 1;
  24. const PAY_TYPE_MINI_PROGRAM = 2;
  25. /*发起支付方式*/
  26. const TRADE_TYPE_JS_API = 'JSAPI';
  27. public $appId;
  28. public $mchId;
  29. public $key;
  30. public $certPath;
  31. public $keyPath;
  32. public $notifyUrl = 'http://baidu.com';
  33. public $tradeType;
  34. public $payType;
  35. public $app;
  36. public $order;
  37. /**
  38. * @param $payType
  39. * @throws BadRequestHttpException
  40. * @throws Exception
  41. * 微信统一下单
  42. */
  43. public function wxPayment($payType)
  44. {
  45. $this->payType = $payType;
  46. $unifyParams = $this->applyPaymentData();
  47. $this->unify($unifyParams);
  48. }
  49. /**
  50. * @return array
  51. * @throws BadRequestHttpException
  52. * @throws Exception
  53. * 生成支付参数
  54. */
  55. private function applyPaymentData()
  56. {
  57. $orderId = Yii::$app->request->getBodyParam('order_id');/*int 商品id*/
  58. $paymentAmount = Yii::$app->request->getBodyParam('payment_amount');/*int 商品id*/
  59. $notifyUrl = Yii::$app->request->getBodyParam('notify_url');/*int 商品id*/
  60. if (empty($orderId) || empty($paymentAmount) || empty($notifyUrl)) {
  61. throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
  62. }
  63. $this->tradeType = self::TRADE_TYPE_JS_API;
  64. $this->savePaymentLog($orderId, $paymentAmount, $notifyUrl);
  65. $params = [
  66. 'body' => '订单支付',
  67. 'out_trade_no' => $orderId,
  68. 'total_fee' => round($paymentAmount * 100),
  69. 'openid' => Yii::$app->user->identity->wx_openid,
  70. ];
  71. return $params;
  72. }
  73. /**
  74. * @param string $orderId
  75. * @param float $paymentAmount
  76. * @param string $notifyUrl
  77. * @throws Exception
  78. * 保存支付信息
  79. */
  80. private function savePaymentLog($orderId, $paymentAmount, $notifyUrl)
  81. {
  82. $paymentLog = PaymentLog::findOne(['order_id' => $this->order->order_sn]);
  83. if (!$paymentLog) {
  84. $paymentLog = new PaymentLog();
  85. }
  86. $paymentLog->order_id = $orderId;
  87. $paymentLog->payment_amount = $paymentAmount;
  88. $paymentLog->notify_url = $notifyUrl;
  89. $paymentLog->type = $this->payType;
  90. if (!$paymentLog->save()) {
  91. throw new Exception(Helper::errorMessageStr($paymentLog->errors));
  92. }
  93. }
  94. private function getPaymentApp()
  95. {
  96. $this->initObject();
  97. $config = [
  98. 'app_id' => $this->appId,
  99. 'mch_id' => $this->mchId,
  100. 'key' => $this->key,
  101. 'cert_path' => $this->certPath,
  102. 'key_path' => $this->keyPath,
  103. 'notify_url' => $this->notifyUrl,
  104. 'trade_type' => $this->tradeType,
  105. 'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
  106. ];
  107. $this->app = Factory::payment($config);
  108. // 判断当前是否为沙箱模式:
  109. $this->app->inSandbox();
  110. }
  111. /**
  112. * @var WxPayConfig $wxPayConfig
  113. */
  114. private function initObject()
  115. {
  116. $path = Yii::getAlias('@backend');
  117. // switch ($this->payType) {
  118. // case self::PAY_TYPE_WEB:
  119. // $this->appId = trim($config->wx_appId);
  120. // $this->mchId = trim($config->wx_mchId);
  121. // $this->key = trim($config->wx_key);
  122. // $this->keyPath = trim($path . $config->wx_keyPath);
  123. // break;
  124. // case self::PAY_TYPE_MINI_PROGRAM:
  125. // $this->appId = trim($config->mini_program_appId);
  126. // $this->mchId = trim($config->mini_program_mchId);
  127. // $this->key = trim($config->mini_program_key);
  128. // break;
  129. // }
  130. $wxPayConfig = WxPayConfig::find()->one();
  131. $this->mchId = $wxPayConfig->mch_id;
  132. $this->certPath = trim($path . $wxPayConfig->cert_path);
  133. $this->keyPath = trim($path . $wxPayConfig->key_path);
  134. }
  135. /**
  136. * @param $unifyParams
  137. * @return mixed
  138. * @throws Exception
  139. * 统一下单
  140. */
  141. private function unify($unifyParams)
  142. {
  143. $this->getPaymentApp();
  144. return $this->app->order->unify($unifyParams);
  145. }
  146. /**
  147. * @return array|bool
  148. * @throws BadRequestHttpException
  149. * 支付回调
  150. */
  151. public function notify()
  152. {
  153. $result = [
  154. 'appid' => 'wxdccdddaa336353e1',
  155. 'bank_type' => 'OTHERS',
  156. 'cash_fee' => '926',
  157. 'fee_type' => 'CNY',
  158. 'is_subscribe' => 'N',
  159. 'mch_id' => '1395812402',
  160. 'nonce_str' => '5df366a248edb',
  161. 'openid' => 'ovT0C0UB8xQbfYHVVS2VGWMjDMhI',
  162. 'out_trade_no' => '201912131823156640Q',
  163. 'result_code' => 'SUCCESS',
  164. 'return_code' => 'SUCCESS',
  165. 'sign' => 'EA6FFD717A206D203856EAFB554F666F',
  166. 'time_end' => '20191213182333',
  167. 'total_fee' => '926',
  168. 'trade_type' => 'JSAPI',
  169. 'transaction_id' => '4200000423201912134049863433',
  170. ];
  171. $xmlString = $this->arrayToXml($result);
  172. $notifyData = json_decode(json_encode(simplexml_load_string($xmlString, 'SimpleXMLElement', LIBXML_NOCDATA)));
  173. // $notifyData = json_decode(json_encode(simplexml_load_string(Yii::$app->request->getRawBody(), 'SimpleXMLElement', LIBXML_NOCDATA)));
  174. // Yii::info($notifyData, "notify");
  175. // if (!$this->checkSign($notifyData)) {
  176. // throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
  177. // }
  178. $tra = Yii::$app->db->beginTransaction('SERIALIZABLE');
  179. try {
  180. if ($notifyData->result_code != 'SUCCESS' || $notifyData->return_code != 'SUCCESS') {
  181. throw new Exception('result_code or return_code is false');
  182. }
  183. $user = User::findOne(['wx_openid' => $notifyData->openid, 'status' => User::STATUS_ACTIVE])
  184. ?? User::findOne(['mini_openid' => $notifyData->openid, 'status' => User::STATUS_ACTIVE]);/*根据openid获取用户*/
  185. if (empty($user)) {
  186. throw new Exception('用户不存在或已被禁用');
  187. }
  188. $paymentLog = PaymentLog::findOne(['order_id' => $notifyData->out_trade_no]);
  189. $paymentLog->mch_id = $this->mch_id;
  190. $paymentLog->wx_refund_id = $notifyData->transaction_id; //交易号
  191. $paymentLog->status = 1;
  192. if (!$paymentLog->save()) {
  193. throw new Exception(Helper::errorMessageStr($paymentLog->errors));
  194. }
  195. if (!$tra->commit()) {
  196. throw new Exception('保存数据失败');
  197. }
  198. $this->forwardNotify($notifyData, true);
  199. return ['return_code' => 'SUCCESS', 'return_msg' => 'OK'];//回传成功信息到微信服务器
  200. } catch (Exception $e) {
  201. $tra->rollBack();
  202. $this->forwardNotify($notifyData, false);
  203. Yii::info($e->getMessage(), 'notify');
  204. return false;
  205. }
  206. }
  207. private function forwardNotify($notifyData, $status)
  208. {
  209. return true;
  210. return false;
  211. }
  212. /**
  213. * @param $data
  214. * @return bool
  215. * 支付成功回调验证签名和支付金额
  216. */
  217. public function checkSign($data)
  218. {
  219. $this->initObject();
  220. $notifySign = $data['sign'];
  221. unset($data['sign']);
  222. $sign = $this->_sign($data);
  223. if ($notifySign == $sign) {
  224. return true;
  225. } else {
  226. return false;
  227. }
  228. }
  229. /**
  230. * @param $arr
  231. * @return string
  232. * 微信签名方法
  233. */
  234. private function _sign($arr)
  235. {
  236. $arr = array_filter($arr);
  237. ksort($arr);
  238. $arr['key'] = $this->key;
  239. $queryString = http_build_query($arr);
  240. $queryString = urldecode($queryString);
  241. return strtoupper(md5($queryString));
  242. }
  243. /**
  244. * @param $arr
  245. * @param null $dom
  246. * @param null $node
  247. * @param string $root
  248. * @param bool $cdata
  249. * @return string
  250. * 数组转xml字符串
  251. */
  252. function arrayToXml($arr, $dom = null, $node = null, $root = 'xml', $cdata = false)
  253. {
  254. if (!$dom) {
  255. $dom = new \DOMDocument('1.0','utf-8');
  256. }
  257. if (!$node) {
  258. $node = $dom->createElement($root);
  259. $dom->appendChild($node);
  260. }
  261. foreach ($arr as $key=>$value) {
  262. $child_node = $dom->createElement(is_string($key) ? $key : 'node');
  263. $node->appendChild($child_node);
  264. if (!is_array($value)) {
  265. if (!$cdata) {
  266. $data = $dom->createTextNode($value);
  267. } else {
  268. $data = $dom->createCDATASection($value);
  269. }
  270. $child_node->appendChild($data);
  271. } else {
  272. $this->arrayToXml($value,$dom,$child_node,$root,$cdata);
  273. }
  274. }
  275. return $dom->saveXML();
  276. }
  277. }