2023-11-13 21:20:10 +08:00
<? php
namespace wchat\wx\V3 ;
use Exception ;
2023-11-13 22:57:36 +08:00
use OpenSSLAsymmetricKey ;
2023-11-13 22:13:44 +08:00
use Psr\Http\Message\RequestInterface ;
2023-11-13 21:20:10 +08:00
use wchat\wx\SmallProgram ;
use wchat\wx\V3\Notify\GoodsDetail ;
use wchat\wx\V3\Notify\NotifyModel ;
use wchat\wx\V3\Notify\PromotionDetail ;
2023-11-13 22:37:49 +08:00
const KEY_TYPE_PUBLIC = 'public' ;
const KEY_TYPE_PRIVATE = 'private' ;
2023-11-13 22:13:44 +08:00
const LOCAL_FILE_PROTOCOL = 'file://' ;
const PKEY_PEM_NEEDLE = ' KEY-' ;
const PKEY_PEM_FORMAT = "-----BEGIN %1 \$ s KEY----- \n %2 \$ s \n -----END %1 \$ s KEY-----" ;
const PKEY_PEM_FORMAT_PATTERN = '#-{5}BEGIN ((?:RSA )?(?:PUBLIC|PRIVATE)) KEY-{5}\r?\n([^-]+)\r?\n-{5}END \1 KEY-{5}#' ;
const CHR_CR = " \r " ;
const CHR_LF = " \n " ;
const RULES = [
2023-11-13 22:37:49 +08:00
'private.pkcs1' => [ PKEY_PEM_FORMAT , 'RSA PRIVATE' , 16 ],
'private.pkcs8' => [ PKEY_PEM_FORMAT , 'PRIVATE' , 16 ],
'public.pkcs1' => [ PKEY_PEM_FORMAT , 'RSA PUBLIC' , 15 ],
'public.spki' => [ PKEY_PEM_FORMAT , 'PUBLIC' , 14 ],
2023-11-13 22:13:44 +08:00
];
const ASN1_OID_RSAENCRYPTION = '300d06092a864886f70d0101010500' ;
const ASN1_SEQUENCE = 48 ;
const CHR_NUL = " \0 " ;
const CHR_ETX = " \3 " ;
2023-11-13 21:20:10 +08:00
class WxV3PaymentNotify extends SmallProgram
{
use WxV3PaymentTait ;
/**
* @param string $id
* @param string $create_time
* @param string $resource_type
* @param string $event_type
* @param string $summary
* @param array $resource
2023-11-13 22:57:36 +08:00
* @param string $publicKey
2023-11-13 21:20:10 +08:00
*/
public function __construct (
public string $id = "EV-2018022511223320873" ,
public string $create_time = "2015-05-20T13:29:35+08:00" ,
public string $resource_type = "encrypt-resource" ,
public string $event_type = "TRANSACTION.SUCCESS" ,
public string $summary = "支付成功" ,
2023-11-13 22:57:36 +08:00
public array $resource = [],
public string $publicKey = ''
2023-11-13 21:20:10 +08:00
)
{
}
/**
* @var NotifyModel
*/
public NotifyModel $notifyModel ;
2023-11-13 22:13:44 +08:00
/**
* @param RequestInterface $request
* @return bool
* @throws Exception
*/
public function verify ( RequestInterface $request ) : bool
{
2023-11-13 22:57:36 +08:00
$platformPublicKeyInstance = $this -> rsaFrom ( $this -> publicKey , KEY_TYPE_PUBLIC );
2023-11-13 22:42:26 +08:00
$inWechatpaySignature = $request -> getHeaderLine ( 'Wechatpay-Signature' ); // 请根据实际情况获取
$inWechatpayTimestamp = $request -> getHeaderLine ( 'Wechatpay-Timestamp' ); // 请根据实际情况获取
$inWechatpayNonce = $request -> getHeaderLine ( 'Wechatpay-Nonce' ); // 请根据实际情况获取
$inBody = $request -> getBody () -> getContents (); // 请根据实际情况获取,例如: file_get_contents('php://input');
2023-11-13 22:14:14 +08:00
$timeOffsetStatus = 300 >= abs ( time () - ( int ) $inWechatpayTimestamp );
$verifiedStatus = $this -> notifyVerify (
$this -> lineFeed ([ $inWechatpayTimestamp , $inWechatpayNonce , $inBody ]),
$inWechatpaySignature ,
$platformPublicKeyInstance );
2023-11-13 22:13:44 +08:00
if ( ! $timeOffsetStatus || ! $verifiedStatus ) {
return false ;
}
2023-11-13 22:37:49 +08:00
return $this -> decode ( $this -> resource [ 'ciphertext' ], $this -> resource [ 'nonce' ], $this -> resource [ 'associated_data' ]);
2023-11-13 22:13:44 +08:00
}
/**
* @param ...$pieces
* @return string
*/
protected function lineFeed ( ... $pieces ) : string
{
return implode ( " \n " , array_merge ( $pieces , [ '' ]));
}
/**
* @return string|bool
*/
protected function body () : string | bool
{
return json_encode ([ 'id' => $this -> id , 'create_time' => $this -> create_time , 'resource_type' => $this -> resource_type , 'event_type' => $this -> event_type , 'summary' => $this -> summary , 'resource' => $this -> resource ]);
}
/**
* @param string $message
* @param string $signature
* @param $publicKey
* @return bool
*/
protected function notifyVerify ( string $message , string $signature , $publicKey ) : bool
{
if (( $result = openssl_verify ( $message , base64_decode ( $signature ), $publicKey , OPENSSL_ALGO_SHA256 )) === false ) {
throw new \UnexpectedValueException ( 'Verified the input $message failed, please checking your $publicKey whether or nor correct.' );
}
return $result === 1 ;
}
/**
* @param $thing
* @param string $type
2023-11-13 22:57:36 +08:00
* @return OpenSSLAsymmetricKey
2023-11-13 22:13:44 +08:00
*/
2023-11-13 22:57:36 +08:00
protected function rsaFrom ( $thing , string $type = KEY_TYPE_PRIVATE ) : OpenSSLAsymmetricKey
2023-11-13 22:13:44 +08:00
{
2023-11-13 22:57:36 +08:00
$pkey = (( $isPublic = $type === KEY_TYPE_PUBLIC ) ? openssl_pkey_get_public ( file_get_contents ( $thing )) : openssl_pkey_get_private ( file_get_contents ( $thing )));
2023-11-13 22:13:44 +08:00
if ( false === $pkey ) {
2023-11-13 22:57:36 +08:00
throw new \UnexpectedValueException ( sprintf ( 'Cannot load %s from(%s), please take care about the $thing input.' , $isPublic ? 'publicKey' : 'privateKey' , gettype ( $thing )));
2023-11-13 22:13:44 +08:00
}
return $pkey ;
}
2023-11-13 21:20:10 +08:00
/**
2023-11-13 22:37:49 +08:00
* @param $ciphertext
* @param $nonce
* @param $associated_data
* @return bool
2023-11-13 21:20:10 +08:00
*/
2023-11-13 22:37:49 +08:00
public function decode ( $ciphertext , $nonce , $associated_data ) : bool
2023-11-13 21:20:10 +08:00
{
2023-11-13 22:59:34 +08:00
$data = $this -> decrypt ( $ciphertext , $this -> getConfig () -> getMchKey (), $nonce , $associated_data );
2023-11-13 21:20:10 +08:00
$this -> notifyModel = new NotifyModel ();
$this -> notifyModel -> amount = $data [ 'amount' ];
$this -> notifyModel -> payer = $data [ 'payer' ];
$this -> notifyModel -> scene_info = $data [ 'payer' ];
$this -> notifyModel -> appid = $data [ 'appid' ];
$this -> notifyModel -> mchid = $data [ 'mchid' ];
$this -> notifyModel -> out_trade_no = $data [ 'out_trade_no' ];
$this -> notifyModel -> transaction_id = $data [ 'transaction_id' ];
$this -> notifyModel -> trade_type = $data [ 'trade_type' ];
$this -> notifyModel -> trade_state = $data [ 'trade_state' ];
$this -> notifyModel -> trade_state_desc = $data [ 'trade_state_desc' ];
$this -> notifyModel -> bank_type = $data [ 'bank_type' ];
$this -> notifyModel -> attach = $data [ 'attach' ];
$this -> notifyModel -> success_time = $data [ 'success_time' ];
$this -> notifyModel -> promotion_detail = [];
foreach ( $data [ 'promotion_detail' ] as $datum ) {
$detail = new PromotionDetail ();
$detail -> amount = $datum [ 'amount' ];
$detail -> wechatpay_contribute = $datum [ 'wechatpay_contribute' ];
$detail -> coupon_id = $datum [ 'coupon_id' ];
$detail -> scope = $datum [ 'scope' ];
$detail -> merchant_contribute = $datum [ 'merchant_contribute' ];
$detail -> name = $datum [ 'name' ];
$detail -> other_contribute = $datum [ 'other_contribute' ];
$detail -> currency = $datum [ 'currency' ];
$detail -> stock_id = $datum [ 'stock_id' ];
$detail -> goods_detail = [];
foreach ( $datum [ 'goods_detail' ] as $value ) {
$detail -> goods_detail [] = new GoodsDetail ( $value );
}
$this -> notifyModel -> promotion_detail [] = $detail ;
}
2023-11-13 22:37:49 +08:00
return true ;
2023-11-13 21:20:10 +08:00
}
}