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