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 pub appid: String,
13 pub secret: String,
15 pub sp_mchid: String,
17 pub serial_no: String,
19 pub app_private: String,
21 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(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
54
55 let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
56 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 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 match signer.update(sign_txt.as_bytes()) {
78 Ok(_) => {}
79 Err(e) => {
80 return Err(e.to_string())
81 }
82 };
83 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(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .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 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 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 match signer.update(sign_txt.as_bytes()) {
132 Ok(_) => {}
133 Err(e) => {
134 return Err(e.to_string())
135 }
136 };
137 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 let payload = Payload {
282 msg: &data,
283 aad: associated_data.as_bytes(),
284 };
285
286 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 let payload = Payload {
367 msg: &data,
368 aad: associated_data.as_bytes(),
369 };
370
371 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}