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};
7
8#[derive(Clone)]
9pub struct Wechat {
10 pub appid: String,
12 pub secret: String,
14 pub sp_mchid: String,
16 pub serial_no: String,
18 pub app_private: String,
20 pub apikey: String,
22 pub notify_url: 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) -> Result<String, 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 rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
43 Ok(e) => e,
44 Err(e) => {
45 return Err(e.to_string())
46 }
47 };
48 let pkey = match PKey::from_rsa(rsa) {
49 Ok(e) => e,
50 Err(e) => {
51 return Err(format!("Failed to create PKey: {}", e))
52 }
53 };
54 let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
56 Ok(e) => e,
57 Err(e) => {
58 return Err(format!("Failed to create signer:{}", e));
59 }
60 };
61 match signer.update(sign_txt.as_bytes()) {
63 Ok(_) => {}
64 Err(e) => {
65 return Err(e.to_string())
66 }
67 };
68 let signature = match signer.sign_to_vec() {
70 Ok(e) => e,
71 Err(e) => {
72 return Err(format!("Failed to sign: {}", e));
73 }
74 };
75 let signature_b64 = STANDARD.encode(signature);
76 let sign = format!(
77 r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
78 self.sp_mchid.as_str(),
79 self.serial_no
80 );
81 Ok(sign)
82 }
83
84 pub fn paysign(&mut self, prepay_id: &str) -> Result<JsonValue, String> {
85 let timestamp = Utc::now().timestamp(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
89
90 let sign_txt = format!(
91 "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
92 self.appid
93 );
94
95 let rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
97 Ok(e) => e,
98 Err(e) => {
99 return Err(e.to_string())
100 }
101 };
102 let pkey = match PKey::from_rsa(rsa) {
103 Ok(e) => e,
104 Err(e) => {
105 return Err(format!("Failed to create PKey: {}", e))
106 }
107 };
108 let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
110 Ok(e) => e,
111 Err(e) => {
112 return Err(format!("Failed to create signer:{}", e));
113 }
114 };
115 match signer.update(sign_txt.as_bytes()) {
117 Ok(_) => {}
118 Err(e) => {
119 return Err(e.to_string())
120 }
121 };
122 let signature = match signer.sign_to_vec() {
124 Ok(e) => e,
125 Err(e) => {
126 return Err(format!("Failed to sign: {}", e));
127 }
128 };
129 let signature_b64 = STANDARD.encode(signature);
130 let sign = signature_b64;
131 Ok(object! {
132 timeStamp:timestamp,
133 nonceStr:random_string,
134 package:prepay_id,
135 signType:"RSA",
136 paySign:sign
137 })
138 }
139}
140impl PayMode for Wechat {
141 fn login(&mut self, code: &str) -> Result<JsonValue, String> {
142 let mut http = br_reqwest::Client::new();
143 match http.get(
144 "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
145 ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
146 appid: self.appid.as_str(),
147 secret: self.secret.as_str(),
148 js_code:code,
149 grant_type:"authorization_code",
150 }).send()?.json() {
151 Ok(e) => Ok(e),
152 Err(e) => Err(e),
153 }
154 }
155
156 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
157 let url = format!(
158 "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
159 out_trade_no, sub_mchid, self.sp_mchid
160 );
161 let sign = self.sign("GET", url.as_str(), "")?;
162 let mut http = br_reqwest::Client::new();
163 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() {
164 Ok(e) => {
165 if e.has_key("message") {
166 return Err(e["message"].to_string());
167 }
168 let res = PayNotify {
169 trade_type: TradeType::from(e["trade_type"].as_str().unwrap()),
170 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
171 sp_mchid: e["sp_mchid"].as_str().unwrap().to_string(),
172 sub_mchid: e["sub_mchid"].as_str().unwrap().to_string(),
173 sp_appid: e["sp_appid"].as_str().unwrap().to_string(),
174 transaction_id: e["transaction_id"].as_str().unwrap().to_string(),
175 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
176 sp_openid: e["payer"]["sp_openid"].as_str().unwrap().to_string(),
177 sub_openid: e["payer"]["sub_openid"].as_str().unwrap().to_string(),
178 total: e["amount"]["total"].as_u64().unwrap() as usize,
179 currency: e["amount"]["currency"].to_string(),
180 payer_total: e["amount"]["payer_total"].as_u64().unwrap() as usize,
181 payer_currency: e["amount"]["payer_currency"].to_string(),
182 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
183 };
184
185 Ok(res.json())
186 }
187 Err(e) => Err(e),
188 }
189 }
190
191 fn refund(
192 &mut self,
193 sub_mchid: &str,
194 out_trade_no: &str,
195 transaction_id: &str,
196 out_refund_no: &str,
197 amount: f64,
198 total: f64,
199 currency: &str,
200 ) -> Result<JsonValue, String> {
201 let url = "/v3/refund/domestic/refunds";
202
203 let refund = format!("{:.0}", amount * 100.0);
204 let total = format!("{:.0}", total * 100.0);
205
206 let body = object! {
207 "sub_mchid"=> sub_mchid,
208 "transaction_id"=>transaction_id,
209 "out_trade_no"=>out_trade_no,
210 "out_refund_no"=>out_refund_no,
211 "amount"=>object! {
212 refund: refund.parse::<i64>().unwrap(),
213 total: total.parse::<i64>().unwrap(),
214 currency:currency
215 }
216 };
217 let sign = self.sign("POST", url, body.to_string().as_str())?;
218 let mut http = br_reqwest::Client::new();
219 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() {
220 Ok(e) => {
221 if e.is_empty() {
222 return Err("已执行".to_string());
223 }
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
254 fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
255 if self.apikey.is_empty() {
256 return Err("apikey 不能为空".to_string());
257 }
258 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
259 let cipher = Aes256Gcm::new(key);
260 let nonce = Nonce::from_slice(nonce.as_bytes());
261 let data = match STANDARD.decode(ciphertext) {
262 Ok(e) => e,
263 Err(e) => return Err(format!("Invalid data received from API :{}", e))
264 };
265 let payload = Payload {
267 msg: &data,
268 aad: associated_data.as_bytes(),
269 };
270
271 let plaintext = match cipher.decrypt(nonce, payload) {
273 Ok(e) => e,
274 Err(e) => {
275 return Err(format!("解密 API:{}", e));
276 }
277 };
278 let rr = match String::from_utf8(plaintext) {
279 Ok(d) => d,
280 Err(_) => return Err("utf8 error".to_string())
281 };
282 let json = match json::parse(rr.as_str()) {
283 Ok(e) => e,
284 Err(_) => return Err("json error".to_string())
285 };
286 let res = PayNotify {
287 trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
288 out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
289 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
290 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
291 sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
292 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
293 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
294 sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
295 sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
296 total: json["amount"]["total"].as_u64().unwrap() as usize,
297 payer_total: json["amount"]["payer_total"].as_u64().unwrap() as usize,
298 currency: json["amount"]["currency"].to_string(),
299 payer_currency: json["amount"]["payer_currency"].to_string(),
300 trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
301 };
302 Ok(res.json())
303 }
304
305 fn refund_query(&mut self, _trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
306 let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
307 let sign = self.sign("GET", url.as_str(), "")?;
308 let mut http = br_reqwest::Client::new();
309 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() {
310 Ok(e) => {
311 if e.is_empty() {
312 return Err("已执行".to_string());
313 }
314 if e.has_key("message") {
315 return Err(e["message"].to_string());
316 }
317
318
319 let res = RefundNotify {
320 out_trade_no: e["out_trade_no"].to_string(),
321 refund_no: e["out_refund_no"].to_string(),
322 sp_mchid: "".to_string(),
323 sub_mchid: sub_mchid.to_string(),
324 transaction_id: e["transaction_id"].to_string(),
325 refund_id: e["refund_id"].to_string(),
326 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
327 total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
328 payer_total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
329 refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
330 payer_refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
331 status: RefundStatus::from(e["status"].as_str().unwrap()),
332 };
333
334 Ok(res.json())
335 }
336 Err(e) => Err(e),
337 }
338 }
339 fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
340 if self.apikey.is_empty() {
341 return Err("apikey 不能为空".to_string());
342 }
343 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
344 let cipher = Aes256Gcm::new(key);
345 let nonce = Nonce::from_slice(nonce.as_bytes());
346 let data = match STANDARD.decode(ciphertext) {
347 Ok(e) => e,
348 Err(e) => return Err(format!("Invalid data received from API :{}", e))
349 };
350 let payload = Payload {
352 msg: &data,
353 aad: associated_data.as_bytes(),
354 };
355
356 let plaintext = match cipher.decrypt(nonce, payload) {
358 Ok(e) => e,
359 Err(e) => {
360 return Err(format!("解密 API:{}", e));
361 }
362 };
363 let rr = match String::from_utf8(plaintext) {
364 Ok(d) => d,
365 Err(_) => return Err("utf8 error".to_string())
366 };
367 let json = match json::parse(rr.as_str()) {
368 Ok(e) => e,
369 Err(_) => return Err("json error".to_string())
370 };
371 let res = RefundNotify {
372 out_trade_no: json["out_trade_no"].to_string(),
373 refund_no: json["out_refund_no"].to_string(),
374 refund_id: json["refund_id"].to_string(),
375 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
376 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
377 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
378 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
379 total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
380 refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
381 payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
382 payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
383 status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
384 };
385 Ok(res.json())
386 }
387
388 fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
389 todo!()
390 }
391
392 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
393 let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
394 let body = object! {
395 "sp_mchid"=> self.sp_mchid.clone(),
396 "sub_mchid"=> sub_mchid
397 };
398 let sign = self.sign("POST", url.as_str(), body.to_string().as_str())?;
399 let mut http = br_reqwest::Client::new();
400 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() {
401 Ok(_) => Ok(true.into()),
402 Err(e) => Err(e)
403 }
404 }
405
406 fn config(&mut self) -> JsonValue {
407 todo!()
408 }
409
410 fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
411 let url = match types {
412 Types::Jsapi => "/v3/pay/partner/transactions/jsapi",
413 Types::Native => "/v3/pay/partner/transactions/native",
414 Types::H5 => "/v3/pay/partner/transactions/h5",
415 Types::MiniJsapi => "/v3/pay/partner/transactions/jsapi",
416 Types::App => "/v3/pay/partner/transactions/app",
417 Types::Micropay => "/pay/micropay"
418 };
419 let total = format!("{:.0}", total_fee * 100.0);
420 let mut body = object! {
421 "sp_appid" => self.appid.clone(),
422 "sp_mchid"=> self.sp_mchid.clone(),
423 "sub_mchid"=> sub_mchid,
424 "description"=>description,
425 "out_trade_no"=>out_trade_no,
426 "notify_url"=>self.notify_url.clone(),
427 "support_fapiao"=>true,
428 "amount"=>object! {
429 total: total.parse::<i64>().unwrap(),
430 currency:"CNY"
431 }
432 };
433 match types {
434 Types::Native => {}
435 _ => {
436 body["payer"] = object! {
437 sp_openid:sp_openid
438 };
439 }
440 };
441
442 let sign = self.sign("POST", url, body.to_string().as_str())?;
443 let mut http = br_reqwest::Client::new();
444 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() {
445 Ok(e) => {
446 match types {
447 Types::Native => {
448 if e.has_key("code_url") {
449 Ok(e["code_url"].clone())
450 } else {
451 Err(e["message"].to_string())
452 }
453 }
454 Types::Jsapi | Types::MiniJsapi => {
455 if e.has_key("prepay_id") {
456 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str())?;
457 Ok(signinfo)
458 } else {
459 Err(e["message"].to_string())
460 }
461 }
462 _ => {
463 Ok(e)
464 }
465 }
466 }
467 Err(e) => Err(e),
468 }
469 }
470
471 fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
472 todo!()
473 }
474}