1use br_reqwest::Client;
2use std::collections::{HashMap};
3use base64::{Engine};
4use base64::engine::general_purpose::STANDARD;
5use chrono::{Local};
6use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
7use json::{array, object, JsonValue};
8use openssl::hash::MessageDigest;
9use openssl::pkey::{PKey};
10use openssl::rsa::Rsa;
11use openssl::sign::{Signer};
12use urlencoding::{encode};
13
14#[derive(Clone, Debug)]
15pub struct AliPay {
16 pub appid: String,
18 pub sp_mchid: String,
20 pub app_auth_token: String,
22 pub app_private: String,
24 pub content_encryp: String,
26 pub alipay_public_key: String,
28 pub notify_url: String,
29 pub appid_mini: String,
31}
32impl AliPay {
33 pub fn sign(&mut self, txt: &str) -> Result<JsonValue, String> {
34 let t = self.app_private.as_bytes().chunks(64).map(|chunk| std::str::from_utf8(chunk).unwrap_or("")).collect::<Vec<&str>>().join("\n");
35 if t.is_empty() {
36 return Err("No pem".to_string());
37 }
38 let cart = format!("-----BEGIN PRIVATE KEY-----\n{t}\n-----END PRIVATE KEY-----\n");
39
40 let rsa = match Rsa::private_key_from_pem(cart.as_bytes()) {
42 Ok(e) => e,
43 Err(e) => return Err(e.to_string())
44 };
45
46 let pkey = match PKey::from_rsa(rsa) {
47 Ok(e) => e,
48 Err(e) => return Err(e.to_string())
49 };
50
51 let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
53 Ok(e) => e,
54 Err(e) => return Err(e.to_string())
55 };
56 match signer.update(txt.as_bytes()) {
57 Ok(()) => {}
58 Err(e) => return Err(e.to_string())
59 };
60 let signature = match signer.sign_to_vec() {
62 Ok(e) => e,
63 Err(e) => return Err(e.to_string())
64 };
65 Ok(STANDARD.encode(signature).into())
67 }
68 pub fn http(&mut self, method: &str, biz_content: JsonValue) -> Result<JsonValue, String> {
69 let mut http = Client::new();
70 let sign = "";
72
73 let now = Local::now();
74 let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
75
76 let mut data = object! {
77 "charset":"UTF-8",
78 "method":method,
79 "app_id":self.appid.clone(),
80 "app_private_key":self.app_private.clone(),
81 "version":"1.0",
82 "sign_type":"RSA2",
83 "timestamp":timestamp,
84 "alipay_public_key":self.alipay_public_key.clone(),
85 "sign":sign
86 };
87 if !self.app_auth_token.is_empty() {
88 data["app_auth_token"] = self.app_auth_token.clone().into();
89 }
90 if method.contains("alipay.trade.") {
91 data["notify_url"] = self.notify_url.clone().into();
92 }
93 for (key, value) in biz_content.entries() {
94 data[key] = value.clone()
95 }
96 let mut map = HashMap::new();
97 for (key, value) in data.entries() {
98 if key == "sign" {
99 continue;
100 }
101 if value.is_empty() {
102 continue;
103 }
104 map.insert(key, value);
105 }
106
107 let mut keys: Vec<_> = map.keys().cloned().collect();
108 keys.sort();
109 let mut txt = vec![];
110 for key in keys {
111 txt.push(format!("{}={}", key, map.get(&key).unwrap()));
112 }
113 let txt = txt.join("&");
114 data["sign"] = self.sign(&txt)?;
115
116 let mut new_data = object! {};
117 for (key, value) in data.entries() {
118 let t = encode(value.to_string().as_str()).to_string();
119 new_data[key] = t.into();
120 }
121 data = new_data;
122 let url = "https://openapi.alipay.com/gateway.do".to_string();
123 let res = match method {
124 "alipay.trade.wap.pay" => {
125 let tt = http.get(url.as_str()).query(data);
126 return Ok(tt.url.clone().into());
127 }
128 _ => {
129 match http.get(&url).query(data).form_data(biz_content).send() {
130 Ok(e) => e,
131 Err(e) => return Err(e.to_string())
132 }
133 }
134 };
135
136 let res = res.json()?;
137 if res.has_key("error_response") {
138 return Err(res["error_response"]["sub_msg"].to_string());
139 }
140 let key = method.replace(".", "_");
141 let key = format!("{key}_response");
142 let data = res[key].clone();
143 if data.has_key("code") {
144 if data["code"] != "10000" {
145 Err(data["sub_msg"].to_string())
146 } else {
147 Ok(data)
148 }
149 } else {
150 Err(data.to_string())
151 }
152 }
153 pub fn https(&mut self, method: &str, biz_content: JsonValue) -> Result<JsonValue, String> {
154 let mut http = Client::new();
155 let sign = "";
157
158 let now = Local::now();
159 let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
160
161 let mut data = object! {
162 "charset":"UTF-8",
163 "method":method,
164 "app_id":self.appid.clone(),
165 "app_private_key":self.app_private.clone(),
166 "version":"1.0",
167 "sign_type":"RSA2",
168 "timestamp":timestamp,
169 "alipay_public_key":self.alipay_public_key.clone(),
170 "sign":sign
171 };
172 if !self.app_auth_token.is_empty() {
173 data["app_auth_token"] = self.app_auth_token.clone().into();
174 }
175 if method.contains("alipay.trade.") {
176 data["notify_url"] = self.notify_url.clone().into();
177 }
178 for (key, value) in biz_content.entries() {
179 data[key] = value.clone()
180 }
181 let mut map = HashMap::new();
182 for (key, value) in data.entries() {
183 if key == "sign" {
184 continue;
185 }
186 if value.is_empty() {
187 continue;
188 }
189 map.insert(key, value);
190 }
191
192 let mut keys: Vec<_> = map.keys().cloned().collect();
193 keys.sort();
194 let mut txt = vec![];
195 for key in keys {
196 txt.push(format!("{}={}", key, map.get(&key).unwrap()));
197 }
198 let txt = txt.join("&");
199 data["sign"] = self.sign(&txt)?;
200
201 let mut new_data = object! {};
202 for (key, value) in data.entries() {
203 let t = encode(value.to_string().as_str()).to_string();
204 new_data[key] = t.into();
205 }
206 data = new_data;
207 let url = "https://openapi.alipay.com/gateway.do".to_string();
208 let res = match method {
209 "alipay.trade.wap.pay" => {
210 let tt = http.get(url.as_str()).query(data);
211 return Ok(tt.url.clone().into());
212 }
213 _ => {
214 match http.get(&url).query(data).form_data(biz_content).send() {
215 Ok(e) => e,
216 Err(e) => return Err(e.to_string())
217 }
218 }
219 };
220
221 let res = res.json()?;
222 if res.has_key("error_response") {
223 return Err(res["error_response"]["sub_msg"].to_string());
224 }
225 let key = method.replace(".", "_");
226 let key = format!("{key}_response");
227 let data = res[key].clone();
228 Ok(data)
229 }
230}
231impl PayMode for AliPay {
232 fn check(&mut self) -> Result<bool, String> {
233 let biz_content = object! {
234 "biz_content":{
235 "grant_type":"authorization_code",
236 "code":"123456"
237 }
238 };
239 match self.http("alipay.open.auth.token.app", biz_content) {
240 Ok(e) => e,
241 Err(e) => {
242 if e.contains("auth_code不存在") {
243 return Ok(true);
244 }
245 return Err(e);
246 }
247 };
248 Ok(true)
249 }
250
251 fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
252 let res = self.https("alipay.open.agent.signstatus.query", object! {
253 "biz_content":{
254 "pid":sub_mchid,
255 "product_codes":array!["QUICK_WAP_WAY"]
256 }
257 })?;
258 if !res["code"].eq("10000") {
259 return Err(res["msg"].to_string());
260 }
261 for item in res["sign_status_list"].members() {
262 if item["status"].eq("none") {
263 return Err(format!("{} 未开通", res["product_name"]));
264 }
265 }
266 Ok(true.into())
267 }
268
269
270 fn config(&mut self) -> JsonValue {
271 todo!()
272 }
273
274
275 fn pay(&mut self, _channel: &str, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
276 let mut api = "";
277 let mut order = object! {
278 out_trade_no:out_trade_no,
279 total_amount:total_fee,
280 subject:description,
281 product_code:"JSAPI_PAY",
282 op_app_id:sub_mchid,
283 buyer_open_id:""
284 };
285 match types {
286 Types::MiniJsapi => {
287 api = "alipay.trade.create";
288 order["product_code"] = "JSAPI_PAY".into();
289 order["op_app_id"] = self.appid_mini.clone().into();
290 if sp_openid.starts_with("2088") {
291 order["buyer_id"] = sp_openid.into();
292 } else {
293 order["op_buyer_open_id"] = sp_openid.into();
294 }
295 }
296 Types::Jsapi => {
297 api = "alipay.trade.wap.pay";
298 order["product_code"] = "QUICK_WAP_WAY".into();
299 }
300 Types::H5 => {
301 api = "alipay.trade.wap.pay";
302 order["product_code"] = "QUICK_WAP_WAY".into();
303 }
304 Types::Native => {
305 api = "alipay.trade.wap.pay";
306 order["product_code"] = "QUICK_WAP_WAY".into();
307 }
308 _ => {
309 order["product_code"] = "JSAPI_PAY".into();
310 }
311 };
312 match self.http(api, object! {"biz_content":order}) {
313 Ok(e) => {
314 match types {
315 Types::Native => {}
316 Types::Jsapi | Types::H5 => {
317 return Ok(object! {url:e});
318 }
319 Types::MiniJsapi => {
320 return Ok(e);
321 }
322 Types::App => {}
323 Types::Micropay => {}
324 }
325 Ok(e)
326 }
327 Err(e) => {
328 if e == "ACCESS_FORBIDDEN" {
329 return Err("请商户授权JSAPI 绑定 服务商小程序APPID".to_string());
330 }
331 println!("Err: {e:#}");
332 Err(e)
333 }
334 }
335 }
336
337 fn micropay(&mut self, _channel: &str, auth_code: &str, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, org_openid: &str, _ip: &str) -> Result<JsonValue, String> {
338 let order = object! {
339 out_trade_no:out_trade_no,
340 total_amount:total_fee,
341 subject:description,
342 seller_id:sub_mchid,
343 auth_code:auth_code,
344 scene:"bar_code",
345 operator_id:org_openid,
346 };
347 match self.http("alipay.trade.pay", object! {"biz_content":order}) {
348 Ok(e) => {
349 println!("alipay.trade.pay:{e:#}");
350 if e["code"].ne("10000") {
351 return Err(e["msg"].to_string());
352 }
353 if e["msg"].eq("FAIL") {
354 if e["err_code_des"].ne("需要用户输入支付密码") {
355 return Err(e["err_code_des"].to_string());
356 }
357 let res = PayNotify {
358 trade_type: TradeType::MICROPAY,
359 out_trade_no: out_trade_no.to_string(),
360 sp_mchid: self.sp_mchid.clone(),
361 sub_mchid: sub_mchid.to_string(),
362 sp_appid: self.appid.to_string(),
363 transaction_id: "".to_string(),
364 success_time: 0,
365 sp_openid: "".to_string(),
366 sub_openid: "".to_string(),
367 total: total_fee,
368 payer_total: total_fee,
369 currency: "CNY".to_string(),
370 payer_currency: "CNY".to_string(),
371 trade_state: TradeState::NOTPAY,
372 };
373 return Ok(res.json());
374 }
375 let res = PayNotify {
376 trade_type: TradeType::MICROPAY,
377 out_trade_no: out_trade_no.to_string(),
378 sp_mchid: self.sp_mchid.clone(),
379 sub_mchid: sub_mchid.to_string(),
380 sp_appid: self.appid.to_string(),
381 transaction_id: e["trade_no"].to_string(),
382 success_time: PayNotify::datetime_to_timestamp(e["gmt_payment"].as_str().unwrap(), "%Y-%m-%d %H:%M:%S"),
383 sp_openid: e["buyer_open_id"].to_string(),
384 sub_openid: e["buyer_open_id"].to_string(),
385 total: total_fee,
386 payer_total: total_fee,
387 currency: "CNY".to_string(),
388 payer_currency: "CNY".to_string(),
389 trade_state: TradeState::SUCCESS,
390 };
391 Ok(res.json())
392 }
393 Err(e) => Err(e)
394 }
395 }
396
397
398 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
399 let order = object! {
400 "biz_content"=> object! {
401 out_trade_no:out_trade_no,
402 operator_id:sub_mchid
403 }
404 };
405 match self.http("alipay.trade.close", order) {
406 Ok(_) => {
407 Ok(true.into())
408 }
409 Err(e) => {
410 if e.contains("交易不存在") {
411 return Ok(true.into());
412 }
413 Err(e)
414 }
415 }
416 }
417
418 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
419 let order = object! {
420 "biz_content"=> object! {
421 out_trade_no:out_trade_no
422 }
423 };
424 match self.http("alipay.trade.query", order) {
425 Ok(e) => {
426 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
427 return Err(e["msg"].to_string());
428 }
429 let buyer_open_id = if e.has_key("buyer_open_id") {
430 e["buyer_open_id"].to_string()
431 } else {
432 e["buyer_user_id"].to_string()
433 };
434 let send_pay_date = e["send_pay_date"].as_str().unwrap_or("");
435 let success_time = if !send_pay_date.is_empty() {
436 PayNotify::datetime_to_timestamp(send_pay_date, "%Y-%m-%d %H:%M:%S")
437 } else {
438 0
439 };
440 let res = PayNotify {
441 trade_type: TradeType::None,
442 out_trade_no: e["out_trade_no"].to_string(),
443 sp_mchid: "".to_string(),
444 sub_mchid: sub_mchid.to_string(),
445 sp_appid: "".to_string(),
446 transaction_id: e["trade_no"].to_string(),
447 success_time,
448 sp_openid: buyer_open_id.clone(),
449 sub_openid: buyer_open_id.clone(),
450 total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0)),
451 payer_total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0)),
452 currency: "CNY".to_string(),
453 payer_currency: "CNY".to_string(),
454 trade_state: TradeState::from(e["trade_status"].as_str().unwrap()),
455 };
456 Ok(res.json())
457 }
458 Err(e) => Err(e)
459 }
460 }
461
462 fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str, _channel: &str) -> Result<JsonValue, String> {
463 todo!()
464 }
465
466 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
467 todo!()
468 }
469
470 fn refund(&mut self, sub_mchid: &str, out_trade_no: &str, transaction_id: &str, out_refund_no: &str, amount: f64, total: f64, _currency: &str) -> Result<JsonValue, String> {
471 let body = object! {
472 "biz_content"=> object! {
473 "trade_no"=>transaction_id,
474 "out_trade_no"=>out_trade_no,
475 "out_request_no"=>out_refund_no,
476 "refund_amount"=>format!("{:.2}",amount),
477 }
478 };
479 match self.http("alipay.trade.refund", body.clone()) {
480 Ok(e) => {
481 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
482 return Err(e["msg"].to_string());
483 }
484 let res = RefundNotify {
485 out_trade_no: e["out_trade_no"].to_string(),
486 refund_no: out_refund_no.to_string(),
487 sp_mchid: "".to_string(),
488 sub_mchid: sub_mchid.to_string(),
489 transaction_id: e["trade_no"].to_string(),
490 refund_id: out_refund_no.to_string(),
491 success_time: PayNotify::datetime_to_timestamp(e["gmt_refund_pay"].as_str().unwrap(), "%Y-%m-%d %H:%M:%S"),
492 total,
493 refund: e["refund_fee"].to_string().parse::<f64>().unwrap(),
494 payer_total: e["refund_fee"].to_string().parse::<f64>().unwrap(),
495 payer_refund: e["send_back_fee"].to_string().parse::<f64>().unwrap(),
496 status: RefundStatus::from(e["fund_change"].as_str().unwrap()),
497 };
498 Ok(res.json())
499 }
500 Err(e) => Err(e)
501 }
502 }
503
504 fn micropay_refund(&mut self, _sub_mchid: &str, _out_trade_no: &str, _transaction_id: &str, _out_refund_no: &str, _amount: f64, _total: f64, _currency: &str, _refund_text: &str) -> Result<JsonValue, String> {
505 todo!()
506 }
507
508 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
509 todo!()
510 }
511
512 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
513 let body = object! {
514 "biz_content"=> object! {
515 "out_request_no"=>out_refund_no,
516 "trade_no"=>trade_no,
517 }
518 };
519 match self.http("alipay.trade.fastpay.refund.query", body.clone()) {
520 Ok(e) => {
521 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
522 return Err(e["msg"].to_string());
523 }
524 let res = RefundNotify {
525 out_trade_no: e["out_trade_no"].to_string(),
526 refund_no: e["out_request_no"].to_string(),
527 sp_mchid: "".to_string(),
528 sub_mchid: sub_mchid.to_string(),
529 transaction_id: e["trade_no"].to_string(),
530 refund_id: e["out_request_no"].to_string(),
531 success_time: Local::now().timestamp(),
532 total: e["total_amount"].to_string().parse::<f64>().unwrap(),
533 payer_total: e["total_amount"].to_string().parse::<f64>().unwrap(),
534 refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
535 payer_refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
536 status: RefundStatus::from(e["refund_status"].as_str().unwrap()),
537 };
538 Ok(res.json())
539 }
540 Err(e) => Err(e)
541 }
542 }
543
544 fn incoming(&mut self, _business_code: &str, _contact_info: JsonValue, _subject_info: JsonValue, _business_info: JsonValue, _settlement_info: JsonValue, _bank_account_info: JsonValue) -> Result<JsonValue, String> {
545 todo!()
546 }
547}