br_pay/
wechat.rs

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