br_pay/
wechat.rs

1use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
2use base64::engine::general_purpose::STANDARD;
3use base64::{Engine};
4use json::{object, JsonValue};
5use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
6use aes_gcm::aead::{Aead, Payload};
7use br_reqwest::Method;
8
9#[derive(Clone)]
10pub struct Wechat {
11    /// 服务商APPID
12    pub appid: String,
13    /// 密钥
14    pub secret: String,
15    /// 服务商户号
16    pub sp_mchid: String,
17    /// 证书号
18    pub serial_no: String,
19    /// API证书私钥
20    pub app_private: String,
21    /// APIv3密钥
22    pub apikey: String,
23    pub notify_url: String,
24}
25
26use chrono::{DateTime, Utc};
27use openssl::hash::MessageDigest;
28use openssl::pkey::{PKey};
29use openssl::rsa::Rsa;
30use openssl::sign::Signer;
31use rand::distr::Alphanumeric;
32use rand::{rng, Rng};
33
34impl Wechat {
35    pub fn http(&mut self, url: &str, method: Method, body: JsonValue) -> Result<JsonValue, String> {
36        let sign = self.sign(method.to_str().to_uppercase().as_str(), url, body.to_string().as_str())?;
37        let mut http = br_reqwest::Client::new();
38        let url = format!("https://api.mch.weixin.qq.com{}", url);
39        let send = match method {
40            Method::GET => http.get(url.as_str()),
41            Method::POST => http.post(url.as_str()).raw_json(body),
42            _ => http.post(url.as_str()),
43        };
44        match send.header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
45            Ok(e) => Ok(e),
46            Err(e) => Err(e)
47        }
48    }
49    pub fn sign(&mut self, method: &str, url: &str, body: &str) -> Result<String, String> {
50        let timestamp = Utc::now().timestamp(); // 秒级时间戳
51        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
52                                         .take(10) // 指定长度
53                                         .map(char::from).collect();
54
55        let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
56        // 加载 RSA 私钥
57        let rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
58            Ok(e) => e,
59            Err(e) => {
60                return Err(e.to_string())
61            }
62        };
63        let pkey = match PKey::from_rsa(rsa) {
64            Ok(e) => e,
65            Err(e) => {
66                return Err(format!("Failed to create PKey: {}", e))
67            }
68        };
69        // 创建签名器
70        let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
71            Ok(e) => e,
72            Err(e) => {
73                return Err(format!("Failed to create signer:{}", e));
74            }
75        };
76        // 输入待签名数据
77        match signer.update(sign_txt.as_bytes()) {
78            Ok(_) => {}
79            Err(e) => {
80                return Err(e.to_string())
81            }
82        };
83        // 生成签名
84        let signature = match signer.sign_to_vec() {
85            Ok(e) => e,
86            Err(e) => {
87                return Err(format!("Failed to sign: {}", e));
88            }
89        };
90        let signature_b64 = STANDARD.encode(signature);
91        let sign = format!(
92            r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
93            self.sp_mchid.as_str(),
94            self.serial_no
95        );
96        Ok(sign)
97    }
98
99    pub fn paysign(&mut self, prepay_id: &str) -> Result<JsonValue, String> {
100        let timestamp = Utc::now().timestamp(); // 秒级时间戳
101        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
102                                         .take(10) // 指定长度
103                                         .map(char::from).collect();
104
105        let sign_txt = format!(
106            "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
107            self.appid
108        );
109
110        // 加载 RSA 私钥
111        let rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
112            Ok(e) => e,
113            Err(e) => {
114                return Err(e.to_string())
115            }
116        };
117        let pkey = match PKey::from_rsa(rsa) {
118            Ok(e) => e,
119            Err(e) => {
120                return Err(format!("Failed to create PKey: {}", e))
121            }
122        };
123        // 创建签名器
124        let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
125            Ok(e) => e,
126            Err(e) => {
127                return Err(format!("Failed to create signer:{}", e));
128            }
129        };
130        // 输入待签名数据
131        match signer.update(sign_txt.as_bytes()) {
132            Ok(_) => {}
133            Err(e) => {
134                return Err(e.to_string())
135            }
136        };
137        // 生成签名
138        let signature = match signer.sign_to_vec() {
139            Ok(e) => e,
140            Err(e) => {
141                return Err(format!("Failed to sign: {}", e));
142            }
143        };
144        let signature_b64 = STANDARD.encode(signature);
145        let sign = signature_b64;
146        Ok(object! {
147            timeStamp:timestamp,
148            nonceStr:random_string,
149            package:prepay_id,
150            signType:"RSA",
151            paySign:sign
152        })
153    }
154}
155impl PayMode for Wechat {
156    fn login(&mut self, code: &str) -> Result<JsonValue, String> {
157        let mut http = br_reqwest::Client::new();
158        match http.get(
159            "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
160        ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
161                appid: self.appid.as_str(),
162                secret: self.secret.as_str(),
163                js_code:code,
164                grant_type:"authorization_code",
165            }).send()?.json() {
166            Ok(e) => Ok(e),
167            Err(e) => Err(e),
168        }
169    }
170
171    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
172        let url = format!(
173            "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
174            out_trade_no, sub_mchid, self.sp_mchid
175        );
176        let sign = self.sign("GET", url.as_str(), "")?;
177        let mut http = br_reqwest::Client::new();
178        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
179            Ok(e) => {
180                if e.has_key("message") {
181                    return Err(e["message"].to_string());
182                }
183                let res = PayNotify {
184                    trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
185                    out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
186                    sp_mchid: e["sp_mchid"].as_str().unwrap().to_string(),
187                    sub_mchid: e["sub_mchid"].as_str().unwrap().to_string(),
188                    sp_appid: e["sp_appid"].as_str().unwrap().to_string(),
189                    transaction_id: e["transaction_id"].to_string(),
190                    success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
191                    sp_openid: e["payer"]["sp_openid"].to_string(),
192                    sub_openid: e["payer"]["sub_openid"].to_string(),
193                    total: e["amount"]["total"].as_u64().unwrap() as usize,
194                    currency: e["amount"]["currency"].to_string(),
195                    payer_total: e["amount"]["payer_total"].as_u64().unwrap_or(0) as usize,
196                    payer_currency: e["amount"]["payer_currency"].to_string(),
197                    trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
198                };
199
200                Ok(res.json())
201            }
202            Err(e) => Err(e),
203        }
204    }
205
206    fn refund(
207        &mut self,
208        sub_mchid: &str,
209        out_trade_no: &str,
210        transaction_id: &str,
211        out_refund_no: &str,
212        amount: f64,
213        total: f64,
214        currency: &str,
215    ) -> Result<JsonValue, String> {
216        let url = "/v3/refund/domestic/refunds";
217
218        let refund = format!("{:.0}", amount * 100.0);
219        let total = format!("{:.0}", total * 100.0);
220
221        let body = object! {
222            "sub_mchid"=> sub_mchid,
223            "transaction_id"=>transaction_id,
224            "out_trade_no"=>out_trade_no,
225            "out_refund_no"=>out_refund_no,
226            "amount"=>object! {
227                refund: refund.parse::<i64>().unwrap(),
228                total: total.parse::<i64>().unwrap(),
229                currency:currency
230            }
231        };
232        let sign = self.sign("POST", url, body.to_string().as_str())?;
233        let mut http = br_reqwest::Client::new();
234        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
235            Ok(e) => {
236                if e.is_empty() {
237                    return Err("已执行".to_string());
238                }
239                if e.has_key("message") {
240                    return Err(e["message"].to_string());
241                }
242                let mut refund_time = 0.0;
243                if e.has_key("success_time") {
244                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
245                    if !success_time.is_empty() {
246                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
247                        refund_time = datetime.timestamp() as f64;
248                    }
249                }
250
251                let status = match e["status"].as_str().unwrap() {
252                    "PROCESSING" => "退款中",
253                    "SUCCESS" => "已退款",
254                    _ => "无退款",
255                };
256                let info = object! {
257                    refund_id: e["refund_id"].clone(),
258                    user_received_account:e["user_received_account"].clone(),
259                    status:status,
260                    refund_time:refund_time,
261                    out_refund_no: e["out_refund_no"].clone(),
262                };
263                Ok(info)
264            }
265            Err(e) => Err(e)
266        }
267    }
268
269    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
270        if self.apikey.is_empty() {
271            return Err("apikey 不能为空".to_string());
272        }
273        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
274        let cipher = Aes256Gcm::new(key);
275        let nonce = Nonce::from_slice(nonce.as_bytes());
276        let data = match STANDARD.decode(ciphertext) {
277            Ok(e) => e,
278            Err(e) => return Err(format!("Invalid data received from API :{}", e))
279        };
280        // 组合 Payload(带 aad)
281        let payload = Payload {
282            msg: &data,
283            aad: associated_data.as_bytes(),
284        };
285
286        // 解密
287        let plaintext = match cipher.decrypt(nonce, payload) {
288            Ok(e) => e,
289            Err(e) => {
290                return Err(format!("解密 API:{}", e));
291            }
292        };
293        let rr = match String::from_utf8(plaintext) {
294            Ok(d) => d,
295            Err(_) => return Err("utf8 error".to_string())
296        };
297        let json = match json::parse(rr.as_str()) {
298            Ok(e) => e,
299            Err(_) => return Err("json error".to_string())
300        };
301        let res = PayNotify {
302            trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
303            out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
304            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
305            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
306            sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
307            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
308            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
309            sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
310            sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
311            total: json["amount"]["total"].as_u64().unwrap() as usize,
312            payer_total: json["amount"]["payer_total"].as_u64().unwrap() as usize,
313            currency: json["amount"]["currency"].to_string(),
314            payer_currency: json["amount"]["payer_currency"].to_string(),
315            trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
316        };
317        Ok(res.json())
318    }
319
320    fn refund_query(&mut self, _trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
321        let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
322        let sign = self.sign("GET", url.as_str(), "")?;
323        let mut http = br_reqwest::Client::new();
324        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
325            Ok(e) => {
326                if e.is_empty() {
327                    return Err("已执行".to_string());
328                }
329                if e.has_key("message") {
330                    return Err(e["message"].to_string());
331                }
332
333
334                let res = RefundNotify {
335                    out_trade_no: e["out_trade_no"].to_string(),
336                    refund_no: e["out_refund_no"].to_string(),
337                    sp_mchid: "".to_string(),
338                    sub_mchid: sub_mchid.to_string(),
339                    transaction_id: e["transaction_id"].to_string(),
340                    refund_id: e["refund_id"].to_string(),
341                    success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
342                    total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
343                    payer_total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
344                    refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
345                    payer_refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
346                    status: RefundStatus::from(e["status"].as_str().unwrap()),
347                };
348
349                Ok(res.json())
350            }
351            Err(e) => Err(e),
352        }
353    }
354    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
355        if self.apikey.is_empty() {
356            return Err("apikey 不能为空".to_string());
357        }
358        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
359        let cipher = Aes256Gcm::new(key);
360        let nonce = Nonce::from_slice(nonce.as_bytes());
361        let data = match STANDARD.decode(ciphertext) {
362            Ok(e) => e,
363            Err(e) => return Err(format!("Invalid data received from API :{}", e))
364        };
365        // 组合 Payload(带 aad)
366        let payload = Payload {
367            msg: &data,
368            aad: associated_data.as_bytes(),
369        };
370
371        // 解密
372        let plaintext = match cipher.decrypt(nonce, payload) {
373            Ok(e) => e,
374            Err(e) => {
375                return Err(format!("解密 API:{}", e));
376            }
377        };
378        let rr = match String::from_utf8(plaintext) {
379            Ok(d) => d,
380            Err(_) => return Err("utf8 error".to_string())
381        };
382        let json = match json::parse(rr.as_str()) {
383            Ok(e) => e,
384            Err(_) => return Err("json error".to_string())
385        };
386        let res = RefundNotify {
387            out_trade_no: json["out_trade_no"].to_string(),
388            refund_no: json["out_refund_no"].to_string(),
389            refund_id: json["refund_id"].to_string(),
390            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
391            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
392            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
393            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
394            total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
395            refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
396            payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
397            payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
398            status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
399        };
400        Ok(res.json())
401    }
402
403    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
404        todo!()
405    }
406
407    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
408        let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
409        let body = object! {
410            "sp_mchid"=> self.sp_mchid.clone(),
411            "sub_mchid"=> sub_mchid
412        };
413        let sign = self.sign("POST", url.as_str(), body.to_string().as_str())?;
414        let mut http = br_reqwest::Client::new();
415        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
416            Ok(_) => Ok(true.into()),
417            Err(e) => Err(e)
418        }
419    }
420
421    fn config(&mut self) -> JsonValue {
422        todo!()
423    }
424
425    fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
426        let url = match types {
427            Types::Jsapi => "/v3/pay/partner/transactions/jsapi",
428            Types::Native => "/v3/pay/partner/transactions/native",
429            Types::H5 => "/v3/pay/partner/transactions/h5",
430            Types::MiniJsapi => "/v3/pay/partner/transactions/jsapi",
431            Types::App => "/v3/pay/partner/transactions/app",
432            Types::Micropay => "/pay/micropay"
433        };
434        let total = format!("{:.0}", total_fee * 100.0);
435        let mut body = object! {
436            "sp_appid" => self.appid.clone(),
437            "sp_mchid"=> self.sp_mchid.clone(),
438            "sub_mchid"=> sub_mchid,
439            "description"=>description,
440            "out_trade_no"=>out_trade_no,
441            "notify_url"=>self.notify_url.clone(),
442            "support_fapiao"=>true,
443            "amount"=>object! {
444                total: total.parse::<i64>().unwrap(),
445                currency:"CNY"
446            }
447        };
448        match types {
449            Types::Native => {}
450            _ => {
451                body["payer"] = object! {
452                sp_openid:sp_openid
453            };
454            }
455        };
456
457        let sign = self.sign("POST", url, body.to_string().as_str())?;
458        let mut http = br_reqwest::Client::new();
459        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", &sign).raw_json(body).send()?.json() {
460            Ok(e) => {
461                match types {
462                    Types::Native => {
463                        if e.has_key("code_url") {
464                            Ok(e["code_url"].clone())
465                        } else {
466                            Err(e["message"].to_string())
467                        }
468                    }
469                    Types::Jsapi | Types::MiniJsapi => {
470                        if e.has_key("prepay_id") {
471                            let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str())?;
472                            Ok(signinfo)
473                        } else {
474                            Err(e["message"].to_string())
475                        }
476                    }
477                    _ => {
478                        Ok(e)
479                    }
480                }
481            }
482            Err(e) => Err(e),
483        }
484    }
485
486    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
487        todo!()
488    }
489
490    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
491        let url = format!("/v3/apply4sub/sub_merchants/{sub_mchid}/settlement");
492        let res = self.http(url.as_str(), Method::GET, "".into())?;
493        if res.has_key("verify_result") && res["verify_result"] == "VERIFY_SUCCESS" {
494            return Ok(true.into());
495        }
496        Err(res.to_string())
497    }
498}