1use json::{object, JsonValue};
2use crate::{PayMode, Types};
3
4#[derive(Clone, Debug)]
6pub struct Ccbc {
7 pub appid: String,
9 pub secret: String,
11 pub pass: String,
13 pub mchid: String,
15 pub sp_mchid: String,
17 pub notify_url: String,
19 pub posid: String,
21 pub branchid: String,
23 pub smername: String,
25 pub smertypeid: String,
27 pub smertype: String,
29 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.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(); 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.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 }
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 todo!()
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}