Skip to content

Commit 929430f

Browse files
authored
- Stripe Sources API被弃用,更新为Payment Intents API (#142)
- 支持 支付宝、微信、信用卡 (当然首先你的账户需要支持) - 增加推送用户邮箱及元数据到 Stripe - 增加 货币转换 API 到 3 个,并支持故障自动切换
1 parent fa23f30 commit 929430f

File tree

2 files changed

+256
-1
lines changed

2 files changed

+256
-1
lines changed

app/Payments/StripeALL.php

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<?php
2+
3+
/**
4+
* 自己写别抄,抄NMB抄
5+
*/
6+
7+
namespace App\Payments;
8+
9+
use Stripe\Stripe;
10+
use App\Models\User;
11+
12+
class StripeALL
13+
{
14+
public function __construct($config)
15+
{
16+
$this->config = $config;
17+
}
18+
19+
public function form()
20+
{
21+
return [
22+
'currency' => [
23+
'label' => '货币单位',
24+
'description' => '请使用符合ISO 4217标准的三位字母,例如GBP',
25+
'type' => 'input',
26+
],
27+
'stripe_sk_live' => [
28+
'label' => 'SK_LIVE',
29+
'description' => '',
30+
'type' => 'input',
31+
],
32+
'stripe_webhook_key' => [
33+
'label' => 'WebHook密钥签名',
34+
'description' => 'whsec_....',
35+
'type' => 'input',
36+
],
37+
'payment_method' => [
38+
'label' => '支付方式',
39+
'description' => '请输入alipay, wechat_pay, cards',
40+
'type' => 'input',
41+
]
42+
];
43+
}
44+
45+
public function pay($order)
46+
{
47+
$currency = $this->config['currency'];
48+
$exchange = $this->exchange('CNY', strtoupper($currency));
49+
if (!$exchange) {
50+
throw new abort('Currency conversion has timed out, please try again later', 500);
51+
}
52+
//jump url
53+
$jumpUrl = null;
54+
$actionType = 0;
55+
$stripe = new \Stripe\StripeClient($this->config['stripe_sk_live']);
56+
// 获取用户邮箱
57+
$userEmail = $this->getUserEmail($order['user_id']);
58+
if ($this->config['payment_method'] != "cards") {
59+
$stripePaymentMethod = $stripe->paymentMethods->create([
60+
'type' => $this->config['payment_method'],
61+
]);
62+
// 准备支付意图的基础参数
63+
$params = [
64+
'amount' => floor($order['total_amount'] * $exchange),
65+
'currency' => $currency,
66+
'confirm' => true,
67+
'payment_method' => $stripePaymentMethod->id,
68+
'automatic_payment_methods' => ['enabled' => true],
69+
'statement_descriptor' => '修改为你的订单描述英文前缀-#' . $order['user_id'] . '-' . substr($order['trade_no'], -8),
70+
'metadata' => [
71+
'user_id' => $order['user_id'],
72+
'customer_email' => $userEmail,
73+
'out_trade_no' => $order['trade_no'],
74+
'identifier' => '修改为你的站点名称'
75+
],
76+
'return_url' => $order['return_url']
77+
];
78+
79+
// 如果支付方式为 wechat_pay,添加相应的支付方式选项
80+
if ($this->config['payment_method'] === 'wechat_pay') {
81+
$params['payment_method_options'] = [
82+
'wechat_pay' => [
83+
'client' => 'web'
84+
],
85+
];
86+
}
87+
//更新支持最新的paymentIntents方法,Sources API将在今年被彻底替
88+
$stripeIntents = $stripe->paymentIntents->create($params);
89+
90+
$nextAction = null;
91+
92+
if (!$stripeIntents['next_action']) {
93+
throw new abort(__('Payment gateway request failed'));
94+
} else {
95+
$nextAction = $stripeIntents['next_action'];
96+
}
97+
98+
switch ($this->config['payment_method']) {
99+
case "alipay":
100+
if (isset($nextAction['alipay_handle_redirect'])) {
101+
$jumpUrl = $nextAction['alipay_handle_redirect']['url'];
102+
$actionType = 1;
103+
} else {
104+
throw new abort('unable get Alipay redirect url', 500);
105+
}
106+
break;
107+
case "wechat_pay":
108+
if (isset($nextAction['wechat_pay_display_qr_code'])) {
109+
$jumpUrl = $nextAction['wechat_pay_display_qr_code']['data'];
110+
} else {
111+
throw new abort('unable get WeChat Pay redirect url', 500);
112+
}
113+
}
114+
} else {
115+
$creditCheckOut = $stripe->checkout->sessions->create([
116+
'success_url' => $order['return_url'],
117+
'client_reference_id' => $order['trade_no'],
118+
'payment_method_types' => ['card'],
119+
'line_items' => [
120+
[
121+
'price_data' => [
122+
'currency' => $currency,
123+
'unit_amount' => floor($order['total_amount'] * $exchange),
124+
'product_data' => [
125+
'name' => '修改为你的订单描述英文前缀-#' . $order['user_id'] . '-' . substr($order['trade_no'], -8),
126+
]
127+
],
128+
'quantity' => 1,
129+
],
130+
],
131+
'mode' => 'payment',
132+
'invoice_creation' => ['enabled' => true],
133+
'phone_number_collection' => ['enabled' => false],
134+
'customer_email' => $userEmail,
135+
]);
136+
$jumpUrl = $creditCheckOut['url'];
137+
$actionType = 1;
138+
}
139+
140+
return [
141+
'type' => $actionType,
142+
'data' => $jumpUrl
143+
];
144+
}
145+
146+
public function notify($params)
147+
{
148+
try {
149+
$event = \Stripe\Webhook::constructEvent(
150+
request()->getContent() ?: json_encode($_POST),
151+
$_SERVER['HTTP_STRIPE_SIGNATURE'],
152+
$this->config['stripe_webhook_key']
153+
);
154+
} catch (\Stripe\Error\SignatureVerification $e) {
155+
abort(400);
156+
}
157+
switch ($event->type) {
158+
case 'payment_intent.succeeded':
159+
$object = $event->data->object;
160+
if ($object->status === 'succeeded') {
161+
if (!isset($object->metadata->out_trade_no)) {
162+
return ('order error');
163+
}
164+
$metaData = $object->metadata;
165+
$tradeNo = $metaData->out_trade_no;
166+
return [
167+
'trade_no' => $tradeNo,
168+
'callback_no' => $object->id
169+
];
170+
}
171+
break;
172+
case 'checkout.session.completed':
173+
$object = $event->data->object;
174+
if ($object->payment_status === 'paid') {
175+
return [
176+
'trade_no' => $object->client_reference_id,
177+
'callback_no' => $object->payment_intent
178+
];
179+
}
180+
break;
181+
case 'checkout.session.async_payment_succeeded':
182+
$object = $event->data->object;
183+
return [
184+
'trade_no' => $object->client_reference_id,
185+
'callback_no' => $object->payment_intent
186+
];
187+
break;
188+
default:
189+
throw new abort('event is not support');
190+
}
191+
return ('success');
192+
}
193+
// 货币转换 API 1
194+
private function exchange($from, $to)
195+
{
196+
$from = strtolower($from);
197+
$to = strtolower($to);
198+
199+
// 使用第一个API进行货币转换
200+
try {
201+
$result = file_get_contents("https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/" . $from . ".min.json");
202+
$result = json_decode($result, true);
203+
204+
// 如果转换成功,返回结果
205+
if (isset($result[$from][$to])) {
206+
return $result[$from][$to];
207+
} else {
208+
throw new \Exception("Primary currency API failed.");
209+
}
210+
} catch (\Exception $e) {
211+
// 如果第一个API不可用,调用备用API
212+
return $this->backupExchange($from, $to);
213+
}
214+
}
215+
216+
// 备用货币转换 API 方法
217+
private function backupExchange($from, $to)
218+
{
219+
try {
220+
$url = "https://api.exchangerate-api.com/v4/latest/{$from}";
221+
$result = file_get_contents($url);
222+
$result = json_decode($result, true);
223+
224+
// 如果转换成功,返回结果
225+
if (isset($result['rates'][$to])) {
226+
return $result['rates'][$to];
227+
} else {
228+
throw new \Exception("Backup currency API failed.");
229+
}
230+
} catch (\Exception $e) {
231+
// 如果备用API也失败,调用第二个备用API
232+
return $this->thirdExchange($from, $to);
233+
}
234+
}
235+
236+
// 第二个备用货币转换 API 方法
237+
private function thirdExchange($from, $to)
238+
{
239+
try {
240+
$url = "https://api.frankfurter.app/latest?from={$from}&to={$to}";
241+
$result = file_get_contents($url);
242+
$result = json_decode($result, true);
243+
244+
// 如果转换成功,返回结果
245+
if (isset($result['rates'][$to])) {
246+
return $result['rates'][$to];
247+
} else {
248+
throw new \Exception("Third currency API failed.");
249+
}
250+
} catch (\Exception $e) {
251+
// 如果所有API都失败,抛出异常
252+
throw new \Exception("All currency conversion APIs failed.");
253+
}
254+
}
255+
}

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"linfo/linfo": "^4.0",
2424
"paragonie/sodium_compat": "^1.20",
2525
"php-curl-class/php-curl-class": "^8.6",
26-
"stripe/stripe-php": "^7.36.1",
26+
"stripe/stripe-php": "^v14.9.0",
2727
"symfony/yaml": "^4.3"
2828
},
2929
"require-dev": {

0 commit comments

Comments
 (0)