br_pay/
lib.rs

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