1use br_reqwest::Client;
2use base64::Engine;
3use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
4use json::{array, object, JsonValue};
5use openssl::hash::MessageDigest;
6use openssl::pkey::PKey;
7use openssl::rsa::Padding;
8use openssl::sign::Signer;
9use rand::distr::Alphanumeric;
10use rand::Rng;
11
12#[derive(Clone, Debug)]
16pub struct Allinpay {
17 pub debug: bool,
18 pub appid: String,
20 pub sp_mchid: String,
22 pub notify_url: String,
24 pub appid_mini: String,
26 pub rsa_private: String,
28}
29impl Allinpay {
30 pub fn https(&mut self, url: &str, mut data: JsonValue) -> Result<JsonValue, String> {
31 let randomstr: String = rand::rng().sample_iter(&Alphanumeric).take(16).map(char::from).collect();
32 data["randomstr"] = randomstr.into();
33 data["signtype"] = "RSA".into();
34
35 let http_url = if self.debug {
36 format!("https://syb-test.allinpay.com{}", url)
37 } else {
38 format!("https://vsp.allinpay.com{}", url)
39 };
40 let mut http = Client::new();
41
42 let mut keys = vec![];
43
44 for (key, value) in data.entries() {
45 if !value.is_empty() && key != "sign" {
46 keys.push(key);
47 }
48 }
49 keys.sort();
50
51 let mut txt = vec![];
52 for key in keys {
53 txt.push(format!("{}={}", key, data[key]));
54 }
55 let txt = txt.join("&");
56 let sig_b64 = match sign_sha1withrsa_base64(self.rsa_private.as_str(), txt.as_bytes()) {
57 Ok(e) => e,
58 Err(e) => {
59 return Err(e.to_string())
60 }
61 };
62 data["sign"] = sig_b64.into();
63 let res = match http.post(&http_url).form_urlencoded(data).send() {
64 Ok(e) => e,
65 Err(e) => return Err(e.to_string())
66 };
67 let res = res.json()?;
68 if res["retcode"].eq("FAIL") {
69 return Err(res["retmsg"].to_string());
70 }
71 println!("{:#}", res);
72 Ok(res)
73 }
74}
75impl PayMode for Allinpay {
76 fn check(&mut self) -> Result<bool, String> {
77 let data = object! {
78 appid:self.appid.clone(),
79 orgid:self.sp_mchid.clone(),
80 cusid:"",
81 reqsn:"",
82 };
83 match self.https("/apiweb/tranx/query", data) {
84 Ok(e) => e,
85 Err(e) => {
86 if e.contains("auth_code不存在") {
87 return Ok(true);
88 }
89 return Err(e);
90 }
91 };
92 Ok(true)
93 }
94
95 fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
96 let res = self.https("alipay.open.agent.signstatus.query", object! {
97 "biz_content":{
98 "pid":sub_mchid,
99 "product_codes":array!["QUICK_WAP_WAY"]
100 }
101 })?;
102 if !res["code"].eq("10000") {
103 return Err(res["msg"].to_string());
104 }
105 for item in res["sign_status_list"].members() {
106 if item["status"].eq("none") {
107 return Err(format!("{} 未开通", res["product_name"]));
108 }
109 }
110 Ok(true.into())
111 }
112
113
114 fn config(&mut self) -> JsonValue {
115 todo!()
116 }
117
118
119 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> {
120 let total = format!("{:.0}", total_fee * 100.0);
121
122 let mut order = object! {
123 appid:self.appid.clone(),
124 orgid:self.sp_mchid.clone(),
125 cusid:sub_mchid,
126 trxamt:total,
127 reqsn:out_trade_no,
128 paytype:"",
129 body:description,
130 acct:"",
131 notify_url:self.notify_url.clone()
132 };
133 match (types.clone(), channel) {
134 (Types::MiniJsapi, "wechat") => {
135 order["paytype"] = "W06".into();
136 order["sub_appid"] = self.appid_mini.clone().into();
137 order["acct"] = sp_openid.into();
138 }
139 (Types::MiniJsapi, "alipay") => {
140 order["paytype"] = "A02".into();
141 order["sub_appid"] = self.appid_mini.clone().into();
142 order["acct"] = sp_openid.into();
143 }
144 (Types::Jsapi, "wechat") => {
145 order["paytype"] = "W02".into();
146 }
147 (Types::Jsapi, "alipay") => {
148 order["paytype"] = "A02".into();
149 }
150 (Types::H5, "wechat") => {
151 order["paytype"] = "W02".into();
152 }
153 (Types::H5, "alipay") => {
154 order["paytype"] = "A02".into();
155 }
156 (Types::Native, "wechat") => {
157 order["paytype"] = "W01".into();
158 }
159 (Types::Native, "alipay") => {
160 order["paytype"] = "A01".into();
161 }
162 _ => {
163 order["paytype"] = "".into();
164 }
165 };
166 match self.https("/apiweb/unitorder/pay", order) {
167 Ok(e) => {
168 match types {
169 Types::Native => {}
170 Types::Jsapi | Types::H5 => {
171 return Ok(object! {url:e});
172 }
173 Types::MiniJsapi => {
174 return Ok(e);
175 }
176 Types::App => {}
177 Types::Micropay => {}
178 }
179 Ok(e)
180 }
181 Err(e) => {
182 if e == "ACCESS_FORBIDDEN" {
183 return Err("请商户授权JSAPI 绑定 服务商小程序APPID".to_string());
184 }
185 println!("Err: {e:#}");
186 Err(e)
187 }
188 }
189 }
190
191 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> {
192 let total = format!("{:.0}", total_fee * 100.0);
193
194 let order = object! {
195 appid:self.appid.clone(),
196 orgid:self.sp_mchid.clone(),
197 cusid:sub_mchid,
198 trxamt:total,
199 reqsn:out_trade_no,
200 body:description,
201 authcode:auth_code,
202 operatorid:org_openid,
203 };
204 match self.https("/apiweb/unitorder/scanqrpay", order) {
205 Ok(e) => {
206 if e["code"].ne("10000") {
207 return Err(e["msg"].to_string());
208 }
209 if e["msg"].eq("FAIL") {
210 if e["err_code_des"].ne("需要用户输入支付密码") {
211 return Err(e["err_code_des"].to_string());
212 }
213 let res = PayNotify {
214 trade_type: TradeType::MICROPAY,
215 out_trade_no: out_trade_no.to_string(),
216 sp_mchid: self.sp_mchid.clone(),
217 sub_mchid: sub_mchid.to_string(),
218 sp_appid: self.appid.to_string(),
219 transaction_id: "".to_string(),
220 success_time: 0,
221 sp_openid: "".to_string(),
222 sub_openid: "".to_string(),
223 total: total_fee,
224 payer_total: total_fee,
225 currency: "CNY".to_string(),
226 payer_currency: "CNY".to_string(),
227 trade_state: TradeState::NOTPAY,
228 };
229 return Ok(res.json());
230 }
231 let res = PayNotify {
232 trade_type: TradeType::MICROPAY,
233 out_trade_no: out_trade_no.to_string(),
234 sp_mchid: self.sp_mchid.clone(),
235 sub_mchid: sub_mchid.to_string(),
236 sp_appid: self.appid.to_string(),
237 transaction_id: e["trade_no"].to_string(),
238 success_time: PayNotify::datetime_to_timestamp(e["gmt_payment"].as_str().unwrap(), "%Y-%m-%d %H:%M:%S"),
239 sp_openid: e["buyer_open_id"].to_string(),
240 sub_openid: e["buyer_open_id"].to_string(),
241 total: total_fee,
242 payer_total: total_fee,
243 currency: "CNY".to_string(),
244 payer_currency: "CNY".to_string(),
245 trade_state: TradeState::SUCCESS,
246 };
247 Ok(res.json())
248 }
249 Err(e) => Err(e)
250 }
251 }
252
253
254 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
255 let order = object! {
256 appid:self.appid.clone(),
257 orgid:self.sp_mchid.clone(),
258 cusid:sub_mchid,
259 oldreqsn:out_trade_no,
260 };
261 match self.https("/apiweb/tranx/close", order) {
262 Ok(_) => Ok(true.into()),
263 Err(e) => {
264 if e.contains("交易不存在") {
265 return Ok(true.into());
266 }
267 Err(e)
268 }
269 }
270 }
271
272 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
273 let order = object! {
274 appid:self.appid.clone(),
275 orgid:self.sp_mchid.clone(),
276 cusid:sub_mchid,
277 reqsn:out_trade_no,
278 };
279 match self.https("/apiweb/tranx/query", order) {
280 Ok(e) => {
281 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
282 return Err(e["msg"].to_string());
283 }
284 let buyer_open_id = if e.has_key("buyer_open_id") {
285 e["buyer_open_id"].to_string()
286 } else {
287 e["buyer_user_id"].to_string()
288 };
289 let send_pay_date = e["send_pay_date"].as_str().unwrap_or("");
290 let success_time = if !send_pay_date.is_empty() {
291 PayNotify::datetime_to_timestamp(send_pay_date, "%Y-%m-%d %H:%M:%S")
292 } else {
293 0
294 };
295 let res = PayNotify {
296 trade_type: TradeType::None,
297 out_trade_no: e["out_trade_no"].to_string(),
298 sp_mchid: "".to_string(),
299 sub_mchid: sub_mchid.to_string(),
300 sp_appid: "".to_string(),
301 transaction_id: e["trade_no"].to_string(),
302 success_time,
303 sp_openid: buyer_open_id.clone(),
304 sub_openid: buyer_open_id.clone(),
305 total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0)),
306 payer_total: (e["total_amount"].to_string().parse::<f64>().unwrap_or(0.0)),
307 currency: "CNY".to_string(),
308 payer_currency: "CNY".to_string(),
309 trade_state: TradeState::from(e["trade_status"].as_str().unwrap_or("")),
310 };
311 Ok(res.json())
312 }
313 Err(e) => Err(e)
314 }
315 }
316
317 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, _channel: &str) -> Result<JsonValue, String> {
318 self.pay_query(out_trade_no, sub_mchid)
319 }
320
321 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
322 todo!()
323 }
324
325 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> {
326 let total = format!("{:.0}", amount * 100.0);
327
328 let body = object! {
329 appid:self.appid.clone(),
330 orgid:self.sp_mchid.clone(),
331 cusid:sub_mchid,
332 trxamt:total.clone(),
333 reqsn:out_refund_no,
334 oldreqsn:out_trade_no,
335 notify_url:self.notify_url.clone(),
336
337 };
338 match self.https("/apiweb/tranx/refund", body.clone()) {
339 Ok(e) => {
340 if e.has_key("code") && e["code"].as_str().unwrap() != "10000" {
341 return Err(e["msg"].to_string());
342 }
343 let res = RefundNotify {
344 out_trade_no: e["out_trade_no"].to_string(),
345 refund_no: out_refund_no.to_string(),
346 sp_mchid: "".to_string(),
347 sub_mchid: sub_mchid.to_string(),
348 transaction_id: e["trade_no"].to_string(),
349 refund_id: out_refund_no.to_string(),
350 success_time: PayNotify::datetime_to_timestamp(e["gmt_refund_pay"].as_str().unwrap(), "%Y-%m-%d %H:%M:%S"),
351 total: total.to_string().parse::<f64>().unwrap_or(0.0),
352 refund: e["refund_fee"].to_string().parse::<f64>().unwrap(),
353 payer_total: e["refund_fee"].to_string().parse::<f64>().unwrap(),
354 payer_refund: e["send_back_fee"].to_string().parse::<f64>().unwrap(),
355 status: RefundStatus::from(e["fund_change"].as_str().unwrap()),
356 };
357 Ok(res.json())
358 }
359 Err(e) => Err(e)
360 }
361 }
362
363 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> {
364 todo!()
365 }
366
367 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
368 todo!()
369 }
370
371 fn refund_query(&mut self, trade_no: &str, _out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
372 self.pay_query(trade_no, sub_mchid)
373 }
374
375 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> {
376 todo!()
377 }
378}
379
380
381fn sign_sha1withrsa_base64(private_pem: &str, data: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
382 let private_pem = pkcs1_private_pem_from_base64(private_pem);
383 let pkey = PKey::private_key_from_pem(private_pem.as_bytes())?;
384 let mut signer = Signer::new(MessageDigest::sha1(), &pkey)?;
385 signer.set_rsa_padding(Padding::PKCS1)?;
386 signer.update(data)?;
387 let sig = signer.sign_to_vec()?;
388 Ok(base64::engine::general_purpose::STANDARD.encode(sig))
389}
390
391fn pkcs1_private_pem_from_base64(body: &str) -> String {
392 fn wrap64(s: &str) -> String {
393 s.as_bytes().chunks(64).map(|c| std::str::from_utf8(c).unwrap()).collect::<Vec<&str>>().join("\n")
394 }
395 format!(
396 "-----BEGIN RSA PRIVATE KEY-----\n{}\n-----END RSA PRIVATE KEY-----\n",
397 wrap64(body.trim().replace(['\r', '\n', ' '], "").as_str())
398 )
399}