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