br_pay/
ccbc.rs

1use json::{object, JsonValue};
2use crate::{PayMode, Types};
3
4/// 建设银行
5#[derive(Clone, Debug)]
6pub struct Ccbc {
7    /// 服务商APPID
8    pub appid: String,
9    /// 密钥
10    pub secret: String,
11    /// 登录密码
12    pub pass: String,
13    /// 银行商户号
14    pub mchid: String,
15    /// 微信商户号
16    pub sp_mchid: String,
17    /// 通知地址
18    pub notify_url: String,
19    /// 商户柜台代码
20    pub posid: String,
21    /// 分行代码
22    pub branchid: String,
23    /// 二级商户名称
24    pub smername: String,
25    /// 二级商户类别代码
26    pub smertypeid: String,
27    /// 二级商户类别名称
28    pub smertype: String,
29    /// 二级商户公钥
30    pub public_key: String,
31    pub client_ip: String,
32
33}
34
35impl Ccbc {
36    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
37        let mut mac = vec![];
38        let mut path = vec![];
39        let fields = ["MAC"];
40        for (key, value) in body.entries() {
41            if value.is_empty() && fields.contains(&key) {
42                continue;
43            }
44            if key != "PUB" {
45                path.push(format!("{key}={value}"));
46            }
47            mac.push(format!("{key}={value}"));
48        }
49
50
51        let mac_text = mac.join("&");
52        let path = path.join("&");
53        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
54        body.remove("PUB");
55        let mac = format!("{}&MAC={}", path, body["MAC"]);
56
57
58        let mut http = br_reqwest::Client::new();
59        let url = format!("{url}&{mac}");
60        let send = http.set_openssl_legacy_provider().post(url.as_str()).raw_json(body);
61        let res = send.header("Content-Type", "application/json").send()?;
62        match res.content_type().as_str() {
63            "text/html" => {
64                match res.json() {
65                    Ok(e) => Ok(e),
66                    Err(_) => Err(res.body().to_string())
67                }
68            }
69            _ => {
70                match res.json() {
71                    Ok(e) => Ok(e),
72                    Err(e) => Err(e)
73                }
74            }
75        }
76    }
77    fn escape_unicode(&mut self, s: &str) -> String {
78        s.chars().map(|c| {
79            if c.is_ascii() {
80                c.to_string()
81            } else {
82                format!("%u{:04X}", c as u32)
83            }
84        }).collect::<String>()
85    }
86    fn _unescape_unicode(&mut self, s: &str) -> String {
87        let mut output = String::new();
88        let mut chars = s.chars().peekable();
89        while let Some(c) = chars.next() {
90            if c == '%' && chars.peek() == Some(&'u') {
91                chars.next(); // consume 'u'
92                let codepoint: String = chars.by_ref().take(4).collect();
93                if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
94                    if let Some(ch) = std::char::from_u32(value) {
95                        output.push(ch);
96                    }
97                }
98            } else {
99                output.push(c);
100            }
101        }
102        output
103    }
104    pub fn http_q(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
105        let mut path = vec![];
106        let mut mac = vec![];
107        let fields = ["MAC"];
108        for (key, value) in body.entries() {
109            if value.is_empty() && fields.contains(&key) {
110                continue;
111            }
112            mac.push(format!("{key}={value}"));
113            if key.contains("QUPWD") {
114                path.push(format!("{key}="));
115                continue;
116            }
117            path.push(format!("{key}={value}"));
118        }
119
120        let mac = mac.join("&");
121        body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
122        println!("{body:#}");
123        let mac = format!("{}&MAC={}", path.join("&"), body["MAC"]);
124        println!("{mac:#}");
125        let mut http = br_reqwest::Client::new();
126        let url = format!("{url}?{mac}");
127        println!("{url}");
128        let send = http.set_openssl_legacy_provider().post(url.as_str()).form_urlencoded(body);
129        let res = send.send()?;
130        match res.content_type().as_str() {
131            "text/html" => {
132                println!("{}", res.body());
133                match res.json() {
134                    Ok(e) => Ok(e),
135                    Err(_) => Err(res.body().to_string())
136                }
137            }
138            _ => {
139                match res.json() {
140                    Ok(e) => Ok(e),
141                    Err(e) => Err(e)
142                }
143            }
144        }
145    }
146}
147impl PayMode for Ccbc {
148    fn check(&mut self) -> Result<bool, String> {
149        todo!()
150    }
151
152    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
153        todo!()
154    }
155
156    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
157        todo!()
158    }
159
160    fn config(&mut self) -> JsonValue {
161        todo!()
162    }
163
164
165    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
166        todo!()
167    }
168
169    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> {
170        let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
171        let mut body = object! {
172            MERCHANTID:self.mchid.clone(),
173            POSID:self.posid.clone(),
174            BRANCHID:self.branchid.clone(),
175            ORDERID:out_trade_no,
176            PAYMENT:total_fee,
177            CURCODE:"01",
178            TXCODE:"530590",
179            REMARK1:"",
180            REMARK2:"",
181            TYPE:"1",
182            PUB:pubtext,
183            GATEWAY:"0",
184            CLIENTIP:self.client_ip.clone(),
185            REGINFO:self.escape_unicode(&self.smername.clone()),
186            PROINFO: self.escape_unicode(description),
187            REFERER:"",
188            TRADE_TYPE:"",
189            MAC:"",
190        };
191
192        let url = match channel {
193            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
194            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
195            _ => return Err(format!("Invalid channel: {channel}")),
196        };
197
198        match channel {
199            "wechat" => {
200                body["TRADE_TYPE"] = match types {
201                    Types::Jsapi => "JSAPI",
202                    Types::MiniJsapi => "MINIPRO",
203                    _ => return Err(format!("Invalid channel: {types:?}")),
204                }.into();
205                body["SUB_APPID"] = self.appid.clone().into();
206                body["SUB_OPENID"] = sp_openid.into();
207
208                //body["WX_CHANNELID"] = self.sp_mchid.clone().into();
209
210                //body["SMERID"] = sub_mchid.into();
211                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
212                //body["SMERTYPEID"] = self.smertypeid.clone().into();
213                //body["SMERTYPE"] = self.escape_unicode(self.smertype.clone().as_str()).into();
214
215                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
216                //body["SMERTYPEID"] = 1.into();
217                //body["SMERTYPE"] = self.escape_unicode("宾馆餐娱类").into();
218
219                //body["TRADECODE"] = "交易类型代码".into();
220                //body["TRADENAME"] = self.escape_unicode("消费").into();
221                //body["SMEPROTYPE"] = "商品类别代码".into();
222                //body["PRONAME"] = self.escape_unicode("商品").into();
223            }
224            "alipay" => {
225                body["TXCODE"] = "530591".into();
226                body["TRADE_TYPE"] = match types {
227                    Types::Jsapi => "JSAPI",
228                    Types::MiniJsapi => "JSAPI",
229                    _ => return Err(format!("Invalid channel: {types:?}")),
230                }.into();
231                body["USERID"] = sp_openid.into();
232            }
233            _ => return Err(format!("Invalid channel: {channel}")),
234        }
235        let res = self.http(url, body)?;
236        match types {
237            Types::Jsapi | Types::MiniJsapi => {
238                if res.has_key("PAYURL") {
239                    let url = res["PAYURL"].to_string();
240                    let mut http = br_reqwest::Client::new();
241                    let re = http.post(url.as_str()).send()?;
242                    let res = re.json()?;
243                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
244                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
245                    }
246                    Ok(res)
247                } else {
248                    Err(res.to_string())
249                }
250            }
251            _ => {
252                Ok(res)
253            }
254        }
255    }
256
257    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> {
258        let mut body = object! {
259            MERCHANTID:self.sp_mchid.clone(),
260            POSID:self.posid.clone(),
261            BRANCHID:self.branchid.clone(),
262            ccbParam:"",
263            TXCODE:"PAY100",
264            MERFLAG:"1",
265            ORDERID:out_trade_no,
266            QRCODE:auth_code,
267            AMOUNT:total_fee,
268            PROINFO:"商品名称",
269            REMARK1:description
270        };
271
272        let url = match channel {
273            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
274            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
275            _ => return Err(format!("Invalid channel: {channel}")),
276        };
277
278        match channel {
279            "wechat" => {
280                body["SUB_APPID"] = self.appid.clone().into();
281            }
282            "alipay" => {}
283            _ => return Err(format!("Invalid channel: {channel}")),
284        }
285
286        let res = self.http(url, body)?;
287        Ok(res)
288    }
289
290    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
291       Ok(true.into())
292    }
293
294    fn pay_query(&mut self, out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
295        let body = object! {
296            MERCHANTID:self.mchid.clone(),
297            BRANCHID:self.branchid.clone(),
298            POSID:self.posid.clone(),
299            ORDERDATE:"",
300            BEGORDERTIME:"00:00:00",
301            ENDORDERTIME:"23:59:59",
302            ORDERID:out_trade_no,
303            QUPWD:self.pass.clone(),
304            TXCODE:"410408",
305            TYPE:"0",
306            KIND:"0",
307            STATUS:"1",
308            SEL_TYPE:"3",
309            PAGE:"1",
310            OPERATOR:"",
311            CHANNEL:"",
312            MAC:""
313        };
314        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
315        println!("{res:#}");
316        Ok(object! {})
317    }
318
319    fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
320        todo!()
321    }
322
323    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
324        todo!()
325    }
326
327    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> {
328        todo!()
329    }
330
331    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> {
332        todo!()
333    }
334
335    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
336        todo!()
337    }
338
339    fn refund_query(&mut self, _trade_no: &str, _out_refund_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
340        todo!()
341    }
342
343    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> {
344        todo!()
345    }
346}