新增物流中专
This commit is contained in:
@@ -56,23 +56,25 @@ class GancaoCallbackController extends BaseApiController
|
|||||||
*/
|
*/
|
||||||
public function orderStatus(): Response
|
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 {
|
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)) {
|
if (!$this->verifySign($accessAppkey, $accessNonce, $accessTimestamp, $accessSign, $rawBody)) {
|
||||||
Log::warning('Gancao callback sign verification failed');
|
Log::warning('Gancao callback sign verification failed');
|
||||||
return $this->ok();
|
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)
|
* 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
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = Config::get('gancao_scm', []);
|
|
||||||
$cfgAppkey = (string) ($config['biz_ak'] ?? '');
|
|
||||||
$secretKey = (string) ($config['biz_sk'] ?? '');
|
|
||||||
|
|
||||||
if ($appkey !== $cfgAppkey) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected = md5($appkey . $secretKey . $nonce . $timestamp . $body);
|
$expected = md5($appkey . $secretKey . $nonce . $timestamp . $body);
|
||||||
if (!hash_equals($expected, $sign)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ return [
|
|||||||
'biz_ak' => $bizAk,
|
'biz_ak' => $bizAk,
|
||||||
'biz_sk' => $bizSk,
|
'biz_sk' => $bizSk,
|
||||||
'callback_url' => (string) zyt_gancao_scm_env('CALLBACK_URL', ''),
|
'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', '甄养堂互联网医院'),
|
'cradle_store' => (string) zyt_gancao_scm_env('CRADLE_STORE', '甄养堂互联网医院'),
|
||||||
'df_id' => (int) zyt_gancao_scm_env('DF_ID', 102),
|
'df_id' => (int) zyt_gancao_scm_env('DF_ID', 102),
|
||||||
'express_type' => (string) zyt_gancao_scm_env('EXPRESS_TYPE', 'general'),
|
'express_type' => (string) zyt_gancao_scm_env('EXPRESS_TYPE', 'general'),
|
||||||
|
|||||||
Reference in New Issue
Block a user