| <?php
namespace app\api\controller;
use think\Controller;
use think\Db;
class Wxpay extends Controller
{
    private $config = array (
        
        'mchid'=>'',
        
        'appid' => "",
        
        'notify_url' => "",
        
        'apiv3_private_key' => "",
        
        'xlid'=>'',
        
        'refund_notify_url'=> "",
    );
    
    public function wxpay(){
        $out_trade_no=$this->request->param('out_trade_no','');
        if(empty($order_sn)) return json(['code'=>202,'msg'=>'缺少参数order_sn']);
        
        $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        $urlarr = parse_url($url); 
        $time = time(); 
        $noncestr = $this->getNonceStr();
        $appid = $this->config['appid'];
        $mchid = $this->config['mchid'];
        $xlid = $this->config['xlid'];
        $data = array();
        $data['appid'] = $appid;
        $data['mchid'] = $mchid;
        $data['description'] = '商品描述';
        $data['out_trade_no'] = $out_trade_no;
        $data['notify_url'] = $this->config['notify_url'];
        $data['amount']['total'] = 1;
        $data['scene_info']['payer_client_ip'] = $_SERVER["REMOTE_ADDR"];;
        $data['payer']['openid']='openid'; 
        $data = json_encode($data);
        
        $key = $this->getSign($data,$urlarr['path'],$noncestr,$time);
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$noncestr,$time,$key);
        
        $header  = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
        
        $res = $this->curl_post_https($url,$data,$header);
        $prepay_id=json_decode($res,true)['prepay_id'];
        $paySign=$this->getWechartSign($appid,$time,$noncestr,'prepay_id='.$prepay_id);
        $payData=[
            'timeStamp'=>$time,
            'nonceStr'=>$noncestr,
            'package'=>'prepay_id='.$prepay_id,
            'signType'=>'RSA',
            'paySign'=>$paySign
        ];
        return json(['code'=>200,'msg'=>'ok','data'=>$payData]);
    }
    
    public function getNonceStr(){
        $strs="QWERTYUIOPASDFGHJKLZXCVBNM1234567890";
        $name=substr(str_shuffle($strs),mt_rand(0,strlen($strs)-11),32);
        return $name;
    }
    
    function getSign($data=array(),$url,$randstr,$time){
        $str = "POST"."\n".$url."\n".$time."\n".$randstr."\n".$data."\n";
        $key = file_get_contents('apiclient_key.pem');
        $str = $this->getSha256WithRSA($str,$key);
        return $str;
    }
    
    function getWechartSign($appid,$timeStamp,$noncestr,$prepay_id){
        $str = $appid."\n".$timeStamp."\n".$noncestr."\n".$prepay_id."\n";
        $key = file_get_contents('apiclient_key.pem');
        $str = $this->getSha256WithRSA($str,$key);
        return $str;
    }
    
    public function getSha256WithRSA($content, $privateKey){
        $binary_signature = "";
        $algo = "SHA256";
        openssl_sign($content, $binary_signature, $privateKey, $algo);
        $sign = base64_encode($binary_signature);
        return $sign;
    }
    
    function curl_post_https($url,$data,$header){ 
        $curl = curl_init(); 
        curl_setopt($curl, CURLOPT_URL, $url); 
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); 
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); 
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); 
        curl_setopt($curl, CURLOPT_POST, 1); 
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); 
        curl_setopt($curl, CURLOPT_HEADER, 0); 
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl); 
        if (curl_errno($curl)) {
            echo 'Errno'.curl_error($curl);
        }
        curl_close($curl); 
        return $tmpInfo; 
    }
    
    public function notify(){
        $data = file_get_contents('php://input');
        $data = json_decode($data, true);
        $nonceStr = $data['resource']['nonce'];
        $associatedData = $data['resource']['associated_data'];
        $ciphertext = $data['resource']['ciphertext'];
        $ciphertext = base64_decode($ciphertext);
        
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            
            $orderData = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->config['apiv3_private_key']);
            $orderData = json_decode($orderData, true);
            if ($orderData['trade_state']=='SUCCESS'){
                $order_sn=$orderData['out_trade_no']; 
                $transaction_id=$orderData['transaction_id']; 
                
                Db::startTrans();
                try{
                    
                    Db::commit();
                    
                    return json(['code' => 'SUCCESS', 'message' =>'ok']);
                } catch (\Exception $e) {
                    
                    Db::rollback();
                    return json(['code' => 'ERROR', 'message' =>'no']);
                }
            }
        }
    }
    
    public function Refund($transaction_id,$out_trade_no){ 
        $time=time();
        $out_refund_no=$time.rand(1111,9999);
        $refundData=[
            'out_refund_no'=>$out_refund_no,
            'reason'=>'商品退款',
            'notify_url'=>$this->config['refund_notify_url'],
            'funds_account'=>'AVAILABLE',
            'amount'=>[
                'refund'=>1, 
                'total'=>1, 
                'currency'=>'CNY'
            ]
        ];
        if(!$transaction_id){ 
            if(!$out_trade_no){
                return ['code'=>0,'msg'=>'退款订单号不能为空'];
            }else{
                $refundData['out_trade_no']=$out_trade_no;
            }
        }else{
            $refundData['transaction_id']=$transaction_id;
        }
        $url='https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';
        $urlarr = parse_url($url); 
        $mchid = $this->config['mchid'];
        $xlid = $this->config['xlid'];
        $refundData=json_encode($refundData);
        $nonce = $this->getNonceStr();
        $key = $this->getSign($refundData,$urlarr['path'],$nonce,$time);
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$nonce,$time,$key);
        $header  = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
        $res=$this->curl_post_https($url,$refundData,$header);
        $res_array=json_decode($res,true);
        if(($res_array['status']=='PROCESSING' || $res_array['status']=='SUCCESS') && isset($res_array['status'])){
            return ['code'=>1,'msg'=>'退款成功'];
        }else{
            return ['code'=>0,'msg'=>$res_array['message']];
        }
    }
    
    public function refund_notify(){
        $notifiedData = file_get_contents('php://input');
        $data = json_decode($notifiedData, true);
        $nonceStr = $data['resource']['nonce'];
        $associatedData = $data['resource']['associated_data'];
        $ciphertext = $data['resource']['ciphertext'];
        $ciphertext = base64_decode($ciphertext);
        
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            
            $orderData = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->config['apiv3_private_key']);
            $orderData = json_decode($orderData, true);
            if ($orderData['refund_status']=='SUCCESS'){
                $transaction_id=$orderData['transaction_id']; 
                
                return json(['code'=>'SUCCESS','message'=>'成功']);
            }
        }
    }
}
 |