(string) $order['trade_no'], 'total_amount' => (string) $order['amount'], 'subject' => (string) $order['subject'], ]; // 每单回调地址只能走 params(下划线前缀),写在配置里会被容器忽略,见 PayFactory 注释 if (!empty($urls['notify'])) { $params['_notify_url'] = (string) $urls['notify']; } if (!empty($urls['return'])) { $params['_return_url'] = (string) $urls['return']; } $pay = PayFactory::alipay(); switch ($scene) { case 'wap': $result = $pay->wap($params); break; case 'qr': $result = $pay->scan($params); break; case 'app': $result = $pay->app($params); break; default: $result = $pay->web($params); } return ['trade_no' => (string) $order['trade_no']] + self::normalize($result); } public function query(string $tradeNo): array { $result = PayFactory::alipay()->query(['out_trade_no' => $tradeNo]); return [ 'paid' => in_array((string) $result->get('trade_status', ''), self::PAID_STATUS, true), 'amount' => (float) $result->get('total_amount', 0), 'gateway_no' => (string) $result->get('trade_no', ''), ]; } public function notify(): array { // v3.7 无 verify(),callback() 内部完成验签 $data = PayFactory::alipay()->callback(); return [ 'paid' => in_array((string) $data->get('trade_status', ''), self::PAID_STATUS, true), 'trade_no' => (string) $data->get('out_trade_no', ''), 'amount' => (float) $data->get('total_amount', 0), 'gateway_no' => (string) $data->get('trade_no', ''), ]; } public function ack(bool $ok = true): Response { if (!$ok) { return response('fail', 200, ['Content-Type' => 'text/plain; charset=utf-8']); } $psr = PayFactory::alipay()->success(); return response((string) $psr->getBody(), $psr->getStatusCode(), ['Content-Type' => 'text/plain; charset=utf-8']); } public function refund(string $tradeNo, float $amount, float $total): array { try { PayFactory::alipay()->refund([ 'out_trade_no' => $tradeNo, 'trade_no' => '', 'refund_amount' => $amount, 'out_request_no' => 'R' . $tradeNo . date('His'), 'refund_reason' => '买家申请退款', ]); return ['ok' => true, 'message' => '退款申请已提交']; } catch (\Throwable $e) { return ['ok' => false, 'message' => $e->getMessage()]; } } /** * 网关返回值归一化。 * * shortcut 的返回类型声明为 ResponseInterface|Rocket,实际由 ResponsePlugin 决定; * 直接调 getBody() 在拿到 Rocket 时会致命错误,这里按类型分流。 * * @return array{type: string, content: mixed} */ private static function normalize($result): array { if ($result instanceof Rocket) { $result = $result->getDestination() ?? $result->getPayload(); } if ($result instanceof MessageInterface) { return ['type' => 'form', 'content' => $result->getBody()->getContents()]; } if ($result instanceof Collection) { $qr = (string) ($result->get('qr_code') ?: $result->get('code_url') ?: ''); if ($qr !== '') { return ['type' => 'qrcode', 'content' => $qr]; } $url = (string) ($result->get('h5_url') ?: $result->get('mweb_url') ?: ''); if ($url !== '') { return ['type' => 'url', 'content' => $url]; } return ['type' => 'params', 'content' => $result->toArray()]; } if (is_string($result)) { return ['type' => 'form', 'content' => $result]; } throw new PayException('支付发起失败:网关返回了无法识别的响应', 500); } }