br_pay/
wechat.rs

1use crate::PayMode;
2use base64::engine::general_purpose::STANDARD;
3use base64::Engine;
4use json::{object, JsonValue};
5use std::fs;
6
7#[derive(Clone)]
8pub struct Wechat {
9    /// 服务商APPID
10    pub appid: String,
11    /// 密钥
12    pub secret: String,
13    /// 服务商户号
14    pub mchid: String,
15    /// 证书号
16    pub serial_no: String,
17    /// 证书路径
18    pub cert_path: String,
19}
20
21use chrono::{DateTime, Utc};
22use openssl::hash::MessageDigest;
23use openssl::pkey::PKey;
24use openssl::rsa::Rsa;
25use openssl::sign::Signer;
26use rand::distr::Alphanumeric;
27use rand::{rng, Rng};
28
29impl Wechat {
30    pub fn sign(&mut self, method: &str, url: &str, body: &str) -> String {
31        let timestamp = Utc::now().timestamp(); // 秒级时间戳
32        let random_string: String = rng()
33            .sample_iter(&Alphanumeric) // 生成随机字母+数字
34            .take(10) // 指定长度
35            .map(char::from)
36            .collect();
37
38        let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
39        // 读取私钥(PEM 格式)
40        let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
41
42        // 加载 RSA 私钥
43        let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
44        let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
45        // 创建签名器
46        let mut signer =
47            Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
48        // 输入待签名数据
49        signer
50            .update(sign_txt.as_bytes())
51            .expect("Failed to update signer");
52        // 生成签名
53        let signature = signer.sign_to_vec().expect("Failed to sign");
54        let signature_b64 = STANDARD.encode(signature);
55        let sign = format!(
56            r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
57            self.mchid.as_str(),
58            self.serial_no
59        );
60        sign
61    }
62
63    pub fn paysign(&mut self, prepay_id: &str) -> JsonValue {
64        let timestamp = Utc::now().timestamp(); // 秒级时间戳
65        let random_string: String = rng()
66            .sample_iter(&Alphanumeric) // 生成随机字母+数字
67            .take(10) // 指定长度
68            .map(char::from)
69            .collect();
70
71        let sign_txt = format!(
72            "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
73            self.appid
74        );
75        // 读取私钥(PEM 格式)
76        let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
77
78        // 加载 RSA 私钥
79        let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
80        let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
81        // 创建签名器
82        let mut signer =
83            Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
84        // 输入待签名数据
85        signer
86            .update(sign_txt.as_bytes())
87            .expect("Failed to update signer");
88        // 生成签名
89        let signature = signer.sign_to_vec().expect("Failed to sign");
90        let signature_b64 = STANDARD.encode(signature);
91        let sign = signature_b64;
92        object! {
93            timeStamp:timestamp,
94            nonceStr:random_string,
95            package:prepay_id,
96            signType:"RSA",
97            paySign:sign
98        }
99    }
100}
101impl PayMode for Wechat {
102    fn login(&self, code: &str) -> Result<JsonValue, String> {
103        let mut http = br_http::Http::new();
104        match http
105            .get(
106                "https://api.weixin.qq.com/sns/jscode2session"
107                    .to_string()
108                    .as_str(),
109            )
110            .header("Accept", "application/json")
111            .header("User-Agent", "api")
112            .header("Content-Type", "application/json")
113            .query(object! {
114                appid: self.appid.as_str(),
115                secret: self.secret.as_str(),
116                js_code:code,
117                grant_type:"authorization_code",
118            })
119            .json()
120        {
121            Ok(e) => Ok(e),
122            Err(e) => Err(e),
123        }
124    }
125
126    fn jsapi(
127        &mut self,
128        sub_mchid: &str,
129        out_trade_no: &str,
130        description: &str,
131        total_fee: f64,
132        notify_url: &str,
133        sp_openid: &str,
134    ) -> Result<JsonValue, String> {
135        let url = "/v3/pay/partner/transactions/jsapi";
136        let total = (total_fee * 100.0) as u64;
137        let body = object! {
138            "sp_appid" => self.appid.clone(),
139            "sp_mchid"=> self.mchid.clone(),
140            "sub_mchid"=> sub_mchid,
141            "description"=>description,
142            "out_trade_no"=>out_trade_no,
143            "notify_url"=>notify_url,
144            "support_fapiao"=>true,
145            "amount"=>object! {
146                total: total,
147                currency:"CNY"
148            },
149            "payer"=>object! {
150                sp_openid:sp_openid
151            }
152        };
153        let sign = self.sign("POST", url, body.to_string().as_str());
154        let mut http = br_http::Http::new();
155        match http
156            .post(format!("https://api.mch.weixin.qq.com{}", url).as_str())
157            .header("Accept", "application/json")
158            .header("User-Agent", "api")
159            .header("Content-Type", "application/json")
160            .header("Authorization", sign.as_str())
161            .raw_json(body)
162            .json()
163        {
164            Ok(e) => {
165                if e.has_key("prepay_id") {
166                    let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str());
167                    Ok(signinfo)
168                } else {
169                    Err(e["message"].to_string())
170                }
171            }
172            Err(e) => Err(e),
173        }
174    }
175
176    fn order_trade(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
177        let url = format!(
178            "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
179            out_trade_no, sub_mchid, self.mchid
180        );
181        let sign = self.sign("GET", url.as_str(), "");
182        let mut http = br_http::Http::new();
183        match http
184            .get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str())
185            .header("Accept", "application/json")
186            .header("User-Agent", "api")
187            .header("Content-Type", "application/json")
188            .header("Authorization", sign.as_str())
189            .json()
190        {
191            Ok(e) => {
192                if e.has_key("message") {
193                    return Err(e["message"].to_string());
194                }
195                let pay_time = e["success_time"].as_str().unwrap();
196                let datetime = DateTime::parse_from_rfc3339(pay_time).unwrap();
197                let pay_time = datetime.timestamp();
198                let mut info = object! {
199                    pay_time:pay_time,
200                    sp_appid:e["sp_appid"].clone(),
201                    transaction_id:e["transaction_id"].clone(),
202                    sp_mchid:e["sp_mchid"].clone(),
203                    sub_mchid:e["sub_mchid"].clone(),
204                    order_no:e["out_trade_no"].clone(),
205                    status:"",
206                    sp_openid:e["payer"]["sp_openid"].clone(),
207                    sub_openid:e["payer"]["sub_openid"].clone(),
208                    amount_currency:e["amount"]["currency"].clone(),
209                    amount_total:e["amount"]["total"].clone(),
210                    trade_state_desc:e["trade_state_desc"].clone(),
211                    trade_type:e["trade_type"].clone(),
212                };
213                info["status"] = match e["trade_state"].as_str().unwrap() {
214                    "SUCCESS" => "已支付",
215                    _ => "未知",
216                }
217                .into();
218                Ok(info)
219            }
220            Err(e) => Err(e),
221        }
222    }
223
224    fn refunds(
225        &mut self,
226        sub_mchid: &str,
227        out_trade_no: &str,
228        transaction_id: &str,
229        out_refund_no: &str,
230        refund: f64,
231        total: f64,
232        currency: &str,
233    ) -> Result<JsonValue, String> {
234        let url = "/v3/refund/domestic/refunds";
235        let refund = (refund * 100.0) as u64;
236        let total = (total * 100.0) as u64;
237        let body = object! {
238            "sub_mchid"=> sub_mchid,
239            "transaction_id"=>transaction_id,
240            "out_trade_no"=>out_trade_no,
241            "out_refund_no"=>out_refund_no,
242            "amount"=>object! {
243                refund: refund,
244                total: total,
245                currency:currency
246            }
247        };
248        let sign = self.sign("POST", url, body.to_string().as_str());
249        let mut http = br_http::Http::new();
250        match http
251            .post(format!("https://api.mch.weixin.qq.com{}", url).as_str())
252            .header("Accept", "application/json")
253            .header("User-Agent", "api")
254            .header("Content-Type", "application/json")
255            .header("Authorization", sign.as_str())
256            .raw_json(body)
257            .json()
258        {
259            Ok(e) => {
260                if e.has_key("message") {
261                    return Err(e["message"].to_string());
262                }
263                let mut refund_time = 0.0;
264                if e.has_key("success_time"){
265                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
266                    if !success_time.is_empty() {
267                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
268                        refund_time = datetime.timestamp() as f64;
269                    }
270                }
271
272                let status = match e["status"].as_str().unwrap() {
273                    "PROCESSING" => "退款中",
274                    "SUCCESS" => "已退款",
275                    _ => "无退款",
276                };
277                let info = object! {
278                    refund_id: e["refund_id"].clone(),
279                    user_received_account:e["user_received_account"].clone(),
280                    status:status,
281                    refund_time:refund_time,
282                    out_refund_no: e["out_refund_no"].clone(),
283                };
284                Ok(info)
285            }
286            Err(e) => Err(e),
287        }
288    }
289}