Stripe.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php
  2. namespace App\Utils\Payments;
  3. use App\Models\Payment;
  4. use App\Services\PaymentService;
  5. use App\Utils\Library\Templates\Gateway;
  6. use Auth;
  7. use Exception;
  8. use Illuminate\Http\JsonResponse;
  9. use Illuminate\Http\Request;
  10. use Log;
  11. use Response;
  12. use Stripe\Checkout\Session;
  13. use Stripe\Exception\SignatureVerificationException;
  14. use Stripe\Source;
  15. use Stripe\Webhook;
  16. use UnexpectedValueException;
  17. class Stripe extends PaymentService implements Gateway
  18. {
  19. public function __construct()
  20. {
  21. \Stripe\Stripe::setApiKey(sysConfig('stripe_secret_key'));
  22. }
  23. public function purchase(Request $request): JsonResponse
  24. {
  25. $type = (int) $request->input('type');
  26. $payment = $this->createPayment(Auth::id(), $request->input('id'), $request->input('amount'));
  27. if ($type === 1 || $type === 3) {
  28. $source = Source::create([
  29. 'amount' => ceil($payment->amount * 100),
  30. 'currency' => strtolower(sysConfig('standard_currency')),
  31. 'type' => $type === 1 ? 'alipay' : 'wechat',
  32. 'statement_descriptor' => $payment->trade_no,
  33. 'metadata' => [
  34. 'user_id' => $payment->user_id,
  35. 'out_trade_no' => $payment->trade_no,
  36. 'identifier' => '',
  37. ],
  38. 'redirect' => [
  39. 'return_url' => route('invoice.index'),
  40. ],
  41. ]);
  42. if ($type === 3) {
  43. if (! $source['wechat']['qr_code_url']) {
  44. Log::warning('创建订单错误:未知错误');
  45. $payment->failed();
  46. return Response::json(['status' => 'fail', 'message' => trans('user.payment.order_creation.failed')]);
  47. }
  48. $payment->update(['qr_code' => 1, 'url' => $source['wechat']['qr_code_url']]);
  49. return Response::json(['status' => 'success', 'data' => $payment->trade_no, 'message' => trans('user.payment.order_creation.success')]);
  50. }
  51. if (! $source['redirect']['url']) {
  52. Log::warning('创建订单错误:未知错误');
  53. $payment->failed();
  54. return response()->json(['code' => 0, 'msg' => trans('user.payment.order_creation.failed')]);
  55. }
  56. $payment->update(['url' => $source['redirect']['url']]);
  57. return Response::json(['status' => 'success', 'url' => $source['redirect']['url'], 'message' => trans('user.payment.order_creation.success')]);
  58. }
  59. $data = $this->getCheckoutSessionData($payment->trade_no, $payment->amount, $type);
  60. try {
  61. $session = Session::create($data);
  62. $url = route('stripe.checkout', ['session_id' => $session->id]);
  63. $payment->update(['url' => $url]);
  64. return Response::json(['status' => 'success', 'url' => $url, 'message' => trans('user.payment.order_creation.success')]);
  65. } catch (Exception $e) {
  66. Log::error('【Stripe】错误: '.$e->getMessage());
  67. exit;
  68. }
  69. }
  70. protected function getCheckoutSessionData(string $tradeNo, float|int $amount, int $type): array
  71. {
  72. $unitAmount = $amount * 100;
  73. return [
  74. 'payment_method_types' => ['card'],
  75. 'line_items' => [
  76. [
  77. 'price_data' => [
  78. 'currency' => 'usd',
  79. 'product_data' => ['name' => sysConfig('subject_name') ?: sysConfig('website_name')],
  80. 'unit_amount' => $unitAmount,
  81. ],
  82. 'quantity' => 1,
  83. ],
  84. ],
  85. 'mode' => 'payment',
  86. 'success_url' => route('invoice.index'),
  87. 'cancel_url' => route('invoice.index'),
  88. 'client_reference_id' => $tradeNo,
  89. 'customer_email' => Auth::getUser()->email,
  90. ];
  91. }
  92. public function redirectPage($session_id)
  93. { // redirect to Stripe Payment url
  94. return view('user.components.payment.stripe', ['session_id' => $session_id]);
  95. }
  96. public function notify(Request $request): void
  97. { // url = '/callback/notify?method=stripe'
  98. $sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'];
  99. $endpointSecret = sysConfig('stripe_signing_secret');
  100. $payload = @file_get_contents('php://input');
  101. try {
  102. $event = Webhook::constructEvent($payload, $sigHeader, $endpointSecret);
  103. } catch (UnexpectedValueException $e) {
  104. // Invalid payload
  105. http_response_code(400);
  106. exit;
  107. } catch (SignatureVerificationException $e) {
  108. // Invalid signature
  109. http_response_code(400);
  110. exit;
  111. }
  112. Log::info('【Stripe】Passed signature verification!');
  113. switch ($event->type) {
  114. case 'checkout.session.completed':
  115. /* @var $session Session */
  116. $session = $event->data->object;
  117. // Check if the order is paid (e.g., from a card payment)
  118. //
  119. // A delayed notification payment will have an `unpaid` status, as
  120. // you're still waiting for funds to be transferred from the customer's
  121. // account.
  122. if ($session->payment_status === 'paid') {
  123. // Fulfill the purchase
  124. $this->paymentReceived($session->client_reference_id);
  125. }
  126. break;
  127. case 'checkout.session.async_payment_succeeded':
  128. $session = $event->data->object;
  129. // Fulfill the purchase
  130. $this->paymentReceived($session->client_reference_id);
  131. break;
  132. case 'checkout.session.async_payment_failed':
  133. $session = $event->data->object;
  134. // Send an email to the customer asking them to retry their order
  135. $this->failedPayment($session);
  136. break;
  137. }
  138. http_response_code(200);
  139. exit;
  140. }
  141. public function failedPayment(Session $session): void
  142. { // 未支付成功则关闭订单
  143. $payment = Payment::whereTradeNo($session->client_reference_id)->first();
  144. $payment?->order->close();
  145. }
  146. }