Ionic 3 如何在 Php Laravel 5.4 服务端生成 Alipay orderpay string
结论
Ionic 3 客户端不应自行拼接支付参数或生成签名。正确的处理流程如下:
- Ionic 将业务订单号提交给 Laravel 服务端。
- Laravel 从数据库读取订单金额,生成支付宝
order string,并使用商户私钥完成 RSA2 签名。 - Laravel 将完整字符串返回给 Ionic。
- Ionic 不做任何修改,直接把字符串传给支付宝插件的
pay()或payment()方法。 - 支付是否成功,最终以 Laravel 收到并验签通过的支付宝异步通知为准。
微信支付的流程相近,但服务端返回给插件的通常是参数对象,而不是支付宝格式的字符串。支付宝和微信支付使用不同的签名规则,不能混用。
支付宝订单字符串的结构
以支付宝 App 支付为例,服务端需要准备两类参数:
- 公共请求参数,如
app_id、method、charset、sign_type、timestamp。 biz_content,其中包含商户订单号、金额、商品名称等业务数据。
签名前的参数大致如下:
app_id=你的APP_ID
&biz_content={"body":"订单说明","out_trade_no":"202608290001","product_code":"QUICK_MSECURITY_PAY","subject":"商品名称","timeout_express":"30m","total_amount":"0.01"}
&charset=utf-8
&format=JSON
&method=alipay.trade.app.pay
¬ify_url=https://example.com/api/payments/alipay/notify
&sign_type=RSA2
×tamp=2026-08-29 12:00:00
&version=1.0
服务端对排序后的参数字符串签名,再追加经过 URL 编码的 sign。生成的完整结果才是可以传给 Ionic 插件的 orderInfo。
实际项目应优先使用支付宝官方 PHP SDK 生成请求字符串。手工拼接有助于理解流程,但更容易在参数排序、编码和签名格式上出错。
Laravel 服务端实现
下面的代码展示了核心处理逻辑。支付宝 SDK 的类名和初始化方式可能因版本而异,请以项目实际安装的官方 SDK 为准。
配置支付参数
不要把私钥直接写进控制器,也不要将私钥提交到 Git。可以在 .env 中配置:
ALIPAY_APP_ID=你的APP_ID
ALIPAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
你的应用私钥
-----END PRIVATE KEY-----"
ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
支付宝公钥
-----END PUBLIC KEY-----"
ALIPAY_NOTIFY_URL=https://example.com/api/payments/alipay/notify
对应的配置文件可以写成:
<?php
return [
'app_id' => env('ALIPAY_APP_ID'),
'private_key' => env('ALIPAY_PRIVATE_KEY'),
'alipay_public_key' => env('ALIPAY_PUBLIC_KEY'),
'notify_url' => env('ALIPAY_NOTIFY_URL'),
];
这三类密钥的用途不同:
- 应用私钥由 Laravel 服务端用于签名。
- 应用公钥需要上传到支付宝开放平台。
- 支付宝公钥由 Laravel 用于验证支付宝通知的签名。
不要把”应用公钥”误当成”支付宝公钥”。
使用支付宝 SDK 生成 order string
如果使用的 SDK 包含 AopClient 和 AlipayTradeAppPayRequest,控制器的核心代码可以这样组织:
<?php
namespace App\Http\Controllers;
use App\Order;
use Illuminate\Http\Request;
use AlipayTradeAppPayRequest;
use AopClient;
class AlipayController extends Controller
{
public function createOrderString(Request $request)
{
$this->validate($request, [
'order_id' => 'required|integer',
]);
$order = Order::findOrFail($request->input('order_id'));
// 实际项目还应校验订单所属用户、订单状态以及是否允许重复支付。
if ($order->status !== 'pending') {
return response()->json([
'message' => '订单当前状态不允许支付',
], 422);
}
$client = new AopClient();
$client->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$client->appId = config('alipay.app_id');
$client->rsaPrivateKey = config('alipay.private_key');
$client->alipayrsaPublicKey = config('alipay.alipay_public_key');
$client->format = 'json';
$client->charset = 'UTF-8';
$client->signType = 'RSA2';
$bizContent = [
'body' => '订单 ' . $order->order_no,
'subject' => $order->subject,
'out_trade_no' => $order->order_no,
'timeout_express' => '30m',
'total_amount' => number_format((float) $order->amount, 2, '.', ''),
'product_code' => 'QUICK_MSECURITY_PAY',
];
$alipayRequest = new AlipayTradeAppPayRequest();
$alipayRequest->setNotifyUrl(config('alipay.notify_url'));
$alipayRequest->setBizContent(
json_encode($bizContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
// SDK通常会完成参数排序、签名和编码。
$orderString = $client->sdkExecute($alipayRequest);
return response()->json([
'order_id' => $order->id,
'order_string' => $orderString,
]);
}
}
金额的来源尤其需要注意。客户端只能提交 order_id、商品选择等业务信息,不能直接提供用于签名的金额。服务端必须从数据库重新读取并核对实际应付金额。
不同支付宝 PHP SDK 发行包的命名空间和属性名称可能不同。例如,一些旧版 SDK 没有命名空间,需要先加载 SDK 入口文件。上面的示例用于说明调用流程,并不表示所有 SDK 包都能在 Laravel 5.4 中原样运行。
Ionic 3 调用支付宝插件
服务端响应示例:
{
"order_id": 123,
"order_string": "app_id=...&biz_content=...&sign=...&sign_type=RSA2&..."
}
Ionic 收到响应后,应将 order_string 原样传给插件,不要再次拆分、排序、进行 URL 编码或签名:
payWithAlipay(orderId: number): void {
this.http
.post('https://example.com/api/payments/alipay/order', {
order_id: orderId
})
.subscribe((response: any) => {
const orderInfo = response.order_string;
this.alipay.pay(orderInfo)
.then((result: any) => {
console.log('Alipay plugin result:', result);
// 此处只能用于更新界面或提示用户。
// 是否支付成功仍应向服务端查询。
this.refreshOrderStatus(orderId);
})
.catch((error: any) => {
console.error('Alipay payment error:', error);
});
});
}
支付宝插件的名称和方法签名并不统一。有些插件使用:
alipay.pay(orderInfo)
另一些插件可能使用:
alipay.payment(orderInfo)
具体写法应以当前安装插件的 TypeScript 定义和原生安装说明为准。无论使用哪种插件,服务端返回的都应该是已经签名的完整支付宝订单字符串。
处理支付宝异步通知
不能只根据 Ionic 插件返回的”成功”判断支付结果。客户端结果可能被伪造,应用也可能在付款完成后被关闭。Laravel 必须提供一个公网可访问的 notify_url,用于接收支付宝服务器发送的异步通知。
路由示例:
Route::post(
'payments/alipay/notify',
'AlipayController@notify'
);
通知处理至少要完成以下检查:
- 使用支付宝公钥验证
sign。 - 确认
app_id属于当前应用。 - 根据
out_trade_no查询本地订单。 - 核对
total_amount和本地应付金额。 - 如果当前接口返回
seller_id,还要核对该字段。 - 只接受符合业务要求的交易状态。
- 以幂等方式更新订单,避免重复通知造成重复发货。
- 处理成功后返回纯文本
success。
结构示例:
public function notify(Request $request)
{
$params = $request->all();
$verified = $this->verifyAlipaySignature($params);
if (!$verified) {
return response('failure', 400)
->header('Content-Type', 'text/plain');
}
$order = Order::where(
'order_no',
$params['out_trade_no']
)->first();
if (!$order) {
return response('failure', 404)
->header('Content-Type', 'text/plain');
}
$expectedAmount = number_format((float) $order->amount, 2, '.', '');
if (($params['app_id'] ?? '') !== config('alipay.app_id')
|| ($params['total_amount'] ?? '') !== $expectedAmount) {
return response('failure', 400)
->header('Content-Type', 'text/plain');
}
$acceptedStatuses = ['TRADE_SUCCESS', 'TRADE_FINISHED'];
if (!in_array($params['trade_status'] ?? '', $acceptedStatuses, true)) {
return response('success', 200)
->header('Content-Type', 'text/plain');
}
// 实际项目应放入数据库事务,并进行幂等判断。
if ($order->status !== 'paid') {
$order->status = 'paid';
$order->paid_at = date('Y-m-d H:i:s');
$order->payment_trade_no = $params['trade_no'] ?? null;
$order->save();
}
return response('success', 200)
->header('Content-Type', 'text/plain');
}
verifyAlipaySignature() 应调用当前 SDK 提供的通知验签方法,不建议自行处理 RSA 验签和字符编码转换。验签时通常需要排除 sign 和 sign_type,具体细节应交给 SDK 处理。
如果 Laravel 项目启用了 CSRF 保护,API 通知路由需要放在不使用 Web CSRF 中间件的路由组中,或者根据项目架构进行精确豁免。不要因为支付回调而全局关闭 CSRF。
微信支付的区别
微信 App 支付通常分为两个阶段:
- Laravel 调用微信支付下单接口,获得
prepay_id。 - Laravel 生成供 App 插件调用的二次签名参数。
服务端返回给 Ionic 的对象一般包含以下字段:
{
"appid": "wx1234567890",
"partnerid": "1900000109",
"prepayid": "wx201410272009395522657a690389285100",
"package": "Sign=WXPay",
"noncestr": "随机字符串",
"timestamp": "时间戳字符串",
"sign": "服务端生成的签名"
}
Ionic 将这个对象传给微信支付插件:
this.wechat.sendPaymentRequest(paymentParams)
.then(() => {
this.refreshOrderStatus(orderId);
})
.catch((error: any) => {
console.error('WeChat payment error:', error);
});
字段大小写、时间戳类型和方法名由实际插件决定。有些插件要求 timestamp 是字符串,有些则要求数字,因此必须按照插件声明传值。
微信支付 API 的版本也会影响签名算法和下单接口。旧版流程通常使用 XML 请求以及 MD5 或 HMAC-SHA256 签名。较新的 API v3 使用证书、平台证书或平台公钥,并采用不同的请求签名机制。不能把旧版示例中的 sign 算法直接用于 API v3。
常见问题
为什么在客户端签名会失败或不安全?
支付签名依赖商户私钥。如果把私钥放进 Ionic 安装包,私钥就可能被提取,攻击者也可能借此伪造订单。因此,私钥只能保存在受控的 Laravel 服务端。
为什么插件提示签名错误?
可以依次检查以下内容:
- 签名算法是否与平台配置一致,例如
RSA2。 - 签名时使用的是否为应用私钥,而不是错误的公钥。
- 支付宝公钥是否配置正确。
- 签名前是否按参数名称排序。
biz_content是否被重复进行 JSON 编码。- 签名后是否正确进行了 URL 编码。
- Ionic 是否修改了服务端返回的字符串。
- 测试环境和正式环境的
app_id、密钥、网关是否混用。 - 服务器时间和时区是否正确。
为什么客户端显示成功,后台订单仍是未支付?
插件回调和支付平台的异步通知不是同一件事。客户端可以先显示”支付结果确认中”,然后轮询 Laravel 中的订单状态。只有 Laravel 完成通知验签、核对金额并更新数据库后,才能将订单标记为已支付。
Laravel 5.4 能否直接使用当前 SDK?
Laravel 5.4 及其常见的 PHP 运行环境已经比较旧,而当前版本的支付 SDK 可能要求更高的 PHP 版本。接入前需要检查 SDK 的 composer.json、PHP 版本要求和 OpenSSL 支持情况。
如果版本不兼容,可以选用与现有 PHP 环境匹配、同时仍符合支付平台接口要求的 SDK 版本,或者先升级运行环境。不能只根据 Laravel 版本判断 SDK 是否兼容。
备注:内容仅供参考。
