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