Laravel Billing Package 能否集成其他支付平台?
结论
能否接入 Rave payment,取决于你使用的 Laravel Billing Package 有没有提供支付网关适配器接口。
如果这个 package 只封装了 Stripe、Braintree 等指定平台,仅修改配置无法切换到 Rave。更合适的处理方式是:
- 继续用现有 package 管理套餐和订阅状态等业务;
- 单独实现 Rave 支付网关适配器;
- 如果 package 与特定支付平台强耦合,不建议勉强扩展。可以自行维护订阅逻辑,或者改用明确支持自定义 driver 的计费 package。
Rave 的产品名称和 API 可能已经发生变化。接口地址、签名算法和字段应以你当前使用的 Flutterwave/Rave 官方文档为准。
为什么通常不能直接集成
计费 package 通常不只是调用支付 API,还依赖特定平台的数据模型和功能,例如:
- customer、subscription、plan 等远程对象;
- 支付方式 token;
- 自动续费和扣款失败处理;
- 退款、取消及订阅变更接口;
- webhook 事件名称和签名验证;
- 试用期、按比例计费和账单周期计算。
所以,即使 Rave 可以完成单次支付,也不等于它能直接替换 package 原有的支付平台。只有 package 提供 Gateway、Driver、Provider 等扩展接口时,才适合加入自定义实现。
推荐的实现方式
先定义一个与支付平台无关的接口,让业务代码只依赖这个接口:
<?php
namespace App\Contracts;
interface PaymentGateway
{
public function initializePayment(array $payment): array;
public function verifyPayment(string $transactionReference): array;
public function refund(
string $transactionReference,
int $amount
): array;
}
再实现 Rave 网关。下面的代码只展示结构,不代表 Rave 当前版本的真实参数或 API 路径:
<?php
namespace App\Payments;
use App\Contracts\PaymentGateway;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class RavePaymentGateway implements PaymentGateway
{
public function __construct(
private readonly string $baseUrl,
private readonly string $secretKey,
) {
}
public function initializePayment(array $payment): array
{
$response = Http::withToken($this->secretKey)
->acceptJson()
->post($this->baseUrl . '/payments', $payment)
->throw();
return $response->json();
}
public function verifyPayment(string $transactionReference): array
{
$response = Http::withToken($this->secretKey)
->acceptJson()
->get(
$this->baseUrl . '/transactions/' .
rawurlencode($transactionReference) .
'/verify'
)
->throw();
$result = $response->json();
if (!is_array($result)) {
throw new RuntimeException('Invalid payment verification response.');
}
return $result;
}
public function refund(
string $transactionReference,
int $amount
): array {
$response = Http::withToken($this->secretKey)
->acceptJson()
->post($this->baseUrl . '/refunds', [
'transaction_reference' => $transactionReference,
'amount' => $amount,
])
->throw();
return $response->json();
}
}
在服务容器中绑定具体实现:
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Payments\RavePaymentGateway;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(PaymentGateway::class, function () {
return new RavePaymentGateway(
baseUrl: (string) config('services.rave.base_url'),
secretKey: (string) config('services.rave.secret_key'),
);
});
}
}
相关配置可以放在 config/services.php 中:
'rave' => [
'base_url' => env('RAVE_BASE_URL'),
'secret_key' => env('RAVE_SECRET_KEY'),
'webhook_secret' => env('RAVE_WEBHOOK_SECRET'),
],
业务层统一通过接口调用支付网关:
<?php
namespace App\Services;
use App\Contracts\PaymentGateway;
class SubscriptionService
{
public function __construct(
private readonly PaymentGateway $gateway,
) {
}
public function startCheckout(
int $userId,
int $amount,
string $currency
): array {
$reference = 'sub_' . $userId . '_' . bin2hex(random_bytes(8));
return $this->gateway->initializePayment([
'tx_ref' => $reference,
'amount' => $amount / 100,
'currency' => $currency,
'redirect_url' => route('billing.callback'),
]);
}
}
tx_ref 等字段只是示例,必须按照实际 API 文档调整。
支付成功后的处理
不要仅凭浏览器跳转结果开通订阅。用户可能伪造回调参数,也可能已经完成付款,却没有返回网站。
通常可以按以下流程处理:
- 创建本地待支付记录,并生成唯一交易编号;
- 跳转到支付平台完成付款;
- 接收 webhook;
- 验证 webhook 签名;
- 主动调用交易查询接口进行复核;
- 核对金额、币种、商户编号和交易状态;
- 在数据库事务中更新付款和订阅状态;
- 对同一笔交易进行幂等处理。
示例结构如下:
public function webhook(Request $request, PaymentGateway $gateway)
{
// 此处必须先按照支付平台文档验证 webhook 签名。
// 不要直接信任请求正文中的 status。
$reference = (string) $request->input('tx_ref');
$transaction = $gateway->verifyPayment($reference);
DB::transaction(function () use ($reference, $transaction) {
$payment = Payment::query()
->where('reference', $reference)
->lockForUpdate()
->firstOrFail();
if ($payment->status === 'paid') {
return;
}
// 这里还应核对交易状态、金额、币种和收款账户。
$payment->update([
'status' => 'paid',
'gateway_payload' => $transaction,
]);
$payment->subscription()->update([
'status' => 'active',
]);
});
return response()->json(['received' => true]);
}
如何选择其他 package
选择替代方案时,不能只看 package 是否标注了 “billing” 或 “subscription”,还要确认它是否具备以下能力:
- 明确支持自定义 payment driver;
- 允许替换 customer、charge、refund 和 webhook 实现;
- 将订阅业务与支付平台 SDK 解耦;
- 支持你使用的 Laravel 和 PHP 版本;
- 仍在维护;
- 能覆盖自动续费、退款、失败重试和取消订阅等需求。
如果没有成熟的 package 明确支持 Rave,自建一层轻量的支付适配器,通常比修改一个与 Stripe 或 Braintree 强绑定的 package 更容易维护。套餐、订阅周期、权限和账单记录可以保存在本地数据库中,支付平台只负责收款、退款和查询交易。
注意事项
- 金额建议以最小货币单位的整数保存在本地,例如分,以免产生浮点数误差。
- API 密钥只能保存在服务端,不能写入 JavaScript 或公开仓库。
- webhook 必须验证签名,并支持重复调用。
- 确认支付成功前,需要再次核对金额、币种和交易编号。
- 网络超时不代表支付失败,应通过查询接口确认最终状态。
- 不要直接修改第三方 package 的
vendor源码。需要扩展时,应使用接口、事件或独立适配器。 - 能否自动续费,取决于 Rave 当前 API 是否支持 tokenization、授权扣款或订阅能力,Laravel package 无法单方面实现。
备注:内容仅供参考。
