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.

225 lines
7.5 KiB

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 EasyWeChat\Factory;
  15. use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
  16. use EasyWeChat\Kernel\Support\Collection;
  17. use GuzzleHttp\Exception\GuzzleException;
  18. use Hyperf\DbConnection\Db;
  19. use Hyperf\Guzzle\CoroutineHandler;
  20. use Hyperf\Di\Annotation\Inject;
  21. use Psr\Http\Message\ResponseInterface;
  22. use function AlibabaCloud\Client\json;
  23. class PaymentService implements PaymentServiceInterface
  24. {
  25. /**
  26. * @Inject
  27. * @var Log
  28. */
  29. protected $log;
  30. public function do($globalOrderId, $money, $userId, $notifyUrl)
  31. {
  32. try {
  33. $config = config('wxpay');
  34. $app = Factory::payment($config);
  35. $app['guzzle_handler'] = CoroutineHandler::class;
  36. // 待支付的,未超时(15min,900sec)的订单
  37. $orderMain = OrderMain::query()
  38. ->where(['state' => OrderState::UNPAID, 'global_order_id' => $globalOrderId, 'user_id' => $userId])
  39. ->where('created_at', '>=', (time()-900))
  40. ->first();
  41. if (empty($orderMain)) {
  42. throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
  43. }
  44. $payMoney = bcmul((string)$orderMain->money, 100, 0);
  45. if (env('APP_ENV') != 'prod' && $orderMain->type == OrderType::ONLINE) {
  46. $payMoney = 1;
  47. }
  48. $user = User::select('openid')->find($userId);
  49. $result = $app->order->unify([
  50. 'body' => '懒族生活',
  51. 'out_trade_no' => $orderMain->global_order_id,
  52. 'total_fee' => $payMoney,
  53. 'notify_url' => $notifyUrl,
  54. 'trade_type' => 'JSAPI',
  55. 'openid' => $user['openid'],
  56. ]);
  57. if ($result['return_code'] == 'FAIL') {
  58. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[支付失败]'.$result['return_msg']);
  59. }
  60. // 返回支付参数给前端
  61. $parameters = [
  62. 'appId' => $result['appid'],
  63. 'timeStamp' => '' . time() . '',
  64. 'nonceStr' => uniqid(),
  65. 'package' => 'prepay_id=' . $result['prepay_id'],
  66. 'signType' => 'MD5'
  67. ];
  68. $parameters['paySign'] = $this->signture($parameters, $config['key']);
  69. $parameters['order_main_id'] = $orderMain->global_order_id;
  70. return $parameters;
  71. } catch (\Exception $e) {
  72. $this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
  73. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[支付失败]'.$e->getMessage());
  74. }
  75. }
  76. public function check()
  77. {
  78. // TODO: Implement check() method.
  79. }
  80. /**
  81. * 退款的整单,允许后台操作退款
  82. * @param $globalOrderId
  83. * @param $userId
  84. * @return array|bool|Collection|object|ResponseInterface|string
  85. */
  86. public function undo($globalOrderId, $userId)
  87. {
  88. try{
  89. $config = config('wxpay');
  90. $app = Factory::payment($config);
  91. $app['guzzle_handler'] = CoroutineHandler::class;
  92. // 已支付的,未退款的,使用微信支付的订单
  93. $orderMain = OrderMain::query()
  94. ->whereIn('state', [OrderState::PAID, OrderState::DELIVERY, OrderState::COMPLETED, OrderState::EVALUATED, OrderState::REFUNDING])
  95. ->where(['global_order_id' => $globalOrderId, 'user_id' => $userId, 'pay_type' => Payment::WECHAT])
  96. ->whereRaw('refund_time is null')
  97. ->first();
  98. if (empty($orderMain)) {
  99. throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
  100. }
  101. $result = $app->refund->byOutTradeNumber(
  102. $orderMain->global_order_id,
  103. $orderMain->global_order_id,
  104. bcmul($orderMain->money, 100, 0),
  105. bcmul($orderMain->money, 100, 0),
  106. [
  107. 'refund_desc' => '订单退款',
  108. 'notify_url' => config('wechat.notify_url.refund'),
  109. ]
  110. );
  111. } catch (\Exception $e) {
  112. $this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
  113. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[退款失败]'.$e->getMessage());
  114. }
  115. }
  116. /**
  117. * 企业付款到微信钱包
  118. * @param $money
  119. * @param $tradeNo
  120. * @param $openId
  121. * @param $userName
  122. * @param string $desc
  123. * @param string $checkName
  124. * @throws GuzzleException
  125. */
  126. public function payToWx($money, $tradeNo, $openId, $userName, $desc = '', $checkName = 'NO_CHECK')
  127. {
  128. $config = config('wxpay');
  129. $app = Factory::payment($config);
  130. $app['guzzle_handler'] = CoroutineHandler::class;
  131. $result = $app->transfer->toBalance([
  132. 'partner_trade_no' => $tradeNo, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
  133. 'openid' => $openId,
  134. 'check_name' => $checkName, // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
  135. 're_user_name' => $userName, // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名
  136. 'amount' => $money, // 企业付款金额,单位为分
  137. 'desc' => $desc, // 企业付款操作说明信息
  138. ]);
  139. if ($result['return_code'] != 'SUCCESS') {
  140. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  141. 'exception_payToWx' => $result['return_msg'],
  142. 'result' => json_encode($result),
  143. 'desc' => $desc
  144. ]);
  145. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, $result['return_msg']);
  146. }
  147. if ($result['result_code'] != 'SUCCESS') {
  148. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  149. 'exception_payToWx' => $result['err_code_des'],
  150. 'result' => json_encode($result),
  151. 'desc' => $desc
  152. ]);
  153. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, $result['err_code_des']);
  154. }
  155. return true;
  156. }
  157. /**
  158. * 支付参数加签
  159. * @param $parameters
  160. * @param $key
  161. * @return string
  162. */
  163. private function signture($parameters, $key)
  164. {
  165. // 按字典序排序参数
  166. ksort($parameters);
  167. // http_query
  168. $queryParams = $this->http_query($parameters);
  169. // 加入KEY
  170. $queryParams = $queryParams . "&key=" . $key;
  171. // MD5加密
  172. $queryParams = md5($queryParams);
  173. // 字符转为大写
  174. return strtoupper($queryParams);
  175. }
  176. /**
  177. * 参数转为http query字串
  178. * @param $parameters
  179. * @return string
  180. */
  181. private function http_query($parameters) {
  182. $http_query = [];
  183. foreach ($parameters as $key => $value) {
  184. $http_query[] = $key.'='.$value;
  185. }
  186. return implode('&', $http_query);
  187. }
  188. }