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