rust 微信支付

2年前 (2022) 程序员胖胖胖虎阿
368 0 0

微信支付有很多接口,我们就以jsapi为例,来展示小程序如何发起支付,和进行支付回调解密。

简单封装微信支付方法

wx_pay.rs

use reqwest::header::{CONTENT_TYPE, ACCEPT, HeaderMap, AUTHORIZATION, USER_AGENT};
use reqwest::{Error, Response};
use serde::{Serialize, Deserialize};
use serde_json::value::Value;

use rsa::{RsaPrivateKey, PaddingScheme, Hash, pkcs8::DecodePrivateKey};
use crypto::sha2::Sha256;
use crypto::digest::Digest;
use std::iter::repeat;

use crate::utils::rand_string;
use crate::constants::WECHAT_PAY_APIV3;
use chrono::Utc;
use aes_gcm::{
    aead::{Aead, KeyInit, generic_array::GenericArray, Payload},
    Aes256Gcm, Nonce
};

pub struct WxPay<'a> {
    pub appid: &'a str,
    pub mchid: &'a str,
    pub private_key: &'a str,
    pub serial_no: &'a str,
    pub apiv3_private_key: &'a str,
    pub notify_url: &'a str,
    pub certificates: Option<&'a str>,
}

#[derive(Serialize, Debug)]
pub struct WxData {
    pub sign_type: String,
    pub pay_sign: String,
    pub package: String,
    pub nonce_str: String,
    pub time_stamp: String
}


#[derive(Serialize, Deserialize)]
pub struct Amount {
    pub total: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Payer {
    pub openid: String,
}

#[derive(Serialize, Deserialize)]
pub struct JsapiParams {
    pub description: String,
    pub out_trade_no: String,
    pub amount: Amount,
    pub payer: Payer,
}

#[derive(Clone, Copy)]
struct ApiBody<'a> {
    url: &'a str,
    method: Method,
    pathname: &'a str,
}
#[derive(Clone, Copy)]
enum Method {
    GET,
    POST,  
} 


