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()
33 .sample_iter(&Alphanumeric) .take(10) .map(char::from)
36 .collect();
37
38 let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
39 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
41
42 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
44 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
45 let mut signer =
47 Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
48 signer
50 .update(sign_txt.as_bytes())
51 .expect("Failed to update signer");
52 let signature = signer.sign_to_vec().expect("Failed to sign");
54 let signature_b64 = STANDARD.encode(signature);
55 let sign = format!(
56 r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
57 self.mchid.as_str(),
58 self.serial_no
59 );
60 sign
61 }
62
63 pub fn paysign(&mut self, prepay_id: &str) -> JsonValue {
64 let timestamp = Utc::now().timestamp(); let random_string: String = rng()
66 .sample_iter(&Alphanumeric) .take(10) .map(char::from)
69 .collect();
70
71 let sign_txt = format!(
72 "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
73 self.appid
74 );
75 let private_key_pem = fs::read(self.cert_path.as_str()).expect("Failed to read key file");
77
78 let rsa = Rsa::private_key_from_pem(&private_key_pem).expect("Failed to load private key");
80 let pkey = PKey::from_rsa(rsa).expect("Failed to create PKey");
81 let mut signer =
83 Signer::new(MessageDigest::sha256(), &pkey).expect("Failed to create signer");
84 signer
86 .update(sign_txt.as_bytes())
87 .expect("Failed to update signer");
88 let signature = signer.sign_to_vec().expect("Failed to sign");
90 let signature_b64 = STANDARD.encode(signature);
91 let sign = signature_b64;
92 object! {
93 timeStamp:timestamp,
94 nonceStr:random_string,
95 package:prepay_id,
96 signType:"RSA",
97 paySign:sign
98 }
99 }
100}
101impl PayMode for Wechat {
102 fn login(&self, code: &str) -> Result<JsonValue, String> {
103 let mut http = br_http::Http::new();
104 match http
105 .get(
106 "https://api.weixin.qq.com/sns/jscode2session"
107 .to_string()
108 .as_str(),
109 )
110 .header("Accept", "application/json")
111 .header("User-Agent", "api")
112 .header("Content-Type", "application/json")
113 .query(object! {
114 appid: self.appid.as_str(),
115 secret: self.secret.as_str(),
116 js_code:code,
117 grant_type:"authorization_code",
118 })
119 .json()
120 {
121 Ok(e) => Ok(e),
122 Err(e) => Err(e),
123 }
124 }
125
126 fn jsapi(
127 &mut self,
128 sub_mchid: &str,
129 out_trade_no: &str,
130 description: &str,
131 total_fee: f64,
132 notify_url: &str,
133 sp_openid: &str,
134 ) -> Result<JsonValue, String> {
135 let url = "/v3/pay/partner/transactions/jsapi";
136 let total = (total_fee * 100.0) as u64;
137 let body = object! {
138 "sp_appid" => self.appid.clone(),
139 "sp_mchid"=> self.mchid.clone(),
140 "sub_mchid"=> sub_mchid,
141 "description"=>description,
142 "out_trade_no"=>out_trade_no,
143 "notify_url"=>notify_url,
144 "support_fapiao"=>true,
145 "amount"=>object! {
146 total: total,
147 currency:"CNY"
148 },
149 "payer"=>object! {
150 sp_openid:sp_openid
151 }
152 };
153 let sign = self.sign("POST", url, body.to_string().as_str());
154 let mut http = br_http::Http::new();
155 match http
156 .post(format!("https://api.mch.weixin.qq.com{}", url).as_str())
157 .header("Accept", "application/json")
158 .header("User-Agent", "api")
159 .header("Content-Type", "application/json")
160 .header("Authorization", sign.as_str())
161 .raw_json(body)
162 .json()
163 {
164 Ok(e) => {
165 if e.has_key("prepay_id") {
166 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str());
167 Ok(signinfo)
168 } else {
169 Err(e["message"].to_string())
170 }
171 }
172 Err(e) => Err(e),
173 }
174 }
175
176 fn order_trade(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
177 let url = format!(
178 "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
179 out_trade_no, sub_mchid, self.mchid
180 );
181 let sign = self.sign("GET", url.as_str(), "");
182 let mut http = br_http::Http::new();
183 match http
184 .get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str())
185 .header("Accept", "application/json")
186 .header("User-Agent", "api")
187 .header("Content-Type", "application/json")
188 .header("Authorization", sign.as_str())
189 .json()
190 {
191 Ok(e) => {
192 if e.has_key("message") {
193 return Err(e["message"].to_string());
194 }
195 let pay_time = e["success_time"].as_str().unwrap();
196 let datetime = DateTime::parse_from_rfc3339(pay_time).unwrap();
197 let pay_time = datetime.timestamp();
198 let mut info = object! {
199 pay_time:pay_time,
200 sp_appid:e["sp_appid"].clone(),
201 transaction_id:e["transaction_id"].clone(),
202 sp_mchid:e["sp_mchid"].clone(),
203 sub_mchid:e["sub_mchid"].clone(),
204 order_no:e["out_trade_no"].clone(),
205 status:"",
206 sp_openid:e["payer"]["sp_openid"].clone(),
207 sub_openid:e["payer"]["sub_openid"].clone(),
208 amount_currency:e["amount"]["currency"].clone(),
209 amount_total:e["amount"]["total"].clone(),
210 trade_state_desc:e["trade_state_desc"].clone(),
211 trade_type:e["trade_type"].clone(),
212 };
213 info["status"] = match e["trade_state"].as_str().unwrap() {
214 "SUCCESS" => "已支付",
215 _ => "未知",
216 }
217 .into();
218 Ok(info)
219 }
220 Err(e) => Err(e),
221 }
222 }
223
224 fn refunds(
225 &mut self,
226 sub_mchid: &str,
227 out_trade_no: &str,
228 transaction_id: &str,
229 out_refund_no: &str,
230 refund: f64,
231 total: f64,
232 currency: &str,
233 ) -> Result<JsonValue, String> {
234 let url = "/v3/refund/domestic/refunds";
235 let refund = (refund * 100.0) as u64;
236 let total = (total * 100.0) as u64;
237 let body = object! {
238 "sub_mchid"=> sub_mchid,
239 "transaction_id"=>transaction_id,
240 "out_trade_no"=>out_trade_no,
241 "out_refund_no"=>out_refund_no,
242 "amount"=>object! {
243 refund: refund,
244 total: total,
245 currency:currency
246 }
247 };
248 let sign = self.sign("POST", url, body.to_string().as_str());
249 let mut http = br_http::Http::new();
250 match http
251 .post(format!("https://api.mch.weixin.qq.com{}", url).as_str())
252 .header("Accept", "application/json")
253 .header("User-Agent", "api")
254 .header("Content-Type", "application/json")
255 .header("Authorization", sign.as_str())
256 .raw_json(body)
257 .json()
258 {
259 Ok(e) => {
260 if e.has_key("message") {
261 return Err(e["message"].to_string());
262 }
263 let mut refund_time = 0.0;
264 if e.has_key("success_time"){
265 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
266 if !success_time.is_empty() {
267 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
268 refund_time = datetime.timestamp() as f64;
269 }
270 }
271
272 let status = match e["status"].as_str().unwrap() {
273 "PROCESSING" => "退款中",
274 "SUCCESS" => "已退款",
275 _ => "无退款",
276 };
277 let info = object! {
278 refund_id: e["refund_id"].clone(),
279 user_received_account:e["user_received_account"].clone(),
280 status:status,
281 refund_time:refund_time,
282 out_refund_no: e["out_refund_no"].clone(),
283 };
284 Ok(info)
285 }
286 Err(e) => Err(e),
287 }
288 }
289}