描述
微信支付可分為V2版本與V3版本,項(xiàng)目需要使用V3版本微信支付,簡述java如何實(shí)現(xiàn)微信支付V3版本
支付流程
概要流程:
1、小程序,傳遞微信支付需要參數(shù),如商品價(jià)格等,調(diào)用后端的支付訂單接口
2、后端調(diào)用微信支付系統(tǒng)后生成6個(gè)必要參數(shù)返回給前端。
后臺(tái)調(diào)用微信支付系統(tǒng)需要組裝必要的參數(shù):
{
"amount": {
"total": 100 //支付金額 這個(gè)是分單位 100分=1元
},
"mchid": "1621239178", //商戶號(hào)
"description": "充值支付",//支付描述
"notify_url": "http://127.0.0.1/pay/payNotify",//支付后回調(diào)接收地址
"payer": {
"openid": "o4GgauInH_RCEdvrrNGrntXDuXXX" //當(dāng)前用戶的openID
},
"out_trade_no": "3_6318816544250929333",//支付的訂單號(hào),支付后回調(diào)信息會(huì)將這個(gè)訂單號(hào)帶回來
"appid": "wx1d0b55c33e1e0e333" //小程序的APPID
}
6個(gè)必要參數(shù)用于小程序能否成功喚起微信支付
如下:
訂單號(hào)、金額、openid等去請(qǐng)求微信下單接口,微信返回預(yù)支付交易會(huì)話標(biāo)識(shí)prepay_id
后端給appid、timestamp、nonceStr、prepayId簽名,并將簽名、timestamp、nonceStr、prepay_id返回給小程序
{
"appId": "wx1d0b55c33e1e0e333",//小程序的APPID
"timeStamp": "16279901923",//時(shí)間戳
"nonceStr": "asd1231asdas",//隨機(jī)串,保證唯一
"package": "sdgscvb",
"signType": "RSA",//簽名類型
"paySign": "zzzghhxcvsdfs2q3412sdfsdf" //簽名信息
}
3、小程序調(diào)用wx.requestPayment拉起微信支付
4、用戶支付后,微信支付系統(tǒng)會(huì)回調(diào)信息,后端接收,做對(duì)應(yīng)的業(yè)務(wù)邏輯處理
微信支付前必要準(zhǔn)備
1、微信官方文檔–小程序支付接口文檔描述(最終是組成概要流程中的參數(shù)形式):
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
2、微信官方文檔–商戶號(hào)及微信V3證書下載
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
V3證書下載成功后文件(共3個(gè))如下圖:
apiclient_key.pem,apiclient_cert.pem,apiclient_cert.p12
文章來源:http://www.zghlxwxcb.cn/news/detail-656971.html
maven導(dǎo)包
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
</dependency>
文件配置
wxpay_v3.properties(微信支付配置,有appId,商戶id,API密鑰,證書地址,回調(diào)地址,
該文件在resource根目錄)
文件地址等信息根據(jù)自己項(xiàng)目情況自行配置
文章來源地址http://www.zghlxwxcb.cn/news/detail-656971.html
java代碼
微信V3證書支付需要根據(jù)下載的3個(gè)證書文件(微信支付前必要準(zhǔn)備第2點(diǎn)的)生成第四個(gè)證書文件(重要、重要、重要),
微信支付前,必須先生成這個(gè)文件,不然V3版本的支付無法喚醒,
調(diào)用這個(gè)接口生成即可:http://127.0.0.1:8080/項(xiàng)目名稱/v1/wx/pay/createPlatformCert
后續(xù)會(huì)說明如何生成
廢話不多說,直接上代碼
Controller
import com.core.def.vo.renew.IWxPayParamVO;
import com.wechat.service.WXPayNewService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 微信支付
* @author 夕四
**/
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/v1/wx/pay")
public class WXPayController {
private final WXPayNewService wxPayNewService;
/**
* 微信統(tǒng)一下單接口
* @param iWxPayParamVO 業(yè)務(wù)需要的參數(shù)
* @return
*/
@PostMapping("/doUnifiedOrder")
public Map doUnifiedOrder(@RequestBody IWxPayParamVO iWxPayParamVO) throws Exception {
return wxPayNewService.doUnifiedOrder(iWxPayParamVO);
}
/**
* 微信支付的回調(diào)接收接口
* @param request
* @param response
*/
@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
public void callBack(HttpServletRequest request, HttpServletResponse response) {
wxPayNewService.callBack(request, response);
}
/**
* 生成v3證書
*
*/
@RequestMapping("/createPlatformCert")
@ResponseBody
public String createPlatformCert() throws IOException {
return wxPayNewService.createPlatformCert();
}
}
Service
import com.core.def.vo.renew.IWxPayParamVO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 小程序微信支付
*
* @author 夕四
**/
public interface WXPayNewService {
/**
* 微信統(tǒng)一下單接口
*
* @param iWxPayParamVO
* @return
* @throws Exception
*/
Map doUnifiedOrder(IWxPayParamVO iWxPayParamVO) throws Exception;
/**
* 獲取v3的證書
*
* @return
* @throws IOException
*/
String createPlatformCert() throws IOException;
/**
* 微信支付回調(diào)接口
*
* @param request
* @param response
*/
void callBack(HttpServletRequest request, HttpServletResponse response);
}
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.vo.renew.IWxPayParamVO;
import com.config.WxPayParameterConfig;
import com.wechat.service.WXPayNewService;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethod;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.enums.WxDomain;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
/**
* @author 夕四
**/
@RequiredArgsConstructor
@Slf4j
@Service
public class WXPayNewServiceImpl implements WXPayNewService {
@Override
public Map doUnifiedOrder(IWxPayParamVO iWxPayParamVO) throws Exception {
//先寫死1, 1就是1分錢,100=1元
Integer price = 1;
//商戶訂單號(hào)(隨機(jī)字符串),這個(gè)回調(diào)的時(shí)候會(huì)給回
String orderSn = generateNonceStr();
//微信用戶openId
String openid = iWxPayParamVO.getOpenId();
//這個(gè)是微信小程序的appid
String appid = iWxPayParamVO.getAppId();
//商戶號(hào)ID
String mchId = iWxPayParamVO.getMchId();
//設(shè)置下支付的超時(shí)時(shí)間
String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
Map<String, Object> data = requestWxPayParam(mchId,orderSn, openid, price, appid, timeExpire);
log.info("統(tǒng)一下單參數(shù) {}", JSONUtil.toJsonStr(data));
//這個(gè)是證書文件,先寫死,后續(xù)調(diào)整成讀取證書文件的服務(wù)器存放地址
String privateKeyPath = "D:\\MyTools\\WXWork\\WxPayFile\\apiclient_key.pem";
//請(qǐng)求下單
IJPayHttpResponse response = WxPayApi.v3(
RequestMethod.POST,
WxDomain.CHINA.toString(),
WxApiType.JS_API_PAY.toString(),
// 商戶號(hào)
mchId,
// 獲取證書序列號(hào)
getSerialNumber(),
null,
// 私鑰
privateKeyPath,
// 請(qǐng)求參數(shù)
JSONUtil.toJsonStr(data)
);
log.info("統(tǒng)一下單響應(yīng) {}", response);
//這個(gè)就是小程序喚醒微信支付必要的那6個(gè)參數(shù)了
Map<String, String> map =new HashMap<>();
if (response.getStatus() == 200) {
//這個(gè)是證書文件,先寫死,后續(xù)調(diào)整成讀取證書文件的服務(wù)器存放地址
// 根據(jù)證書序列號(hào)查詢對(duì)應(yīng)的證書來驗(yàn)證簽名結(jié)果
String platformCertPath = "D:\\MyTools\\WXWork\\WxPayFile\\cert.pem";
//平臺(tái)證書
boolean verifySignature = WxPayKit.verifySignature(response, platformCertPath);
log.info("verifySignature: {}", verifySignature);
if (verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
// 私鑰
map = WxPayKit.jsApiCreateSign(appid, prepayId,privateKeyPath);
log.info("喚起支付參數(shù):{}", map);
}
}
//todo 微信預(yù)支付的訂單新入庫,為了業(yè)務(wù)查詢記錄
return map;
}
@Override
public void callBack(HttpServletRequest request, HttpServletResponse response) {
log.info("收到微信支付回調(diào)");
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("支付通知密文 {}", result);
// 根據(jù)證書序列號(hào)查詢對(duì)應(yīng)的證書來驗(yàn)證簽名結(jié)果
String platformCertPath = "D:\\MyTools\\WXWork\\WxPayFile\\cert.pem";
//這個(gè)商戶號(hào)對(duì)應(yīng)的那個(gè)V3秘鑰
String mckKey="cjajsrtasdqw21523asdf1";
//需要通過證書序列號(hào)查找對(duì)應(yīng)的證書,verifyNotify 中有驗(yàn)證證書的序列號(hào)
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,mckKey, platformCertPath);
log.info("支付通知明文 {}", plainText);
//這個(gè)就是具體的業(yè)務(wù)情況
savePayPlainText(plainText);
//回復(fù)微信
if (StrUtil.isNotEmpty(plainText)) {
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "簽名錯(cuò)誤");
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String createPlatformCert() {
//商戶號(hào)ID
String mchId = "6382395623";
//這個(gè)商戶號(hào)對(duì)應(yīng)的那個(gè)V3秘鑰
String mckKey="cjajsrtasdqw21523asdf1";
// 獲取平臺(tái)證書列表
try {
//這個(gè)是證書文件,先寫死,后續(xù)調(diào)整成讀取證書文件的服務(wù)器存放地址
String privateKeyPath = "D:\\MyTools\\WXWork\\WxPayFile\\apiclient_key.pem";
IJPayHttpResponse response = WxPayApi.v3(
RequestMethod.GET,
WxDomain.CHINA.toString(),
WxApiType.GET_CERTIFICATES.toString(),
mchId,
getSerialNumber(),
null,
privateKeyPath,
""
);
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String serialNumber = response.getHeader("Wechatpay-Serial");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
int status = response.getStatus();
log.info("serialNumber: {}", serialNumber);
log.info("status: {}", status);
log.info("body: {}", body);
int isOk = 200;
// 根據(jù)證書序列號(hào)查詢對(duì)應(yīng)的證書來驗(yàn)證簽名結(jié)果
String platformCertPath = "D:\\MyTools\\WXWork\\WxPayFile\\cert.pem";
if (status == isOk) {
JSONObject jsonObject = JSONUtil.parseObj(body);
JSONArray dataArray = jsonObject.getJSONArray("data");
// 默認(rèn)認(rèn)為只有一個(gè)平臺(tái)證書
JSONObject encryptObject = dataArray.getJSONObject(0);
JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getStr("associated_data");
String cipherText = encryptCertificate.getStr("ciphertext");
String nonce = encryptCertificate.getStr("nonce");
String serialNo = encryptObject.getStr("serial_no");
//生成第四個(gè)證書文件
final String platSerialNo = savePlatformCert(associatedData,mckKey, nonce, cipherText, platformCertPath);
log.info("平臺(tái)證書序列號(hào): {} serialNo: {}", platSerialNo, serialNo);
}
// 根據(jù)證書序列號(hào)查詢對(duì)應(yīng)的證書來驗(yàn)證簽名結(jié)果
boolean verifySignature = WxPayKit.verifySignature(response, platformCertPath);
log.info("verifySignature:{}" + verifySignature);
return body;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String savePlatformCert(String associatedData, String apiKey3, String nonce, String cipherText, String certPath) {
try {
AesUtil aesUtil = new AesUtil(apiKey3.getBytes(StandardCharsets.UTF_8));
// 平臺(tái)證書密文解密
// encrypt_certificate 中的 associated_data nonce ciphertext
String publicKey = aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
log.info("獲取證書key:{},保存路徑platformCert:{}", publicKey, certPath);
//將生成的證書寫入指定路徑,文件名為:cert.pem
FileOutputStream fos = new FileOutputStream(certPath);
fos.write(publicKey.getBytes());
fos.close();
// 獲取平臺(tái)證書序列號(hào)
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
return certificate.getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
log.error("寫入證書錯(cuò)誤:{}", e);
return e.getMessage();
}
}
/**
* 保存訂單的支付通知明文
*
* @param plainText
*/
private void savePayPlainText(String plainText) {
JSONObject jsonObject = JSONUtil.parseObj(plainText);
//這個(gè)就是發(fā)起訂單時(shí)的那個(gè)訂單號(hào)
String outTradeNo = jsonObject.getStr("out_trade_no");
//todo 把微信支付回調(diào)的明文消息存進(jìn)數(shù)據(jù)庫,方便后續(xù)校驗(yàn)查看
//todo 把微信支付后需要處理的具體業(yè)務(wù)處理了
}
/**
* 直連支付組裝參數(shù)
*
* @return
*/
private Map<String, Object> requestWxPayParam(String mchId,String orderSn, String openid, Integer price,String appid, String timeExpire) {
Map<String, Object> data = new HashMap<String, Object>();
Map<String, String> user = new HashMap<>();
user.put("openid", openid);
Map<String, Object> fee = new HashMap<>();
fee.put("total", price);
//APPID
data.put("appid", appid);
//商戶ID
data.put("mchid", mchId);
//生成的隨機(jī)字符串
data.put("description", "*****支付");
data.put("out_trade_no", orderSn);
//支付金額,單位分,1是1分錢
data.put("amount", fee);
data.put("time_expire", timeExpire);
//通知地址,用戶支付成功之后,微信訪問的接口
data.put("notify_url", "填上你自己的微信回調(diào)接收地址");
data.put("payer", user);
return data;
}
/**
* 獲取證書序列號(hào)
*
* @return
*/
private String getSerialNumber() throws IOException {
//這個(gè)是證書文件,先寫死,后續(xù)調(diào)整成讀取證書文件的服務(wù)器存放地址
String certPath = "D:\\MyTools\\WXWork\\WxPayFile\\apiclient_cert.pem";
log.info("path:{}", certPath);
// 獲取證書序列號(hào)
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(certPath));
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
log.info("獲取證書序列號(hào):{},", serialNo);
return serialNo;
}
/**
* 獲取隨機(jī)字符串 Nonce Str
*
* @return String 隨機(jī)字符串
*/
public String generateNonceStr() {
StringBuffer stringBuffer = new StringBuffer();
int prefix = RandomUtil.randomInt(10000, 99999);
int suffix = RandomUtil.randomInt(10000, 99999);
Long time = System.currentTimeMillis();
return stringBuffer.append(prefix).append(time).append(suffix).toString();
}
}
到此,微信支付對(duì)接業(yè)務(wù)邏輯處理完成
到了這里,關(guān)于Java實(shí)現(xiàn)微信小程序V3支付的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!