微信二維碼支付

来源:https://www.cnblogs.com/aitiknowledge/archive/2022/08/13/16584071.html
-Advertisement-
Play Games

準備工作 概述:微信掃碼支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信掃一掃完成支付的模式。該模式適用於PC網站支付、實體店單品或訂單支付、媒體廣告支付等場景。 第一步:註冊公眾號(類型須為:服務號):請根據營業執照類型選擇以下主體註冊:個體工商戶| 企業/公司| 政府| 媒體| 其他類型 ...


  • 準備工作
    • 概述:微信掃碼支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信掃一掃完成支付的模式。該模式適用於PC網站支付、實體店單品或訂單支付、媒體廣告支付等場景。
    • 第一步:註冊公眾號(類型須為:服務號):請根據營業執照類型選擇以下主體註冊:個體工商戶| 企業/公司| 政府| 媒體| 其他類型。
    • 第二步:認證公眾號:公眾號認證後才可申請微信支付,認證費:300元/年。
    • 第三步:提交資料申請微信支付:登錄公眾平臺,點擊左側菜單【微信支付】,開始填寫資料等待審核,審核時間為1-5個工作日內。
    • 第四步:登錄商戶平臺進行驗證:資料審核通過後,請登錄聯繫人郵箱查收商戶號和密碼,並登錄商戶平臺填寫財付通備付金打的小額資金數額,完成賬戶驗證。
    • 第五步:線上簽署協議:本協議為線上電子協議,簽署後方可進行交易及資金結算,簽署完立即生效。
  • 前期工作完成會下發重要信息
    • appid: 微信公眾賬號或開放平臺APP的唯一標識
    • mch_id: 商戶號,稍後用配置文件中的partner代替,見名知意
    • partnerkey: 商戶密鑰
    • sign: 數字簽名,根據微信官方提供的密鑰和一套演算法生成的一個加密信息,就是為了保證交易的安全性
  • 在支付過程中,主要會用到微信支付SDK的以下功能
    • 獲取隨機字元串: WXPayUtil.generateNonceStr()
    • Map類型轉換為xml字元串(自動添加簽名): WXPayUtil.generateSignedXml(map, partnerkey)
    • xml字元串轉換為Map類型: WXPayUtil.xmlToMap(result)
  • 在支付模塊中導入微信支付依賴
<dependencies>
  <dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
  </dependency>
</dependencies>
  • 配置文件添加微信支付信息,為了設置預付單的有效時間,還需要添加redis緩存信息
spring.redis.host=your_ip
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待時間(負數表示沒限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

#關聯的公眾號appid
wx.appid=your_app_id
#商戶號
wx.partner=your_partner
#商戶key
wx.partnerkey=your_partner_key
  • 編寫常量類讀取配置文件中微信支付信息
@Component
public class WxPayProperties implements InitializingBean {
  @Value("${wx.appid}")
  private String appId;
  @Value("${wx.partner}")
  private String partner;
  @Value("${wx.partnerkey}")
  private String partnerKey;

  public static String WX_APP_ID;
  public static String WX_PARTNER;
  public static String WX_PARTNER_KEY;

  @Override
  public void afterPropertiesSet() throws Exception {
    WX_APP_ID = appId;
    WX_PARTNER = partner;
    WX_PARTNER_KEY = partnerKey;
  }
}
  • 借用HttpClient工具類,內容基本不用改,可嘗試優化下
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/** http請求客戶端 */
public class HttpClient {

    private String url;
    private Map<String, String> param;
    private int statusCode;
    private String content;
    private String xmlParam;
    private boolean isHttps;
    private boolean isCert = false;
    //證書密碼 微信商戶號(mch_id)
    private String certPassword;
    
    public HttpClient(String url, Map<String, String> param) {
        this.url = url;
        this.param = param;
    }

    public HttpClient(String url) {
        this.url = url;
    }

    public void addParameter(String key, String value) {
        if (param == null) param = new HashMap<>();
        param.put(key, value);
    }
    
    public void post() throws ClientProtocolException, IOException {
        HttpPost http = new HttpPost(url);
        setEntity(http);
        execute(http);
    }

    public void put() throws ClientProtocolException, IOException {
        HttpPut http = new HttpPut(url);
        setEntity(http);
        execute(http);
    }

    public void get() throws ClientProtocolException, IOException {
        if (param != null) {
            StringBuilder completeUrl = new StringBuilder(this.url);
            boolean isFirst = true;
            for(Map.Entry<String, String> entry: param.entrySet()) {
                if(isFirst)  completeUrl.append("?");
                else completeUrl.append("&");
                completeUrl.append(entry.getKey()).append("=").append(entry.getValue());
                isFirst = false;
            }
            // 拼接成完整的url
            this.url = completeUrl.toString();
        }
        HttpGet http = new HttpGet(url);
        execute(http);
    }

    /** 設置post和put參數 */
    private void setEntity(HttpEntityEnclosingRequestBase http) {
        if (param != null) {
            List<NameValuePair> nvps = new LinkedList<>();
            for(Map.Entry<String, String> entry: param.entrySet())
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));   // 參數
            http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 設置參數
        }
        if (!StringUtils.isEmpty(xmlParam)) http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
    }
    
    /** 執行get、post、put請求 */
    private void execute(HttpUriRequest http) throws ClientProtocolException, IOException {
        CloseableHttpClient httpClient = null;
        try {
            if (isHttps) {
                if(isCert) {
                    FileInputStream inputStream = new FileInputStream(WxPayProperties.CERT);
                    KeyStore keystore = KeyStore.getInstance("PKCS12");
                    char[] partnerId2charArray = certPassword.toCharArray();
                    keystore.load(inputStream, partnerId2charArray);
                    SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
                    SSLConnectionSocketFactory sslsf =
                            new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" },
                                    null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
                    httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
                } else {
                    TrustSelfSignedStrategy strategy = new TrustSelfSignedStrategy() {
                        @Override
                        public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                            // 信任所有
                            return true;
                        }
                    };
                    SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, strategy).build();
                    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
                    httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
                }
            } else {
                httpClient = HttpClients.createDefault();
            }
            CloseableHttpResponse response = httpClient.execute(http);
            try {
                if (response != null) {
                    if (response.getStatusLine() != null) statusCode = response.getStatusLine().getStatusCode();
                    HttpEntity entity = response.getEntity();
                    // 響應內容
                    content = EntityUtils.toString(entity, Consts.UTF_8);
                }
            } finally {
                assert response != null;
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            assert httpClient != null;
            httpClient.close();
        }
    }
    
    
    public int getStatusCode() {
        return statusCode;
    }
    
    public String getContent() throws ParseException, IOException {
        return content;
    }

    public boolean isHttps() {
        return isHttps;
    }

    public void setHttps(boolean isHttps) {
        this.isHttps = isHttps;
    }

    public boolean isCert() {
        return isCert;
    }

    public void setCert(boolean cert) {
        isCert = cert;
    }

    public String getXmlParam() {
        return xmlParam;
    }

    public void setXmlParam(String xmlParam) {
        this.xmlParam = xmlParam;
    }
    
    public String getCertPassword() {
        return certPassword;
    }

    public void setCertPassword(String certPassword) {
        this.certPassword = certPassword;
    }

    public void setParameter(Map<String, String> map) {
        param = map;
    }
}
  • 用戶微信支付(前後端工作)
    • 後端生成微信支付二維碼業務邏輯代碼
