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.

226 lines
7.7 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,'refund_time'=>0])
  96. ->first();
  97. if (empty($orderMain)) {
  98. throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
  99. }
  100. $result = $app->refund->byOutTradeNumber(
  101. $orderMain->global_order_id,
  102. $orderMain->global_order_id,
  103. bcmul($orderMain->money, 100, 0),
  104. bcmul($orderMain->money, 100, 0),
  105. [
  106. 'refund_desc' => '订单退款',
  107. 'notify_url' => config('wechat.notify_url.refund'),
  108. ]
  109. );
  110. if ($result['return_code'] == 'SUCCESS' && isset($result['result_code']) && $result['result_code'] == "SUCCESS") {
  111. return true;
  112. } else {
  113. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL,$result['err_code_des']);
  114. }
  115. } catch (\Exception $e) {
  116. $this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
  117. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[退款失败]'.$e->getMessage());
  118. }
  119. }
  120. /**
  121. * 企业付款到微信钱包
  122. * @param $money
  123. * @param $tradeNo
  124. * @param $openId
  125. * @param $userName
  126. * @param string $desc
  127. * @param string $checkName
  128. * @throws GuzzleException
  129. */
  130. public function payToWx($money, $tradeNo, $openId, $userName, $desc = '', $checkName = 'NO_CHECK')
  131. {
  132. $config = config('wxpay');
  133. $app = Factory::payment($config);
  134. $app['guzzle_handler'] = CoroutineHandler::class;
  135. $result = $app->transfer->toBalance([
  136. 'partner_trade_no' => $tradeNo, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
  137. 'openid' => $openId,
  138. 'check_name' => $checkName, // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
  139. 're_user_name' => $userName, // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名
  140. 'amount' => $money, // 企业付款金额,单位为分
  141. 'desc' => $desc, // 企业付款操作说明信息
  142. ]);
  143. if ($result['return_code'] != 'SUCCESS') {
  144. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  145. 'exception_payToWx' => $result['return_msg'],
  146. 'result' => json_encode($result),
  147. 'desc' => $desc
  148. ]);
  149. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, $result['return_msg']);
  150. }
  151. if ($result['result_code'] != 'SUCCESS') {
  152. $this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
  153. 'exception_payToWx' => $result['err_code_des'],
  154. 'result' => json_encode($result),
  155. 'desc' => $desc
  156. ]);
  157. throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, $result['err_code_des']);
  158. }
  159. return true;
  160. }
  161. /**
  162. * 支付参数加签
  163. * @param $parameters
  164. * @param $key
  165. * @return string
  166. */
  167. private function signture($parameters, $key)
  168. {
  169. // 按字典序排序参数
  170. ksort($parameters);
  171. // http_query
  172. $queryParams = $this->http_query($parameters);
  173. // 加入KEY
  174. $queryParams = $queryParams . "&key=" . $key;
  175. // MD5加密
  176. $queryParams = md5($queryParams);
  177. // 字符转为大写
  178. return strtoupper($queryParams);
  179. }
  180. /**
  181. * 参数转为http query字串
  182. * @param $parameters
  183. * @return string
  184. */
  185. private function http_query($parameters) {
  186. $http_query = [];
  187. foreach ($parameters as $key => $value) {
  188. $http_query[] = $key.'='.$value;
  189. }
  190. return implode('&', $http_query);
  191. }
  192. }