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