1use std::collections::{HashMap};
2use base64::Engine;
3use base64::engine::general_purpose;
4use chrono::{Local};
5use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
6use json::{object, JsonValue};
7use openssl::hash::MessageDigest;
8use openssl::pkey::{PKey};
9use openssl::rsa::Rsa;
10use openssl::sign::Signer;
11use urlencoding::encode;
12use log::error;
13
14#[derive(Clone)]
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 http(&mut self, method: &str, biz_content: JsonValue) -> Result<JsonValue, String> {
32 let mut http = br_http::Http::new();
33 let sign = "";
34
35 let now = Local::now();
36 let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
37
38
39 let mut data = object! {
40 "charset":"UTF-8",
41 "method":method,
42 "app_id":self.appid.clone(),
43 "version":"1.0",
44 "sign_type":"RSA2",
45 "timestamp":timestamp,
46 "alipay_public_key":self.alipay_public_key.clone(),
47 "app_auth_token":self.app_auth_token.clone(),
48 "sign":sign,
49 "biz_content": biz_content,
50 };
51
52 let mut map = HashMap::new();
53 for (key, value) in data.entries() {
54 if key == "sign" {
55 continue;
56 }
57 if value.is_empty() {
58 continue;
59 }
60 map.insert(key, value);
61 }
62
63 let mut keys: Vec<_> = map.keys().cloned().collect();
64 keys.sort();
65 let mut txt = vec![];
66 for key in keys {
67 txt.push(format!("{}={}", key, map.get(&key).unwrap()));
68 }
69 let txt = txt.join("&");
70 let t = self.app_private.as_bytes().chunks(64).map(|chunk| std::str::from_utf8(chunk).unwrap_or("")).collect::<Vec<&str>>().join("\n");
71 let cart = format!("-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----\n", t);
72
73 let rsa = match Rsa::private_key_from_pem(cart.as_bytes()) {
75 Ok(e) => e,
76 Err(e) => return Err(e.to_string())
77 };
78
79 let pkey = match PKey::from_rsa(rsa) {
80 Ok(e) => e,
81 Err(e) => return Err(e.to_string())
82 };
83
84 let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
86 Ok(e) => e,
87 Err(e) => return Err(e.to_string())
88 };
89 match signer.update(txt.as_bytes()) {
90 Ok(()) => {}
91 Err(e) => return Err(e.to_string())
92 };
93 let signature = match signer.sign_to_vec() {
95 Ok(e) => e,
96 Err(e) => return Err(e.to_string())
97 };
98 data["sign"] = general_purpose::STANDARD.encode(signature).into();
100
101 let mut new_data = object! {};
102 for (key, value) in data.entries() {
103 let t = encode(value.to_string().as_str()).to_string();
104 new_data[key] = t.into();
105 }
106 data = new_data;
107 let url = "https://openapi.alipay.com/gateway.do".to_string();
108 let res = match method {
109 "alipay.trade.wap.pay" => {
110 let tt = http.get(url.as_str()).query(data);
111 return Ok(tt.url.clone().into());
112 }
122 _ => {
123 match http.post(url.as_str()).query(data).json() {
124 Ok(e) => e,
125 Err(e) => {
126 println!(">>>>>>{}", e);
127 return Err(e);
128 }
129 }
130 }
131 };
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!("{}_response", key);
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}
149impl PayMode for AliPay {
150 fn config(&mut self) -> JsonValue {
151 todo!()
152 }
153
154 fn login(&mut self, code: &str) -> Result<JsonValue, String> {
155 let res = self.http("alipay.system.oauth.token", object! {
156 code:code
158 })?;
159 Ok(res)
160 }
161
162 fn auth(&mut self, code: &str) -> Result<JsonValue, String> {
163 let res = match self.http("alipay.open.auth.token.app", object! {
164 grant_type:"authorization_code",
165 code:code
166 }) {
167 Ok(e) => e,
168 Err(e) => {
169 error!("Err: {:#}", e);
170 return Err(e);
171 }
172 };
173 Ok(res)
179 }
180
181 fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
182 let mut api = "";
183 let mut order = object! {
184 out_trade_no:out_trade_no,
185 total_amount:total_fee,
186 subject:description,
187 product_code:"",
188 op_app_id:sub_mchid,
189 buyer_open_id:sp_openid
190 };
191
192 match types {
193 Types::Jsapi => {
194 api = "alipay.trade.create";
195 order["product_code"] = "JSAPI_PAY".into();
196 }
197 Types::H5 => {
198 api = "alipay.trade.wap.pay";
199 order["product_code"] = "QUICK_WAP_WAY".into();
200 }
201 Types::Native => {
202 api = "alipay.trade.wap.pay";
203 order["product_code"] = "QUICK_WAP_WAY".into();
204 }
205 _ => {
206 order["product_code"] = "JSAPI_PAY".into();
207 }
208 };
209
210 match self.http(api, order) {
211 Ok(e) => {
212 match types {
213 Types::Jsapi => {}
214 Types::Native => {}
215 Types::H5 => {
216 return Ok(object! {url:e});
217 }
218 Types::MiniJsapi => {}
219 Types::App => {}
220 Types::Micropay => {}
221 }
222 Ok(e)
223 }
224 Err(e) => {
225 println!("Err: {:#}", e);
226 Err(e)
227 }
228 }
229 }
230
231
232 fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
233 todo!()
234 }
235
236 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
237 let order = object! {
238 out_trade_no:out_trade_no
239 };
240 match self.http("alipay.trade.query", order) {
241 Ok(e) => {
242 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
243 return Err(e["msg"].to_string());
244 }
245 let res = PayNotify {
246 trade_type: TradeType::None,
247 out_trade_no: e["out_trade_no"].to_string(),
248 sp_mchid: "".to_string(),
249 sub_mchid: sub_mchid.to_string(),
250 sp_appid: "".to_string(),
251 transaction_id: e["trade_no"].to_string(),
252 success_time: PayNotify::alipay_time(e["send_pay_date"].as_str().unwrap()),
253 sp_openid: e["buyer_user_id"].to_string(),
254 sub_openid: e["buyer_user_id"].to_string(),
255 total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0) * 100.0) as usize,
256 payer_total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0) * 100.0) as usize,
257 currency: "CNY".to_string(),
258 payer_currency: "CNY".to_string(),
259 trade_state: TradeState::from(e["trade_status"].as_str().unwrap()),
260 };
261 Ok(res.json())
262 }
263 Err(e) => {
264 println!("Err: {:#}", e);
265 Err(e)
266 }
267 }
268 }
269
270 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
271 todo!()
272 }
273
274 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> {
275 let body = object! {
276 "trade_no"=>transaction_id,
277 "out_trade_no"=>out_trade_no,
278 "out_request_no"=>out_refund_no,
279 "refund_amount"=>format!("{:.2}",amount),
280 };
281 match self.http("alipay.trade.refund", body.clone()) {
282 Ok(e) => {
283 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
284 return Err(e["msg"].to_string());
285 }
286 let res = RefundNotify {
287 out_trade_no: e["out_trade_no"].to_string(),
288 refund_no: out_refund_no.to_string(),
289 sp_mchid: "".to_string(),
290 sub_mchid: sub_mchid.to_string(),
291 transaction_id: e["trade_no"].to_string(),
292 refund_id: out_refund_no.to_string(),
293 success_time: PayNotify::alipay_time(e["gmt_refund_pay"].as_str().unwrap()),
294 total,
295 refund: e["refund_fee"].to_string().parse::<f64>().unwrap(),
296 payer_total: e["refund_fee"].to_string().parse::<f64>().unwrap(),
297 payer_refund: e["send_back_fee"].to_string().parse::<f64>().unwrap(),
298 status: RefundStatus::from(e["fund_change"].as_str().unwrap()),
299 };
300 Ok(res.json())
301 }
302 Err(e) => Err(e)
303 }
304 }
305
306 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
307 todo!()
308 }
309
310 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
311 let body = object! {
312 "out_request_no"=>out_refund_no,
313 "trade_no"=>trade_no,
314 };
315 match self.http("alipay.trade.fastpay.refund.query", body.clone()) {
316 Ok(e) => {
317 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
318 return Err(e["msg"].to_string());
319 }
320 let res = RefundNotify {
321 out_trade_no: e["out_trade_no"].to_string(),
322 refund_no: e["out_request_no"].to_string(),
323 sp_mchid: "".to_string(),
324 sub_mchid: sub_mchid.to_string(),
325 transaction_id: e["trade_no"].to_string(),
326 refund_id: e["out_request_no"].to_string(),
327 success_time: Local::now().timestamp(),
328 total: e["total_amount"].to_string().parse::<f64>().unwrap(),
329 payer_total: e["total_amount"].to_string().parse::<f64>().unwrap(),
330 refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
331 payer_refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
332 status: RefundStatus::from(e["refund_status"].as_str().unwrap()),
333 };
334 Ok(res.json())
335 }
336 Err(e) => Err(e)
337 }
338 }
339}