rust_pay_wf 0.9.0

Rust 微信、支付宝、银联支付SDK
Documentation
use crate::alipay::{AlipayNotify, AlipayNotifyData};
use crate::config::{AlipayConfig, Mode};
use crate::errors::PayError;
use crate::utils::{get_cert_sn, get_root_cert_sn, rsa_sign_sha256_pem};
use reqwest::Client;
use std::collections::BTreeMap;
use std::sync::Arc;
use serde_json::json;
use urlencoding::encode;

pub struct AlipayClient {
    cfg: Arc<AlipayConfig>,
    http: Client,
    gateway: String,
    mode: Mode,
}

impl AlipayClient {
    pub fn new(cfg: Arc<AlipayConfig>, mode: Mode) -> Self {
        Self::with_mode(cfg, mode)
    }

    pub fn with_mode(cfg: Arc<AlipayConfig>, mode: Mode) -> Self {
        let gateway = match mode {
            Mode::Sandbox => "https://openapi.alipaydev.com/gateway.do".to_string(),
            _ => cfg.gateway.clone(),
        };
        Self {
            cfg,
            http: Client::new(),
            gateway,
            mode,
        }
    }

    fn build_sign_string(params: &BTreeMap<String, String>) -> String {
        params
            .iter()
            .map(|(k, v)| format!("{}={}", k, v))
            .collect::<Vec<_>>()
            .join("&")
    }

    fn build_service_provider_params(&self, order: &mut serde_json::Value) {
        if let Mode::Service = self.mode {
            if let Some(provider_id) = &self.cfg.sys_service_provider_id {
                if !order.get("extend_params").is_some() {
                    order["extend_params"] = serde_json::json!({});
                }
                if let Some(obj) = order["extend_params"].as_object_mut() {
                    obj.insert(
                        "sys_service_provider_id".to_string(),
                        serde_json::Value::String(provider_id.clone()),
                    );
                }
            }
        }
    }

    fn build_common_params(
        &self,
        method: &str,
        order: &serde_json::Value,
    ) -> BTreeMap<String, String> {
        let mut params = BTreeMap::new();

        params.insert("app_id".into(), self.cfg.app_id.clone());
        params.insert("method".into(), method.to_string());
        params.insert("format".into(), "json".into());
        params.insert("charset".into(), self.cfg.charset.clone());
        params.insert("sign_type".into(), self.cfg.sign_type.clone());
        params.insert(
            "timestamp".into(),
            chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
        );
        params.insert("version".into(), "1.0".to_string());

        // 证书模式
        if self.cfg.app_cert_path.is_some() && self.cfg.alipay_root_cert_path.is_some() {
            if let Some(app_cert_path) = &self.cfg.app_cert_path {
                let app_sn = get_cert_sn(app_cert_path);
                println!("app_cert_sn: {:?}", app_sn);
                if let Ok(app_sn) = app_sn {
                    params.insert("app_cert_sn".into(), app_sn);
                }
            }

            if let Some(root_cert_path) = &self.cfg.alipay_root_cert_path {
                let root_sn = get_root_cert_sn(root_cert_path);
                println!("alipay_root_cert_sn: {:?}", root_sn);
                if let Ok(root_sn) = root_sn {
                    params.insert("alipay_root_cert_sn".into(), root_sn);
                }
            }
        }
        // 服务商参数
        if let Mode::Service = self.mode {
            if let Some(auth_token) = &self.cfg.app_auth_token {
                params.insert("app_auth_token".into(), auth_token.clone());
            }
        }
        //如果order中没有notify_url和return_url,才使用配置中的
        if order.get("notify_url").is_none(){
            if let Some(n) = &self.cfg.notify_url {
                params.insert("notify_url".into(), n.clone());
            }
        }
        params
    }

