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 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(); 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 }
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}