@Service
public class WxPayServiceImpl implements WxPayService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /** 生成微信支付二維碼 */
    @Override
    public Map<String, Object> createNative(String orderId) {
        try {
            // 如果緩存中有,就直接返回
            Map<String, Object> payMap = (Map<String, Object>) redisTemplate.opsForValue().get(orderId);
            if(payMap != null) return payMap;
            // 根據實際的業務需求編寫相應的代碼
            // 省略業務代碼.......
            // 設置參數,
            // 把參數轉換xml格式,使用商戶key進行加密
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("appid", WxPayProperties.WX_APP_ID);
            paramMap.put("mch_id", WxPayProperties.WX_PARTNER);
            // 隨機字元串
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
            String body = "請求體,應該是掃碼支付時的文本信息";
            paramMap.put("body", body);
            paramMap.put("out_trade_no", "交易流水號,自定義即可,保證唯一性");
            paramMap.put("total_fee", "1"); // 支付的費用
            // 當前支付的IP地址
            paramMap.put("spbill_create_ip", "可以通過HttpServletRequest對象獲取請求的IP地址");
            // 微信支付的回調地址
            paramMap.put("notify_url", "申請微信支付時填的回調地址");
            // 微信掃碼支付
            paramMap.put("trade_type", "NATIVE");
            //4 調用微信生成二維碼介面,httpclient調用,請求地址一般固定
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            //設置xml參數(Map類型轉換為xml)
            client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, WxPayProperties.WX_PARTNER_KEY));
            // 是https協議
            client.setHttps(true);
            client.post();
            //5 返回相關數據
            String xml = client.getContent();
            // xml數據轉換為Map類型
            Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
            //6 封裝返回結果集
            Map<String, Object> map = new HashMap<>();
            map.put("orderId", orderId);
            map.put("totalFee", orderInfo.getAmount());
            // 狀態碼
            map.put("resultCode", resultMap.get("result_code"));
            //二維碼地址
            map.put("codeUrl", resultMap.get("code_url"));

            if(resultMap.get("result_code") != null) {
                // 保存到Redis緩存中
                redisTemplateString.opsForValue().set(orderId.toString(),map,120, TimeUnit.MINUTES);
            }
            return map;
        }catch (Exception e) {
            e.printStackTrace();
            return new HashMap<>();
        }
    }
}
  • 前端引入vue-qriously生成二維碼
    • 安裝vue-qriously: npm install vue-qriously
    • 在插件文件中導入
    import VueQriously from 'vue-qriously'
    Vue.use(VueQriously)
    
    • vue組件顯示二維碼,只展示二維碼區域
