167 lines
5.3 KiB
PHP
167 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace wchat\wx\V3;
|
|
|
|
use Exception;
|
|
use Kiri\Client;
|
|
use wchat\common\Help;
|
|
|
|
|
|
/**
|
|
* Bytes Length of the AES block
|
|
*/
|
|
const BLOCK_SIZE = 16;
|
|
|
|
/**
|
|
* Bytes length of the AES secret key.
|
|
*/
|
|
const KEY_LENGTH_BYTE = 32;
|
|
|
|
/**
|
|
* Bytes Length of the authentication tag in AEAD cipher mode
|
|
* @deprecated 1.0 - As of the OpenSSL described, the `auth_tag` length may be one of 16, 15, 14, 13, 12, 8 or 4.
|
|
* Keep it only compatible for the samples on the official documentation.
|
|
*/
|
|
const AUTH_TAG_LENGTH_BYTE = 16;
|
|
|
|
/**
|
|
* The `aes-256-gcm` algorithm string
|
|
*/
|
|
const ALGO_AES_256_GCM = 'aes-256-gcm';
|
|
|
|
/**
|
|
* The `aes-256-ecb` algorithm string
|
|
*/
|
|
const ALGO_AES_256_ECB = 'aes-256-ecb';
|
|
|
|
trait WxV3PaymentTait
|
|
{
|
|
|
|
/**
|
|
* @param $orderNo
|
|
* @param $total
|
|
* @return array
|
|
*/
|
|
public function getInitCore($orderNo, $total): array
|
|
{
|
|
$payConfig = $this->getPayConfig();
|
|
if ($payConfig->typeIsApp()) {
|
|
$body['appid'] = $payConfig->pay->wx->appId;
|
|
} else {
|
|
$body['appid'] = $payConfig->appId;
|
|
}
|
|
$body['mchid'] = $payConfig->pay->wx->mchId;
|
|
$body['description'] = $payConfig->getBody();
|
|
$body['out_trade_no'] = $orderNo;
|
|
$body['notify_url'] = $payConfig->getNotifyUrl();
|
|
$body['amount'] = ['total' => $total, 'currency' => $payConfig->getCurrency()];
|
|
return $body;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param string $sign
|
|
* @param string $json
|
|
* @return Client
|
|
*/
|
|
public function createClient(string $sign, string $json): Client
|
|
{
|
|
$client = new Client('api.mch.weixin.qq.com', 443, TRUE);
|
|
$client->withAddedHeader('Authorization', $sign)
|
|
->withContentType('application/json')->withAddedHeader('User-Agent', 'application/json')
|
|
->withBody($json)->withAddedHeader("Accept", "*/*");
|
|
|
|
$proxyHost = $this->getConfig()->getProxyHost();
|
|
$proxyPort = $this->getConfig()->getProxyPort();
|
|
if (!empty($proxyHost) && $proxyPort > 0) {
|
|
$client->withProxyHost($proxyHost)->withProxyPort($proxyPort);
|
|
}
|
|
return $client;
|
|
}
|
|
|
|
|
|
/**
|
|
* @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
|
|
{
|
|
$payConfig = $this->getPayConfig();
|
|
|
|
$rand = md5(random_bytes(32));
|
|
$time = time();
|
|
|
|
$message = sprintf("%s\n%s\n%d\n%s\n%s\n", $http_method, $canonical_url, $time, $rand, $body);
|
|
|
|
$sign = $this->openssl_signature($message);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $body
|
|
* @return string
|
|
*/
|
|
public function openssl_signature($body): string
|
|
{
|
|
$payConfig = $this->getPayConfig();
|
|
|
|
$mch_private_key = openssl_get_privatekey($payConfig->pay->wx->mchKey);
|
|
|
|
openssl_sign($body, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
|
|
return base64_encode($raw_sign);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param $json
|
|
* @param $body
|
|
* @return array
|
|
*/
|
|
private function createResponse($json, $body): array
|
|
{
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* @param string $ciphertext
|
|
* @param string $v3Key
|
|
* @param string $iv
|
|
* @param string $aad
|
|
* @return array
|
|
*/
|
|
public function decrypt(string $ciphertext, string $v3Key, string $iv = '', string $aad = ''): array
|
|
{
|
|
$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.');
|
|
}
|
|
$plaintext = openssl_decrypt(substr($ciphertext, 0, $tailLength), ALGO_AES_256_GCM, $v3Key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);
|
|
if (false === $plaintext) {
|
|
throw new \UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $key and $iv whether or nor correct.');
|
|
}
|
|
return json_decode($plaintext, true);
|
|
}
|
|
}
|