添加虚拟支付

This commit is contained in:
Dev
2026-10-27 16:19:46 +08:00
parent b9122fd6c8
commit ed5c1e8c48
9 changed files with 805 additions and 114 deletions
+139 -1
View File
@@ -5,13 +5,14 @@ namespace app\api\controller;
use app\common\controller\Api;
use app\common\service\OrderService;
use app\common\service\ToolService;
use app\common\service\VirtualPayService;
use think\Exception;
use think\exception\HttpResponseException;
use think\Log;
class Order extends Api
{
protected $noNeedLogin = ['payNotify', 'detail'];
protected $noNeedLogin = ['payNotify', 'virtualPayNotify', 'detail'];
protected $noNeedRight = ['*'];
/**
@@ -296,6 +297,143 @@ class Order extends Api
}
}
/**
* 虚拟支付收银台成功后确认入账
* @ApiMethod (POST)
*/
public function virtualPayConfirm()
{
try {
$orderNo = $this->request->post('orderNo', '');
if (!$orderNo) {
$this->error('订单号不能为空');
}
$res = OrderService::confirmVirtualPay($orderNo, $this->auth->id);
$this->success('支付成功', $res);
} catch (\Exception $e) {
if ($e instanceof HttpResponseException) {
throw $e;
}
$this->error($e->getMessage());
}
}
/**
* 虚拟支付消息推送。MP 后台消息推送 URL 填本接口,明文模式即可
*/
public function virtualPayNotify()
{
$echostr = (string)$this->request->get('echostr', '');
if ($echostr !== '') {
try {
$this->assertVirtualPaySignature();
} catch (\Exception $e) {
Log::error('虚拟支付消息验签失败 ' . $e->getMessage());
http_response_code(403);
exit;
}
echo $echostr;
exit;
}
$raw = trim((string)$this->request->getInput());
$asJson = strpos($raw, '{') === 0;
Log::info('虚拟支付推送[进入] ' . $raw);
try {
$payload = $this->parseVirtualPayBody($raw);
if (!empty($payload['Encrypt'])) {
$plain = VirtualPayService::decryptMessage(
(string)$payload['Encrypt'],
(string)$this->request->get('msg_signature', ''),
(string)$this->request->get('nonce', ''),
(string)$this->request->get('timestamp', '')
);
$payload = $this->parseVirtualPayBody($plain);
} else {
$this->assertVirtualPaySignature();
}
$reply = VirtualPayService::handleMessage($payload);
} catch (\Exception $e) {
Log::error('虚拟支付推送处理失败 ' . $e->getMessage());
$message = $e->getMessage();
if (strpos($message, '订单不存在') !== false || strpos($message, '获取订单数据失败') !== false) {
$reply = ['ErrCode' => 0, 'ErrMsg' => 'success'];
} else {
$reply = ['ErrCode' => 1, 'ErrMsg' => $message];
}
}
$this->replyVirtualPay(isset($reply) ? $reply : ['ErrCode' => 1, 'ErrMsg' => 'fail'], $asJson);
}
/**
* 校验消息推送签名
* @throws \Exception
*/
private function assertVirtualPaySignature()
{
$token = (string)config('common.virtual_pay.msg_token');
if ($token === '') {
throw new \Exception('未配置虚拟支付消息令牌');
}
$parts = [
$token,
(string)$this->request->get('timestamp', ''),
(string)$this->request->get('nonce', ''),
];
sort($parts, SORT_STRING);
if (sha1(implode($parts)) !== (string)$this->request->get('signature', '')) {
throw new \Exception('消息签名校验失败');
}
}
/**
* @param string $raw
* @return array
*/
private function parseVirtualPayBody($raw)
{
$raw = trim((string)$raw);
if ($raw === '') {
return [];
}
if ($raw[0] === '{') {
$data = json_decode($raw, true);
return is_array($data) ? $data : [];
}
$xml = simplexml_load_string($raw, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NONET);
if (!$xml) {
return [];
}
$data = json_decode(json_encode($xml), true);
return is_array($data) ? $data : [];
}
/**
* @param array $data
* @param bool $asJson
*/
private function replyVirtualPay(array $data, $asJson)
{
if ($asJson) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
header('Content-Type: text/xml; charset=utf-8');
$xml = '<xml>';
foreach ($data as $key => $value) {
if (is_int($value) || is_float($value)) {
$xml .= '<' . $key . '>' . $value . '</' . $key . '>';
} else {
$xml .= '<' . $key . '><![CDATA[' . $value . ']]></' . $key . '>';
}
}
$xml .= '</xml>';
echo $xml;
exit;
}
public function businessTime()
{
$res = [