br_pay/
alipay.rs

1use br_reqwest::Client;
2use std::collections::{HashMap};
3use base64::{Engine};
4use base64::engine::general_purpose;
5use base64::engine::general_purpose::STANDARD;
6use chrono::{Local};
7use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
8use json::{object, JsonValue};
9use openssl::hash::MessageDigest;
10use openssl::pkey::{PKey};
11use openssl::rsa::Rsa;
12use openssl::sign::{Signer, Verifier};
13use urlencoding::{decode, encode};
14use log::error;
15
16#[derive(Clone)]
17pub struct AliPay {
18    /// 应用appid
19    pub appid: String,
20    /// 服务商商家号
21    pub sp_mchid: String,
22    /// 授权token
23    pub app_auth_token: String,
24    /// 应用私钥证书路径
25    pub app_private: String,
26    /// 接口内容加密密钥
27    pub content_encryp: String,
28    /// 支付宝公钥
29    pub alipay_public_key: String,
30    pub notify_url: 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        let cart = format!("-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----\n", t);
36
37        // 1. 加载私钥
38        let rsa = match Rsa::private_key_from_pem(cart.as_bytes()) {
39            Ok(e) => e,
40            Err(e) => return Err(e.to_string())
41        };
42
43        let pkey = match PKey::from_rsa(rsa) {
44            Ok(e) => e,
45            Err(e) => return Err(e.to_string())
46        };
47
48        // 2. 创建签名器,使用 SHA256
49        let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
50            Ok(e) => e,
51            Err(e) => return Err(e.to_string())
52        };
53        match signer.update(txt.as_bytes()) {
54            Ok(()) => {}
55            Err(e) => return Err(e.to_string())
56        };
57        // 3. 生成签名
58        let signature = match signer.sign_to_vec() {
59            Ok(e) => e,
60            Err(e) => return Err(e.to_string())
61        };
62        // 4. Base64 编码输出
63        Ok(general_purpose::STANDARD.encode(signature).into())
64    }
65    pub fn http(&mut self, method: &str, biz_content: JsonValue) -> Result<JsonValue, String> {
66        let mut http = Client::new();
67        //http.debug();
68        let sign = "";
69
70        let now = Local::now();
71        let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
72
73        let mut data = object! {
74                    "charset":"UTF-8",
75                    "method":method,
76                    "app_id":self.appid.clone(),
77                    "app_private_key":self.app_private.clone(),
78                    "version":"1.0",
79                    "sign_type":"RSA2",
80                    "timestamp":timestamp,
81                    "alipay_public_key":self.alipay_public_key.clone(),
82                    "app_auth_token":self.app_auth_token.clone(),
83                    "sign":sign
84        };
85        for (key, value) in biz_content.entries() {
86            data[key] = value.clone()
87        }
88
89        let mut map = HashMap::new();
90        for (key, value) in data.entries() {
91            if key == "sign" {
92                continue;
93            }
94            if value.is_empty() {
95                continue;
96            }
97            map.insert(key, value);
98        }
99
100        let mut keys: Vec<_> = map.keys().cloned().collect();
101        keys.sort();
102        let mut txt = vec![];
103        for key in keys {
104            txt.push(format!("{}={}", key, map.get(&key).unwrap()));
105        }
106        let txt = txt.join("&");
107        data["sign"] = self.sign(&txt)?;
108
109        let mut new_data = object! {};
110        for (key, value) in data.entries() {
111            let t = encode(value.to_string().as_str()).to_string();
112            new_data[key] = t.into();
113        }
114        data = new_data;
115        let url = "https://openapi.alipay.com/gateway.do".to_string();
116        let res = match method {
117            "alipay.trade.wap.pay" => {
118                let tt = http.get(url.as_str()).query(data);
119                return Ok(tt.url.clone().into());
120            }
121            _ => {
122                match http.get(&url).query(data).form_data(biz_content).send() {
123                    Ok(e) => e,
124                    Err(e) => {
125                        return Err(e.to_string())
126                    }
127                }
128            }
129        };
130
131        let res = res.json()?;
132
133        if res.has_key("error_response") {
134            return Err(res["error_response"]["sub_msg"].to_string());
135        }
136        let key = method.replace(".", "_");
137        let key = format!("{}_response", key);
138        let data = res[key].clone();
139        if data.has_key("code") {
140            if data["code"] != "10000" {
141                Err(data["sub_msg"].to_string())
142            } else {
143                Ok(data)
144            }
145        } else {
146            Err(data.to_string())
147        }
148    }
149}
150impl PayMode for AliPay {
151    fn notify(&mut self, data: JsonValue) -> Result<JsonValue, String> {
152        let sign = match STANDARD.decode(data["sign"].to_string()) {
153            Ok(e) => e,
154            Err(e) => return Err(format!("decode sign: {}", e))
155        };
156        let mut map = HashMap::new();
157        for (key, value) in data.entries() {
158            if key == "sign" {
159                continue;
160            }
161            if value.is_empty() {
162                continue;
163            }
164            map.insert(key, value);
165        }
166
167        let mut keys: Vec<_> = map.keys().cloned().collect();
168        keys.sort();
169
170        let mut txt = vec![];
171        for key in keys {
172            let value = decode(map.get(&key).unwrap().to_string().as_str()).unwrap().to_string();
173            txt.push(format!("{}={}", key, value));
174        }
175        let txt = txt.join("&");
176
177        let public_key_pem = self.alipay_public_key.clone();
178        let public_key_pem = format!("-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n", public_key_pem);
179        let public_key = match PKey::public_key_from_pem(public_key_pem.as_bytes()) {
180            Ok(e) => e,
181            Err(e) => return Err(format!("Invalid public key: {}", e))
182        };
183
184        // 4. 验签
185        let mut verifier = match Verifier::new(MessageDigest::sha256(), &public_key) {
186            Ok(e) => e,
187            Err(e) => return Err(format!("Invalid verifier: {}", e))
188        };
189        match verifier.update(txt.as_bytes()) {
190            Ok(_) => {}
191            Err(_) => return Err("Invalid transaction signature".to_string())
192        };
193
194        let result = match verifier.verify(&sign) {
195            Ok(e) => e,
196            Err(_) => return Err("Invalid transaction signature".to_string())
197        };
198        if !result {
199            return Err("sign error".to_string());
200        }
201        if data.has_key("service") {
202            let service = data["service"].to_string();
203            if service.as_str() == "alipay.service.check" {
204                let sign_txt = "<success>true</success>";
205                let sign = self.sign(sign_txt)?;
206                let text = format!(r#"<?xml version="1.0" encoding="GBK"?><alipay><response><success>true</success></response><sign>{}</sign><sign_type>RSA2</sign_type></alipay>"#, sign);
207                return Ok(JsonValue::String(text));
208            }
209        }
210        Ok(result.into())
211    }
212
213    fn config(&mut self) -> JsonValue {
214        todo!()
215    }
216
217    fn login(&mut self, code: &str) -> Result<JsonValue, String> {
218        let res = self.http("alipay.system.oauth.token", object! {
219            "grant_type":"authorization_code",
220            "code":code
221        })?;
222        println!(">>>>{:#}", res);
223        Ok(res)
224    }
225
226    fn auth(&mut self, code: &str) -> Result<JsonValue, String> {
227        let res = match self.http("alipay.open.auth.token.app", object! {
228            grant_type:"authorization_code",
229            code:code
230        }) {
231            Ok(e) => e,
232            Err(e) => {
233                error!("Err: {:#}", e);
234                return Err(e);
235            }
236        };
237        println!(">>>>{:#}", res);
238        //let user_id = res["user_id"].clone();
239        //let auth_app_id = res["auth_app_id"].clone();
240        //let re_expires_in = res["re_expires_in"].clone();
241        //let expires_in = res["expires_in"].clone();
242        //let app_auth_token = res["app_auth_token"].clone();
243        Ok(res)
244    }
245
246    fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
247        let mut api = "";
248        let mut order = object! {
249            out_trade_no:out_trade_no,
250            total_amount:total_fee,
251            subject:description,
252            product_code:"",
253            op_app_id:sub_mchid,
254            buyer_open_id:sp_openid
255        };
256
257        match types {
258            Types::Jsapi => {
259                api = "alipay.trade.create";
260                order["product_code"] = "JSAPI_PAY".into();
261            }
262            Types::H5 => {
263                api = "alipay.trade.wap.pay";
264                order["product_code"] = "QUICK_WAP_WAY".into();
265            }
266            Types::Native => {
267                api = "alipay.trade.wap.pay";
268                order["product_code"] = "QUICK_WAP_WAY".into();
269            }
270            _ => {
271                order["product_code"] = "JSAPI_PAY".into();
272            }
273        };
274
275        match self.http(api, object! {"biz_content":order}) {
276            Ok(e) => {
277                match types {
278                    Types::Jsapi => {}
279                    Types::Native => {}
280                    Types::H5 => {
281                        return Ok(object! {url:e});
282                    }
283                    Types::MiniJsapi => {}
284                    Types::App => {}
285                    Types::Micropay => {}
286                }
287                Ok(e)
288            }
289            Err(e) => {
290                println!("Err: {:#}", e);
291                Err(e)
292            }
293        }
294    }
295
296
297    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
298        todo!()
299    }
300
301    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
302        let order = object! {
303            out_trade_no:out_trade_no
304        };
305        match self.http("alipay.trade.query", order) {
306            Ok(e) => {
307                if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
308                    return Err(e["msg"].to_string());
309                }
310                let res = PayNotify {
311                    trade_type: TradeType::None,
312                    out_trade_no: e["out_trade_no"].to_string(),
313                    sp_mchid: "".to_string(),
314                    sub_mchid: sub_mchid.to_string(),
315                    sp_appid: "".to_string(),
316                    transaction_id: e["trade_no"].to_string(),
317                    success_time: PayNotify::alipay_time(e["send_pay_date"].as_str().unwrap()),
318                    sp_openid: e["buyer_user_id"].to_string(),
319                    sub_openid: e["buyer_user_id"].to_string(),
320                    total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0) * 100.0) as usize,
321                    payer_total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0) * 100.0) as usize,
322                    currency: "CNY".to_string(),
323                    payer_currency: "CNY".to_string(),
324                    trade_state: TradeState::from(e["trade_status"].as_str().unwrap()),
325                };
326                Ok(res.json())
327            }
328            Err(e) => {
329                println!("Err: {:#}", e);
330                Err(e)
331            }
332        }
333    }
334
335    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
336        todo!()
337    }
338
339    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> {
340        let body = object! {
341            "trade_no"=>transaction_id,
342            "out_trade_no"=>out_trade_no,
343            "out_request_no"=>out_refund_no,
344            "refund_amount"=>format!("{:.2}",amount),
345        };
346        match self.http("alipay.trade.refund", body.clone()) {
347            Ok(e) => {
348                if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
349                    return Err(e["msg"].to_string());
350                }
351                let res = RefundNotify {
352                    out_trade_no: e["out_trade_no"].to_string(),
353                    refund_no: out_refund_no.to_string(),
354                    sp_mchid: "".to_string(),
355                    sub_mchid: sub_mchid.to_string(),
356                    transaction_id: e["trade_no"].to_string(),
357                    refund_id: out_refund_no.to_string(),
358                    success_time: PayNotify::alipay_time(e["gmt_refund_pay"].as_str().unwrap()),
359                    total,
360                    refund: e["refund_fee"].to_string().parse::<f64>().unwrap(),
361                    payer_total: e["refund_fee"].to_string().parse::<f64>().unwrap(),
362                    payer_refund: e["send_back_fee"].to_string().parse::<f64>().unwrap(),
363                    status: RefundStatus::from(e["fund_change"].as_str().unwrap()),
364                };
365                Ok(res.json())
366            }
367            Err(e) => Err(e)
368        }
369    }
370
371    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
372        todo!()
373    }
374
375    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
376        let body = object! {
377            "out_request_no"=>out_refund_no,
378            "trade_no"=>trade_no,
379        };
380        match self.http("alipay.trade.fastpay.refund.query", body.clone()) {
381            Ok(e) => {
382                if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
383                    return Err(e["msg"].to_string());
384                }
385                let res = RefundNotify {
386                    out_trade_no: e["out_trade_no"].to_string(),
387                    refund_no: e["out_request_no"].to_string(),
388                    sp_mchid: "".to_string(),
389                    sub_mchid: sub_mchid.to_string(),
390                    transaction_id: e["trade_no"].to_string(),
391                    refund_id: e["out_request_no"].to_string(),
392                    success_time: Local::now().timestamp(),
393                    total: e["total_amount"].to_string().parse::<f64>().unwrap(),
394                    payer_total: e["total_amount"].to_string().parse::<f64>().unwrap(),
395                    refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
396                    payer_refund: e["refund_amount"].to_string().parse::<f64>().unwrap(),
397                    status: RefundStatus::from(e["refund_status"].as_str().unwrap()),
398                };
399                Ok(res.json())
400            }
401            Err(e) => Err(e)
402        }
403    }
404}