<!-- 微信支付彈出框 -->
<el-dialog :visible.sync="dialogPayVisible" style="text-align: left" :append-to-body="true" width="500px" @close="closeDialog">
  <div class="container">
    <div class="operate-view" style="height: 350px;">
      <div class="wrapper wechat">
        <div>
          <qriously :value="payObj.codeUrl" :size="220"/>
          <div style="text-align: center;line-height: 25px;margin-bottom: 40px;">
             請使用微信掃一掃<br/>
             掃描二維碼支付
          </div>
        </div>
      </div>
    </div>
  </div>
</el-dialog>
<script>
import orderInfoApi from '@/api/order/orderInfo'
import weixinApi from '@/api/order/weixin'
export default {
  data() {
    return {
      orderId: null,
      orderInfo: {
        param: {}
      },

      dialogPayVisible: false,
      payObj: {},
      timer: null  // 定時器名稱
    }
  },

  created() {
    this.orderId = this.$route.query.orderId
    this.init()
  },

  methods: {
    init() {
      orderInfoApi.getOrderInfo(this.orderId).then(response => {
        this.orderInfo = response.data
      })
    },
    pay() {
      this.dialogPayVisible = true

      weixinApi.createNative(this.orderId).then(response => {
        this.payObj = response.data
        if(this.payObj.codeUrl == '') {
          this.dialogPayVisible = false
          this.$message.error("支付錯誤")
        } else {
          this.timer = setInterval(() => {
            // 查看微信支付狀態
            this.queryPayStatus(this.orderId)
          }, 3000);
        }
      })
    },

    queryPayStatus(orderId) {
      weixinApi.queryPayStatus(orderId).then(response => {
        if (response.message == '支付中') {
          return
        }
        clearInterval(this.timer);
        window.location.reload()
      })
    },
  }
}
</script>
  • 後端編寫微信支付狀態介面,同樣省略支付成功之後的業務需求代碼
@Service
public class WxPayServiceImpl implements WxPayService {
/** 查詢支付狀態 */
    @Override
    public Map<String, String> queryPayStatus(Long orderId) {
        try {
            // 封裝提交參數
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("appid", WxPayProperties.WX_APP_ID);
            paramMap.put("mch_id", WxPayProperties.WX_PARTNER);
            paramMap.put("out_trade_no", "當前支付訂單的流水號");
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());

            // 設置請求內容
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, WxPayProperties.WX_PARTNER_KEY));
            client.setHttps(true);
            client.post();

            // 得到微信介面返回數據
            String xml = client.getContent();
            return WXPayUtil.xmlToMap(xml);
        }catch(Exception e) {
            e.printStackTrace();
            return new HashMap<>();
        }
    }
}
  • 未完待續

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 當微服務是個壞主意時 這篇文章可能是給大家潑冷水,請各位理性看待。從書面上看,微服務聽起來很好。它們是模塊化、可擴展和容錯的。很多公司使用這種模式取得了巨大的成功,所以微服務可能自然而然地成為卓越的架構和啟動新應用程式的最佳方式。然而,大多數利用微服務取得成功的公司並不是從微服務開始的。考慮一下Ai ...
  • 一、環境準備 1.資料庫 創建2個庫2個表: xdclass_shop_order_0 product_order_0 product_order_1 ad_config product_order_item_0 product_order_item_1 xdclass_shop_order_1 p ...
  • 1.什麼是序列化組件 在django中,自帶一個序列化組件,它是用來將數據進行整理、轉化成特定的為一個特定的格式(比如json數據格式),然後傳輸給前端,以便前端對數據進行處理操作。 2.為什麼要用序列化組件 當我們從資料庫取出一些數據時,我們需要將數據轉成特定的格式,比如列表套字典的形式,然後將這 ...
  • @(文章目錄) 提示:本文僅供學習交流,請勿用於非法活動! 前言 本文大概內容: 例如:隨著MongoDB的廣泛應用,電商用到MongoDB也越來越多。本文主要是在將購物車模塊切換到MongoDB框架前,如何快速將Mysql中購物車大批量訂單拷貝到MongoDB資料庫中? 一、原來代碼 如下,我們將 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • @(文章目錄) 提示:本文僅供學習交流,請勿用於非法活動! 前言 本文內容: 日誌搭建 一、依賴 <!-- Spring集成日誌包 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</arti ...
  • 一、基本 1.hashmap: 1.1 轉紅黑樹條件: a.數組長度大於等於64(預設16,要經過2次擴容--當達到16*預設擴容因數0.75=12就擴容) b.鏈表長度大於8 1.2 hashmap先計算hash值,再用hash值計算下標。 2.sleep與await: 1.1 sleep是線程方 ...
  • @(文章目錄) 前言 一、建項目 1. 在父項目ams-cloud下建立maven子項目ams-websocket 2.pom文件添加常用依賴,另外添加redis依賴等,我這裡直接引用common模塊 <dependencies> <dependency> <groupId>com.alibaba. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...