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.

276 lines
9.9 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. <?php
  2. namespace App\Service\v3\Implementations;
  3. use App\Commons\Log;
  4. use App\Constants\v3\ErrorCode;
  5. use App\Constants\v3\LogLabel;
  6. use App\Constants\v3\OrderState;
  7. use App\Constants\v3\OrderType;
  8. use App\Constants\v3\Payment;
  9. use App\Exception\ErrorCodeException;
  10. use App\Model\v3\OrderMain;
  11. use App\Model\v3\User;
  12. use App\Service\v3\Interfaces\GoodsActivityServiceInterface;
  13. use App\Service\v3\Interfaces\PaymentServiceInterface;
  14. use App\Service\v3\Interfaces\SmsSendServiceInterface;
  15. use EasyWeChat\Factory;
  16. use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
  17. use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
  18. use EasyWeChat\Kernel\Support\Collection;
  19. use GuzzleHttp\Exception\GuzzleException;
  20. use Hyperf\DbConnection\Db;
  21. use Hyperf\Guzzle\CoroutineHandler;
  22. use Hyperf\Di\Annotation\Inject;
  23. use Hyperf\Redis\Redis;
  24. use Hyperf\Utils\ApplicationContext;
  25. use Psr\Http\Message\ResponseInterface;
  26. use function AlibabaCloud\Client\json;
  27. class PaymentService implements PaymentServiceInterface
  28. {
  29. /**
  30. * @Inject
  31. * @var Log
  32. */
  33. protected $log;
  34. /**
  35. * @Inject
  36. * @var SmsSendServiceInterface
  37. */
  38. protected $smsAliSendService;
  39. public function do($globalOrderId, $money, $userId, $notifyUrl)
  40. {
  41. try {
  42. $config = config('wxpay');
  43. $app = Factory::payment($config);
  44. $app['guzzle_handler'] = CoroutineHandler::class;
  45. // 待支付的,未超时(15min,900sec)的订单
  46. $orderMain = OrderMain::query()
  47. ->where(['state' => OrderState::UNPAID, 'global_order_id' => $globalOrderId, 'user_id' => $userId])
  48. ->where('created_at', '>=', (time()-900))
  49. ->first();
  50. if (empty($orderMain)) {
  51. throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
  52. }
  53. $payMoney = bcmul((string)$orderMain->money, 100, 0);
  54. if (env('APP_ENV') != 'prod' && $orderMain->type == OrderType::ONLINE) {
  55. $payMoney = 1;
  56. }
  57. $user = User::select('openid')->find($userId);
  58. $result = $app->order->unify([
  59. 'body' => '链街生活',
  60. 'out_trade_no' => $orderMain->global_order_id,
  61. 'total_fee' => $payMoney,
  62. 'notify_url' => $notifyUrl,
  63. 'trade_type' => 'JSAPI',
  64. 'openid' => $user['openid'],
  65. ]);
  66. if ($result['return_code'] == 'FAIL') {
  67. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[支付失败]'.$result['return_msg']);
  68. }
  69. // 返回支付参数给前端
  70. $parameters = [
  71. 'appId' => $result['appid'],
  72. 'timeStamp' => '' . time() . '',
  73. 'nonceStr' => uniqid(),
  74. 'package' => 'prepay_id=' . $result['prepay_id'],
  75. 'signType' => 'MD5'
  76. ];
  77. $parameters['paySign'] = $this->signture($parameters, $config['key']);
  78. $parameters['order_main_id'] = $orderMain->global_order_id;
  79. return $parameters;
  80. } catch (\Exception $e) {
  81. $this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
  82. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[稍后重试]');
  83. }
  84. }
  85. public function check()
  86. {
  87. // TODO: Implement check() method.
  88. }
  89. /**
  90. * 退款的整单,用户申请的退款
  91. * @param $globalOrderId
  92. * @param $userId
  93. * @return array|bool|Collection|object|ResponseInterface|string
  94. */
  95. public function undo($globalOrderId, $userId)
  96. {
  97. try{
  98. $config = config('wxpay');
  99. $app = Factory::payment($config);
  100. $app['guzzle_handler'] = CoroutineHandler::class;
  101. // 已支付的,未退款的,使用微信支付的订单
  102. $orderMain = OrderMain::query()
  103. ->whereIn('state', [OrderState::PAID, OrderState::DELIVERY, OrderState::COMPLETED, OrderState::EVALUATED, OrderState::REFUNDING])
  104. ->where(['global_order_id' => $globalOrderId, 'user_id' => $userId, 'pay_type' => Payment::WECHAT,'refund_time'=>0])
  105. ->first();
  106. if (empty($orderMain)) {
  107. throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
  108. }
  109. $result = $app->refund->byOutTradeNumber(
  110. $orderMain->global_order_id,
  111. $orderMain->global_order_id,
  112. bcmul($orderMain->money, 100, 0),
  113. bcmul($orderMain->money, 100, 0),
  114. [
  115. 'refund_desc' => '订单退款',
  116. 'notify_url' => config('wechat.notify_url.refund'),
  117. ]
  118. );
  119. if ($result['return_code'] == 'SUCCESS' && isset($result['result_code']) && $result['result_code'] == "SUCCESS") {
  120. return true;
  121. } else {
  122. throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL,$result['err_code_des']);
  123. }
  124. } catch (\Exception $e) {
  125. $this->log->event(LogLabel::ORDER_REFUND_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
  126. throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL);
  127. }
  128. }
  129. /**
  130. * 企业付款到微信钱包
  131. * @param $money
  132. * @param $tradeNo
  133. * @param $openId
  134. * @param $userName
  135. * @param string $desc
  136. * @param string $checkName
  137. * @return bool
  138. * @throws GuzzleException
  139. * @throws InvalidConfigException
  140. * @throws InvalidArgumentException
  141. */
  142. public function payToWx($money, $tradeNo, $openId, $userName, $desc = '', $checkName = 'NO_CHECK')
  143. {
  144. $config = config('wxpay');
  145. $app = Factory::payment($config);
  146. $app['guzzle_handler'] = CoroutineHandler::class;
  147. // 特殊原因如资金不够等,发短信给老板或者老总
  148. $redis = ApplicationContext::getContainer()->get(Redis::class);
  149. $refuseReasonForSpecialKey = 'send_withdraw_refuse_reson_'.date('Ymd');
  150. $result = $app->transfer->toBalance([
  151. 'partner_trade_no' => $tradeNo, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
  152. 'openid' => $openId,
  153. 'check_name' => $checkName, // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
  154. 're_user_name' => $userName, // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名
  155. 'amount' => $money, // 企业付款金额,单位为分
  156. 'desc' => $desc, // 企业付款操作说明信息
  157. ]);
  158. if ($result['return_code'] != 'SUCCESS') {
  159. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  160. 'exception_payToWx' => $result['return_msg'],
  161. 'result' => json_encode($result),
  162. 'desc' => $desc
  163. ]);
  164. throw new ErrorCodeException(ErrorCode::WITHDRAW_PAYMENT_FAIL);
  165. }
  166. if ($result['result_code'] != 'SUCCESS') {
  167. // 如果是商户余额不足等原因,要发送短信给老总
  168. $arr = ['NOTENOUGH','AMOUNT_LIMIT','NO_AUTH'];
  169. if (in_array($result['err_code'], $arr)) {
  170. if (!$redis->exists($refuseReasonForSpecialKey)) { // 当天如果没发过就发
  171. $res = $this->smsAliSendService->doWithdrawFail($desc, $money, $result['err_code'], $result['err_code_des']);
  172. if (isset($res['Code']) && $res['Code'] == 'OK') {
  173. $redis->set($refuseReasonForSpecialKey, $result['err_code'].'/'.$result['err_code_des']);
  174. }
  175. }
  176. }
  177. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  178. 'exception_payToWx' => $result['err_code_des'],
  179. 'result' => json_encode($result),
  180. 'desc' => $desc
  181. ]);
  182. $arr = ['NAME_MISMATCH','V2_ACCOUNT_SIMPLE_BAN', 'SENDNUM_LIMIT'];
  183. $msg = '';
  184. if (in_array($result['err_code'], $arr)) {
  185. $msg = $result['err_code_des'];
  186. }
  187. // if ($result['err_code'] == 'SENDNUM_LIMIT') {
  188. // throw new ErrorCodeException(ErrorCode::PAYMENT_SEND_NUM_LIMIT);
  189. // } elseif ($result['err_code'] == 'V2_ACCOUNT_SIMPLE_BAN') {
  190. // throw new ErrorCodeException(ErrorCode::PAYMENT_V2_ACCOUNT_SIMPLE_BAN);
  191. // } elseif ($result['err_code'] == 'NAME_MISMATCH') {
  192. // throw new ErrorCodeException(ErrorCode::PAYMENT_NAME_MISMATCH);
  193. // } elseif ($result['err_code'] == 'AMOUNT_LIMIT') {
  194. // throw new ErrorCodeException(ErrorCode::PAYMENT_AMOUNT_LIMIT);
  195. // }
  196. throw new ErrorCodeException(ErrorCode::WITHDRAW_PAYMENT_FAIL);
  197. }
  198. // 一旦有提现成功的,说明没有特殊原因影响,可能临时解除了,比如临时充钱了,去除标志防止再次缺钱的发生
  199. $redis->del($refuseReasonForSpecialKey);
  200. return true;
  201. }
  202. /**
  203. * 支付参数加签
  204. * @param $parameters
  205. * @param $key
  206. * @return string
  207. */
  208. private function signture($parameters, $key)
  209. {
  210. // 按字典序排序参数
  211. ksort($parameters);
  212. // http_query
  213. $queryParams = $this->http_query($parameters);
  214. // 加入KEY
  215. $queryParams = $queryParams . "&key=" . $key;
  216. // MD5加密
  217. $queryParams = md5($queryParams);
  218. // 字符转为大写
  219. return strtoupper($queryParams);
  220. }
  221. /**
  222. * 参数转为http query字串
  223. * @param $parameters
  224. * @return string
  225. */
  226. private function http_query($parameters) {
  227. $http_query = [];
  228. foreach ($parameters as $key => $value) {
  229. $http_query[] = $key.'='.$value;
  230. }
  231. return implode('&', $http_query);
  232. }
  233. }