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.

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