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