/// 微信支付,回调解密
#[derive(Serialize, Deserialize, Debug)]
pub struct WxNotifyData {
    pub mchid: String,
    pub appid: String,
    pub out_trade_no: String,
    pub transaction_id: String,
    pub trade_type: String,
    pub trade_state: String,
    pub trade_state_desc: String,
    pub bank_type: String,
    pub attach: String,
    pub success_time: String,
    pub payer: Payer,
    pub amount: WxNotifyDataAmount
}
#[derive(Serialize, Deserialize, Debug)]
pub struct WxNotifyDataAmount {
    pub total: u32,
    pub payer_total: u32,
    pub currency: String,
    pub payer_currency: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct  WxPayNotifyResource {
    pub algorithm: String,
    pub associated_data: String,
    pub ciphertext: String,
    pub nonce: String,
    pub original_type: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct WxPayNotify {
    pub create_time: String,
    pub event_type: String,
    pub id: String,
    pub resource: WxPayNotifyResource,
    pub resource_type: String,
    pub summary: String,
}

impl<'a> WxPay<'a> {
    fn rsa_sign(&self, content: String, private_key: &str) -> String {
        // let der_bytes = base64::decode(der_encoded).expect("Failed to decode base64 content");
        // 获取私钥对象
        let private_key = RsaPrivateKey::from_pkcs8_pem(private_key).expect("Failed to parse key");
        
        // 创建一个Sha256对象
        let mut hasher = Sha256::new();
        // 对内容进行摘要
        hasher.input_str(content.as_str());
        // 将摘要结果保存到buf中
        let mut buf: Vec<u8> = repeat(0).take((hasher.output_bits() + 7) / 8).collect();
        hasher.result(&mut buf);

        // 对摘要进行签名
        let sign_result = private_key.sign(
            PaddingScheme::PKCS1v15Sign { hash: Option::from(Hash::SHA2_256) },
            &buf
        );

        // 签名结果转化为 base64.
        let vec = sign_result.expect("Create sign error for base64");

        base64::encode(vec)
    }

    fn get_headers(&self, api_body:ApiBody, params_string: String) -> Result<HeaderMap, Error> {

        let dt = Utc::now();
        let timestamp = dt.timestamp();
        let onece_str = rand_string(32);
        let method = match api_body.method {
            Method::GET => "GET",
            Method::POST => "POST"
        };

        // 获取签名
        let signature = self.rsa_sign(
            method.to_string() + "\n"
                + api_body.pathname + "\n" 
                + timestamp.to_string().as_str() + "\n"
                + onece_str.as_str() + "\n"
                + params_string.as_str() + "\n"
            , 
            &self.private_key
        );


        // 组装header
        let authorization = "WECHATPAY2-SHA256-RSA2048 mchid=\"".to_string()
            + &self.mchid + "\",nonce_str=\""
            + onece_str.as_str() + "\",timestamp=\""
            + timestamp.to_string().as_str() + "\",signature=\""
            + signature.as_str() + "\",serial_no=\""
            + &self.serial_no + "\"";
        
        
        let mut headers = HeaderMap::new();
        headers.insert(CONTENT_TYPE, "application/json; charset=utf-8".parse().unwrap());
        headers.insert(ACCEPT, "application/json".parse().unwrap());
        headers.insert(AUTHORIZATION, authorization.parse().unwrap());
        headers.insert(USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71".parse().unwrap());

        Ok(headers)

    }
    
    /// js 微信支付
    pub async fn jsapi(&self, params: JsapiParams) -> Result<WxData, Error> {
        println!("aaaj jsapi {}", &self.appid);
        println!("aaaj jsapi {:#?}", params.payer.openid);

        #[derive(Serialize, Deserialize)]
        struct Jsapi<'a> {
            description: String,
            out_trade_no: String,
            amount: Amount,
            payer: Payer,
            appid: &'a str,
            mchid: &'a str,
            notify_url: &'a str,
        }
        let jsapi_params = Jsapi {
            description: params.description,
            out_trade_no: params.out_trade_no,
            amount: params.amount,
            payer: params.payer,
            appid: &self.appid,
            mchid: &self.mchid,
            notify_url: &self.notify_url,
        };

        let jsapi_str = serde_json::to_string(&jsapi_params).unwrap();

        let api_body = ApiBody {
            url: "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi",
            method: Method::POST,
            pathname: "/v3/pay/transactions/jsapi",
        };

        let headers_all = self.get_headers(api_body, jsapi_str).unwrap();

        #[derive(Serialize, Deserialize, Debug)]
        struct JsapiRes {
            prepay_id: String
        }
        let client = reqwest::Client::new();
        let pre_data: JsapiRes = client.post(api_body.url.clone())
            .headers(headers_all)
            .json(&jsapi_params)
            .send()
            .await
            .unwrap()
            .json()
            .await.unwrap();


        let ran_str = rand_string(32);
        //  package: `prepay_id=${JSON.parse(preData.data).prepay_id}`,
        let pack = "prepay_id=".to_string() + pre_data.prepay_id.as_str();
        let dt = Utc::now();
        let now_time = dt.timestamp();
        
        // 获取签名
        let pay_si = self.rsa_sign(
            self.appid.to_string() + "\n"
                + now_time.to_string().as_str() + "\n" 
                + ran_str.as_str() + "\n"
                + pack.as_str() + "\n"
            ,
            &self.private_key
        );

        let wx_data = WxData {
            sign_type: "RSA".into(),
            pay_sign: pay_si,
            package: pack,
            nonce_str: ran_str,
            time_stamp: now_time.to_string(),
        };
        
        Ok(wx_data)
    }


}


/// 微信支付,回调解密
pub fn decode_wx(params: WxPayNotify) -> Result<WxNotifyData, Error> {
    let auth_key_length = 16;

    let mut t_key = [0u8; 32];
    hex::decode_to_slice(hex::encode(WECHAT_PAY_APIV3), &mut t_key as &mut [u8]).unwrap();
    let key = GenericArray::from_slice(&t_key);

    let mut t_nonce = [0u8; 12];
    hex::decode_to_slice(hex::encode(params.resource.nonce.clone()), &mut t_nonce as &mut [u8]).unwrap();
    let nonce = GenericArray::from_slice(&t_nonce);
    
    let t_ciphertext_base = base64::decode(params.resource.ciphertext.clone()).unwrap();
    let cipherdata_length = t_ciphertext_base.len() - auth_key_length;

    let cipherdata = &t_ciphertext_base[0..cipherdata_length];
    let auth_tag = &t_ciphertext_base[cipherdata_length..];

    let mut ciphertext = Vec::from(cipherdata);
    ciphertext.extend_from_slice(&auth_tag);

    let mut t_add = [0u8; 11];  // 这里可能会根据返回值 associated_data 长度而不同,目前应该是固定为 "transaction" 。
    hex::decode_to_slice(hex::encode(params.resource.associated_data.clone()), &mut t_add as &mut [u8]).unwrap();
    let payload = Payload {
        msg: &ciphertext,
        aad: &t_add,
    };
    let cipher = Aes256Gcm::new(key);
    let plaintext = cipher.decrypt(nonce, payload).unwrap();
    let content = std::str::from_utf8(&plaintext).unwrap();
    let data: WxNotifyData = serde_json::from_str(content).unwrap();

    Ok(data)
}

写支付接口,和回调接口

pay.rs


use actix_web::{get, post, web, Responder, Result};
use futures_util::TryFutureExt;
use serde::{Serialize, Deserialize};
use mysql::prelude::Queryable;

use crate::db::mysql_conn;
use crate::middleware::AuthUser;
use crate::routes::ResultStatus;
use crate::utils::{WxPay, JsapiParams, WxData, Amount, Payer, rand_string, WxPayNotify, decode_wx};
use crate::constants::{
    WECHAT_MINI_APP_ID,
    WECHAT_PAY_MCH_ID,
    WECHAT_PAY_SERIAL,
    WECHAT_PAY_APIV3,
    WECHAT_PRIVATE_KEY,
    WECHAT_PAY_NOTIFY_URL,
};


/// 微信支付 回调
#[post("/pay/notify_url/action")]
pub async fn pay_notify_url_action(params: web::Json<WxPayNotify>) -> Result<impl Responder> {
    println!("##############  微信支付 回调 #############");
    println!("{:#?}", params);
    let t_params = params.0;
    let data = decode_wx(t_params).unwrap();
    println!("json  {:#?}", data);
    println!("##############  微信支付 回调end #############");

    Ok(web::Json(ResultStatus {status: 2, message: "成功".into()}))
}




#[derive(Serialize, Deserialize)]
pub struct TestPay {
    total: u32,
}
/// 微信支付测试接口
#[post("/pay/wx/v3/test")]
pub async fn pay_wx_v3_test(user: AuthUser, params: web::Json<TestPay>) -> Result<impl Responder> {
    // 查寻当前用户
    let mut conn = mysql_conn();
    let user_info: Option<(u64,Option<String>,Option<String>)> = conn.query_first("select id,nickname,openid from users where id = ".to_string() + user.id.to_string().as_str()).unwrap();

    if let Some((uid, nickname, u_openid)) = user_info {
        if let Some(openid) = u_openid {
            println!("privkkkkkkkkkk, {}", openid);
            let wx_pay = WxPay {
                appid: WECHAT_MINI_APP_ID,
                mchid: WECHAT_PAY_MCH_ID,
                private_key: WECHAT_PRIVATE_KEY,
                serial_no: WECHAT_PAY_SERIAL,
                apiv3_private_key: WECHAT_PAY_APIV3,
                notify_url: WECHAT_PAY_NOTIFY_URL,
                certificates: None
            };


            let data = wx_pay.jsapi(JsapiParams {
                description: "测试122".to_string(),
                out_trade_no: rand_string(16),
                amount: Amount { total: 1 },
                payer: Payer { openid: openid}
            }).await.unwrap();

            println!("jsapi 返回的 wx_data 为: {:#?}", data);

            return Ok(web::Json(data));
        }
    }

    Ok(web::Json(WxData {
        sign_type: "".into(),
        pay_sign: "".into(),
        package: "".into(),
        nonce_str: "".into(),
        time_stamp: "".into(),
    }))
}

微信小程序端,发起支付

<Button
    margin='80 0 0 0'
    onClick={async () => {
      if(loading) return
      setLoading(true)
      // 调用支付接口
      let res = await post("/pay/wx/v3/test", {
        total: 1,  // 金额 分
      }, dispatch)

      Taro.requestPayment({
        timeStamp: res.data.time_stamp,
        nonceStr: res.data.nonce_str,
        package: res.data.package,
        signType: res.data.sign_type,
        paySign: res.data.pay_sign,
        success (res) {
          setLoading(false)
        },
        fail (res) { 
          Taro.showToast({title: '支付失败', icon: 'none'})
        }
      })
    }}
>
    支付测试
</Button>

至此,rust 的微信支付就差不多。主要是加密解密,这个懂了,其他接口,都是一样的操作。

rust 微信支付

版权声明:程序员胖胖胖虎阿 发表于 2022年10月9日 上午8:48。
转载请注明:rust 微信支付 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...