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 mchid: String,
13 pub sp_mchid: String,
15 pub notify_url: String,
17 pub posid: String,
19 pub branchid: String,
21 pub smername: String,
23 pub smertypeid: String,
25 pub smertype: String,
27 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(); 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 }
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}