Stripe 订单与退款查询:yansongda/pay 中 Pay::stripe()->query() 的用法与插件管道解析
Stripe 订单与退款查询:yansongda/pay 中 Pay::stripe()->query() 的用法与插件管道解析
本篇围绕 Stripe 查询订单文档 展开,介绍在 yansongda/pay 中通过 Pay::stripe()->query() 查询 Stripe PaymentIntent 与退款记录两种订单状态的完整用法,并结合 QueryShortcut、QueryPlugin、AddRadarPlugin 等源码,讲清 _action 路由、请求拼装与响应校验的底层机制,帮助你既能正确调用查询接口,也能理解其插件化实现以便自行扩展任意 Stripe API。
注意:根据文档原文提示,Stripe 的实现由 AI 生成并经人工审核,但由于测试资源限制,尚未经过实际测试,请在充分验证后再用于生产环境。
方法签名与总体用法
query 是 Stripe Provider 直接暴露的查询入口,其签名为:
| 方法名 | 参数 | 返回值 |
|---|---|---|
| query | array $order | Collection |
调用入口位于 Stripe Provider:
public function query(array $order): Collection|Rocket
{
Event::dispatch(new MethodCalled(Pay::PROVIDER_STRIPE, __METHOD__, $order, null));
return $this->__call('query', [$order]);
}
从源码结构看,query() 会先派发 MethodCalled 事件(便于监听器记录调用行为),再通过 __call 把方法名 query 映射到快捷方式插件 \Yansongda\Pay\Shortcut\Stripe\QueryShortcut(见 Provider 的 __call 实现),最终返回一个封装了 API 响应 JSON 的 Collection 对象,可直接用 $result->get('status') 等方式取字段。
查询支付订单(PaymentIntent)
查询已创建的 PaymentIntent(支付意图)状态时,只需传入 payment_intent_id:
Pay::config($config);
$order = [
'payment_intent_id' => 'pi_3OxxxxxxxxxxxxxxxxxxxxY', // Stripe PaymentIntent ID
// '_action' => 'order', // 查询支付订单,默认
];
$result = Pay::stripe()->query($order);
订单配置参数与 Stripe 官方 API 无任何差别,兼容所有功能;除 payment_intent_id 外的官方请求参数都可以直接放入 $order,完整参数清单请参考 Stripe 官方 API「PaymentIntents — Retrieve」的「请求参数」一栏。
对应的底层业务插件是 QueryPlugin,核心逻辑为:
$paymentIntentId = $params['payment_intent_id'] ?? '';
if (empty($paymentIntentId)) {
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Stripe 查询 PaymentIntent,缺少 payment_intent_id 参数');
}
$rocket->mergePayload([
'_method' => 'GET',
'_url' => '/v1/payment_intents/'.$paymentIntentId,
]);
// PaymentIntent ID 已拼入 `_url`,需要从 payload 中剔除,避免残留在最终请求 query string 中被 Stripe 拒绝
$rocket->exceptPayload('payment_intent_id');
两个值得注意的实现细节:
- 必填校验前置:缺少
payment_intent_id时直接抛出InvalidParamsException,错误码为Exception::PARAMS_NECESSARY_PARAMS_MISSING,这一点由 QueryPluginTest 中的testMissingPaymentIntentId用例明确验证; - 参数剔除(
exceptPayload):ID 拼进 URL 路径后会被从 payload 中移除,防止它以 query string 形式残留在最终请求中被 Stripe 拒绝。
查询退款订单
通过 _action 参数切换到退款查询模式:
Pay::config($config);
$order = [
'refund_id' => 're_3OxxxxxxxxxxxxxxxxxxxxY', // Stripe 退款 ID
'_action' => 'refund',
];
$result = Pay::stripe()->query($order);
同样,除 refund_id 外的官方请求参数都可以直接传入,完整参数请参考 Stripe 官方 API「Refunds — Retrieve」的「请求参数」一栏。
底层由 QueryRefundPlugin 拼装请求,逻辑与 PaymentIntent 查询对称:
$refundId = $params['refund_id'] ?? '';
if (empty($refundId)) {
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Stripe 查询退款,缺少 refund_id 参数');
}
$rocket->mergePayload([
'_method' => 'GET',
'_url' => '/v1/refunds/'.$refundId,
]);
$rocket->exceptPayload('refund_id');
QueryRefundPluginTest 验证了正常路径会生成 GET /v1/refunds/{id} 的请求,缺少 refund_id 时抛出参数缺失异常。
_action 路由机制
query() 支持一次入口查询两类资源,其分派逻辑在 QueryShortcut::getPlugins():
$action = $params['_action'] ?? StripeAction::QUERY_DEFAULT;
return match ($action) {
StripeAction::QUERY_DEFAULT, StripeAction::QUERY_ORDER => $this->orderPlugins(),
StripeAction::QUERY_REFUND => $this->refundPlugins(),
default => throw new InvalidParamsException(
Exception::PARAMS_SHORTCUT_ACTION_INVALID,
'不支持的 _action ['.($params['_action'] ?? '').']',
),
};
_action 的合法取值定义在 StripeAction:
/* QUERY */
public const QUERY_DEFAULT = 'default';
public const QUERY_ORDER = 'order';
public const QUERY_REFUND = 'refund';
即不传 _action 时默认为 default(查 PaymentIntent);order 与 default 等价;refund 走退款查询;传入其他值(如 'foo')会抛出 PARAMS_SHORTCUT_ACTION_INVALID 异常——该行为在 QueryShortcutTest 的 testFoo、testDefault、testOrder、testRefund 四个用例中被完整覆盖,同时确认了三条插件链的精确顺序。
插件管道:一次 query 调用的完整链路
根据 QueryShortcutTest 断言的插件顺序,查询支付订单与查询退款订单分别执行以下管道:
| 阶段 | 插件 | 职责 |
|---|---|---|
| 1 | StartPlugin |
初始化 Rocket,把用户传入的 $order 参数装载进 payload |
| 2 | QueryPlugin / QueryRefundPlugin |
校验必填 ID,拼装 _method(GET)与 _url(/v1/payment_intents/{id} 或 /v1/refunds/{id}),并剔除已拼入 URL 的 ID 字段 |
| 3 | AddRadarPlugin |
依据 payload 与配置构建最终 HTTP 请求(方法、URL、请求头、Body) |
| 4 | ResponsePlugin |
校验响应状态码是否为 2xx |
| 5 | ParserPlugin |
解析响应 JSON 为 Collection 返回给调用方 |
请求组装的核心是 AddRadarPlugin,它把 payload 中的 _method、_url 转换成一次真实的 GuzzleHttp\Psr7\Request:
$rocket->setRadar(new Request(
get_radar_method($payload),
self::getStripeUrl($config, $payload).$this->getQueryString($payload),
$this->getHeaders($config, $payload),
$this->getBody($payload),
));
与查询相关的几个关键行为:
- 鉴权方式:getHeaders() 使用 Stripe 要求的 Bearer Token 认证,
Authorization: Bearer {secret_key},同时携带Accept: application/json与User-Agent: yansongda/pay-v3;GET 请求不设置Content-Type,也不携带 Body(getBody() 对 GET 直接返回空字符串)。 - 自定义请求头注入:源码注释明确支持通过订单参数中的
_headers注入自定义请求头(如Idempotency-Key、Stripe-Version),且可覆盖默认值。因此查询时可以传入'_headers' => ['Stripe-Version' => '2025-01-01']之类的固定版本请求头。 - URL 前缀补全:StripeTrait::getStripeUrl() 会把业务插件写入的相对路径
/v1/payment_intents/pi_xxx拼接到 Provider 的基地址上。从 Stripe Provider 的 URL 常量 看,normal、sandbox、service三种mode的基地址均为https://api.stripe.com——即 Stripe 的环境切换实际由sk_test_/sk_live_前缀的密钥本身决定,mode在此处不影响请求域名。若 payload 中_url缺失(例如插件顺序用错),会抛出带明确提示的InvalidParamsException:「你可能用错插件顺序,应该先使用业务插件」。
响应侧由 ResponsePlugin 兜底校验:只要 HTTP 状态码落在 200 之外(4xx 参数错误、401/403 密钥错误、404 资源不存在等),都会抛出 InvalidResponseException(错误码 RESPONSE_CODE_WRONG),提示「Stripe 返回状态码异常,请检查参数是否错误」。这意味着你不需要手工判断响应码,查询失败会以异常形式抛出,异常信息中携带原始响应体($rocket->getDestination())可供排查。
前置配置要求
调用 query() 之前需要先完成 Pay::config() 注册 Stripe 配置。从 StripeConfig 的 validateRequired() 及 StripeConfigTest 看,唯一必填项是 secret_key,缺失时抛出 配置异常: 缺少 Stripe 配置 -- [secret_key]:
use Yansongda\Pay\Config\StripeConfig;
use Yansongda\Pay\Pay;
$config = new StripeConfig([
'secret_key' => 'sk_test_xxx', // 必填:可写为 sk_test_(测试环境)或 sk_live_(生产环境)
// 'webhook_secret' => 'whsec_xxx', // 选填:仅回调验签需要
// 'notify_url' => 'https://example.com/stripe/notify', // 选填
// 'success_url' => 'https://example.com/stripe/success', // 选填
// 'cancel_url' => 'https://example.com/stripe/cancel', // 选填
// 'mode' => PayMode::NORMAL, // 选填:normal/sandbox/service,默认 normal
]);
Pay::config($config);
查询场景本身不依赖 webhook_secret、notify_url 等选填项,它们分别服务于回调验签与 Checkout 跳转流程;secret_key 会以 Bearer Token 形式随每次查询请求发出,请务必按环境区分测试/生产密钥并妥善保管。
异常与边界行为汇总
结合源码与测试用例,query() 的失败场景可归纳为:
| 场景 | 异常 | 触发位置 |
|---|---|---|
缺少 payment_intent_id(订单查询) |
InvalidParamsException(PARAMS_NECESSARY_PARAMS_MISSING) |
QueryPlugin |
缺少 refund_id(退款查询) |
InvalidParamsException(PARAMS_NECESSARY_PARAMS_MISSING) |
QueryRefundPlugin |
_action 取值非法(非 default/order/refund) |
InvalidParamsException(PARAMS_SHORTCUT_ACTION_INVALID) |
QueryShortcut |
| HTTP 状态码非 2xx(401/403/404/4xx 等) | InvalidResponseException(RESPONSE_CODE_WRONG) |
ResponsePlugin |
payload 中缺少 _url(插件顺序错误) |
InvalidParamsException(PARAMS_STRIPE_URL_MISSING) |
StripeTrait |
另需注意:Stripe Provider 不支持 close API(close() 直接抛异常提示「Stripe 不支持 close API」),如需关闭支付单请使用 Stripe 的 cancel 入口(见 cancel.md 对应插件 CancelPlugin)。
延伸阅读:用插件自行组装任意 Stripe API
得益于 pay 的基础架构和插件机制,你可以不依赖快捷方式、直接组合内置插件调用 Stripe 的任何 API。查询 PaymentIntent 的等价写法是(见 Stripe 更多方便的插件文档):
use Yansongda\Artful\Plugin\ParserPlugin;
use Yansongda\Artful\Plugin\StartPlugin;
use Yansongda\Pay\Plugin\Stripe\V1\AddRadarPlugin;
use Yansongda\Pay\Plugin\Stripe\V1\Pay\QueryPlugin;
use Yansongda\Pay\Plugin\Stripe\V1\ResponsePlugin;
Pay::config($config);
$params = [
'payment_intent_id' => 'pi_3OxxxxxxxxxxxxxxxxxxxxY',
];
$allPlugins = [StartPlugin::class, QueryPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class];
$result = Pay::stripe()->pay($allPlugins, $params);
内置的支付产品插件一览(完整清单见 all.md):
| 场景 | 插件 |
|---|---|
| 创建 PaymentIntent | \Yansongda\Pay\Plugin\Stripe\V1\Pay\PayPlugin |
| 查询 PaymentIntent | \Yansongda\Pay\Plugin\Stripe\V1\Pay\QueryPlugin |
| 取消 PaymentIntent | \Yansongda\Pay\Plugin\Stripe\V1\Pay\CancelPlugin |
| 退款申请 | \Yansongda\Pay\Plugin\Stripe\V1\Pay\RefundPlugin |
| 查询退款 | \Yansongda\Pay\Plugin\Stripe\V1\Pay\QueryRefundPlugin |
| 创建 Checkout Session | \Yansongda\Pay\Plugin\Stripe\V1\Pay\WebPlugin |
掌握上述插件顺序与各自职责后,你就可以以同样的方式组合出查询 Balance、订阅等其他任意 Stripe 接口的调用链。