1use crate::PayMode;
2use base64::engine::general_purpose::STANDARD;
3use base64::Engine;
4use json::{object, JsonValue};
5use std::fs;
6
7#[derive(Clone)]
8pub struct Wechat {
9 pub appid: String,
11 pub secret: String,
13 pub mchid: String,
15 pub serial_no: String,
17 pub cert_path: String,
19}
20
21use chrono::{DateTime, Utc};
22use openssl::hash::MessageDigest;
23use openssl::pkey::PKey;
24use openssl::rsa::Rsa;
25use openssl::sign::Signer;
26use rand::distr::Alphanumeric;
27use rand::{rng, Rng};
28
29impl Wechat {
30 pub fn sign(&mut self, method: &str, url: &str, body: &str) -> String {
31 let timestamp = Utc::now().timestamp(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
35
36 let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
37 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
39
40 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
42 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
43 let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
45 signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
47 let signature = signer.sign_to_vec().expect("Failed to sign");
49 let signature_b64 = STANDARD.encode(signature);
50 let sign = format!(
51 r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
52 self.mchid.as_str(),
53 self.serial_no
54 );
55 sign
56 }
57
58 pub fn paysign(&mut self, prepay_id: &str) -> JsonValue {
59 let timestamp = Utc::now().timestamp(); let random_string: String = rng().sample_iter(&Alphanumeric) .take(10) .map(char::from).collect();
63
64 let sign_txt = format!(
65 "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
66 self.appid
67 );
68 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
70
71 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
73 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
74 let mut signer = Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
76 signer.update(sign_txt.as_bytes()).expect("Failed to update signer");
78 let signature = signer.sign_to_vec().expect("Failed to sign");
80 let signature_b64 = STANDARD.encode(signature);
81 let sign = signature_b64;
82 object! {
83 timeStamp:timestamp,
84 nonceStr:random_string,
85 package:prepay_id,
86 signType:"RSA",
87 paySign:sign
88 }
89 }
90}
91impl PayMode for Wechat {
92 fn login(&self, code: &str) -> Result<JsonValue, String> {
93 let mut http = br_http::Http::new();
94 match http.get(
95 "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
96 ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
97 appid: self.appid.as_str(),
98 secret: self.secret.as_str(),
99 js_code:code,
100 grant_type:"authorization_code",
101 }).json() {
102 Ok(e) => Ok(e),
103 Err(e) => Err(e),
104 }
105 }
106
107 fn jsapi(
108 &mut self,
109 sub_mchid: &str,
110 out_trade_no: &str,
111 description: &str,
112 total_fee: f64,
113 notify_url: &str,
114 sp_openid: &str,
115 ) -> Result<JsonValue, String> {
116 let url = "/v3/pay/partner/transactions/jsapi";
117 let total = (total_fee * 100.0) as u64;
118 let body = object! {
119 "sp_appid" => self.appid.clone(),
120 "sp_mchid"=> self.mchid.clone(),
121 "sub_mchid"=> sub_mchid,
122 "description"=>description,
123 "out_trade_no"=>out_trade_no,
124 "notify_url"=>notify_url,
125 "support_fapiao"=>true,
126 "amount"=>object! {
127 total: total,
128 currency:"CNY"
129 },
130 "payer"=>object! {
131 sp_openid:sp_openid
132 }
133 };
134 let sign = self.sign("POST", url, body.to_string().as_str());
135 let mut http = br_http::Http::new();
136 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).json() {
137 Ok(e) => {
138 if e.has_key("prepay_id") {
139 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str());
140 Ok(signinfo)
141 } else {
142 Err(e["message"].to_string())
143 }
144 }
145 Err(e) => Err(e),
146 }
147 }
148
149 fn order_trade(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
150 let url = format!(
151 "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
152 out_trade_no, sub_mchid, self.mchid
153 );
154 let sign = self.sign("GET", url.as_str(), "");
155 let mut http = br_http::Http::new();
156 match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).json() {
157 Ok(e) => {
158 if e.has_key("message") {
159 return Err(e["message"].to_string());
160 }
161
162 let mut pay_time = 0.0;
163 if e.has_key("success_time") {
164 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
165 if !success_time.is_empty() {
166 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
167 pay_time = datetime.timestamp() as f64;
168 }
169 }
170
171 let mut info = object! {
172 pay_time:pay_time,
173 sp_appid:e["sp_appid"].clone(),
174 transaction_id:e["transaction_id"].clone(),
175 sp_mchid:e["sp_mchid"].clone(),
176 sub_mchid:e["sub_mchid"].clone(),
177 order_no:e["out_trade_no"].clone(),
178 status:"",
179 sp_openid:e["payer"]["sp_openid"].clone(),
180 sub_openid:e["payer"]["sub_openid"].clone(),
181 amount_currency:e["amount"]["currency"].clone(),
182 amount_total:e["amount"]["total"].clone(),
183 trade_state_desc:e["trade_state_desc"].clone(),
184 trade_type:e["trade_type"].clone(),
185 };
186 info["status"] = match e["trade_state"].as_str().unwrap() {
187 "SUCCESS" => "已支付",
188 _ => "未知",
189 }.into();
190 Ok(info)
191 }
192 Err(e) => Err(e),
193 }
194 }
195
196 fn refunds(
197 &mut self,
198 sub_mchid: &str,
199 out_trade_no: &str,
200 transaction_id: &str,
201 out_refund_no: &str,
202 refund: f64,
203 total: f64,
204 currency: &str,
205 ) -> Result<JsonValue, String> {
206 let url = "/v3/refund/domestic/refunds";
207 let refund = (refund * 100.0) as u64;
208 let total = (total * 100.0) as u64;
209 let body = object! {
210 "sub_mchid"=> sub_mchid,
211 "transaction_id"=>transaction_id,
212 "out_trade_no"=>out_trade_no,
213 "out_refund_no"=>out_refund_no,
214 "amount"=>object! {
215 refund: refund,
216 total: total,
217 currency:currency
218 }
219 };
220 let sign = self.sign("POST", url, body.to_string().as_str());
221 let mut http = br_http::Http::new();
222 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).json() {
223 Ok(e) => {
224 if e.has_key("message") {
225 return Err(e["message"].to_string());
226 }
227 let mut refund_time = 0.0;
228 if e.has_key("success_time") {
229 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
230 if !success_time.is_empty() {
231 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
232 refund_time = datetime.timestamp() as f64;
233 }
234 }
235
236 let status = match e["status"].as_str().unwrap() {
237 "PROCESSING" => "退款中",
238 "SUCCESS" => "已退款",
239 _ => "无退款",
240 };
241 let info = object! {
242 refund_id: e["refund_id"].clone(),
243 user_received_account:e["user_received_account"].clone(),
244 status:status,
245 refund_time:refund_time,
246 out_refund_no: e["out_refund_no"].clone(),
247 };
248 Ok(info)
249 }
250 Err(e) => Err(e),
251 }
252 }
253}