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 ? '支付成功' : '支付结果确认中'); } /** * 旺铺支付结果后台通知(公开路由,解密信封后幂等结账) * * 通知报文与网关应答同为加密信封(data + signature,商户私钥可解), * 解密成功即视为合法通知;应答 {"code":"00"} 视为通知成功,否则网关按 2^n 分钟重试 7 次; * 重复通知必须幂等(settle 内部行锁 + 状态判断)。 */ #[PostRoute('/payment/notify', authorize: false)] public function notify(Request $request): JsonResponse { try { $params = $this->wangpu->decryptNotify($request->all()); } catch (Throwable $e) { Log::warning('旺铺支付通知解密失败', ['error' => $e->getMessage()]); 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, '成功'); } /** * 通知应答报文(网关约定格式,timestamp:yyyyMMddHHmmssSSS) */ protected function notifyAck(string $code, string $msg): JsonResponse { return response()->json([ 'code' => $code, 'msg' => mb_substr($msg, 0, 100), 'timestamp' => now()->format('YmdHisv'), ]); } }