前段时间,给一个客户的微信小程序对接微信支付的接口,后端是用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