目录
工程总介绍:
下面会有几期关于app微信支付的经验分享
微信支付功能已通过测试可以使用 可能一些设计不合理 欢迎提出更好的方案 让我们改进
第一弹: 微信 支付下单
展示结果截图
1.配置说明
1.1数据库设计 需要拿到微信官方给的一些参数
1.2用到的jar包
可能不太全 如果项目代码中有没有写出的包可以指出来
<!--mybatis-puls-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.3.4</version>
</dependency>
<!--mybatis-plus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<!--spring data redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- springcache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--微信支付 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.2</version>
</dependency>
<!--hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.6</version>
</dependency>
<!--参数校验 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--google-->
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
<version>0.0.20131108.vaadin1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
1.3信支付接口请求状态枚举
1.3.1接口请求
package com.yqs.Enum;
/**
*微信支付 api调用接口
* @author zhangjunrong
* @date 2022/5/9 19:57
*/
public enum WxApiType {
/** 下单API */
CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/app"),
/** 查询支付订单API ONE{}=>outTradeNo – 商户订单号 系统生 TOW{}=>mchId商户的商户号,由微信支付生成并下发。 */
QUERY_CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}?mchid={}"),
/** 查询退款订单API {}=>outRefundNo 商户退款单号*/
QUERY_REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{}"),
/** 关闭订单API {}=>outTradeNo 商户订单号*/
CLOSE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}/close"),
/** 申请退款API {}=>outTradeNo 商户订单号*/
REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"),
/** 申请交易账单API {}=>bill_date 账单日期 格式yyyy-MM-DD仅支持三个月内的账单下载申请*/
TRADE_BILLS("https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date={}"),
/** 申请资金账单API {}=>bill_date 账单日期 格式yyyy-MM-DD仅支持三个月内的账单下载申请*/
FUND_FLOW_BILLS("https://api.mch.weixin.qq.com/v3/bill/fundflowbill?bill_date={}");
private final String value;
WxApiType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
1.3.2状态
package com.yqs.Enum;
/**
* @Description 微信支付状态
* @Author 小乌龟
* @Date 2022/5/14 10:56
*/
public enum WxPayStatusEnum {
/** 未支付 */
NOTPAY(0,"NOTPAY"),
/** 支付成功 */
SUCCESS(1,"SUCCESS"),
/** 转入退款 */
REFUND(2,"REFUND"),
/** 已关闭 */
CLOSED(3,"CLOSED"),
/** 退款处理中 */
REF_PROCESSING(0,"PROCESSING"),
/** 退款成功 */
REF_SUCCESS(1,"SUCCESS"),
/** 退款已关闭 */
REF_CLOSED(2,"CLOSED"),
/** 退款异常 */
REF_ABNORMAL(3,"ABNORMAL");
private final Integer code;
private final String value;
WxPayStatusEnum(Integer code,String value) {
this.code=code;
this.value = value;
}
public Integer getCode() {
return code;
}
public String getValue() {
return value;
}
/**
*退款状态 转换 String => int
* @param value 退款String
* @return Integer
* @author zhangjunrong
* @date 2022/5/19 13:47
*/
public static Integer getCode(String value){
switch(value){
case "PROCESSING":
return REF_PROCESSING.code;
case "SUCCESS":
return REF_SUCCESS.code;
case "CLOSED":
return REF_CLOSED.code;
default:
return REF_ABNORMAL.code;
}
}
}
1.4 微信参数常量配置
1.4.1微信支付HTTP请求头相关常量
package com.yqs.constant.wechatPay;
/**
*微信支付HTTP请求头相关常量
* @author zhangjunrong
* @return
*/
public final class WechatPayHttpHeaders {
//请求头配置
public static final String ACCEPT = "Accept";
public static final String CONTENT_TYPE = "Content-type";
public static final String APPLICATION_JSON = "application/json";
public static final String APPLICATION_JSON_UTF = "application/json; charset=utf-8";
/**
*微信回调参数==>微信序列号
*/
public static final String WECHATPAY_SERIAL = "Wechatpay-Serial";
/**
*微信回调参数==>应答随机串
*/
public static final String WECHATPAY_NONCE="Wechatpay-Nonce";
/**
*微信回调参数==>应答时间戳
*/
public static final String WECHATPAY_TIMESTAMP="Wechatpay-Timestamp";
/**
*微信回调参数==>应答签名
*/
public static final String WECHATPAY_SIGNATURE="Wechatpay-Signature";
private WechatPayHttpHeaders() {
// Don't allow instantiation
}
}
1.4.2 微信支付参数 常量
package com.yqs.constant.wechatPay;
/**
* @Description 微信支付参数
* @Author 小乌龟
* @Date 2022/5/4 8:44
*/
public class WXOrderConstant {
/**
*应用ID
*/
public final static String APPID ="appid";
/**
*直连商户号
*/
public final static String MCHID ="mchid";
/**
*用户支付状态
*/
public final static String TRADE_STATE ="trade_state";
/**
*用户退款状态
*/
public final static String STATUS ="status";
/**
*商品描述
*/
public final static String DESCRIPTION ="description";
/**
*通知地址
*/
public final static String NOTIFY_URL ="notify_url";
/**
*预支付交易会话标识
*/
public final static String PREPAY_ID ="prepay_id";
/**
*商户订单号
*/
public final static String OUT_TRADE_NO ="out_trade_no";
/**
*商户退款单号
*/
public final static String OUT_REFUND_NO ="out_refund_no";
/**
*微信支付系统生成的订单号
*/
public final static String TRANSACTION_ID ="transaction_id";
/**
*微信支付系统生成的订单号
*/
public final static String REFUND_ID ="refund_id";
/**
*订单金额
*/
public final static String AMOUNT ="amount";
/**
*总金额 下单时系统计算金额 单位为分
*/
public final static String AMOUNT_TOTAL ="total";
/**
*总金额 退款时 退款金额
*/
public final static String AMOUNT_REFUND ="refund";
/**
*总金额 退款时 币种
*/
public final static String AMOUNT_CURRENCY ="currency";
/**
*用户支付金额
*/
public final static String AMOUNT_PAYER_TOTAL ="payer_total";
/**
*优惠功能
*/
public final static String PROMOTION_DETAIL ="promotion_detail";
/**
*优惠类型
*/
public final static String PROMOTION_DETAIL_TYPE ="type";
/**
* 微信优惠 CASH:充值型代金券
*/
public final static String WX_DISCOUNT_TYPE="CASH";
/**
*下单回调给微信支付成功信息
*/
public final static String WX_BACK_OK="SUCCESS";
/**
*退款回调 退款状态
*/
public final static String REFUND_STATUS="refund_status";
/**
*微信回调 通知数据
*/
public final static String RESOURCE="resource";
/**
*微信账单类型 交易账单
*/
public final static String TRADE_BILL="tradebill";
/**
*微信账单类型 资金账单
*/
public final static String FUND_FLOW_BILL="fundflowbill";
/**
*微信账单下载地址
*/
public final static String DOWNLOAD_URL="download_url";
/**
*微信回调 通知数据=>数据密文
*/
public final static String RESOURCE_CIPHERTEXT="ciphertext";
/**
*微信回调 通知数据=>附加数据
*/
public final static String RESOURCE_ASSOCIATED_DATA="associated_data";
/**
*微信回调 通知数据=>随机串
*/
public final static String RESOURCE_NONCE="nonce";
}
2.APP下单
2.1.1创建订单VO
/**
* @Description
* @Author 小乌龟
* @Date 2022/4/13 19:49
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value="前端=>微信创建订单", description="")
public class ToCreatOrderVO {
@ApiModelProperty(value = "订单号")
@NotBlank
String outTradeNo;
@ApiModelProperty(value = "金额")
@NotBlank
String total;
@ApiModelProperty(value = "订单描述")
@NotBlank
String description;
}
2.1.2 构造HttpClient
/**
*构造HttpClient 实现 微信申请接口 调用功能
* @param wxConfig 微信支付数据库参数
* @param verifier 微信验签器
* @return CloseableHttpClient
* @author zhangjunrong
* @date 2022/5/16 8:54
*/
public static CloseableHttpClient getHttpClient(ToolWxConfig wxConfig, Verifier verifier) {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(wxConfig.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
//通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wxConfig.getMchId(), wxConfig.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
return builder.build();
}
2.1.3计算签名
/**
*签名生成 计算签名
* @param message 签名体
* @param privateKey 商户私钥
* @return String
* @author zhangjunrong
* @date 2022/4/10 20:15
*/
public static String sign(byte[] message, PrivateKey privateKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
2.2 微信下单service
//1.数据库查询配置参数
@Override
@Cacheable(key = "'config'")
public ToolWxConfig find() {
ToolWxConfig wxConfig = toolWxConfigMapper.selectById(1L);
return wxConfig;
}
//2.微信下单
@Override
public ReCreatOrderVO createOrder(ToolWxConfig wxConfig, ToCreatOrderVO toCreatOrderVO) {
try {
//1.请求配置参数
HttpPost httpPost = new HttpPost(WxApiType.CREATE_ORDER.getValue());
//格式配置
httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
//2.读取privateKey私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(wxConfig.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
//3.配置下单参数
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put(WXOrderConstant.MCHID, wxConfig.getMchId())
.put(WXOrderConstant.APPID, wxConfig.getAppId())
.put(WXOrderConstant.DESCRIPTION, toCreatOrderVO.getDescription())
.put(WXOrderConstant.NOTIFY_URL, wxConfig.getCreNotifyUrl())
.put(WXOrderConstant.OUT_TRADE_NO, toCreatOrderVO.getOutTradeNo());
rootNode.putObject(WXOrderConstant.AMOUNT)
//total 微信需要int类型 为了不丢失精度 单位为分
.put(WXOrderConstant.AMOUNT_TOTAL, new BigDecimal(toCreatOrderVO.getTotal()).movePointRight(SystemConstant.NUM_TWO).intValue());
log.warn("金额" + new BigDecimal(toCreatOrderVO.getTotal()).movePointRight(SystemConstant.NUM_TWO).intValue());
objectMapper.writeValue(bos, rootNode);
//4.调用微信下单接口
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//接口返回值 申请获取prepay_id
CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
//预支付交易会话标识 prepay_id
String bodyAsString = EntityUtils.toString(response.getEntity());
//5.APP调起支付API 构造签名串
String timestamp = DateUtil.currentSeconds() + "";
String nonce = RandomUtil.randomString(SystemConstant.NUM_32);
StringBuilder builder = new StringBuilder();
//应用id
builder.append(wxConfig.getAppId()).append("\n");
//时间戳
builder.append(timestamp).append("\n");
//随机字符串
builder.append(nonce).append("\n");
//预支付交易会话标识 prepay_id 通过bodyAsString获取 prepay_id
JsonNode node = objectMapper.readTree(bodyAsString);
builder.append(node.get(WXOrderConstant.PREPAY_ID).textValue()).append("\n");
String prepayId = node.get(WXOrderConstant.PREPAY_ID).textValue();
//6.计算签名
String sign = WxPayUtil.sign(builder.toString().getBytes(StandardCharsets.UTF_8), merchantPrivateKey);
//todo 测试签名是否成功
System.err.println(sign);
//7.返回参数 让前端调微信支付
return new ReCreatOrderVO(wxConfig.getAppId(), prepayId, wxConfig.getMchId(), wxConfig.getWxPackage(), nonce, timestamp, sign);
} catch (Exception e) {
log.error(toCreatOrderVO.getOutTradeNo() + "订单下单失败");
//下单失败抛异常
throw new YqsException(MessageEnum.NOT_ACCEPTABLE.getCode(), "下单失败 重新尝试付款");
}
}
2.3 返回前端参数
(这个参数是根据uniapp来的 可能其他的会不一样所以注意了)
/**
* @author 小乌龟
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="微信创建订单", description="")
public class ReCreatOrderVO {
//uniapp需要参数
// "appid": "wx499********7c70e", // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
// "noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
// "package": "Sign=WXPay", // 固定值
// "partnerid": "148*****52", // 微信支付商户号
// "prepayid": "wx202254********************fbe90000", // 统一下单订单号
// "timestamp": 1597935292, // 时间戳(单位:秒)
// "sign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
@ApiModelProperty(value = "appid")
private String appId;
@ApiModelProperty(value = "统一下单订单号 prepayid ")
private String prepayId;
@ApiModelProperty(value = "微信支付商户号")
private String partnerId;
@ApiModelProperty(value = "订单详情扩展字符串")
private String wxPackage;
@ApiModelProperty(value = "随机字符串")
private String wxNonce;
@ApiModelProperty(value = "时间戳")
private String wxTimestamp;
@ApiModelProperty(value = "签名")
private String wxSign;
}
3.3 controller
@PostMapping("/toWxPayApp")
@ApiOperation("微信支付")
@RequiresAuthentication
public ResultResponse<ReCreatOrderVO> toWxPayApp(@ApiParam("订单信息") @RequestBody @Valid ToCreatOrderVO toCreatOrderVO, BindingResult br) {
return process(() -> iToolWxConfigService.createOrder(iToolWxConfigService.find(), toCreatOrderVO), br);
}