前段时间,给一个客户的微信小程序对接微信支付的接口,后端是用rust写的,这是照着官方文档单独写的模块,基本上可以实现两个文件就能发起预支付了,现在分享出来。
(此接口为发起预支付的联调代码,拿到了接口返回的数据之后,还要进行相关的处理,然后通过app、网站或者小程序之类的,在用户前端发起支付)
Cargo.toml
[package] name = "rustwechatpayapi" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # LCSAY TENCENT WECHAT PAY V3 预支付发起 # 建议使用 CLion 来运行,这样调用的类,如果本身没有,就会自动下载。 # WECHAT AND QQ : 516519782 [dependencies] reqwest = { version = "0.11", features = ["blocking"] } md5 = "0.7.0" uuid = { version = "0.8.2", features = ["serde", "v4"] } chrono = "0.4.19" openssl = { version = "0.10", features = ["vendored", ] } serde_json = { version = "1.0.81" } rand = "0.8" base64 = "0.13" rsa = "0.4" tokio = { version = "1", features = ["full"] }
main.rs
use reqwest::Client; use openssl::pkcs12::Pkcs12; use openssl::pkey::PKey; use openssl::sign::Signer; use openssl::hash::MessageDigest; use serde_json::json; use rand::Rng; use openssl::error::ErrorStack; use openssl::asn1::Asn1IntegerRef; /** * LCSAY TENCENT WECHAT PAY V3 预支付发起 * 建议使用 CLion 来运行,这样前面调用的类,如果本身没有,就会自动下载。 * WECHAT AND QQ : 516519782 */ fn main() { let merchant_id = ""; //微信商户id,这个在微信支付的商户平台可以获取 let app_id = ""; //公众号或者小程序的appid let cert_path= include_bytes!("D:\\wwwroot\\wechatpayrustapi\\src\\apiclient_cert.p12"); //微信支付V3证书,这个在微信支付平台按照指引获取 let cert_password = ""; //微信支付V3证书密码 let api_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; let request_data = json!({ "mchid": merchant_id, "appid": app_id, "payer": { "openid": "" //发起微信支付的openid,如果是公众号,就用公众号的openid,如果是小程序,就用小程序的openid }, "description": "test", "out_trade_no": format!("{}{}", chrono::Utc::now().format("%Y%m%d%H%M%S"), rand::random::<u32>() % 10000), "notify_url": "", //异步回调的地址(用户支付成功或者支付异常的情况下,都会给这个地址发送支付成功或者异常数据,异步回调的数据需要验签,如果验签成功要返回SUCCESS给微信支付的接口,告诉微信,不然会一直发数据过来,微信会检测异常和风控。) "amount": { "total": 100 //金额以分为单位,这里表示1元 } }); let json_data = serde_json::to_string(&request_data).unwrap(); let client = Client::new(); let mut headers = reqwest::header::HeaderMap::new(); headers.insert(reqwest::header::CONTENT_TYPE, "application/json".parse().unwrap()); headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap()); headers.insert(reqwest::header::USER_AGENT, "*/*".parse().unwrap()); let authorization = generate_authorization_header(merchant_id, api_url, &json_data, cert_path, cert_password); headers.insert(reqwest::header::AUTHORIZATION, authorization.parse().unwrap()); println!("{:?}", headers); tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async { let response = client .post(api_url) .headers(headers) .body(json_data) .send() .await; // 使用await等待异步操作完成 // 处理响应 match response { Ok(res) => { let body = res.text().await.unwrap(); println!("微信支付统一下单接口返回结果:{}", body); // 在这里根据返回结果进行后续处理,如解析XML、获取prepay_id等 } Err(e) => { println!("请求失败:{}", e); } } }); } fn generate_authorization_header(merchant_id: &str, url: &str, body: &str, cert_path: &[u8], cert_password: &str) -> String { let nonce_str = generate_nonce_string(32); let timestamp = chrono::Utc::now().timestamp(); let message = format!("POST\n{}\n{}\n{}\n{}\n", url.parse::<reqwest::Url>().unwrap().path(), timestamp, nonce_str, body); let signature = generate_signature(&message, &cert_path, cert_password); let serial_no = get_certificate_serial_number(cert_path, cert_password); format!("WECHATPAY2-SHA256-RSA2048 mchid=\"{}\",nonce_str=\"{}\",timestamp=\"{}\",serial_no=\"{}\",signature=\"{}\"", merchant_id, nonce_str, timestamp, serial_no, signature) } fn generate_nonce_string(length: usize) -> String { const CHARACTERS: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; let mut rng = rand::thread_rng(); (0..length) .map(|_| CHARACTERS.chars().nth(rng.gen_range(0..CHARACTERS.len())).unwrap()) .collect() } fn generate_signature(message: &str, cert_path: &[u8], cert_password: &str) -> String { let pkcs12 = Pkcs12::from_der(&cert_path).unwrap(); let parsed = pkcs12.parse2(cert_password).unwrap(); if let Some(stack) = parsed.ca { assert_eq!(stack.len(), 0); } //从p12证书文件提取公钥 -----BEGIN CERTIFICATE-----\n.....\n-----END CERTIFICATE----- //let certificate_pem = parsed.cert.expect("REASON").to_pem().expect("Failed to convert to PEM"); //println!("Private Key:\n{}", String::from_utf8_lossy(&certificate_pem)); //从p12证书文件提取私钥 -----BEGIN PRIVATE KEY-----\n.....\n-----END PRIVATE KEY----- let private_key_pem = parsed.pkey.expect("REASON").private_key_to_pem_pkcs8().expect("Failed to convert to PEM"); //println!("Private Key:\n{}", String::from_utf8_lossy(&private_key_pem)); let pkey = PKey::private_key_from_pem(&private_key_pem).unwrap(); let signature = { let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); signer.update(message.as_bytes()).unwrap(); signer.sign_to_vec().unwrap() }; let signature_base64 = base64::encode(&signature); signature_base64 } // 辅助函数:将 Asn1IntegerRef 转换为字符串表示形式 fn asn1_integer_to_string(asn1_integer: &Asn1IntegerRef) -> Result<String, ErrorStack> { let bn = asn1_integer.to_bn()?; let string = bn.to_hex_str()?; Ok(string.to_string()) } fn get_certificate_serial_number(cert_path: &[u8], cert_password: &str) -> String { let pkcs12 = Pkcs12::from_der(cert_path).unwrap(); let parsed = pkcs12.parse2(cert_password).unwrap(); if let Some(stack) = parsed.ca { assert_eq!(stack.len(), 0); } // 获取证书序列号 let binding = parsed.cert.expect("REASON"); let serial_number = binding.serial_number(); let serial_number_string = asn1_integer_to_string(&serial_number).expect("Failed to convert serial number to string"); serial_number_string.to_string() }
如果顺利运行,返回的是json格式的数据,如下图(通过返回的 prepay_id 参数,就可以在前端JavaScript或者小程序,app拉起支付了):
评论0