    async fn do_request(
        &self,
        params: BTreeMap<String, String>,
    ) -> Result<serde_json::Value, PayError> {
        let sign_src = Self::build_sign_string(&params);
        let sign = rsa_sign_sha256_pem(&self.cfg.private_key_pem, &sign_src)
            .map_err(|e| PayError::Crypto(e.to_string()))?;

        let mut params_with_sign = params;
        params_with_sign.insert("sign".into(), sign);

        let query = params_with_sign
            .iter()
            .map(|(k, v)| format!("{}={}", k, encode(v)))
            .collect::<Vec<_>>()
            .join("&");

        let url = format!("{}?{}", self.gateway, query);

        let resp = self
            .http
            .get(&url)
            .send()
            .await
            .map_err(PayError::Http)?
            .text()
            .await
            .map_err(PayError::Http)?;

        let v: serde_json::Value = serde_json::from_str(&resp).map_err(PayError::Json)?;

        if let Some(err) = v.get("error_response") {
            return Err(PayError::from_alipay_response(err));
        }
        Ok(v)
    }

    pub async fn app(&self, mut order: serde_json::Value) -> Result<serde_json::Value, PayError> {
        if order.get("product_code").is_none() {
            order["product_code"] = json!("QUICK_MSECURITY_PAY");
        }
        self.build_service_provider_params(&mut order);
        let mut params = self.build_common_params("alipay.trade.app.pay", &order);
        params.insert("biz_content".into(), order.to_string());

        let sign_src = Self::build_sign_string(&params);
        let sign = rsa_sign_sha256_pem(&self.cfg.private_key_pem, &sign_src)
            .map_err(|e| PayError::Crypto(e.to_string()))?;
        params.insert("sign".into(), sign);

        let order_str = params
            .iter()
            .map(|(k, v)| format!("{}={}", k, encode(v)))
            .collect::<Vec<_>>()
            .join("&");

        Ok(serde_json::json!({ "order_string": order_str }))
    }

    pub async fn scan(&self, mut order: serde_json::Value) -> Result<serde_json::Value, PayError> {
        //没有 product_code 时,默认值为 FACE_TO_FACE_PAYMENT
        if order.get("product_code").is_none() {
            order["product_code"] = json!("FACE_TO_FACE_PAYMENT");
        }
        self.build_service_provider_params(&mut order);
        let mut params = self.build_common_params("alipay.trade.precreate", &order);
        params.insert("biz_content".into(), order.to_string());
        self.do_request(params).await
    }

    /// ✅ H5 支付(手机浏览器)
    pub async fn h5(&self, mut order: serde_json::Value) -> Result<serde_json::Value, PayError> {
        //没有 product_code 时,默认值为 QUICK_WAP_PAY
        if order.get("product_code").is_none() {
            order["product_code"] = json!("QUICK_WAP_WAY");
        }
        self.build_service_provider_params(&mut order);
        let mut params = self.build_common_params("alipay.trade.wap.pay", &order);
        params.insert("biz_content".into(), order.to_string());

        let sign_src = Self::build_sign_string(&params);
        let sign = rsa_sign_sha256_pem(&self.cfg.private_key_pem, &sign_src)
            .map_err(|e| PayError::Crypto(e.to_string()))?;
        params.insert("sign".into(), sign);

        // 拼接跳转链接
        let query = params
            .iter()
            .map(|(k, v)| format!("{}={}", k, encode(v)))
            .collect::<Vec<_>>()
            .join("&");
        let url = format!("{}?{}", self.gateway, query);

        Ok(serde_json::json!({ "pay_url": url }))
    }

