1use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType};
2use base64::engine::general_purpose::STANDARD;
3use base64::{Engine};
4use json::{object, JsonValue};
5use std::fs;
6use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
7use aes_gcm::aead::{Aead, Payload};
8
9#[derive(Clone)]
10pub struct Wechat {
11 pub appid: String,
13 pub secret: String,
15 pub mchid: String,
17 pub serial_no: String,
19 pub cert_path: String,
21 pub apikey: String,
23}
24
25use chrono::{DateTime, Utc};
26use openssl::hash::MessageDigest;
27use openssl::pkey::PKey;
28use openssl::rsa::Rsa;
29use openssl::sign::Signer;
30use rand::distr::Alphanumeric;
31use rand::{rng, Rng};
32
33impl Wechat {
34 pub fn sign(&mut self, method: &str, url: &str, body: &str) -> String {
35 let timestamp = Utc::now().timestamp(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
39
40 let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
41 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
43
44 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
46 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
47 let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
49 signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
51 let signature = signer.sign_to_vec().expect("Failed to sign");
53 let signature_b64 = STANDARD.encode(signature);
54 let sign = format!(
55 r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
56 self.mchid.as_str(),
57 self.serial_no
58 );
59 sign
60 }
61
62 pub fn paysign(&mut self, prepay_id: &str) -> JsonValue {
63 let timestamp = Utc::now().timestamp(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
67
68 let sign_txt = format!(
69 "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
70 self.appid
71 );
72 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
74
75 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
77 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
78 let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
80 signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
82 let signature = signer.sign_to_vec().expect("Failed to sign");
84 let signature_b64 = STANDARD.encode(signature);
85 let sign = signature_b64;
86 object! {
87 timeStamp:timestamp,
88 nonceStr:random_string,
89 package:prepay_id,
90 signType:"RSA",
91 paySign:sign
92 }
93 }
94}
95impl PayMode for Wechat {
96 fn login(&self, code: &str) -> Result<JsonValue, String> {
97 let mut http = br_http::Http::new();
98 match http.get(
99 "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
100 ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
101 appid: self.appid.as_str(),
102 secret: self.secret.as_str(),
103 js_code:code,
104 grant_type:"authorization_code",
105 }).json() {
106 Ok(e) => Ok(e),
107 Err(e) => Err(e),
108 }
109 }
110
111 fn jsapi(
112 &mut self,
113 sub_mchid: &str,
114 out_trade_no: &str,
115 description: &str,
116 total_fee: f64,
117 notify_url: &str,
118 sp_openid: &str,
119 ) -> Result<JsonValue, String> {
120 let url = "/v3/pay/partner/transactions/jsapi";
121 let total = (total_fee * 100.0) as u64;
122 let body = object! {
123 "sp_appid" => self.appid.clone(),
124 "sp_mchid"=> self.mchid.clone(),
125 "sub_mchid"=> sub_mchid,
126 "description"=>description,
127 "out_trade_no"=>out_trade_no,
128 "notify_url"=>notify_url,
129 "support_fapiao"=>true,
130 "amount"=>object! {
131 total: total,
132 currency:"CNY"
133 },
134 "payer"=>object! {
135 sp_openid:sp_openid
136 }
137 };
138 let sign = self.sign("POST", url, body.to_string().as_str());
139 let mut http = br_http::Http::new();
140 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() {
141 Ok(e) => {
142 if e.has_key("prepay_id") {
143 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str());
144 Ok(signinfo)
145 } else {
146 Err(e["message"].to_string())
147 }
148 }
149 Err(e) => Err(e),
150 }
151 }
152
153 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
154 let url = format!(
155 "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
156 out_trade_no, sub_mchid, self.mchid
157 );
158 let sign = self.sign("GET", url.as_str(), "");
159 let mut http = br_http::Http::new();
160 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() {
161 Ok(e) => {
162 if e.has_key("message") {
163 return Err(e["message"].to_string());
164 }
165
166 let mut pay_time = 0.0;
167 if e.has_key("success_time") {
168 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
169 if !success_time.is_empty() {
170 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
171 pay_time = datetime.timestamp() as f64;
172 }
173 }
174
175 let mut info = object! {
176 pay_time:pay_time,
177 sp_appid:e["sp_appid"].clone(),
178 transaction_id:e["transaction_id"].clone(),
179 sp_mchid:e["sp_mchid"].clone(),
180 sub_mchid:e["sub_mchid"].clone(),
181 order_no:e["out_trade_no"].clone(),
182 status:"",
183 sp_openid:e["payer"]["sp_openid"].clone(),
184 sub_openid:e["payer"]["sub_openid"].clone(),
185 amount_currency:e["amount"]["currency"].clone(),
186 amount_total:e["amount"]["total"].clone(),
187 trade_state_desc:e["trade_state_desc"].clone(),
188 trade_type:e["trade_type"].clone(),
189 };
190 info["status"] = match e["trade_state"].as_str().unwrap() {
191 "SUCCESS" => "已支付",
192 "REFUND" => "退款",
193 _ => e["trade_state"].as_str().unwrap(),
194 }.into();
195 Ok(info)
196 }
197 Err(e) => Err(e),
198 }
199 }
200
201 fn refund(
202 &mut self,
203 sub_mchid: &str,
204 out_trade_no: &str,
205 transaction_id: &str,
206 out_refund_no: &str,
207 refund: f64,
208 total: f64,
209 currency: &str,
210 ) -> Result<JsonValue, String> {
211 let url = "/v3/refund/domestic/refunds";
212 let refund = (refund * 100.0) as u64;
213 let total = (total * 100.0) as u64;
214 let body = object! {
215 "sub_mchid"=> sub_mchid,
216 "transaction_id"=>transaction_id,
217 "out_trade_no"=>out_trade_no,
218 "out_refund_no"=>out_refund_no,
219 "amount"=>object! {
220 refund: refund,
221 total: total,
222 currency:currency
223 }
224 };
225 let sign = self.sign("POST", url, body.to_string().as_str());
226 let mut http = br_http::Http::new();
227 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() {
228 Ok(e) => {
229 if e.has_key("message") {
230 return Err(e["message"].to_string());
231 }
232 let mut refund_time = 0.0;
233 if e.has_key("success_time") {
234 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
235 if !success_time.is_empty() {
236 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
237 refund_time = datetime.timestamp() as f64;
238 }
239 }
240
241 let status = match e["status"].as_str().unwrap() {
242 "PROCESSING" => "退款中",
243 "SUCCESS" => "已退款",
244 _ => "无退款",
245 };
246 let info = object! {
247 refund_id: e["refund_id"].clone(),
248 user_received_account:e["user_received_account"].clone(),
249 status:status,
250 refund_time:refund_time,
251 out_refund_no: e["out_refund_no"].clone(),
252 };
253 Ok(info)
254 }
255 Err(e) => Err(e),
256 }
257 }
258
259 fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
260 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
261 let cipher = Aes256Gcm::new(key);
262 let nonce = Nonce::from_slice(nonce.as_bytes());
263 let data = match STANDARD.decode(ciphertext) {
264 Ok(e) => e,
265 Err(e) => return Err(format!("Invalid data received from API :{}", e))
266 };
267 let payload = Payload {
269 msg: &data,
270 aad: associated_data.as_bytes(),
271 };
272
273 let plaintext = match cipher.decrypt(nonce, payload) {
275 Ok(e) => e,
276 Err(e) => {
277 return Err(format!("解密 API:{}", e));
278 }
279 };
280 let rr = match String::from_utf8(plaintext) {
281 Ok(d) => d,
282 Err(_) => return Err("utf8 error".to_string())
283 };
284 let json = match json::parse(rr.as_str()) {
285 Ok(e) => e,
286 Err(_) => return Err("json error".to_string())
287 };
288 let res = PayNotify {
289 trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
290 out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
291 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
292 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
293 sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
294 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
295 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
296 sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
297 sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
298 total: json["amount"]["total"].as_u64().unwrap() as usize,
299 payer_total: json["amount"]["payer_total"].as_u64().unwrap() as usize,
300 currency: json["amount"]["currency"].to_string(),
301 payer_currency: json["amount"]["payer_currency"].to_string(),
302 trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
303 };
304 Ok(res.json())
305 }
306
307 fn refund_query(&mut self, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
308 let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
309 let sign = self.sign("GET", url.as_str(), "");
310 let mut http = br_http::Http::new();
311 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() {
312 Ok(e) => {
313 if e.has_key("message") {
314 return Err(e["message"].to_string());
315 }
316
317 let mut refund_time = 0.0;
318 if e.has_key("success_time") {
319 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
320 if !success_time.is_empty() {
321 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
322 refund_time = datetime.timestamp() as f64;
323 }
324 }
325 let mut info = object! {
326 refund_time:refund_time,
327 refund_id:e["refund_id"].clone(),
328 refund_no: e["out_refund_no"].clone(),
329 order_no:e["out_trade_no"].clone(),
330 refund_amount: e["amount"]["refund"].clone(),
331 status:"",
332 transaction_id:e["transaction_id"].clone(),
333 amount_currency:e["amount"]["currency"].clone(),
334 amount_total:e["amount"]["total"].clone(),
335 note:e["user_received_account"].clone(),
336 };
337 info["status"] = match e["status"].as_str().unwrap() {
338 "SUCCESS" => "已退款",
339 "PROCESSING" => "退款中",
340 _ => e["status"].as_str().unwrap(),
341 }.into();
342 Ok(info)
343 }
344 Err(e) => Err(e),
345 }
346 }
347 fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
348 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
349 let cipher = Aes256Gcm::new(key);
350 let nonce = Nonce::from_slice(nonce.as_bytes());
351 let data = match STANDARD.decode(ciphertext) {
352 Ok(e) => e,
353 Err(e) => return Err(format!("Invalid data received from API :{}", e))
354 };
355 let payload = Payload {
357 msg: &data,
358 aad: associated_data.as_bytes(),
359 };
360
361 let plaintext = match cipher.decrypt(nonce, payload) {
363 Ok(e) => e,
364 Err(e) => {
365 return Err(format!("解密 API:{}", e));
366 }
367 };
368 let rr = match String::from_utf8(plaintext) {
369 Ok(d) => d,
370 Err(_) => return Err("utf8 error".to_string())
371 };
372 let json = match json::parse(rr.as_str()) {
373 Ok(e) => e,
374 Err(_) => return Err("json error".to_string())
375 };
376 let res = RefundNotify {
377 out_trade_no: json["out_trade_no"].to_string(),
378 refund_no: json["out_refund_no"].to_string(),
379 refund_id: json["refund_id"].to_string(),
380 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
381 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
382 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
383 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
384 total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
385 refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
386 payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
387 payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
388 status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
389 };
390 Ok(res.json())
391 }
392}