Stripe.php 6.5 KB

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