    /// PC 网页支付
    pub async fn page(&self, mut order: serde_json::Value) -> Result<serde_json::Value, PayError> {
        //没有 product_code 时,默认值为 FAST_INSTANT_TRADE_PAY
        if order.get("product_code").is_none() {
            order["product_code"] = json!("FAST_INSTANT_TRADE_PAY");
        }
        self.build_service_provider_params(&mut order);
        let mut params = self.build_common_params("alipay.trade.page.pay", &order);
        params.insert("biz_content".into(), order.to_string());

        let sign_src = Self::build_sign_string(&params);
        let sign = rsa_sign_sha256_pem(&self.cfg.private_key_pem, &sign_src)
            .map_err(|e| PayError::Crypto(e.to_string()))?;
        params.insert("sign".into(), sign);

        // 返回 form 表单字符串(前端可直接渲染提交)
        let form_html = format!(
            r#"<form id="alipaysubmit" name="alipaysubmit" action="{}" method="GET">
{}<input type="submit" value="Pay with Alipay" style="display:none"></form>
<script>document.forms['alipaysubmit'].submit();</script>"#,
            self.gateway,
            params
                .iter()
                .map(|(k, v)| format!(r#"<input type="hidden" name="{}" value="{}"/>"#, k, v))
                .collect::<Vec<_>>()
                .join("\n")
        );

        Ok(serde_json::json!({ "form_html": form_html }))
    }

    /// 小程序支付(创建订单后由前端拉起)
    pub async fn mini_program(
        &self,
        mut order: serde_json::Value,
    ) -> Result<serde_json::Value, PayError> {
        self.build_service_provider_params(&mut order);
        //没有 product_code 时,默认值为 JSAPI_PAY
        if order.get("product_code").is_none() {
            order["JSAPI_PAY"] = json!("JSAPI_PAY");
        }
        let mut params = self.build_common_params("alipay.trade.create", &order);
        params.insert("biz_content".into(), order.to_string());

        let resp = self.do_request(params).await?;

        if let Some(result) = resp.get("alipay_trade_create_response") {
            if result.get("code").and_then(|v| v.as_str()) == Some("10000") {
                let trade_no = result
                    .get("trade_no")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string();
                return Ok(serde_json::json!({
                    "trade_no": trade_no,
                    "out_trade_no": order.get("out_trade_no").and_then(|v| v.as_str()).unwrap_or_default(),
                    "msg": "ok"
                }));
            } else {
                return Err(PayError::from_alipay_response(result));
            }
        }
        Err(PayError::Crypto("invalid alipay response".into()))
    }

    pub async fn refund(
        &self,
        mut order: serde_json::Value,
    ) -> Result<serde_json::Value, PayError> {
        // 构建服务商参数
        self.build_service_provider_params(&mut order);

        // 组装公共参数
        let mut params = self.build_common_params("alipay.trade.refund", &order);

        // 填充 biz_content(包含退款相关的信息)
        params.insert("biz_content".into(), order.to_string());
        println!("Refund request params: {:?}", params);
        // 签名
        let sign_src = Self::build_sign_string(&params);
        let sign = rsa_sign_sha256_pem(&self.cfg.private_key_pem, &sign_src)
            .map_err(|e| PayError::Crypto(e.to_string()))?;

        // 将签名加入到参数中
        params.insert("sign".into(), sign);

        // 发送请求
        let resp = self.do_request(params).await?;
        println!("Refund response: {:?}", resp);
        // 解析支付宝的返回结果
        if let Some(result) = resp.get("alipay_trade_refund_response") {
            if result.get("code").and_then(|v| v.as_str()) == Some("10000") {
                // 退款成功
                let trade_no = result
                    .get("trade_no")
                    .and_then(|v| v.as_str())
                    .unwrap_or_default()
                    .to_string();
                return Ok(serde_json::json!({
                "trade_no": trade_no,
                "refund_amount": order.get("refund_amount").unwrap_or(&json!("0")).as_f64().unwrap_or(0.0),
                "msg": "refund success"
            }));
            } else {
                return Err(PayError::from_alipay_response(result));
            }
        }

        Err(PayError::Crypto("invalid alipay refund response".into()))
    }

    pub fn verify_notify(
        &self,
        params: &std::collections::HashMap<String, String>,
    ) -> Result<AlipayNotifyData, PayError> {
        let notify = AlipayNotify::new(self.cfg.clone());
        notify.verify_notify(params)
    }
}