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 = ["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 println!("url: {url}");
59 println!("body: {body:#}");
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}
105impl PayMode for Ccbc {
106 fn check(&mut self) -> Result<bool, String> {
107 todo!()
108 }
109
110 fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
111 todo!()
112 }
113
114 fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
115 todo!()
116 }
117
118 fn config(&mut self) -> JsonValue {
119 todo!()
120 }
121
122 fn login(&mut self, _code: &str) -> Result<JsonValue, String> {
123 todo!()
124 }
125
126 fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
127 todo!()
128 }
129
130 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> {
131 let pubtext = self.public_key[0..30].to_string();
132 let mut body = object! {
133 MERCHANTID:self.mchid.clone(),
134 POSID:self.posid.clone(),
135 BRANCHID:self.branchid.clone(),
136 ORDERID:out_trade_no,
137 PAYMENT:total_fee,
138 CURCODE:"01",
139 TXCODE:"530590",
140 REMARK1:"",
141 REMARK2:"",
142 TYPE:"1",
143 PUB:pubtext,
144 GATEWAY:"0",
145 CLIENTIP:self.client_ip.clone(),
146 REGINFO:self.escape_unicode(&self.smername.clone()),
147 PROINFO: self.escape_unicode(description),
148 REFERER:"",
149 TRADE_TYPE:"",
150 MAC:"",
151 };
152
153 let url = match channel {
154 "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
155 "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
156 _ => return Err(format!("Invalid channel: {channel}")),
157 };
158
159 match channel {
160 "wechat" => {
161 body["TRADE_TYPE"] = match types {
162 Types::Jsapi => "JSAPI",
163 Types::MiniJsapi => "MINIPRO",
164 _ => return Err(format!("Invalid channel: {types:?}")),
165 }.into();
166 body["SUB_APPID"] = self.appid.clone().into();
167 body["SUB_OPENID"] = sp_openid.into();
168 body["WX_CHANNELID"] = self.sp_mchid.clone().into();
169
170 }
184 "alipay" => {
185 body["TXCODE"] = "530591".into();
186 body["TRADE_TYPE"] = match types {
187 Types::Jsapi => "JSAPI",
188 Types::MiniJsapi => "JSAPI",
189 _ => return Err(format!("Invalid channel: {types:?}")),
190 }.into();
191 body["USERID"] = sp_openid.into();
192 }
193 _ => return Err(format!("Invalid channel: {channel}")),
194 }
195 let res = self.http(url, body)?;
196 match types {
197 Types::Jsapi | Types::MiniJsapi => {
198 if res.has_key("PAYURL") {
199 let url = res["PAYURL"].to_string();
200 let mut http = br_reqwest::Client::new();
201 let re = http.post(url.as_str()).send()?;
202 let res = re.json()?;
203 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
204 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
205 }
206 Ok(res)
207 } else {
208 Err(res.to_string())
209 }
210 }
211 _ => {
212 Ok(res)
213 }
214 }
215 }
216
217 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> {
218 let mut body = object! {
219 MERCHANTID:self.sp_mchid.clone(),
220 POSID:self.posid.clone(),
221 BRANCHID:self.branchid.clone(),
222 ccbParam:"",
223 TXCODE:"PAY100",
224 MERFLAG:"1",
225 ORDERID:out_trade_no,
226 QRCODE:auth_code,
227 AMOUNT:total_fee,
228 PROINFO:"商品名称",
229 REMARK1:description
230 };
231
232 let url = match channel {
233 "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
234 "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
235 _ => return Err(format!("Invalid channel: {channel}")),
236 };
237
238 match channel {
239 "wechat" => {
240 body["SUB_APPID"] = self.appid.clone().into();
241 }
242 "alipay" => {}
243 _ => return Err(format!("Invalid channel: {channel}")),
244 }
245
246 let res = self.http(url, body)?;
247 Ok(res)
248 }
249
250 fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
251 todo!()
252 }
253
254 fn pay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
255 todo!()
256 }
257
258 fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
259 todo!()
260 }
261
262 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
263 todo!()
264 }
265
266 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> {
267 todo!()
268 }
269
270 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> {
271 todo!()
272 }
273
274 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
275 todo!()
276 }
277
278 fn refund_query(&mut self, _trade_no: &str, _out_refund_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
279 todo!()
280 }
281
282 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> {
283 todo!()
284 }
285}