新增物流中专

This commit is contained in:
Your Name
2026-04-17 15:40:50 +08:00
parent a5d2baf731
commit 0528333d5e
2 changed files with 70 additions and 23 deletions
@@ -56,23 +56,25 @@ class GancaoCallbackController extends BaseApiController
*/
public function orderStatus(): Response
{
$rawBody = (string) file_get_contents('php://input');
$headers = $this->request->header();
$accessAppkey = (string) $this->pickHeader($headers, ['access-appkey', 'accessappkey', 'x-access-appkey']);
$accessNonce = (string) $this->pickHeader($headers, ['access-nonce', 'accessnonce', 'x-access-nonce']);
$accessTimestamp = (string) $this->pickHeader($headers, ['access-timestamp', 'accesstimestamp', 'x-access-timestamp']);
$accessSign = (string) $this->pickHeader($headers, ['access-sign', 'accesssign', 'x-access-sign']);
Log::info(sprintf(
'Gancao callback received | appkey=%s | nonce=%s | ts=%s | sign=%s | body=%s | headers=%s',
$accessAppkey !== '' ? $accessAppkey : '(empty)',
$accessNonce !== '' ? $accessNonce : '(empty)',
$accessTimestamp !== '' ? $accessTimestamp : '(empty)',
$accessSign !== '' ? $accessSign : '(empty)',
$rawBody,
json_encode($headers, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
));
try {
$rawBody = file_get_contents('php://input');
$headers = $this->request->header();
$accessAppkey = (string) ($headers['access-appkey'] ?? '');
$accessNonce = (string) ($headers['access-nonce'] ?? '');
$accessTimestamp = (string) ($headers['access-timestamp'] ?? '');
$accessSign = (string) ($headers['access-sign'] ?? '');
Log::info('Gancao callback received', [
'appkey' => $accessAppkey,
'nonce' => $accessNonce,
'ts' => $accessTimestamp,
'sign' => $accessSign,
'body' => $rawBody,
]);
if (!$this->verifySign($accessAppkey, $accessNonce, $accessTimestamp, $accessSign, $rawBody)) {
Log::warning('Gancao callback sign verification failed');
return $this->ok();
@@ -100,27 +102,69 @@ class GancaoCallbackController extends BaseApiController
/* 签名验证 */
/* ------------------------------------------------------------------ */
/**
* 兼容多种 header key 命名(ThinkPHP 默认都会统一成小写-连字符,但不同反向代理/php-fpm 下可能变体)
*
* @param array<string, string|array<int, string>> $headers
* @param array<int, string> $candidates 按优先级排列的 header key
*/
private function pickHeader(array $headers, array $candidates): string
{
foreach ($candidates as $key) {
if (!isset($headers[$key])) {
continue;
}
$v = $headers[$key];
if (is_array($v)) {
$v = reset($v);
}
$v = trim((string) $v);
if ($v !== '') {
return $v;
}
}
return '';
}
/**
* md5(access-appkey + secret-key + access-nonce + access-timestamp + $sBody)
*
* 注意:回调签名使用的是「回调通知账号」—— callback_appkey / callback_secret
* 与下单使用的 biz_ak / biz_sk 是不同的两套凭证。
*/
private function verifySign(string $appkey, string $nonce, string $timestamp, string $sign, string $body): bool
{
if ($sign === '' || $appkey === '') {
$config = Config::get('gancao_scm', []);
$cfgAppkey = (string) ($config['callback_appkey'] ?? '');
$secretKey = (string) ($config['callback_secret'] ?? '');
if ($appkey === '' || $sign === '') {
Log::warning(sprintf(
'Gancao callback missing header | appkey=%s | sign=%s',
$appkey !== '' ? $appkey : '(empty)',
$sign !== '' ? $sign : '(empty)'
));
return false;
}
$config = Config::get('gancao_scm', []);
$cfgAppkey = (string) ($config['biz_ak'] ?? '');
$secretKey = (string) ($config['biz_sk'] ?? '');
if ($appkey !== $cfgAppkey) {
Log::warning('Gancao callback appkey mismatch', compact('appkey', 'cfgAppkey'));
Log::warning(sprintf(
'Gancao callback appkey mismatch | received=%s | expected(config.callback_appkey)=%s',
$appkey,
$cfgAppkey !== '' ? $cfgAppkey : '(empty, check GANCAO_SCM_CALLBACK_APPKEY in .env)'
));
return false;
}
$expected = md5($appkey . $secretKey . $nonce . $timestamp . $body);
if (!hash_equals($expected, $sign)) {
Log::warning('Gancao callback sign mismatch', compact('sign', 'expected'));
Log::warning(sprintf(
'Gancao callback sign mismatch | received=%s | expected=%s | nonce=%s | ts=%s',
$sign,
$expected,
$nonce,
$timestamp
));
return false;
}
+3
View File
@@ -64,6 +64,9 @@ return [
'biz_ak' => $bizAk,
'biz_sk' => $bizSk,
'callback_url' => (string) zyt_gancao_scm_env('CALLBACK_URL', ''),
// 回调签名凭证:与下单业务账号 biz_ak/biz_sk 不同,由甘草控制台「订单流转回调通知」配置
'callback_appkey' => (string) zyt_gancao_scm_env('CALLBACK_APPKEY', ''),
'callback_secret' => (string) zyt_gancao_scm_env('CALLBACK_SECRET', ''),
'cradle_store' => (string) zyt_gancao_scm_env('CRADLE_STORE', '甄养堂互联网医院'),
'df_id' => (int) zyt_gancao_scm_env('DF_ID', 102),
'express_type' => (string) zyt_gancao_scm_env('EXPRESS_TYPE', 'general'),