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 mchid: String,
13    /// 微信商户号
14    pub sp_mchid: String,
15    /// 通知地址
16    pub notify_url: String,
17    /// 商户柜台代码
18    pub posid: String,
19    /// 分行代码
20    pub branchid: String,
21    /// 二级商户名称
22    pub smername: String,
23    /// 二级商户类别代码
24    pub smertypeid: String,
25    /// 二级商户类别名称
26    pub smertype: String,
27    /// 二级商户公钥
28    pub public_key: String,
29    pub client_ip: String,
30
31}
32
33impl Ccbc {
34    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
35        let mut mac = vec![];
36        let mut path = vec![];
37        let fields = ["MAC"];
38        for (key, value) in body.entries() {
39            if value.is_empty() && fields.contains(&key) {
40                continue;
41            }
42            if key != "PUB" {
43                path.push(format!("{key}={value}"));
44            }
45            mac.push(format!("{key}={value}"));
46        }
47
48
49        let mac_text = mac.join("&");
50        let path = path.join("&");
51        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
52        body.remove("PUB");
53        let mac = format!("{}&MAC={}", path, body["MAC"]);
54
55
56        let mut http = br_reqwest::Client::new();
57        let url = format!("{url}&{mac}");
58        println!("url: {url}");
59        println!("body: {body:#}");
60        let send = http.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}
105impl PayMode for Ccbc {
106    fn check(&mut self) -> Result<bool, String> {
107        todo!()
108    }
109
110    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
111        todo!()
112    }
113
114    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
115        todo!()
116    }
117
118    fn config(&mut self) -> JsonValue {
119        todo!()
120    }
121
122    fn login(&mut self, _code: &str) -> Result<JsonValue, String> {
123        todo!()
124    }
125
126    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
127        todo!()
128    }
129
130    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> {
131        let pubtext = self.public_key[0..30].to_string();
132        let mut body = object! {
133            MERCHANTID:self.mchid.clone(),
134            POSID:self.posid.clone(),
135            BRANCHID:self.branchid.clone(),
136            ORDERID:out_trade_no,
137            PAYMENT:total_fee,
138            CURCODE:"01",
139            TXCODE:"530590",
140            REMARK1:"",
141            REMARK2:"",
142            TYPE:"1",
143            PUB:pubtext,
144            GATEWAY:"0",
145            CLIENTIP:self.client_ip.clone(),
146            REGINFO:self.escape_unicode(&self.smername.clone()),
147            PROINFO: self.escape_unicode(description),
148            REFERER:"",
149            TRADE_TYPE:"",
150            MAC:"",
151        };
152
153        let url = match channel {
154            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
155            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
156            _ => return Err(format!("Invalid channel: {channel}")),
157        };
158
159        match channel {
160            "wechat" => {
161                body["TRADE_TYPE"] = match types {
162                    Types::Jsapi => "JSAPI",
163                    Types::MiniJsapi => "MINIPRO",
164                    _ => return Err(format!("Invalid channel: {types:?}")),
165                }.into();
166                body["SUB_APPID"] = self.appid.clone().into();
167                body["SUB_OPENID"] = sp_openid.into();
168                body["WX_CHANNELID"] = self.sp_mchid.clone().into();
169
170                //body["SMERID"] = sub_mchid.into();
171                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
172                //body["SMERTYPEID"] = self.smertypeid.clone().into();
173                //body["SMERTYPE"] = self.escape_unicode(self.smertype.clone().as_str()).into();
174
175                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
176                //body["SMERTYPEID"] = 1.into();
177                //body["SMERTYPE"] = self.escape_unicode("宾馆餐娱类").into();
178
179                //body["TRADECODE"] = "交易类型代码".into();
180                //body["TRADENAME"] = self.escape_unicode("消费").into();
181                //body["SMEPROTYPE"] = "商品类别代码".into();
182                //body["PRONAME"] = self.escape_unicode("商品").into();
183            }
184            "alipay" => {
185                body["TXCODE"] = "530591".into();
186                body["TRADE_TYPE"] = match types {
187                    Types::Jsapi => "JSAPI",
188                    Types::MiniJsapi => "JSAPI",
189                    _ => return Err(format!("Invalid channel: {types:?}")),
190                }.into();
191                body["USERID"] = sp_openid.into();
192            }
193            _ => return Err(format!("Invalid channel: {channel}")),
194        }
195        let res = self.http(url, body)?;
196        match types {
197            Types::Jsapi | Types::MiniJsapi => {
198                if res.has_key("PAYURL") {
199                    let url = res["PAYURL"].to_string();
200                    let mut http = br_reqwest::Client::new();
201                    let re = http.post(url.as_str()).send()?;
202                    let res = re.json()?;
203                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
204                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
205                    }
206                    Ok(res)
207                } else {
208                    Err(res.to_string())
209                }
210            }
211            _ => {
212                Ok(res)
213            }
214        }
215    }
216
217    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> {
218        let mut body = object! {
219            MERCHANTID:self.sp_mchid.clone(),
220            POSID:self.posid.clone(),
221            BRANCHID:self.branchid.clone(),
222            ccbParam:"",
223            TXCODE:"PAY100",
224            MERFLAG:"1",
225            ORDERID:out_trade_no,
226            QRCODE:auth_code,
227            AMOUNT:total_fee,
228            PROINFO:"商品名称",
229            REMARK1:description
230        };
231
232        let url = match channel {
233            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
234            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
235            _ => return Err(format!("Invalid channel: {channel}")),
236        };
237
238        match channel {
239            "wechat" => {
240                body["SUB_APPID"] = self.appid.clone().into();
241            }
242            "alipay" => {}
243            _ => return Err(format!("Invalid channel: {channel}")),
244        }
245
246        let res = self.http(url, body)?;
247        Ok(res)
248    }
249
250    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
251        todo!()
252    }
253
254    fn pay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
255        todo!()
256    }
257
258    fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
259        todo!()
260    }
261
262    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
263        todo!()
264    }
265
266    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> {
267        todo!()
268    }
269
270    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> {
271        todo!()
272    }
273
274    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
275        todo!()
276    }
277
278    fn refund_query(&mut self, _trade_no: &str, _out_refund_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
279        todo!()
280    }
281
282    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> {
283        todo!()
284    }
285}