PaymentProvider.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <?php
  2. declare(strict_types=1);
  3. namespace SixShop\WechatPay;
  4. use GuzzleHttp\Exception\ClientException;
  5. use SixShop\Core\Exception\NotFoundException;
  6. use SixShop\Core\Helper;
  7. use SixShop\Payment\Contracts\PaymentNotifyResult;
  8. use SixShop\Payment\Contracts\PaymentProviderInterface;
  9. use SixShop\Payment\Contracts\PaymentQueryResult;
  10. use SixShop\Payment\Contracts\PaymentRefundQueryResult;
  11. use SixShop\Payment\Contracts\PaymentRefundRequest;
  12. use SixShop\Payment\Contracts\PaymentRefundResult;
  13. use SixShop\Payment\Contracts\PaymentResponse;
  14. use SixShop\Payment\Entity\ExtensionPaymentEntity;
  15. use SixShop\Payment\Entity\ExtensionRefundEntity;
  16. use SixShop\Payment\Enum\PaymentBizEnum;
  17. use SixShop\Payment\Enum\PaymentStatusEnum;
  18. use SixShop\Payment\Enum\RefundStatusEnum;
  19. use SixShop\Payment\Event\PaymentSuccessEvent;
  20. use SixShop\Payment\Event\RefundSuccessEvent;
  21. use SixShop\Wechat\Facade\WechatUser;
  22. use SixShop\WechatPay\Trait\ApiTrait;
  23. use SixShop\WechatPay\Trait\PaymentParamsTrait;
  24. use think\facade\Db;
  25. use think\facade\Event;
  26. use function SixShop\Core\throw_logic_exception;
  27. class PaymentProvider implements PaymentProviderInterface
  28. {
  29. const string PAYMENT_TYPE = 'wechatpay';
  30. use ApiTrait;
  31. use PaymentParamsTrait;
  32. public function __construct(
  33. private readonly ExtensionPaymentEntity $extensionPaymentEntity,
  34. private readonly ExtensionRefundEntity $extensionRefundEntity,
  35. )
  36. {
  37. }
  38. public function create(array $order, PaymentBizEnum $bizType): PaymentResponse
  39. {
  40. $payment = $this->extensionPaymentEntity->where([
  41. 'order_id' => $order['id'],
  42. 'pay_type' => self::PAYMENT_TYPE,
  43. 'biz_type' => $bizType,
  44. ])->findOrEmpty();
  45. if (!$payment->isEmpty()) {
  46. // todo 判断订单是否支付成功
  47. // 支付时间结束关闭订单
  48. // 订单未支付可重新支付
  49. // 交易已关闭请重新下单
  50. //订单已付款请勿重复操作
  51. // 订单已退款请重新下单
  52. //订单已撤销请重新下单
  53. // 订单支付中请稍后再试
  54. // 订单支付失败请重新下单
  55. throw new \RuntimeException('开发测试中,请稍后再试');
  56. }
  57. $payment->transaction(function () use ($bizType, $order, $payment) {
  58. $payment->save([
  59. 'user_id' => $order['user_id'],
  60. 'order_id' => $order['id'],
  61. 'order_sn' => $order['order_sn'],
  62. 'biz_type' => $bizType,
  63. 'pay_type' => self::PAYMENT_TYPE,
  64. 'amount' => $order['pay_amount'],
  65. 'status' => PaymentStatusEnum::PENDING
  66. ]);
  67. $expireTime = time() + 3600;
  68. $payment->expire_time = $expireTime;
  69. $openid = WechatUser::openid($order['user_id']);
  70. if ($openid === null) {
  71. Helper::throw_logic_exception('用户需要先使用微信登录绑定微信身份');
  72. }
  73. $payment->payment_param = $this->wechatPay(
  74. openid: $openid,
  75. outTradeNo: $payment['out_trade_no'],
  76. total: (int)($payment['amount'] * 100),
  77. description: '订单:' . $order['order_sn'],
  78. expireTime: $expireTime
  79. );
  80. $payment->payment_param = $this->paymentParams($payment->payment_param->prepay_id);
  81. $payment->save();
  82. });
  83. return new PaymentResponse(orderNo: $payment->out_trade_no, type: self::PAYMENT_TYPE, raw: $payment->toArray());
  84. }
  85. public function notify(array $request): PaymentNotifyResult
  86. {
  87. throw new \Exception('Not implemented');
  88. }
  89. public function query(int $recordID): PaymentQueryResult
  90. {
  91. $payment = $this->extensionPaymentEntity->findOrEmpty($recordID);
  92. if ($payment->status === PaymentStatusEnum::PENDING) {
  93. try {
  94. $paymentResult = $this->queryByOutTradeNo($payment['out_trade_no']);
  95. } catch (ClientException $e) {
  96. if ($e->getCode() === 404) {
  97. throw new NotFoundException(sprintf('订单%s不存在', $payment['out_trade_no']));
  98. }
  99. throw $e;
  100. }
  101. $payment->payment_result = $paymentResult;
  102. if ($paymentResult->trade_state === 'SUCCESS') {
  103. $payment->transaction_id = $paymentResult->transaction_id;
  104. $payment->status = PaymentStatusEnum::SUCCESS;
  105. $payment->save();
  106. Event::trigger(new PaymentSuccessEvent($payment['order_sn'], self::PAYMENT_TYPE, $payment->toArray(), $payment->biz_type));
  107. } else {
  108. throw_logic_exception($paymentResult->trade_state_desc);
  109. }
  110. }
  111. return new PaymentQueryResult(
  112. orderNo: $payment['out_trade_no'],
  113. status: $payment['status'],
  114. amount: (float)$payment['amount'],
  115. raw: $payment->toArray()
  116. );
  117. }
  118. public function refund(int $recordID, PaymentRefundRequest $param): PaymentRefundResult
  119. {
  120. $payment = $this->extensionPaymentEntity->find($recordID);
  121. $reund = Db::transaction(function () use ($param, $payment) {
  122. $reund = $this->extensionRefundEntity->create([
  123. 'payment_id' => $payment->id,
  124. 'order_sn' => $payment->out_trade_no,
  125. 'reason' => $param->getReason(),
  126. 'amount' => $param->getAmount(),
  127. 'status' => RefundStatusEnum::REFUNDING,
  128. 'refund_param' => $param->getRaw(),
  129. 'status_desc' => '正在申请微信接口退款',
  130. ]);
  131. $this->domesticRefunds(
  132. $reund->out_refund_no,
  133. $payment->out_trade_no,
  134. $param->getAmount(),
  135. $payment->amount,
  136. $param->getReason()
  137. );
  138. return $reund;
  139. });
  140. return new PaymentRefundResult($reund);
  141. }
  142. public function refundQuery(int $refundID): PaymentRefundResult
  143. {
  144. $refund = $this->extensionRefundEntity->with('payment')->find($refundID);
  145. if ($refund->status === RefundStatusEnum::REFUNDING) {
  146. $result = $this->queryRefund($refund->out_refund_no);
  147. $refund->refund_id = $result->refund_id;
  148. $refund->refund_result = $result;
  149. if ($result->status === 'SUCCESS') {
  150. $refund->status = RefunddStatusEnum::SUCCESS;
  151. $refund->success_time = strtotime($result->success_time);
  152. Event::trigger(new RefundSuccessEvent(
  153. $refund->model(),
  154. $refund->payment,
  155. new PaymentRefundRequest($refund->amount,$refund->reason, $refund->refund_param))
  156. );
  157. }
  158. $refund->save();
  159. }
  160. return new PaymentRefundResult($refund->model());
  161. }
  162. }