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 sp_mchid: String,
13    /// 通知地址
14    pub notify_url: String,
15    /// 商户柜台代码
16    pub posid: String,
17    /// 分行代码
18    pub branchid: String,
19    /// 二级商户名称
20    pub smername: String,
21    /// 二级商户类别代码
22    pub smertypeid: String,
23    /// 二级商户类别名称
24    pub smertype: String,
25}
26
27impl Ccbc {
28    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
29        let mut mac = vec![];
30        for (key, value) in body.entries() {
31            if value.is_empty() {
32                continue;
33            }
34            mac.push(format!("{key}={value}"));
35        }
36        let mac = mac.join("&");
37        body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
38        let mac = format!("{}&MAC={}", mac, body["MAC"]);
39
40        let mut http = br_reqwest::Client::new();
41        let url = format!("{url}&{mac}");
42        let send = http.post(url.as_str()).raw_json(body);
43        let res = send.header("Content-Type", "application/json").send()?;
44
45        match res.content_type().as_str() {
46            "text/html" => {
47                Err(res.body().to_string())
48            }
49            _ => {
50                match res.json() {
51                    Ok(e) => Ok(e),
52                    Err(e) => Err(e)
53                }
54            }
55        }
56    }
57    fn escape_unicode(&mut self, s: &str) -> String {
58        s.chars().map(|c| {
59            if c.is_ascii() {
60                c.to_string()
61            } else {
62                format!("%u{:04X}", c as u32)
63            }
64        }).collect::<String>()
65    }
66    fn _unescape_unicode(&mut self, s: &str) -> String {
67        let mut output = String::new();
68        let mut chars = s.chars().peekable();
69        while let Some(c) = chars.next() {
70            if c == '%' && chars.peek() == Some(&'u') {
71                chars.next(); // consume 'u'
72                let codepoint: String = chars.by_ref().take(4).collect();
73                if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
74                    if let Some(ch) = std::char::from_u32(value) {
75                        output.push(ch);
76                    }
77                }
78            } else {
79                output.push(c);
80            }
81        }
82        output
83    }
84}
85impl PayMode for Ccbc {
86    fn check(&mut self) -> Result<bool, String> {
87        todo!()
88    }
89
90    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
91        todo!()
92    }
93
94    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
95        todo!()
96    }
97
98    fn config(&mut self) -> JsonValue {
99        todo!()
100    }
101
102    fn login(&mut self, _code: &str) -> Result<JsonValue, String> {
103        todo!()
104    }
105
106    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
107        todo!()
108    }
109
110    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> {
111        let mut body = object! {
112            MERCHANTID:self.sp_mchid.clone(),
113            POSID:self.posid.clone(),
114            BRANCHID:self.branchid.clone(),
115            ORDERID:out_trade_no,
116            PAYMENT:total_fee,
117            CURCODE:"01",
118            TXCODE:"",
119            MAC:"",
120            PUB:"21313123213"
121        };
122
123        let url = match channel {
124            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
125            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
126            _ => return Err(format!("Invalid channel: {channel}")),
127        };
128
129        match channel {
130            "wechat" => {
131                body["TXCODE"] = "530590".into();
132                body["TYPE"] = "1".into();
133                body["GATEWAY"] = "0".into();
134
135                body["TRADE_TYPE"] = match types {
136                    Types::Jsapi => "JSAPI",
137                    Types::MiniJsapi => "MINIPRO",
138                    _ => return Err(format!("Invalid channel: {types:?}")),
139                }.into();
140
141                body["SUB_APPID"] = self.appid.clone().into();
142                body["SUB_OPENID"] = sp_openid.into();
143
144
145                body["SMERID"] = sub_mchid.into();
146                body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
147                body["SMERTYPEID"] = self.smertypeid.clone().into();
148                body["SMERTYPE"] = self.escape_unicode(self.smertype.clone().as_str()).into();
149                body["TRADECODE"] = "交易类型代码".into();
150                body["TRADENAME"] = self.escape_unicode("消费").into();
151
152                body["SMEPROTYPE"] = "商品类别代码".into();
153                body["PRONAME"] = self.escape_unicode("商品").into();
154            }
155            "alipay" => {
156                body["TXCODE"] = "530591".into();
157                body["TRADE_TYPE"] = match types {
158                    Types::Jsapi => "JSAPI",
159                    Types::MiniJsapi => "JSAPI",
160                    _ => return Err(format!("Invalid channel: {types:?}")),
161                }.into();
162                body["USERID"] = sp_openid.into();
163            }
164            _ => return Err(format!("Invalid channel: {channel}")),
165        }
166
167        let res = self.http(url, body)?;
168        Ok(res)
169    }
170
171    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> {
172        let mut body = object! {
173            MERCHANTID:self.sp_mchid.clone(),
174            POSID:self.posid.clone(),
175            BRANCHID:self.branchid.clone(),
176            ccbParam:"",
177            TXCODE:"PAY100",
178            MERFLAG:"1",
179            ORDERID:out_trade_no,
180            QRCODE:auth_code,
181            AMOUNT:total_fee,
182            PROINFO:"商品名称",
183            REMARK1:description
184        };
185
186        let url = match channel {
187            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
188            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
189            _ => return Err(format!("Invalid channel: {channel}")),
190        };
191
192        match channel {
193            "wechat" => {
194                body["SUB_APPID"] = self.appid.clone().into();
195            }
196            "alipay" => {
197                
198            }
199            _ => return Err(format!("Invalid channel: {channel}")),
200        }
201
202        let res = self.http(url, body)?;
203        Ok(res)
204    }
205
206    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
207        todo!()
208    }
209
210    fn pay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
211        todo!()
212    }
213
214    fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
215        todo!()
216    }
217
218    fn pay_notify(&mut self, _nonce: &str,_ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
219        todo!()
220    }
221
222    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> {
223        todo!()
224    }
225
226    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> {
227        todo!()
228    }
229
230    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
231        todo!()
232    }
233
234    fn refund_query(&mut self, _trade_no: &str, _out_refund_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
235        todo!()
236    }
237
238    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> {
239        todo!()
240    }
241}