PAYATHON 2026

Ionic 3 如何在 Php Laravel 5.4 服务端生成 Alipay orderpay string

支付小周

结论

Ionic 3 客户端不应自行拼接支付参数或生成签名。正确的处理流程如下:

  1. Ionic 将业务订单号提交给 Laravel 服务端。
  2. Laravel 从数据库读取订单金额,生成支付宝 order string,并使用商户私钥完成 RSA2 签名。
  3. Laravel 将完整字符串返回给 Ionic。
  4. Ionic 不做任何修改,直接把字符串传给支付宝插件的 pay() 或 payment() 方法。
  5. 支付是否成功,最终以 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
&notify_url=https://example.com/api/payments/alipay/notify
&sign_type=RSA2
&timestamp=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'
);

通知处理至少要完成以下检查:

  1. 使用支付宝公钥验证 sign。
  2. 确认 app_id 属于当前应用。
  3. 根据 out_trade_no 查询本地订单。
  4. 核对 total_amount 和本地应付金额。
  5. 如果当前接口返回 seller_id,还要核对该字段。
  6. 只接受符合业务要求的交易状态。
  7. 以幂等方式更新订单,避免重复通知造成重复发货。
  8. 处理成功后返回纯文本 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 支付通常分为两个阶段:

  1. Laravel 调用微信支付下单接口,获得 prepay_id。
  2. 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 是否兼容。

备注:内容仅供参考。