準備工作 概述:微信掃碼支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信掃一掃完成支付的模式。該模式適用於PC網站支付、實體店單品或訂單支付、媒體廣告支付等場景。 第一步:註冊公眾號(類型須為:服務號):請根據營業執照類型選擇以下主體註冊:個體工商戶| 企業/公司| 政府| 媒體| 其他類型 ...
- 準備工作
- 概述:微信掃碼支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信
掃一掃
完成支付的模式。該模式適用於PC網站支付、實體店單品或訂單支付、媒體廣告支付等場景。 - 第一步:註冊公眾號(類型須為:服務號):請根據營業執照類型選擇以下主體註冊:個體工商戶| 企業/公司| 政府| 媒體| 其他類型。
- 第二步:認證公眾號:公眾號認證後才可申請微信支付,認證費:300元/年。
- 第三步:提交資料申請微信支付:登錄公眾平臺,點擊左側菜單【微信支付】,開始填寫資料等待審核,審核時間為1-5個工作日內。
- 第四步:登錄商戶平臺進行驗證:資料審核通過後,請登錄聯繫人郵箱查收商戶號和密碼,並登錄商戶平臺填寫財付通備付金打的小額資金數額,完成賬戶驗證。
- 第五步:線上簽署協議:本協議為線上電子協議,簽署後方可進行交易及資金結算,簽署完立即生效。
- 概述:微信掃碼支付是商戶系統按微信支付協議生成支付二維碼,用戶再用微信
- 前期工作完成會下發重要信息
- appid:
微信公眾賬號或開放平臺APP的唯一標識
- mch_id:
商戶號
,稍後用配置文件中的partner代替,見名知意 - partnerkey:
商戶密鑰
- sign:
數字簽名
,根據微信官方提供的密鑰和一套演算法生成的一個加密信息,就是為了保證交易的安全性
- appid:
- 在支付過程中,主要會用到微信支付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組件顯示二維碼,只展示二維碼區域
- 安裝vue-qriously:
<!-- 微信支付彈出框 -->
<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<>();
}
}
}
- 未完待續