在线支付

This commit is contained in:
liu
2026-08-27 21:17:02 +08:00
parent 65eb8ef594
commit c35dac0055
15 changed files with 1381 additions and 10 deletions
@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Models\PaymentModel;
use App\Services\OnlinePaymentService;
use App\Services\WangpuPayService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Throwable;
/**
* 小程序在线支付(旺铺网关 JSAPI)
*
* 链路:POST /mini/payment/online 下单(返回调起支付参数)
* → 小程序 wx.requestPayment 完成支付
* → 网关 POST /mini/payment/notify 后台通知(验签 + 幂等结账)
* → 小程序 GET /mini/payment/online/{paymentNo}/query 主动同步支付结果(回调兜底)
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class OnlinePaymentController extends BaseMiniController
{
public function __construct(
protected OnlinePaymentService $onlinePayment,
protected WangpuPayService $wangpu,
) {}
/**
* 发起在线支付:合并选择本店未支付账单 → 旺铺下单 → 返回调起支付参数
* @throws Throwable
*/
#[PostRoute('/payment/online', authorize: true)]
public function create(Request $request): JsonResponse
{
$data = $request->validate([
'bill_ids' => 'required|array|min:1',
'bill_ids.*' => 'integer|distinct',
'code' => 'required|string|max:64',
'remark' => 'nullable|string|max:255',
], [
'bill_ids.required' => '请选择要付款的账单',
'bill_ids.min' => '请选择要付款的账单',
'code.required' => '微信登录凭证缺失,请重新进入小程序',
'remark.max' => '备注超过最大长度',
]);
$store = $this->currentStore($request);
[$payment, $payParams] = $this->onlinePayment->create(
$store,
array_map('intval', $data['bill_ids']),
(string) $data['code'],
(string) ($data['remark'] ?? ''),
);
return $this->success([
'id' => $payment->id,
'payment_no' => $payment->payment_no,
'amount' => $payment->amount,
'pay_params' => $payParams,
], '下单成功,请调起支付');
}
/**
* 查询支付结果:网关回调可能延迟/丢失,小程序完成支付后主动调用同步结账
* @throws Throwable
*/
#[GetRoute('/payment/online/{paymentNo}/query', authorize: true)]
public function query(string $paymentNo, Request $request): JsonResponse
{
$store = $this->currentStore($request);
$payment = PaymentModel::query()
->where('store_id', $store->id)
->where('payment_no', $paymentNo)
->where('pay_type', PaymentModel::TYPE_ONLINE)
->first();
if ($payment === null) {
throw new RepositoryException('支付记录不存在');
}
$result = $this->onlinePayment->queryAndSettle($payment);
$payment = $result['payment'];
return $this->success([
'payment_no' => $payment->payment_no,
'status' => $payment->status,
'status_name' => PaymentModel::ONLINE_STATUS_NAMES[$payment->status] ?? '待支付',
'paid_at' => $payment->paid_at,
'trade_no' => $payment->trade_no,
], $payment->status === PaymentModel::STATUS_APPROVED ? '支付成功' : '支付结果确认中');
}
/**
* 旺铺支付结果后台通知(公开路由,验签后幂等结账)
*
* 网关约定:应答 {"code":"00"} 视为通知成功,否则按 2^n 分钟重试 7 次;
* 重复通知必须幂等(settle 内部行锁 + 状态判断)。
*/
#[PostRoute('/payment/notify', authorize: false)]
public function notify(Request $request): JsonResponse
{
$params = $request->all();
if (! $this->wangpu->verifyNotifySign($params)) {
Log::warning('旺铺支付通知验签失败', ['params' => $params]);
return $this->notifyAck('01', '验签失败');
}
try {
$this->onlinePayment->settleByNotify($params);
} catch (Throwable $e) {
Log::error('旺铺支付通知处理失败', ['params' => $params, 'error' => $e->getMessage()]);
return $this->notifyAck('01', $e->getMessage());
}
return $this->notifyAck(WangpuPayService::NOTIFY_ACK_OK, '成功');
}
/**
* 通知应答报文(网关约定格式,timestampyyyyMMddHHmmssSSS
*/
protected function notifyAck(string $code, string $msg): JsonResponse
{
return response()->json([
'code' => $code,
'msg' => mb_substr($msg, 0, 100),
'timestamp' => now()->format('YmdHisv'),
]);
}
}