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