2022-09-09 16:42:55 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace wchat\wx\V3;
|
|
|
|
|
|
|
|
|
|
use Exception;
|
2023-11-13 21:20:10 +08:00
|
|
|
use Kiri\Client;
|
2023-12-06 16:37:52 +08:00
|
|
|
use wchat\common\AppConfig;
|
2022-09-09 16:42:55 +08:00
|
|
|
use wchat\common\Help;
|
|
|
|
|
|
2023-11-13 22:13:44 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bytes Length of the AES block
|
|
|
|
|
*/
|
|
|
|
|
const BLOCK_SIZE = 16;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The `aes-256-gcm` algorithm string
|
|
|
|
|
*/
|
|
|
|
|
const ALGO_AES_256_GCM = 'aes-256-gcm';
|
|
|
|
|
|
2022-09-09 16:42:55 +08:00
|
|
|
trait WxV3PaymentTait
|
|
|
|
|
{
|
|
|
|
|
|
2023-11-07 15:39:46 +08:00
|
|
|
/**
|
2023-11-14 14:10:36 +08:00
|
|
|
* @param string $orderNo
|
|
|
|
|
* @param int $total
|
2023-11-07 15:39:46 +08:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2023-11-14 14:10:36 +08:00
|
|
|
public function getInitCore(string $orderNo, int $total): array
|
2023-11-07 15:39:46 +08:00
|
|
|
{
|
2023-11-14 00:06:57 +08:00
|
|
|
$payConfig = $this->getPayConfig();
|
|
|
|
|
if ($payConfig->typeIsApp()) {
|
2023-11-14 09:29:44 +08:00
|
|
|
$body['appid'] = $payConfig->pay->wx->appId;
|
2023-11-14 00:06:57 +08:00
|
|
|
} else {
|
2023-11-14 09:29:44 +08:00
|
|
|
$body['appid'] = $payConfig->appId;
|
2023-11-14 00:06:57 +08:00
|
|
|
}
|
|
|
|
|
$body['mchid'] = $payConfig->pay->wx->mchId;
|
|
|
|
|
$body['description'] = $payConfig->getBody();
|
2023-11-07 15:39:46 +08:00
|
|
|
$body['out_trade_no'] = $orderNo;
|
2023-11-14 00:06:57 +08:00
|
|
|
$body['notify_url'] = $payConfig->getNotifyUrl();
|
|
|
|
|
$body['amount'] = ['total' => $total, 'currency' => $payConfig->getCurrency()];
|
2023-11-07 15:39:46 +08:00
|
|
|
return $body;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-11-13 21:20:10 +08:00
|
|
|
/**
|
|
|
|
|
* @param string $sign
|
|
|
|
|
* @param string $json
|
|
|
|
|
* @return Client
|
|
|
|
|
*/
|
|
|
|
|
public function createClient(string $sign, string $json): Client
|
|
|
|
|
{
|
2023-12-06 16:44:46 +08:00
|
|
|
$client = new Client('api.mch.weixin.qq.com', 80, TRUE);
|
2023-11-13 21:20:10 +08:00
|
|
|
$client->withAddedHeader('Authorization', $sign)
|
|
|
|
|
->withContentType('application/json')->withAddedHeader('User-Agent', 'application/json')
|
2023-12-06 16:43:46 +08:00
|
|
|
->withAddedHeader("Accept", "*/*");
|
2023-12-06 16:50:53 +08:00
|
|
|
if (!empty($json)) {
|
2023-12-06 16:43:46 +08:00
|
|
|
$client->withBody($json);
|
|
|
|
|
}
|
2023-11-14 09:29:44 +08:00
|
|
|
$proxyHost = $this->getPayConfig()->getProxyHost();
|
|
|
|
|
$proxyPort = $this->getPayConfig()->getProxyPort();
|
2023-11-13 21:20:10 +08:00
|
|
|
if (!empty($proxyHost) && $proxyPort > 0) {
|
|
|
|
|
$client->withProxyHost($proxyHost)->withProxyPort($proxyPort);
|
|
|
|
|
}
|
|
|
|
|
return $client;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-12-06 16:37:52 +08:00
|
|
|
/**
|
|
|
|
|
* @param string $orderNo
|
|
|
|
|
* @return array
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
2023-12-06 16:39:11 +08:00
|
|
|
public function searchByOutTradeNo(string $orderNo): array
|
2023-12-06 16:37:52 +08:00
|
|
|
{
|
2023-12-06 16:39:11 +08:00
|
|
|
$config = $this->getPayConfig();
|
|
|
|
|
|
2023-12-06 16:42:49 +08:00
|
|
|
$sign = $this->signature('POST', '/v3/pay/transactions/out-trade-no/' . $orderNo . '?mchid=' . $config->pay->wx->mchId, "");
|
2023-12-06 16:37:52 +08:00
|
|
|
|
2023-12-06 16:50:53 +08:00
|
|
|
var_dump($sign);
|
|
|
|
|
|
2023-12-06 16:43:46 +08:00
|
|
|
$client = $this->createClient($sign, "");
|
2023-12-06 16:50:53 +08:00
|
|
|
$client->get('/v3/pay/transactions/out-trade-no/' . $orderNo . '?mchid=' . $config->pay->wx->mchId);
|
2023-12-06 16:37:52 +08:00
|
|
|
$client->close();
|
|
|
|
|
|
|
|
|
|
$json = json_decode($client->getBody(), TRUE);
|
|
|
|
|
if (!isset($json['prepay_id'])) {
|
|
|
|
|
throw new Exception('微信支付调用失败: ' . $client->getBody());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->createResponse($json, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-11-07 15:39:46 +08:00
|
|
|
/**
|
|
|
|
|
* @param string $http_method
|
|
|
|
|
* @param string $canonical_url
|
|
|
|
|
* @param string $body
|
|
|
|
|
* @return string
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
public function signature(string $http_method, string $canonical_url, string $body = ''): string
|
|
|
|
|
{
|
2023-11-14 00:06:57 +08:00
|
|
|
$payConfig = $this->getPayConfig();
|
|
|
|
|
|
2023-11-13 21:20:10 +08:00
|
|
|
$rand = md5(random_bytes(32));
|
|
|
|
|
$time = time();
|
2023-11-07 16:09:40 +08:00
|
|
|
|
|
|
|
|
$message = sprintf("%s\n%s\n%d\n%s\n%s\n", $http_method, $canonical_url, $time, $rand, $body);
|
2023-11-07 15:39:46 +08:00
|
|
|
|
|
|
|
|
$sign = $this->openssl_signature($message);
|
|
|
|
|
|
2023-11-14 00:06:57 +08:00
|
|
|
return sprintf('%s mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $payConfig->pay->wx->schema,
|
|
|
|
|
$payConfig->pay->wx->mchId, $rand, $time, $payConfig->pay->wx->SerialNumber, $sign);
|
2023-11-07 15:39:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-14 14:10:36 +08:00
|
|
|
* @param string $body
|
2023-11-07 15:39:46 +08:00
|
|
|
* @return string
|
|
|
|
|
*/
|
2023-11-14 14:10:36 +08:00
|
|
|
public function openssl_signature(string $body): string
|
2023-11-07 15:39:46 +08:00
|
|
|
{
|
2023-11-14 00:06:57 +08:00
|
|
|
$payConfig = $this->getPayConfig();
|
2023-11-07 15:39:46 +08:00
|
|
|
|
2023-11-14 12:39:28 +08:00
|
|
|
$mch_private_key = openssl_get_privatekey(file_get_contents($payConfig->pay->wx->mchKey));
|
2023-11-07 15:39:46 +08:00
|
|
|
|
|
|
|
|
openssl_sign($body, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
|
|
|
|
|
return base64_encode($raw_sign);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-14 14:10:36 +08:00
|
|
|
* @param array $json
|
|
|
|
|
* @param array $body
|
2023-11-07 15:39:46 +08:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2023-11-14 14:10:36 +08:00
|
|
|
private function createResponse(array $json, array $body): array
|
2023-11-07 15:39:46 +08:00
|
|
|
{
|
|
|
|
|
$responseArray['appId'] = $body['appid'];
|
|
|
|
|
$responseArray['timeStamp'] = (string)time();
|
|
|
|
|
$responseArray['nonceStr'] = Help::random(32);
|
|
|
|
|
$responseArray['package'] = "prepay_id=" . $json['prepay_id'];
|
|
|
|
|
|
|
|
|
|
$responseBody = $responseArray['appId'] . PHP_EOL . $responseArray['timeStamp'] . PHP_EOL . $responseArray['nonceStr'] . PHP_EOL . $responseArray['package'] . PHP_EOL;
|
|
|
|
|
|
|
|
|
|
$responseArray['signType'] = 'RSA';
|
|
|
|
|
$responseArray['signBody'] = $responseBody;
|
|
|
|
|
$responseArray['paySign'] = $this->openssl_signature($responseBody);
|
|
|
|
|
$responseArray['prepay_id'] = $json['prepay_id'];
|
|
|
|
|
|
|
|
|
|
return $responseArray;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-13 22:13:44 +08:00
|
|
|
* @param string $ciphertext
|
2023-11-13 22:59:34 +08:00
|
|
|
* @param string $v3Key
|
2023-11-13 22:13:44 +08:00
|
|
|
* @param string $iv
|
|
|
|
|
* @param string $aad
|
|
|
|
|
* @return array
|
2023-11-07 15:39:46 +08:00
|
|
|
*/
|
2023-11-13 22:59:34 +08:00
|
|
|
public function decrypt(string $ciphertext, string $v3Key, string $iv = '', string $aad = ''): array
|
2023-11-07 15:39:46 +08:00
|
|
|
{
|
2023-11-13 22:13:44 +08:00
|
|
|
$ciphertext = base64_decode($ciphertext);
|
|
|
|
|
$authTag = substr($ciphertext, $tailLength = 0 - BLOCK_SIZE);
|
|
|
|
|
$tagLength = strlen($authTag);
|
|
|
|
|
|
|
|
|
|
/* Manually checking the length of the tag, because the `openssl_decrypt` was mentioned there, it's the caller's responsibility. */
|
|
|
|
|
if ($tagLength > BLOCK_SIZE || ($tagLength < 12 && $tagLength !== 8 && $tagLength !== 4)) {
|
|
|
|
|
throw new \RuntimeException('The inputs `$ciphertext` incomplete, the bytes length must be one of 16, 15, 14, 13, 12, 8 or 4.');
|
2023-11-07 15:39:46 +08:00
|
|
|
}
|
2023-11-13 22:59:34 +08:00
|
|
|
$plaintext = openssl_decrypt(substr($ciphertext, 0, $tailLength), ALGO_AES_256_GCM, $v3Key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);
|
2023-11-13 22:13:44 +08:00
|
|
|
if (false === $plaintext) {
|
|
|
|
|
throw new \UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $key and $iv whether or nor correct.');
|
2023-11-07 15:39:46 +08:00
|
|
|
}
|
2023-11-13 22:13:44 +08:00
|
|
|
return json_decode($plaintext, true);
|
2023-11-07 15:39:46 +08:00
|
|
|
}
|
2022-09-09 16:42:55 +08:00
|
|
|
}
|