微信领取有很多接口,咱们就以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 的微信领取就差不多。次要是加密解密,这个懂了,其余接口,都是一样的操作。