br_pay/
lib.rs

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