11 releases
new 0.4.0 | Dec 20, 2024 |
---|---|
0.3.9 | Dec 20, 2024 |
#352 in Cryptography
165 downloads per month
86KB
1.5K
SLoC
wechat-pay-rust-sdk
微信支付 © Wechat Pay SDK Official (标准库)
API文档
使用指南
引入依赖
#异步
wechat-pay-rust-sdk-wf = {version = "x.x.x"}
# 同步
wechat-pay-rust-sdk-wf = {version = "x.x.x", features = ["blocking"]}
# debug日志开启
wechat-pay-rust-sdk-wf = {version = "x.x.x", features = ["blocking","debug-print"]}
native支付
use wechat_pay_rust_sdk::model::NativeParams;
use wechat_pay_rust_sdk::pay::WechatPay;
let private_key_path = "./apiclient_key.pem";
let private_key = std::fs::read_to_string(private_key_path).unwrap();
let wechat_pay = WechatPay::new(
"app_id",
"mch_id",
private_key.as_str(),
"serial_no",
"v3_key",
"notifi_url",
);
let body = wechat_pay.native_pay(NativeParams::new(
"测试支付1分",
"124324343",
1.into(),
)).expect("native_pay error");
println!("body: {:?}", body);
输出
NativeResponse {
code: None,
message: None,
code_url: Some("weixin://wxpay/bizpayurl?pr=yL2aIPzz")
}
h5支付
use wechat_pay_rust_sdk::model::{H5Params, H5SceneInfo};
use wechat_pay_rust_sdk::pay::WechatPay;
use wechat_pay_rust_sdk::util;
let wechat_pay = WechatPay::from_env();
let body = wechat_pay.h5_pay(H5Params::new(
"支付1分",
util::random_trade_no().as_str(),
1.into(),
H5SceneInfo::new(
"183.6.105.1", //填写客户端IP
"我的网站",
"https://mydomain.com",
),
)).expect("h5_pay error");
println!("body: {:?}", body);
输出
H5Response {
code: None,
message: None,
h5_url: Some("https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx11154002858116623fasdfasdf&package=760499411")
}
h5_url转换成微信支付链接
let body = wechat_pay.h5_pay(H5Params::new(
"测试支付1分",
util::random_trade_no().as_str(),
1.into(),
H5SceneInfo::new("183.6.105.141", "软件", "https://mydomain.com"),
)).expect("h5_pay error");
let weixin_url = wechat_pay.get_weixin(body.h5_url.unwrap().as_str(), "https://mydomain.com").unwrap();
println!("weixin_url: {}", weixin_url.unwrap());
输出
weixin://wap/pay?prepayid%3Dwx13013716281xa5df8313490000&package=35748946&noncestr=1705081036&sign=8d988c82ded5fb02f097d6f1d70
jsapi支付
use wechat_pay_rust_sdk::model::JsapiParams;
use wechat_pay_rust_sdk::pay::WechatPay;
let wechat_pay = WechatPay::from_env();
let body = wechat_pay.jsapi_pay(JsapiParams::new(
"测试支付1分",
"1243243",
1.into(),
"open_id".into()
)).expect("jsapi_pay error");
println!("body: {:?}", body);
输出
JsapiResponse {
code: None,
message: None,
prepay_id: Some("wx201410272009395522657a690389285100")
}
app支付
use wechat_pay_rust_sdk::model::AppParams;
use wechat_pay_rust_sdk::pay::WechatPay;
let wechat_pay = WechatPay::from_env();
let body = wechat_pay.app_pay(AppParams::new(
"测试支付1分",
"1243243",
1.into()
)).expect("app_pay error");
println!("body: {:?}", body);
输出
AppResponse {
code: None,
message: None,
prepay_id: Some("wx201410272009395522657a690389285100")
}
小程序支付
use wechat_pay_rust_sdk::model::MicroParams;
use wechat_pay_rust_sdk::pay::WechatPay;
let wechat_pay = WechatPay::from_env();
let body = wechat_pay.micro_pay(JsapiParams::new(
"测试支付1分",
"1243243",
1.into(),
"open_id".into()
)).expect("micro_pay error");
println!("body: {:?}", body);
输出
MicroResponse {
code: None,
message: None,
prepay_id: Some("wx201410272009395522657a690389285100")
}
支付回调解密
use wechat_pay_rust_sdk::pay::{PayNotifyTrait, WechatPay};
let associated_data = "transaction";
let nonce = "gZiqzlfayUu2";
let ciphertext = "pCidqdiS5IIj5f9Pw9j69zuzu8l8IxcPCkfsTBKzna4gqZztNAqTMUY/Ai0rtj8qhaX0naYZF3a2lRid/ofK/83MNv+Neb5+w/0+UOO9nLNJvIFy3oFeMf2PTbp6tgDE35T5AoP9iKQ+1VkXTiUdRxzFoRx6/LfBzHmeuVEDHKScRqjrf6NdxuDDD0ciCQaiHmb18Y0BRZdfNxWTAC83Rar5yTX2NNZPBtGdFDG3yAK2I3Vp7ZKLeMa92ecExNGwHrdJ+HxWw66IIdwVqJLlNmTG0c5zUpSc8yovnaJi1Wv/TC7Tm5NzcwdHsdRE110tIWFbvNmIzIIb+3P33JFWmaXXb1VVDC43DqtlplttYwL6H3kU0ABgHMMbccTwYmP4cSY8BCAL01754nqipxWogEC/la9iQiw85+rLRo/Ny9k3mp8n35D6bDNtS1LiaslbLM92ZbfKeglTg54F/R1l5xWolAVpx8iTz8Oc+XJClXdWr8j5poyh8zK2/RrXPRfr+8s2/oGeGvdaqJbN/LviYcCMDbXU9pKDScWlSi4akxfJu0EatPDvFEbn5DYRQnn5v6wCeesYkEL+wiFCAIs=";
let wechat_pay = WechatPay::from_env();
let data = wechat_pay.decrypt_paydata(
ciphertext,
nonce,
associated_data
).unwrap();
println!("data: {:#?}", data);
解密结果
WechatPayDecodeData {
mchid: "163971811111",
appid: "wx15f4803f25xxxxx",
out_trade_no: "8e289eebd1f44604b0b27e05f11bcf10",
transaction_id: "4200001926202401125681342683",
trade_type: "MWEB",
trade_state: "SUCCESS",
trade_state_desc: "支付成功",
bank_type: "OTHERS",
attach: "",
success_time: "2024-01-12T10:36:13+08:00",
payer: PayerInfo {
openid: "oAZUY6DittOj59wCzPn6vNgpK2eY",
},
amount: AmountInfo {
total: 1,
},
}
actix-web demo
支付回调json格式为
{"id":"376151be-0eac-5047-b08a-46b52e15d2e2","create_time":"2024-01-12T12:17:33+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"u+MVmYPLQO4fjRsGWChm3sc/AXFVsytCI362RzYJyG25RbP6RSxYtkC2TIUA2ECfdhaJ0pIYuv4TwHwB1JE+0dn/MVQIjsBgaL9jx6IxmFIbkvNg0o623PF250ZhC9snTzxKJJtPtKFn3E8bR/pmqO4zbwUjQyQI5B4LqmzFcKpiKqGZSyG0BdvEWV2sDlR8oHD3s5RH/YN6c0aI7pEtVa1n7CR4qqQo9/NLAjTwloXWxB0BB+OnmlXQ9fu1UdJBS8L53W9zpREbEpH3BeCjrML/5qBs2nwcgvRV0OM30LkEdX8/lX7PiR6jzT2SexbinpSzx1QyXy9ZZfLRjFWVfQDTcDOrkMIaem4rhRgkAe5UDx6xdtqbgPSi5Ry/KHPm1+ptAl1GmEe9LIz8fRLleew3U0THXTSjnu5dJaXqk0qEizvK1pQBZ97QuzWuC2sVh4pd/OyqSNn93mlslkJIgT/UjQRcTIUE/CphdI7BGJkKYbEz4pSoqD/lxUiZNlMWbDeP4gEu/B7+Uk8n9vCOzR35VroLpweC0aDnCa3ru8DfMOcLQTvq04M4GJha9aodXec399ma3UcLEuw=","associated_data":"transaction","nonce":"pEw6yyO8XiSj"}}
自行使用web框架获取post json数据
#[post("/pay/notify")]
async fn pay_notify(data: Json<WechatPayNotify>, req: HttpRequest) -> impl Responder {
let data = data.into_inner();
let nonce = data.resource.nonce;
let ciphertext = data.resource.ciphertext;
let associated_data = data.resource.associated_data.unwrap_or_default();
dotenv().ok();
let wechat_pay = WechatPay::from_env();
let result: WechatPayDecodeData = wechat_pay.decrypt_paydata(
ciphertext, //加密数据
nonce, //随机串
associated_data, //关联数据
).unwrap();
debug!("result: {:#?}", result);
HttpResponse::Ok().json(serde_json::json!({
"code": "SUCCESS",
"message": "成功"
}))
}
读取平台证书
use wechat_pay_rust_sdk::pay::WechatPay;
let wechat_pay = WechatPay::from_env();
let response = wechat_pay.certificates().expect("certificates error");
println!("response: {:#?}", response);
响应
CertificateResponse {
data: Some(
[
Certificate {
serial_no: "32507F67D05E9443E39ED3E7D5DBF21BB44E5D0C",
effective_time: "2023-03-22T22:58:57+08:00",
expire_time: "2028-03-20T22:58:57+08:00",
encrypt_certificate: EncryptCertificate {
algorithm: "AEAD_AES_256_GCM",
nonce: "034246e50ad4",
associated_data: "certificate",
ciphertext: "/jXFSfxLXhdWii/ArpvW/XTRECZ5I1RoYcrw/At5w0oey+xl35BpWxPP+YoD+8GutY7NUJ2RgNfoAIrNdZhATO4ZxHASh93U06rKJOAnBVJMS18YCvk0TDdyoVkk9RFhKfEq9fdsUjxJ29gosHVcOHGgATR8OZmbpasrWnoAPLU9sEcZanRm5d5Ig3QLPkoBr1GKnX7bDLN8loUitq3+tCzSrDYQB8+SUWEqxjAHxoSy6zb46Vh7T1dJntGFdcCL499e2+8imm5y7XG9B4d69J0U9/a7OhnFMpcVZanFPv+4Y6jZf44isBzIwF5yz32DZ+zXLW46Yuef48DUJOJnHVmoP/R5AY1oBAuwwbZY+hlQNkqmZbbq5NTkxyxk2Qxn9NaVeZv8dKHDMt29MEX+Uzz6uA4k0Gdqd//gIe6dGgHsMrte7fSwNvS0v2Re/jPBNh9AfYpPnlo/J4Cw9T2Eb3BPAnH197mw0Gvc+tMJVDR1II8vBGjXjExhdEVNlQTPeEJh1qvTmfNLr9QfYi8AW2lo3TjOVtEhrqUumMUT4F+RxL0/AiE+sIdTvj0DH7pTvVe9nJ9pR0cbnleBUXvYvYZxl/moBekp/GVbq1XRpDwX/SApzyqW/6ZOPn9gOK63xCm09qlihoXtC1cjKnv6s7ozmqTsyqyD6gk5QkY303HlrQ0EvqfFm9HnYA3ycr4Eh0808QdaELnIB9MCMjb00EvWobvRy02SO0Y5bAV1Ea9SRWcowv3mqtjMx6gBgLxt1U0+qWENFm9CAK2I6gA0tDFGgOvBH12rwTwKePa0efYuleK+y2RV5/7CQ+wFSPjYXITaI37tO8F6iD8mUx0ARCKAoy5ruliuZHBQvllKdg0rkvRABU/UldGildkf5RZ2dyy0MjPmI84Sm5HPpI9qr4VJb/8ZLOebTPHRKIgUks75eAYkJiB6KfuER8Js/4f3hKBcPxdX49lhHm16sp3Layxthv/YAfEeNVTX47qODarl1qtjIstnY8SMHin7XuXflE2JZKDWny63ssnqzgMN0KbLxBtyUzbd3HmAEK2t4fRQgrVyePM5KJ+Zk+s+pGG8UTG8EoysznO/EzZybyttj3MPWhkAVhGMeo9B8uCuuwx1PsSAeYXzvyYRAUqc5Fqh3PfIPYnNFtwd2iEdV1yKFhiNozwp1soOFp1hm7+2o9M7FsDxtofOJ6DMkTbb9/Ba8QIqEb3tiVw0uG340wbPTv8DNvJJUTzL8JdOlE0q4Dlq6rrV+09WamOGzmTMDsoKjFraFc+8XO3PVu0oQ179Zaf37IZrwQshvLH0hHot30dHBz235iW+QW5hMEeMM+Uz/EmWEBj0V4VorJBGfIWA+iy/bDWMfmcZeHGA91ITLDYnxifWu6XGIEKtAt2Y3lsIvfcjtcxLbwgHUMl9v7rewHsGYxFpcG35Y5Yk/uxrj7acCHWVgpBaH2ShCklLrUixqeTfsgBJ7OSysZM7UqWThtzU1GBUqplrHQCLzL2t6YAd8XJWc0mUa6VsfqWuQQZ5HDcnsYExKHExq7LbKf+z+5J2FjYqFWU16cPkndQbBjbOGQqaFd+1BbVU1ZkkUIMxYTKgIo3w4Z3hnlEHSBeb8psD9k9kB4ol831Vp0fH3aGR+a3/uLZaI1sUS5gM6OzsmF8X0nU8XfsVmLq0QK9AvNI97qCwS9zC/3U+KazaYpTCYpwhq9u5nB1Qtbwc95+tlpA+A/2hzPd+ym+Yv5z0JevYbVicO9S0awqAkxIXChQa56QPiLQ0cezPJN2bc+/we2wsoPxVHMWhLj9q2rQojKUycBNMcnK0X0081KaVlTxZ1GB+a0UgSuO+l05tIr0fO03+Eme6huUKSZVC9MOLbGtGBpUFyv0CclDCgiLwjDGFi8+vNyRoQD32Y0zAdRPrKA2K+cINKxK739jBfc/ZjMsYfo8W8HeTTq5tI+DyY734Yo3XgCH/EcZNmsqe1JetkeK+",
},
},
],
),
}
解密上面的证书
use wechat_pay_rust_sdk::pay::{PayNotifyTrait, WechatPay};
use wechat_pay_rust_sdk::response::Certificate;
let wechat_pay = WechatPay::from_env();
let response = wechat_pay.certificates().expect("certificates error");
let data: Certificate = response.data.unwrap()[0].clone();
let ciphertext = data.encrypt_certificate.ciphertext;
let nonce = data.encrypt_certificate.nonce;
let associated_data = data.encrypt_certificate.associated_data;
let data = wechat_pay.decrypt_bytes(ciphertext, nonce, associated_data).unwrap();
let pub_key = util::x509_to_pem(data.as_slice()).unwrap(); //证书转公钥
let mut pub_key_file = std::fs::File::create("pubkey.pem").unwrap();
pub_key_file.write_all(pub_key.as_bytes()).unwrap();
let (pub_key_valid, expire_timestamp) = util::x509_is_valid(data.as_slice()).unwrap();
debug!("pub key valid:{} expire_timestamp:{}", pub_key_valid, expire_timestamp);//检测证书是否可用,打印过期时间
println!("pub key: {}", pub_key);
输出公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ufQLGNv3VlQObbWxoRU
c+8XLaogBL58i8cjapp5YqqS33cN7Qtcmiob5baV84Iu6DMClgWKB9VSaMs7vX6w
7WM++CPxOyg3oewLKDNPBvPtXBATWRScrmFh37XxXt2wJdLItdl86jd8o0sX8o7K
w4TIXKxlR7YQ3sxNWlbCpd4g3gkcXiV0erIbzwEn5d5n74xOTjnmbXf1FTN3K3HA
xxxx...
-----END PUBLIC KEY-----
签名验证
使用上面的公钥用来验签
平台的证书有时效性,请及时检测并下载最新的证书并替换本地公钥。
use wechat_pay_rust_sdk::pay::{PayNotifyTrait, WechatPay};
let wechat_pay = WechatPay::from_env();
let pub_key = std::fs::read_to_string("pubkey.pem").unwrap();
//在支付回调中header获取到的签名
let wechatpay_signature = "mFgmwXAKL3YJj34b7f+cUG3vkW09TiXU4lOSzCbvWFtvyLTb5WiyfAiVXZmMB17Qh9gDVkqboO97zfIYfv+AVdxj3GQljWlW+vE1Ujn2uxiFld6bWwz8Znk+833ruzZ8mAIaqLEjI/HKuVPdTj4LFzh/EO+gEMR6WDXr+7cZV7D3qUTXuO26fHLe0PmleDziG8SPgYjihK1ztF3Os0NhvL5tQMM8LKDOMzO3kxSr/TqTBtsB/OnuP2mH8yaSUeYeTpGStYvSw8KVi+gk6VnrlkVmdFh3DDXY60GCzCZ8zPl12RmzZbBRSK8ocVrzs4tuqRa5Euk3cDIA6qHqS8hyBQ==";
//回调的数据
let body = r#"{"id":"29a61973-babf-599a-966d-6bcdcf17360c","create_time":"2024-01-12T21:39:44+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"5ZfDK+LRJakAkC7kdHKRzCu5WZ0JFC2qSwP4InWNFeUnY0uaOnzfCjiqhDTFYyP4ywxuLxPUOiVI3WT6CcU0NNqbadTQ5XzjVuKLxYSnOYCFULltIrfsT/mUF4VW+xBMgSgG4+ZdzhRXVr+AzihDKFjw2p1iCtLYz9emgToctygNBtV6JDEI2BnCoiEM7qyIU1ALv5IsufQHDQqzjYXd16OD3i6O8UeSE2GOd4ifmQrAKGKalwWPECI73/qTFoAcLcgbhhn1TeSEaHoF7xceDmkL9AGlC21pBwYWoibTgqdlDJiz3IctrCzH6PPXD8XcApEj4A3ByyPjaNs6HxaJGzEHYGUkyM2/b7SzZIzqlBmNRZYFvBC0BOwoktyxrIhg3bKSbYtDYt1+8lMaYIJW6Dgq9GjG6pxAVrYULt8sk8cKZ+OrK9iXHZI11pYyK9YwWJLXbs6GyjMdDxhaGilF9csK8ZSsKzUjvlcLCjboCFX6nuHvCbswchYchQhTeitKDKG3/q+4snY183dBA6rXBHKQduqc1vXRR6odMcU1Evvy5mKnDTDELlI6mqvBtJ10XNED5O43ga5ZAODxYoU=","associated_data":"transaction","nonce":"uaGeNnBYNjl7"}}"#;
//支付回调中header获取到的时间戳
let wechatpay_timestamp = "1705066785";
//支付回调中header获取到的随机串
let wechatpay_nonce = "Jh9oPZelCJIQeQ47kz4stzvDKpLEUhCX";
wechat_pay.verify_signature(
pub_key.as_str(),
wechatpay_timestamp,
wechatpay_nonce,
wechatpay_signature,
body,
).unwrap();
actix-web中验证的例子
#[post("/pay/notify")]
async fn pay_notify(bytes: Bytes, req: HttpRequest) -> impl Responder {
let headers = req.headers();
let pub_key = std::fs::read_to_string("pubkey.pem").unwrap();
let wechatpay_signature = headers.get("wechatpay-signature").unwrap().to_str().unwrap();
let wechatpay_timestamp = headers.get("wechatpay-timestamp").unwrap().to_str().unwrap();
let wechatpay_nonce = headers.get("wechatpay-nonce").unwrap().to_str().unwrap();
let body = String::from_utf8(bytes.to_vec()).unwrap();
let wechat_pay = WechatPay::from_env();
wechat_pay.verify_signature(
pub_key.as_str(),
wechatpay_timestamp,
wechatpay_nonce,
wechatpay_signature,
body,
).expect("签名验证失败,非法数据");
HttpResponse::Ok().json(serde_json::json!({
"code": "SUCCESS",
"message": "成功"
}))
}
Dependencies
~12–29MB
~387K SLoC