br_pay/
lib.rs

1use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
2use crate::wechat::{Wechat};
3use json::{object, JsonValue};
4use crate::alipay::AliPay;
5use crate::ccbc::Ccbc;
6use crate::yrcc::Yrcc;
7
8pub mod alipay;
9pub mod wechat;
10pub mod ccbc;
11pub mod yrcc;
12
13#[derive(Clone, Debug)]
14pub enum Pay {
15    Wechat(Wechat),
16    Alipay(AliPay),
17    /// 建设银行
18    Ccbc(Ccbc),
19    /// 农村商业银行
20    Yrcc(Yrcc),
21    None,
22}
23
24impl Pay {
25    pub fn new(data: JsonValue) -> Self {
26        match data["mode"].as_str().unwrap_or("") {
27            "wechat" => {
28                Pay::Wechat(Wechat {
29                    appid: data["appid"].to_string(),
30                    sp_mchid: data["sp_mchid"].to_string(),
31                    serial_no: data["serial_no"].to_string(),
32                    app_private: data["app_private"].to_string(),
33                    apikey: data["apikey"].to_string(),
34                    apiv2: data["apiv2"].to_string(),
35                    notify_url: data["notify_url"].to_string(),
36                })
37            }
38            "alipay" => Pay::Alipay(AliPay {
39                appid: data["appid"].to_string(),
40                sp_mchid: data["sp_mchid"].to_string(),
41                app_private: data["app_private"].to_string(),
42                app_auth_token: data["app_auth_token"].as_str().unwrap_or("").to_string(),
43                content_encryp: data["content_encryp"].to_string(),
44                alipay_public_key: data["alipay_public_key"].to_string(),
45                notify_url: data["notify_url"].to_string(),
46                appid_mini: data["appid_mini"].to_string(),
47            }),
48            "yrcc" => Pay::Yrcc(Yrcc {
49                appid: "".to_string(),
50                secret: "".to_string(),
51                terminal_number: data["terminal_number"].to_string(),
52                sp_mchid: data["sp_mchid"].to_string(),
53                app_private: data["app_private"].to_string(),
54                app_public: data["app_public"].to_string(),
55                notify_url: data["notify_url"].as_str().unwrap_or("").to_string(),
56                key_name: data["key_name"].to_string(),
57                service_provider: data["service_provider"].to_string(),
58            }),
59            "ccbc" => Pay::Ccbc(Ccbc {
60                appid: data["appid"].as_str().unwrap_or("").to_string(),
61                branchid: data["branchid"].as_str().unwrap_or("").to_string(),
62                appid_subscribe: data["appid_subscribe"].as_str().unwrap_or("").to_string(),
63                sp_mchid: data["sp_mchid"].as_str().unwrap_or("").to_string(),
64                sp_user_id: data["sp_user_id"].as_str().unwrap_or("").to_string(),
65                sp_pass: data["sp_pass"].as_str().unwrap_or("").to_string(),
66                sp_posid: data["sp_posid"].as_str().unwrap_or("").to_string(),
67                notify_url: data["notify_url"].as_str().unwrap_or("").to_string(),
68                sub_posid: data["sub_posid"].as_str().unwrap_or("").to_string(),
69                public_key: data["public_key"].as_str().unwrap_or("").to_string(),
70                client_ip: data["client_ip"].as_str().unwrap_or("").to_string(),
71                retry: 0,
72                debug: data["debug"].as_bool().unwrap_or(false),
73                query_url: data["query_url"].as_str().unwrap_or("").to_string(),
74            }),
75            _ => Pay::None
76        }
77    }
78}
79impl PayMode for Pay {
80    fn check(&mut self) -> Result<bool, String> {
81        match self {
82            Self::Wechat(e) => e.check(),
83            Self::Alipay(e) => e.check(),
84            Self::Ccbc(_) => todo!(),
85            Self::Yrcc(_) => todo!(),
86            Self::None => Err("No login data".to_owned()),
87        }
88    }
89
90    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
91        match self {
92            Self::Wechat(e) => e.get_sub_mchid(sub_mchid),
93            Self::Alipay(e) => e.get_sub_mchid(sub_mchid),
94            Pay::None => Err("No login data".to_owned()),
95            &mut Pay::Ccbc(_) => todo!(),
96            &mut Pay::Yrcc(_) => todo!()
97        }
98    }
99
100
101    fn config(&mut self) -> JsonValue {
102        match self {
103            Self::Wechat(e) => object! {
104                sp_mchid:e.sp_mchid.clone(),
105                appid:e.appid.clone(),
106            },
107            Self::Alipay(e) => object! {
108                appid:e.appid.clone(),
109                sp_mchid:e.sp_mchid.clone()
110            },
111            Pay::None => object! {
112                appid:"",
113                sp_mchid:""
114            },
115            Pay::Ccbc(e) => object! {
116                appid:e.appid.clone(),
117                sp_mchid:e.sp_mchid.clone()
118            },
119            Pay::Yrcc(e) => object! {
120                appid:e.appid.clone(),
121                sp_mchid:e.sp_mchid.clone()
122            },
123        }
124    }
125    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> {
126        match self {
127            Self::Wechat(e) => e.pay(channel,
128                                     types,
129                                     sub_mchid,
130                                     out_trade_no,
131                                     description,
132                                     total_fee,
133                                     sp_openid,
134            ),
135            Self::Alipay(e) => e.pay(channel,
136                                     types,
137                                     sub_mchid,
138                                     out_trade_no,
139                                     description,
140                                     total_fee,
141                                     sp_openid,
142            ),
143            Pay::None => Err("No login data".to_owned()),
144            Pay::Ccbc(e) => e.pay(
145                channel,
146                types,
147                sub_mchid,
148                out_trade_no,
149                description,
150                total_fee,
151                sp_openid,
152            ),
153            Pay::Yrcc(e) => e.pay(
154                channel,
155                types,
156                sub_mchid,
157                out_trade_no,
158                description,
159                total_fee,
160                sp_openid,
161            )
162        }
163    }
164
165    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> {
166        match self {
167            Self::Wechat(e) => e.micropay(
168                channel,
169                auth_code,
170                sub_mchid,
171                out_trade_no,
172                description,
173                total_fee,
174                org_openid,
175                ip,
176            ),
177            Self::Alipay(e) => e.micropay(
178                channel,
179                auth_code,
180                sub_mchid,
181                out_trade_no,
182                description,
183                total_fee,
184                org_openid,
185                ip,
186            ),
187            Pay::Ccbc(e) => e.micropay(
188                channel,
189                auth_code,
190                sub_mchid,
191                out_trade_no,
192                description,
193                total_fee,
194                org_openid,
195                ip,
196            ),
197            Pay::None => Err("No login data".to_owned()),
198            &mut Pay::Yrcc(_) => todo!()
199        }
200    }
201
202    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
203        match self {
204            Self::Wechat(e) => e.close(
205                out_trade_no, sub_mchid,
206            ),
207            Self::Alipay(e) => e.close(
208                out_trade_no, sub_mchid,
209            ),
210            Pay::Ccbc(e) => e.close(
211                out_trade_no, sub_mchid,
212            ),
213            Pay::None => Err("No login data".to_owned()),
214            &mut Pay::Yrcc(_) => todo!()
215        }
216    }
217
218    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
219        match self {
220            Self::Wechat(e) => e.pay_query(
221                out_trade_no,
222                sub_mchid,
223            ),
224            Self::Alipay(e) => e.pay_query(
225                out_trade_no,
226                sub_mchid,
227            ),
228            Self::Ccbc(e) => e.pay_query(
229                out_trade_no,
230                sub_mchid,
231            ),
232            Pay::None => Err("No login data".to_owned()),
233            &mut Pay::Yrcc(_) => todo!()
234        }
235    }
236    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
237        match self {
238            Self::Wechat(e) => e.pay_micropay_query(
239                out_trade_no,
240                sub_mchid,
241                channel,
242            ),
243            Self::Alipay(e) => e.pay_query(
244                out_trade_no,
245                sub_mchid,
246            ),
247            Pay::Ccbc(e) => e.pay_micropay_query(
248                out_trade_no,
249                sub_mchid,
250                channel,
251            ),
252            Pay::None => Err("No login data".to_owned()),
253            &mut Pay::Yrcc(_) => todo!()
254        }
255    }
256
257    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
258        match self {
259            Self::Wechat(e) => e.pay_notify(
260                nonce,
261                ciphertext,
262                associated_data,
263            ),
264            Self::Alipay(e) => e.pay_notify(
265                nonce,
266                ciphertext,
267                associated_data,
268            ),
269            Pay::None => Err("No login data".to_owned()),
270            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
271        }
272    }
273
274    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> {
275        match self {
276            Self::Wechat(e) => e.refund(
277                sub_mchid,
278                out_trade_no,
279                transaction_id,
280                out_refund_no,
281                amount,
282                total,
283                currency,
284            ),
285            Self::Alipay(e) => e.refund(
286                sub_mchid,
287                out_trade_no,
288                transaction_id,
289                out_refund_no,
290                amount,
291                total,
292                currency,
293            ),
294            Self::Ccbc(e) => e.refund(
295                sub_mchid,
296                out_trade_no,
297                transaction_id,
298                out_refund_no,
299                amount,
300                total,
301                currency,
302            ),
303            Pay::None => Err("No login data".to_owned()),
304            &mut Pay::Yrcc(_) => todo!()
305        }
306    }
307
308    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> {
309        match self {
310            Self::Wechat(e) => e.micropay_refund(
311                sub_mchid,
312                out_trade_no,
313                transaction_id,
314                out_refund_no,
315                amount,
316                total,
317                currency,
318                refund_text,
319            ),
320            Self::Alipay(e) => e.refund(
321                sub_mchid,
322                out_trade_no,
323                transaction_id,
324                out_refund_no,
325                amount,
326                total,
327                currency,
328            ),
329            Pay::None => Err("No login data".to_owned()),
330            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
331        }
332    }
333
334    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
335        match self {
336            Self::Wechat(e) => e.refund_notify(
337                nonce,
338                ciphertext,
339                associated_data,
340            ),
341            Self::Alipay(e) => e.refund_notify(
342                nonce,
343                ciphertext,
344                associated_data,
345            ),
346            Pay::None => Err("No login data".to_owned()),
347            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
348        }
349    }
350
351    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
352        match self {
353            Self::Wechat(e) => e.refund_query(
354                trade_no,
355                out_refund_no,
356                sub_mchid,
357            ),
358            Self::Alipay(e) => e.refund_query(
359                trade_no,
360                out_refund_no,
361                sub_mchid,
362            ),
363            Pay::Ccbc(e) => e.refund_query(
364                trade_no,
365                out_refund_no,
366                sub_mchid),
367            Pay::None => Err("No login data".to_owned()),
368            &mut Pay::Yrcc(_) => todo!()
369        }
370    }
371
372    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> {
373        match self {
374            Self::Wechat(e) => e.incoming(
375                business_code,
376                contact_info,
377                subject_info,
378                business_info,
379                settlement_info,
380                bank_account_info,
381            ),
382            Self::Alipay(e) => e.incoming(
383                business_code,
384                contact_info,
385                subject_info,
386                business_info,
387                settlement_info,
388                bank_account_info,
389            ),
390            Pay::None => Err("No incoming data".to_owned()),
391            &mut Pay::Ccbc(_) | &mut Pay::Yrcc(_) => todo!()
392        }
393    }
394}
395
396pub trait PayMode {
397    /// 检查账户是否通畅
398    fn check(&mut self) -> Result<bool, String>;
399    /// 获取商户号和状态
400    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String>;
401    fn config(&mut self) -> JsonValue;
402    #[allow(clippy::too_many_arguments)]
403    /// 支付
404    /// * channel 渠道 wechat alipay
405    /// * out_trade_no 商户订单号
406    /// * sub_mchid 子商户号
407    /// * description 商品名称
408    /// * total_fee 支付金额
409    /// * notify_url 通知地址
410    /// * sp_openid 支付客户ID
411    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>;
412
413    #[allow(clippy::too_many_arguments)]
414    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>;
415    /// 关闭订单
416    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
417    /// 订单查询
418    /// * out_trade_no 商户订单号
419    /// * sub_mchid 子商户号
420    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
421    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String>;
422    /// 支付成功通知
423    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String>;
424
425    #[allow(clippy::too_many_arguments)]
426    /// 订单退款
427    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>;
428    #[allow(clippy::too_many_arguments)]
429    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>;
430    /// 退款通知
431    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String>;
432    /// 退款查询
433    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String>;
434    /// 进件
435    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>;
436}
437
438/// 支付回调数据
439#[derive(Debug)]
440pub struct PayNotify {
441    /// 支付方式
442    trade_type: TradeType,
443    /// 商户订单号
444    out_trade_no: String,
445    /// 服务商商户号
446    sp_mchid: String,
447    /// 子商户号
448    sub_mchid: String,
449    /// 应用APPID
450    sp_appid: String,
451    /// 微信内部订单号
452    transaction_id: String,
453    /// 成功时间
454    success_time: i64,
455    /// 服务商用户ID
456    sp_openid: String,
457    /// 子商户用户ID
458    sub_openid: String,
459    /// 支付金额
460    total: f64,
461    /// 实际支付金额
462    payer_total: f64,
463    /// 币种
464    currency: String,
465    /// 实际支付币种
466    payer_currency: String,
467    /// 状态
468    trade_state: TradeState,
469}
470impl PayNotify {
471    pub fn json(&self) -> JsonValue {
472        object! {
473            "trade_type" => self.trade_type.to_string(),
474            "out_trade_no" => self.out_trade_no.to_string(),
475            "sp_mchid" => self.sp_mchid.to_string(),
476            "sub_mchid" => self.sub_mchid.to_string(),
477            "sp_appid" => self.sp_appid.to_string(),
478            "transaction_id" => self.transaction_id.to_string(),
479            "success_time" => self.success_time,
480            "sp_openid" => self.sp_openid.to_string(),
481            "sub_openid" => self.sub_openid.to_string(),
482            "total" => self.total,
483            "payer_total" => self.payer_total,
484            "currency" => self.currency.clone(),
485            "payer_currency" => self.payer_currency.clone(),
486            "trade_state" => self.trade_state.to_string(),
487        }
488    }
489    pub fn success_time(datetime: &str) -> i64 {
490        if datetime.is_empty() {
491            return 0;
492        }
493        let datetime = DateTime::parse_from_rfc3339(datetime).unwrap();
494        datetime.timestamp()
495    }
496    pub fn alipay_time(datetime: &str) -> i64 {
497        if datetime.is_empty() {
498            return 0;
499        }
500        let t = NaiveDateTime::parse_from_str(datetime, "%Y-%m-%d %H:%M:%S").unwrap();
501        t.and_utc().timestamp()
502    }
503    pub fn datetime_to_timestamp(datetime: &str, fmt: &str) -> i64 {
504        let t = NaiveDateTime::parse_from_str(datetime, fmt).unwrap();
505        let tz = FixedOffset::east_opt(Local::now().offset().local_minus_utc()).unwrap();
506        t.and_local_timezone(tz).unwrap().timestamp()
507    }
508}
509#[derive(Debug)]
510pub enum TradeType {
511    /// 小程序与JSAPI
512    JSAPI,
513    MICROPAY,
514    /// 支付方式未知
515    None,
516}
517impl TradeType {
518    fn from(code: &str) -> TradeType {
519        match code {
520            "JSAPI" => TradeType::JSAPI,
521            "MICROPAY" => TradeType::MICROPAY,
522            _ => TradeType::None
523        }
524    }
525    fn to_string(&self) -> &'static str {
526        match self {
527            TradeType::JSAPI => "JSAPI",
528            TradeType::MICROPAY => "MICROPAY",
529            TradeType::None => ""
530        }
531    }
532}
533#[derive(Debug)]
534pub enum TradeState {
535    /// 支付成功
536    SUCCESS,
537    /// 转入退款
538    REFUND,
539    /// 未支付
540    NOTPAY,
541    /// 已关闭
542    CLOSED,
543    /// 已撤销(仅付款码支付会返回)
544    REVOKED,
545    /// 用户支付中(仅付款码支付会返回)
546    USERPAYING,
547    /// 支付失败(仅付款码支付会返回)
548    PAYERROR,
549    None,
550}
551impl TradeState {
552    fn from(code: &str) -> TradeState {
553        match code {
554            "SUCCESS" | "TRADE_SUCCESS" | "成功" => TradeState::SUCCESS,
555            "REFUND" | "已全额退款" | "已部分退款" => TradeState::REFUND,
556            "NOTPAY" | "WAIT_BUYER_PAY" => TradeState::NOTPAY,
557            "CLOSED" | "TRADE_CLOSED" => TradeState::CLOSED,
558            "REVOKED" => TradeState::REVOKED,
559            "USERPAYING" | "待银行确认" => TradeState::USERPAYING,
560            "PAYERROR" | "TRADE_FINISHED" | "失败" => TradeState::PAYERROR,
561            _ => TradeState::None
562        }
563    }
564
565    fn to_string(&self) -> &'static str {
566        match self {
567            TradeState::SUCCESS => "已支付",
568            TradeState::REFUND => "已退款",
569            TradeState::NOTPAY => "待支付",
570            TradeState::CLOSED => "已关闭",
571            TradeState::REVOKED => "已关闭",
572            TradeState::USERPAYING => "待支付",
573            TradeState::PAYERROR => "支付失败",
574            TradeState::None => "待支付"
575        }
576    }
577}
578
579#[derive(Debug)]
580pub enum RefundStatus {
581    /// 退款成功
582    SUCCESS,
583    /// 退款关闭
584    CLOSED,
585    /// 退款处理中
586    PROCESSING,
587    /// 退款异常,手动处理此笔退款
588    ABNORMAL,
589    None,
590}
591impl RefundStatus {
592    fn from(code: &str) -> RefundStatus {
593        match code {
594            "SUCCESS" | "Y" | "REFUND_SUCCESS" => RefundStatus::SUCCESS,
595            "CLOSED" => RefundStatus::CLOSED,
596            "PROCESSING" | "N" => RefundStatus::PROCESSING,
597            "ABNORMAL" => RefundStatus::ABNORMAL,
598            _ => RefundStatus::None
599        }
600    }
601    fn to_string(&self) -> &'static str {
602        match self {
603            RefundStatus::SUCCESS => "已退款",
604            RefundStatus::None => "未退款",
605            RefundStatus::CLOSED => "已关闭",
606            RefundStatus::PROCESSING => "退款中",
607            RefundStatus::ABNORMAL => "退款异常"
608        }
609    }
610}
611/// 退款回调数据
612#[derive(Debug)]
613pub struct RefundNotify {
614    /// 商户订单号
615    out_trade_no: String,
616    refund_no: String,
617    /// 服务商商户号
618    sp_mchid: String,
619    /// 子商户号
620    sub_mchid: String,
621    /// 微信内部订单号
622    transaction_id: String,
623    /// 退款订单号
624    refund_id: String,
625    /// 成功时间
626    success_time: i64,
627    /// 支付金额
628    total: f64,
629    /// 退款金额
630    refund: f64,
631    /// 实际支付金额
632    payer_total: f64,
633    /// 实际退款金额
634    payer_refund: f64,
635    /// 状态
636    status: RefundStatus,
637}
638
639impl RefundNotify {
640    pub fn json(&self) -> JsonValue {
641        object! {
642            "out_trade_no" => self.out_trade_no.clone(),
643            "sp_mchid" => self.sp_mchid.clone(),
644            "sub_mchid" => self.sub_mchid.clone(),
645            "transaction_id" => self.transaction_id.clone(),
646            "success_time" => self.success_time,
647            "total" => self.total,
648            "refund" => self.refund,
649            "payer_total" => self.payer_total,
650            "payer_refund" => self.payer_refund,
651            "status" => self.status.to_string(),
652            "refund_no"=>self.refund_no.clone(),
653            "refund_id"=>self.refund_id.clone()
654        }
655    }
656}
657
658#[derive(Debug)]
659pub enum Types {
660    /// JS支付
661    Jsapi,
662    /// 扫码支付
663    Native,
664    /// H5页面
665    H5,
666    /// 小程序
667    MiniJsapi,
668    /// App
669    App,
670    /// 付款码
671    Micropay,
672}
673impl Types {
674    pub fn str(self) -> &'static str {
675        match self {
676            Types::Jsapi => "jsapi",
677            Types::Native => "native",
678            Types::H5 => "h5",
679            Types::MiniJsapi => "minijsapi",
680            Types::App => "app",
681            Types::Micropay => "micropay"
682        }
683    }
684    pub fn from(name: &str) -> Self {
685        match name {
686            "jsapi" => Types::Jsapi,
687            "native" => Types::Native,
688            "h5" => Types::H5,
689            "minijsapi" => Types::MiniJsapi,
690            "app" => Types::App,
691            "micropay" => Types::Micropay,
692            _ => Types::Jsapi
693        }
694    }
695}