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 pub appid: String,
12 pub secret: String,
14 pub mchid: String,
16 pub serial_no: String,
18 pub app_private: String,
20 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(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
38
39 let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
40 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 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 match signer.update(sign_txt.as_bytes()) {
62 Ok(_) => {}
63 Err(e) => {
64 return Err(e.to_string())
65 }
66 };
67 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(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .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 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 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 match signer.update(sign_txt.as_bytes()) {
116 Ok(_) => {}
117 Err(e) => {
118 return Err(e.to_string())
119 }
120 };
121 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 let payload = Payload {
322 msg: &data,
323 aad: associated_data.as_bytes(),
324 };
325
326 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.has_key("message") {
367 return Err(e["message"].to_string());
368 }
369
370 let mut refund_time = 0.0;
371 if e.has_key("success_time") {
372 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
373 if !success_time.is_empty() {
374 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
375 refund_time = datetime.timestamp() as f64;
376 }
377 }
378 let mut info = object! {
379 refund_time:refund_time,
380 refund_id:e["refund_id"].clone(),
381 refund_no: e["out_refund_no"].clone(),
382 order_no:e["out_trade_no"].clone(),
383 refund_amount: e["amount"]["refund"].clone(),
384 status:"",
385 transaction_id:e["transaction_id"].clone(),
386 amount_currency:e["amount"]["currency"].clone(),
387 amount_total:e["amount"]["total"].clone(),
388 note:e["user_received_account"].clone(),
389 };
390 info["status"] = match e["status"].as_str().unwrap() {
391 "SUCCESS" => "已退款",
392 "PROCESSING" => "退款中",
393 _ => e["status"].as_str().unwrap(),
394 }.into();
395 Ok(info)
396 }
397 Err(e) => Err(e),
398 }
399 }
400 fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
401 if self.apikey.is_empty() {
402 return Err("apikey 不能为空".to_string());
403 }
404 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
405 let cipher = Aes256Gcm::new(key);
406 let nonce = Nonce::from_slice(nonce.as_bytes());
407 let data = match STANDARD.decode(ciphertext) {
408 Ok(e) => e,
409 Err(e) => return Err(format!("Invalid data received from API :{}", e))
410 };
411 let payload = Payload {
413 msg: &data,
414 aad: associated_data.as_bytes(),
415 };
416
417 let plaintext = match cipher.decrypt(nonce, payload) {
419 Ok(e) => e,
420 Err(e) => {
421 return Err(format!("解密 API:{}", e));
422 }
423 };
424 let rr = match String::from_utf8(plaintext) {
425 Ok(d) => d,
426 Err(_) => return Err("utf8 error".to_string())
427 };
428 let json = match json::parse(rr.as_str()) {
429 Ok(e) => e,
430 Err(_) => return Err("json error".to_string())
431 };
432 let res = RefundNotify {
433 out_trade_no: json["out_trade_no"].to_string(),
434 refund_no: json["out_refund_no"].to_string(),
435 refund_id: json["refund_id"].to_string(),
436 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
437 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
438 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
439 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
440 total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
441 refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
442 payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
443 payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
444 status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
445 };
446 Ok(res.json())
447 }
448
449 fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
450 todo!()
451 }
452
453 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
454 let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
455 let body = object! {
456 "sp_mchid"=> self.mchid.clone(),
457 "sub_mchid"=> sub_mchid
458 };
459 let sign = self.sign("POST", url.as_str(), body.to_string().as_str())?;
460 let mut http = br_http::Http::new();
461 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() {
462 Ok(_) => Ok(true.into()),
463 Err(e) => Err(e)
464 }
465 }
466
467 fn config(&mut self) -> JsonValue {
468 todo!()
469 }
470}