@ 接入准备: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
@ 小程序支付API列表: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_3.shtml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.3.0</version>
</dependency>
#微信支付参数相关
wx:
pay:
app_id: #微信公众号或者小程序等的appid
mch_id: #微信支付商户号
Api_V3_Key: # api v3支付秘钥 #微信支付商户密钥
# subAppId: #服务商模式下的子商户公众账号ID
# subMchId: #服务商模式下的子商户号
private_key_path: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
private_cert_path: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
notify_url: # 支付成功回调
@Component
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid
*/
private String appId;
/**
* 微信支付商户号
*/
private String mchId;
/**
* 微信支付商户密钥
*/
private String apiV3Key;
/**
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
*/
private String subAppId;
/**
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
*/
private String subMchId;
/**
* apiclient_key.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
*/
private String privateKeyPath;
/**
* apiclient_key.pem文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
*/
private String privateCertPath;
/**
* 支付成功回调地址:v3版本,必须是https
*/
private String notifyUrl;
}
/**
* 微信支付配置
*/
@Configuration
@ConditionalOnClass(WxPayService.class)
@AllArgsConstructor
public class AlarmWxPayConfig {
@Autowired
private WxPayProperties properties;
/**
* 初始化微信支付相关配置参数
* @return
*/
@Bean
@ConditionalOnMissingBean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key()));
// 可以指定是否使用沙箱环境
payConfig.setUseSandboxEnv(false);
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
{
"appid": "", //【应用ID】必填
"mchid": "", //【直连商户号】必填
"description ": "", //【商品描述】必填
"out_trade_no": "", //【商户订单号】必填
"notify_url ": "", //【通知地址】必填
"amount": { //【订单金额】必填
"total": "", //【总金额】必填
},
"payer": { //【支付者】
"openid": "" //【用户标识】必填
},
"detail": { //【优惠功能】
"invoice_id": "",
"goods_detail":{ //【单品列表】
"merchant_goods_id": "",//【商户侧商品编码】必填
"quantity": "", //【商品数量】必填
"unit_price": "" //【商品单价】必填
}
},
"scene_info": { //【场景信息】
"payer_client_ip": "", //【用户终端IP】必填
"store_info": { //【商户门店信息】
"id": "", //【门店编号】必填
}
},
}
@ JSAPI 下单----生成预支付交易单----返回小程序调起支付API-----必要参数
/**
*
* JSAPI 下单----生成预支付交易单----返回小程序调起支付API-----必要参数
*
* 填入必填项
*
* @return
*/
public WxPayUnifiedOrderV3Result.JsapiResult prePay("传入业务数据,填充") {
WxPayUnifiedOrderV3Request v3Request = new WxPayUnifiedOrderV3Request();
ArrayList<WxPayUnifiedOrderV3Request.GoodsDetail> goodsDetails = new ArrayList<>();
goodsDetails.add(new WxPayUnifiedOrderV3Request.GoodsDetail() {
}
.setMerchantGoodsId("")
.setUnitPrice(0).setQuantity(0));
v3Request.setAppid("")
.setMchid("")
.setNotifyUrl("")
.setDescription("")
.setOutTradeNo("")
.setAmount(new WxPayUnifiedOrderV3Request.Amount() {
}.setTotal(0))
.setPayer(new WxPayUnifiedOrderV3Request.Payer() {
}.setOpenid(""))
.setDetail(new WxPayUnifiedOrderV3Request.Discount() {
}.setInvoiceId("").setGoodsDetails(goodsDetails))
.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo() {
}
.setStoreInfo(new WxPayUnifiedOrderV3Request.StoreInfo() {
}.setId("")).setPayerClientIp(""));
JsapiResult jsapiResult = null;
try {
jsapiResult = this.wxPayService.createOrderV3(TradeTypeEnum.valueOf("JSAPI"), v3Request);
} catch (WxPayException e) {
e.printStackTrace();
log.info("JSAPI 下单:{}",e.getLocalizedMessage());
}
return jsapiResult;
}
@ 页面支付成功,查询微信后台订单状态,更新商户平台订单状态
/**
* 微信查询订单,参数两者使用其一
*
* @param orderNo 商户订单号
* @param transactionId 微信交易流水号
* @return Result
*/
public Result<?> wxQueryPay(String orderNo, String transactionId) {
WxPayOrderQueryV3Result v3Result = null;
try {
v3Result = this.wxPayService.queryOrderV3(transactionId, orderNo);
} catch (WxPayException e) {
log.error("查询微信后台订单失败:{}", e.getMessage());
return Result.restResult(6001, e.getMessage(), null);
}
// 根据业务需要,更新商户平台订单状态
if(v3Result.getPayState.equels("SUCCES")){
// 业务需求
}
return Result.ok();
}
@ 解决,
/**
* 获取回调请求头:签名相关
*
* @param request HttpServletRequest
* @return SignatureHeader
*/
public SignatureHeader getRequestHeader(HttpServletRequest request) {
// 获取通知签名
String signature = request.getHeader("wechatpay-signature");
String nonce = request.getHeader("wechatpay-nonce");
String serial = request.getHeader("wechatpay-serial");
String timestamp = request.getHeader("wechatpay-timestamp");
SignatureHeader signatureHeader = new SignatureHeader();
signatureHeader.setSignature(signature);
signatureHeader.setNonce(nonce);
signatureHeader.setSerial(serial);
signatureHeader.setTimeStamp(timestamp);
return signatureHeader;
}
/**
* 微信支付回调
*
* @param jsonData String
* @param request HttpServletRequest
* @param response HttpServletResponse
* @return JSONObject
*/
@PostMapping("/wxNotifyUrl")
public JSONObject wxNotifyUrl(@RequestBody String jsonData, HttpServletRequest request, HttpServletResponse response) {
JSONObject wxPayResult = new JSONObject();
if (lock.tryLock()) {
// 支付成功结果通知
OriginNotifyResponse notifyResponse = JSONUtil.toBean(jsonData, OriginNotifyResponse.class);
WxPayOrderNotifyV3Result v3Result = null;
try {
v3Result=wxPayService.parseOrderNotifyV3Result(this.jsonStrSort(notifyResponse),this.getRequestHeader(request));
//解密后的数据
WxPayOrderNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
// 注意:微信会通知多次,因此需判断此订单
LambdaQueryWrapper<WxPayOrder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(wxPayOrder::getOutTradeNo, result.getOutTradeNo());
WxPayOrder wxPayOrder = this.wxPayOrderMapper.selectOne(queryWrapper);
// 0:未支付,1:已支付
if(wxPayOrder.getPayState == 0){
//根据业务需要,更新商户平台订单状态
}
//通知应答:接收成功:HTTP应答状态码需返回200或204,无需返回应答报文。
response.setStatus(HttpServletResponse.SC_OK);
return null;
} catch (WxPayException e) {
e.printStackTrace();
log.error("支付回调失败:{}", e.getLocalizedMessage());
// 通知应答:HTTP应答状态码需返回5XX或4XX,同时需返回应答报文
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
wxPayResult.putOpt("code", "FAIL");
wxPayResult.putOpt("message", "失败");
return wxPayResult;
} finally {
lock.unlock();
}
}
// 通知应答码:HTTP应答状态码需返回5XX或4XX,同时需返回应答报文
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
wxPayResult.putOpt("code", "FAIL");
wxPayResult.putOpt("message", "失败");
return wxPayResult;
}
@ 从图上所说,有点模糊,翻译过来:就是需要按照API列表中所示的顺序,排序才能正确验签。,但实际body中接收到jsonData的顺序不是如下图所示,因此需重新排序。
@最简陋的方式排序。
/**
* 请求报文:按官方接口示例键值 --- 排序(必须)
* 官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml,
* https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
* 《微信支付API v3签名验证》 中注意:应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
*
* @param originNotifyResponse OriginNotifyResponse
* @return String
*/
private String jsonStrSort(OriginNotifyResponse originNotifyResponse) {
Map<String, Object> jsonSort = new LinkedHashMap<>();
jsonSort.put("id", originNotifyResponse.getId());
jsonSort.put("create_time", originNotifyResponse.getCreateTime());
jsonSort.put("resource_type", originNotifyResponse.getResourceType());
jsonSort.put("event_type", originNotifyResponse.getEventType());
jsonSort.put("summary", originNotifyResponse.getSummary());
Map<String, Object> resource = new LinkedHashMap();
resource.put("original_type", originNotifyResponse.getResource().getOriginalType());
resource.put("algorithm", originNotifyResponse.getResource().getAlgorithm());
resource.put("ciphertext", originNotifyResponse.getResource().getCiphertext());
resource.put("associated_data", originNotifyResponse.getResource().getAssociatedData());
resource.put("nonce", originNotifyResponse.getResource().getNonce());
jsonSort.put("resource", resource);
return JSONUtil.toJsonStr(jsonSort);
}