application.xml配置
# 微信配置
wechat:
#appId
appId: wx...
#appSecret
appSecret: 5d0c...
#商户号
merchantId: 168...
# 私钥路径
privateKeyPath: H:\work\...\apiclient_key.pem
# 商户号证书序列号
merchantSerialNumber: 601B...
# 商户apiV3秘钥
apiV3key: 888888...
#支付回调
notifyUrl: https://..../wechatPayCallback
登录微信支付商户平台
商户号这里找↓
然后申请api证书和设置apiv3秘钥,照着步骤来即可
一定要先申请证书再设置秘钥!一定要先申请证书再设置秘钥!一定要先申请证书再设置秘钥!
申请证书后本地生成一个压缩文件,解压后找个地方放,私钥路径是apiclient_key.pem的路径
点击管理证书,复制证书序列号
添加支付授权目录,必须是外网能访问的域名
如果路径是/smallapp/pay/wechatPayCallback,写到/smallapp/pay就可以了
引入sdk
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
新建WechatPayService.java
@Slf4j
@Component
public class WechatPayService {
}
私有化下列属性,给个@Getter方便外部调用
@Getter
private Config config;
@Getter
private JsapiServiceExtension jsapiServiceExtension;
@Getter
@Value("${wechat.appId}")
private String appId;
@Getter
@Value("${wechat.merchantId}")
private String merchantId;
@Getter
@Value("${wechat.privateKeyPath}")
private String privateKeyPath;
@Getter
@Value("${wechat.merchantSerialNumber}")
private String merchantSerialNumber;
@Getter
@Value("${wechat.apiV3key}")
private String apiV3key;
@Getter
@Value("${wechat.notifyUrl}")
private String notifyUrl;
初始化config和jsapiServiceExtension
@PostConstruct
private void init(){
config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
jsapiServiceExtension = new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA")
.build();
}
支付接口处理、支付回调处理
private static final int TAG_LENGTH_BIT = 128;
public <T> T payExecute(Supplier<T> supplier){
try {
T t = supplier.get();
log.info("调用微信支付接口返回: {}", JSON.toJSONString(t));
return t;
} catch (HttpException e){
log.error("调用微信支付接口 HttpException: {}", e.getMessage());
throw new com.ruoyi.common.exception.ServiceException("系统错误");
} catch (ServiceException e){
log.error("调用微信支付接口 ServiceException: {}", e.getMessage());
throw new com.ruoyi.common.exception.ServiceException("系统错误");
} catch (MalformedMessageException e){
log.error("调用微信支付接口 MalformedMessageException: {}", e.getMessage());
throw new com.ruoyi.common.exception.ServiceException("系统错误");
}
}
public Boolean notifyExecute(JSONObject jsonObject, Consumer<String> consumer){
log.info("[notifyExecute] 支付回调: {}", jsonObject.toJSONString());
String outTradeNo = "";
try {
JSONObject resource = jsonObject.getJSONObject("resource");
String associatedData = resource.getString("associated_data");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String decryptData = decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
JSONObject decryptDataJobj = JSONObject.parseObject(decryptData);
if("SUCCESS".equalsIgnoreCase(decryptDataJobj.getString("trade_state"))){
outTradeNo = decryptDataJobj.getString("out_trade_no");
log.info("支付成功 商户订单id: {}", outTradeNo);
try {
consumer.accept(outTradeNo);
} catch (Exception e) {
log.info(" 商户订单id: {} 业务失败 e: {}", outTradeNo, e);
return Boolean.FALSE;
}
}
} catch (Exception e) {
log.info("支付回调: {} 解析回调参数失败 e: {}", jsonObject.toJSONString(), e);
}
return Boolean.TRUE;
}
String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext){
try {
Cipher cipher = Cipher.getInstance("AES/GCM/noPadding");
SecretKey key = new SecretKeySpec(apiV3key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "UTF-8");
}catch (NoSuchAlgorithmException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (NoSuchPaddingException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (InvalidAlgorithmParameterException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (UnsupportedEncodingException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (BadPaddingException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
} catch (InvalidKeyException e) {
throw new com.ruoyi.common.exception.ServiceException(e.getMessage());
}
}
调用支付的Controller
@RequestMapping("/smallapp/pay")
@RequiredArgsConstructor
public class PayController extends BaseController {
private final WechatPayService wechatPayService;
...
}
组装预下单参数,调用预下单
PrepayRequest request = new PrepayRequest();
// 组装预下单参数,其他参数有需要的看着来
request.setAmount(支付金额,单位为分);
request.setAppid(wechatPayService.getAppId());
request.setMchid(wechatPayService.getMerchantId());
request.setOutTradeNo(唯一订单编号);
request.setNotifyUrl(wechatPayService.getNotifyUrl());
request.setTimeExpire(支付到期时间);
...
//支付用户的openid
Payer payer = new Payer();
payer.setOpenid(user.getOpenId());
request.setPayer(payer);
var resp = wechatPayService.payExecute(() ->
wechatPayService.getJsapiServiceExtension()
.prepayWithRequestPayment(request)
);
return success(resp);
uniapp
prepayByWechat({...}).then(res => {
wx.requestPayment({
timeStamp: res.data.timeStamp,
nonceStr: res.data.nonceStr,
package: res.data.packageVal,
signType: res.data.signType,
paySign: res.data.paySign,
success: (suc) => {
//微信支付成功
},
fail: (err) => {
//微信支付失败
}
})
})
支付回调 注意是post请求
@PostMapping("/wechatPayCallback")
@ResponseBody
public AjaxResult wechatPayCallback(@RequestBody JSONObject jsonObject){
var notifyExecute = wechatPayService.notifyExecute(jsonObject, outTradeNo -> {
//处理业务逻辑(outTradeNo唯一订单编号),处理失败要抛业务异常!
...
});
//成功返回200,失败返500
return notifyExecute? success(): error();
}
一套微信支付的祖传代码就完成了→_→
微信那个文档写得真拉,yue