|
|
@@ -5,6 +5,7 @@ namespace common\components;
|
|
|
use yii\helpers\Json;
|
|
|
use linslin\yii2\curl;
|
|
|
use bizHd\wx\classes\WxOpenClass;
|
|
|
+use phpseclib\Crypt\RSA;
|
|
|
|
|
|
class IntraCityExpress
|
|
|
{
|
|
|
@@ -30,28 +31,79 @@ class IntraCityExpress
|
|
|
return $merchant;
|
|
|
}
|
|
|
|
|
|
- private static function generateSign($method, $url, $timestamp, $nonce, $body, $merchant)
|
|
|
+ /**
|
|
|
+ * 使用 WxApi 方式生成微信API签名
|
|
|
+ * @param string $url 请求URL
|
|
|
+ * @param string $appId 小程序AppID
|
|
|
+ * @param int $timestamp 时间戳
|
|
|
+ * @param string $reqData 请求体JSON字符串
|
|
|
+ * @return string 签名字符串
|
|
|
+ */
|
|
|
+ private static function generateWxApiSignature($url, $appId, $timestamp, $reqData)
|
|
|
{
|
|
|
- $appId = $merchant['miniAppId'];
|
|
|
- $serialNo = $merchant['serial_no'];
|
|
|
- $privateKey = $merchant['private_key']; // Ensure this is the raw private key content
|
|
|
-
|
|
|
- $parsedUrl = parse_url($url);
|
|
|
- $path = $parsedUrl['path'] . (isset($parsedUrl['query']) ? '?' . $parsedUrl['query'] : '');
|
|
|
-
|
|
|
- $message = "{$method}\n{$path}\n{$timestamp}\n{$nonce}\n{$body}\n";
|
|
|
-
|
|
|
- openssl_sign($message, $signature, $privateKey, 'sha256WithRSAEncryption');
|
|
|
- $base64Signature = base64_encode($signature);
|
|
|
-
|
|
|
- return sprintf(
|
|
|
- 'WECHATPAY2-SHA256-RSA2048 appid=%s,timestamp="%d",nonce_str="%s",signature="%s",serial_no="%s"',
|
|
|
- $appId,
|
|
|
- $timestamp,
|
|
|
- $nonce,
|
|
|
- $base64Signature,
|
|
|
- $serialNo
|
|
|
- );
|
|
|
+ // 获取私钥
|
|
|
+ $env = getenv('YII_ENV') == 'production' ? 'production' : 'dev';
|
|
|
+ $privateKeyPath = __DIR__ . '/wxapi/' . $env . '/rsa-private-key.txt';
|
|
|
+
|
|
|
+ if (!file_exists($privateKeyPath)) {
|
|
|
+ throw new \Exception("私钥文件不存在: {$privateKeyPath}");
|
|
|
+ }
|
|
|
+
|
|
|
+ $privateKey = file_get_contents($privateKeyPath);
|
|
|
+ if (!$privateKey) {
|
|
|
+ throw new \Exception('无法获取私钥文件');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建签名载荷,格式:$url\n$appId\n$timestamp\n$reqData
|
|
|
+ $payload = "$url\n$appId\n$timestamp\n$reqData";
|
|
|
+
|
|
|
+ // 添加调试日志
|
|
|
+ \Yii::info("签名载荷: " . $payload, 'intracity_signature');
|
|
|
+ \Yii::info("AppID: " . $appId, 'intracity_signature');
|
|
|
+ \Yii::info("Timestamp: " . $timestamp, 'intracity_signature');
|
|
|
+ \Yii::info("URL: " . $url, 'intracity_signature');
|
|
|
+ \Yii::info("ReqData: " . $reqData, 'intracity_signature');
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 方案一: 使用 OpenSSL(推荐方式,与微信官方示例一致)
|
|
|
+ // $signature = '';
|
|
|
+ // $result = openssl_sign($payload, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
|
|
+ // if (!$result) {
|
|
|
+ // throw new \Exception('OpenSSL签名失败: ' . openssl_error_string());
|
|
|
+ // }
|
|
|
+ // $base64Signature = base64_encode($signature);
|
|
|
+ // \Yii::info("生成的签名: " . $base64Signature, 'intracity_signature');
|
|
|
+ // return $base64Signature;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 方案二:使用 RSA SHA256签名
|
|
|
+ try {
|
|
|
+ // 使用RSA SHA256签名
|
|
|
+ // $rsa = new RSA();
|
|
|
+ // $rsa->loadKey($privateKey);
|
|
|
+ // $rsa->setHash("sha256");
|
|
|
+ // $rsa->setMGFHash("sha256");
|
|
|
+ // $signature = $rsa->sign($payload);
|
|
|
+
|
|
|
+ $rsa = new RSA();
|
|
|
+ $rsa->loadKey($privateKey);
|
|
|
+ $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); // 明确使用PKCS#1 v1.5
|
|
|
+ $rsa->setHash("sha256");
|
|
|
+ // 不要设置MGF,PKCS#1 v1.5不使用MGF
|
|
|
+ $signature = $rsa->sign($payload);
|
|
|
+
|
|
|
+ $base64Signature = base64_encode($signature);
|
|
|
+ \Yii::info("phpseclib生成的签名: " . $base64Signature, 'intracity_signature');
|
|
|
+
|
|
|
+ return $base64Signature;
|
|
|
+
|
|
|
+ } catch (\Exception $e2) {
|
|
|
+ \Yii::error("所有签名方法都失败 - OpenSSL: " . $e->getMessage() . " phpseclib: " . $e2->getMessage(), 'intracity_signature');
|
|
|
+ throw new \Exception('签名生成失败: OpenSSL和phpseclib都失败');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -71,22 +123,51 @@ class IntraCityExpress
|
|
|
|
|
|
// 添加access_token到URL
|
|
|
$urlWithToken = $url . (strpos($url, '?') !== false ? '&' : '?') . 'access_token=' . $accessToken;
|
|
|
-
|
|
|
$timestamp = time();
|
|
|
- $nonce = \Yii::$app->security->generateRandomString();
|
|
|
$body = Json::encode($data);
|
|
|
if ($body === '[]') {
|
|
|
$body = '{}';
|
|
|
}
|
|
|
|
|
|
- //$signature = self::generateSign('POST', $urlWithToken, $timestamp, $nonce, $body, $merchant);
|
|
|
+
|
|
|
+ // ================ 选择哪个 appId =================
|
|
|
+ // 微信开放平台 id
|
|
|
+// $open = \biz\wx\classes\WxOpenClass::getByCondition(['style' => dict::getDict('ptStyle', 'hd')]);
|
|
|
+// if (empty($open)) {
|
|
|
+// util::fail('没有找到平台信息');
|
|
|
+// }
|
|
|
+// $component_app_id = $open['appId'];
|
|
|
+// $appId = $component_app_id;
|
|
|
+
|
|
|
+ // 当前小程序的Appid
|
|
|
+ $appId = $merchant['miniAppId'] ?? util::fail('app-id 出错');
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 方式一: 使用WxApi方式处理请求 ============================
|
|
|
+ // try {
|
|
|
+ // $wxApi = new \common\components\wxapi\WxApi($appId);
|
|
|
+ // $result = $wxApi->request($url, $data);
|
|
|
+ // return $result;
|
|
|
+ // } catch (\Exception $e) {
|
|
|
+ // // 如果WxApi方式失败,回退到简单方式
|
|
|
+ // util::fail('error');
|
|
|
+ // }
|
|
|
+
|
|
|
+
|
|
|
+ // 方式二: 自己处理请求 ============================
|
|
|
+ $signature = self::generateWxApiSignature($url, $appId, $timestamp, $body);
|
|
|
+ // 构建请求头,包含微信API签名所需的头部
|
|
|
+ $headers = [
|
|
|
+ 'Content-Type: application/json',
|
|
|
+ 'Accept: application/json',
|
|
|
+ 'Wechatmp-Appid: ' . $appId,
|
|
|
+ 'Wechatmp-TimeStamp: ' . $timestamp,
|
|
|
+ 'Wechatmp-Signature: ' . $signature
|
|
|
+ ];
|
|
|
|
|
|
$response = $curl->setOption(CURLOPT_POSTFIELDS, $body)
|
|
|
- ->setOption(CURLOPT_HTTPHEADER, [
|
|
|
- 'Content-Type: application/json',
|
|
|
- 'Accept: application/json',
|
|
|
- //'Authorization: ' . $signature
|
|
|
- ])
|
|
|
+ ->setOption(CURLOPT_HTTPHEADER, $headers)
|
|
|
->post($urlWithToken);
|
|
|
|
|
|
// 确保响应是字符串格式再进行JSON解码
|
|
|
@@ -97,25 +178,6 @@ class IntraCityExpress
|
|
|
return Json::decode($response, true);
|
|
|
}
|
|
|
|
|
|
- public static function emptyRequest($url, $accessToken)
|
|
|
- {
|
|
|
- $curl = new curl\Curl();
|
|
|
-
|
|
|
- // 添加access_token到URL
|
|
|
- $urlWithToken = $url . (strpos($url, '?') !== false ? '&' : '?') . 'access_token=' . $accessToken;
|
|
|
-
|
|
|
- $response = $curl->setOption(CURLOPT_POSTFIELDS, Json::encode(new \stdClass()))
|
|
|
- ->setOption(CURLOPT_HTTPHEADER, ['Content-Type: application/json'])
|
|
|
- ->post($urlWithToken);
|
|
|
-
|
|
|
- // 确保响应是字符串格式再进行JSON解码
|
|
|
- if (is_array($response)) {
|
|
|
- return $response;
|
|
|
- }
|
|
|
-
|
|
|
- return Json::decode($response, true);
|
|
|
- }
|
|
|
-
|
|
|
// ==================== API URL常量 ====================
|
|
|
const API_BASE_URL = 'https://api.weixin.qq.com/cgi-bin/express/intracity';
|
|
|
|
|
|
@@ -159,16 +221,15 @@ class IntraCityExpress
|
|
|
$merchant = self::getMerchant();
|
|
|
}
|
|
|
|
|
|
-// $re = self::applyStore($merchant, $ptStyle);
|
|
|
-// if (!is_array($re)) {
|
|
|
-// $re = Json::decode($re, true);
|
|
|
-// }
|
|
|
-// if ($re['errcode'] != 0) {
|
|
|
-// return $re;
|
|
|
-// }
|
|
|
+ $re = self::applyStore($merchant, $ptStyle);
|
|
|
+ if (!is_array($re)) {
|
|
|
+ $re = Json::decode($re, true);
|
|
|
+ }
|
|
|
+ if ($re['errcode'] != 0) {
|
|
|
+ return $re;
|
|
|
+ }
|
|
|
|
|
|
$accessToken = self::getAccessToken($merchant, $ptStyle);
|
|
|
-
|
|
|
$data = [
|
|
|
'out_store_id' => $storeData['out_store_id'],
|
|
|
'store_name' => $storeData['store_name'],
|
|
|
@@ -222,7 +283,6 @@ class IntraCityExpress
|
|
|
}
|
|
|
|
|
|
$accessToken = self::getAccessToken($merchant, $ptStyle);
|
|
|
-
|
|
|
$data = [
|
|
|
'out_store_id' => $outStoreId,
|
|
|
];
|
|
|
@@ -243,9 +303,7 @@ class IntraCityExpress
|
|
|
}
|
|
|
|
|
|
$accessToken = self::getAccessToken($merchant, $ptStyle);
|
|
|
-
|
|
|
return self::sendRequest(self::API_APPLY_STORE, new \stdClass(), $accessToken, $merchant);
|
|
|
- //return self::getRequest(self::API_APPLY_STORE, $accessToken);
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -264,7 +322,6 @@ class IntraCityExpress
|
|
|
}
|
|
|
|
|
|
$accessToken = self::getAccessToken($merchant, $ptStyle);
|
|
|
-
|
|
|
$data = [
|
|
|
'out_store_id' => $orderData['out_store_id'],
|
|
|
'store_order_id' => $orderData['store_order_id'], // 注意:文档是 store_order_id,不是 out_order_id
|