赞
踩
1、在 微信公众平台 注册应用,并保存好appId和appSecret
2、在微信支付商户平台 注册一个商户,保存好mchId(商户id)、api_key(支付密钥)、以及商户证书序列号。还需要将支付商户密钥文件下载放到项目resources目录中
(结构中包含的其他内容与支付无关)
1、导入jar包
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.10</version> </dependency> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.1</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.26</version> </dependency>
2、在yaml文件中配置参数
wechatpay: #应用编号 正式号id appId: XXXXXX # 小程序密钥 appSecret: XXXXXX #商户号 mchId: XXXXXX # APIv3密钥(在微信支付平台中自己设置) apiV3Key: XXXXXX # 支付成功后微信官方会自动调用我们设定的接口:域名/接口名 # 支付通知回调, 本地测试内网穿透地址(仅用于做本地测试) # notifyUrl: http://84cd47.natappfree.cc/wechatPay/notify-pay # 支付通知回调, 线上域名地址(正常使用服务器配置的域名即可),小程序上线后前后端访问数据时只能用https不能用http,因此还需要为域名配置SSL证书,可根据使用的服务器查看如何配置 notifyUrl: XXXXXX # 密钥路径,resources根目录下 keyPemPath: src/main/resources/wechatPay/apiclient_key.pem certPath: src/main/resources/wechatPay/apiclient_cert.pem certP12Path: src/main/resources/wechatPay/apiclient_cert.p12 # 商户证书序列号(可在微信支付平台商户信息中查看) serialNo: XXXXXXXXXX
3、将参数注入到配置类中
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 微信支付配置类 */ @Component @Data @ConfigurationProperties(prefix = "wechatpay") public class WechatPayConfig { /** * 小程序appId */ private String appId; /** * 小程序密钥 */ private String appSecret; /** * 商户号(微信支付平台设置) */ private String mchId; /** * APIv3密钥(微信支付平台设置) */ private String apiV3Key; /** * 支付成功回调地址 */ private String notifyUrl; /** * 商户证书序列号 */ private String serialNo; }
4、支付工具类(目前只有支付,没有退款和查询)
import com.alibaba.fastjson2.JSON; import com.project.config.WechatPayConfig; import com.wechat.pay.java.core.RSAAutoCertificateConfig; import com.wechat.pay.java.core.notification.NotificationConfig; import com.wechat.pay.java.core.notification.NotificationParser; import com.wechat.pay.java.core.notification.RequestParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.util.Map; @Service public class WxPayUtil { private final WechatPayConfig wechatPayConfig; @Autowired public WxPayUtil(WechatPayConfig wechatPayConfig) { this.wechatPayConfig = wechatPayConfig; } public <T> T getNotificationParser(HttpServletRequest request, Map<String, Object> body, Class<T> clazz) throws Exception { String privateKey = loadKeyByResource("wechatPay/apiclient_key.pem"); RequestParam requestParam = new RequestParam.Builder() .serialNumber(request.getHeader("Wechatpay-Serial")) .nonce(request.getHeader("Wechatpay-Nonce")) .signature(request.getHeader("Wechatpay-Signature")) .timestamp(request.getHeader("Wechatpay-Timestamp")) .signType(request.getHeader("Wechatpay-Signature-Type")) .body(JSON.toJSONString(body)) .build(); NotificationConfig config = new RSAAutoCertificateConfig.Builder() .merchantId(wechatPayConfig.getMchId()) .privateKey(privateKey) .merchantSerialNumber(wechatPayConfig.getSerialNo()) .apiV3Key(wechatPayConfig.getApiV3Key()) .build(); NotificationParser parser = new NotificationParser(config); return parser.parse(requestParam, clazz); } /** * 通过文件路径获取文件内容 * ClassPathResource可以在jar包中运行,但不能使用其中getFile().getPath() * @param path 文件路径 * @return 文件内容 * @throws Exception 报错信息 */ public static String loadKeyByResource(String path) throws Exception { ClassPathResource resource = new ClassPathResource(path); byte[] byteArray = FileCopyUtils.copyToByteArray(resource.getInputStream()); return new String(byteArray, StandardCharsets.UTF_8); } }
5、service和impl
//service public interface WechatPayService { } //impl import com.project.config.WechatPayConfig; import com.project.service.WechatPayService; import com.project.utils.WxPayUtil; import com.project.vo.WechatPayVo; import com.wechat.pay.java.core.Config; import com.wechat.pay.java.core.RSAAutoCertificateConfig; import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; import com.wechat.pay.java.service.payments.jsapi.model.Amount; import com.wechat.pay.java.service.payments.jsapi.model.Payer; import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest; import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; import com.wechat.pay.java.service.refund.RefundService; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; @Service public class WechatPayServiceImpl implements WechatPayService { public static JsapiServiceExtension jsapiServiceExtension; public static RefundService refundService; private final WechatPayConfig wechatPayConfig; public static final String CNY = "CNY"; public WechatPayServiceImpl(WechatPayConfig wechatPayConfig) throws Exception { this.wechatPayConfig = wechatPayConfig; this.init(); } @PostConstruct public void init() throws Exception { String privateKey = WxPayUtil.loadKeyByResource("wechatPay/apiclient_key.pem"); // 初始化商户配置 Config config = new RSAAutoCertificateConfig.Builder() .merchantId(wechatPayConfig.getMchId()) .privateKey(privateKey) .merchantSerialNumber(wechatPayConfig.getSerialNo()) .apiV3Key(wechatPayConfig.getApiV3Key()) .build(); // 初始化服务 jsapiServiceExtension = new JsapiServiceExtension.Builder() .config(config) .signType("RSA") // 不填默认为RSA .build(); refundService = new RefundService.Builder().config(config).build(); } /** * JSAPI支付下单,并返回JSAPI调起支付数据 * <p> * // * @param details 订单描述 * // * @param outTradeNo id * // * @param money 金额 * // * @param openId 用户openid * // * @param orderBean 购买商品 goods、充值 charge+ * * @return PrepayWithRequestPaymentResponse 支付信息 */ public PrepayWithRequestPaymentResponse prepayWithRequestPayment(WechatPayVo wechatPayVo,String outTradeNo) { PrepayRequest request = new PrepayRequest(); Amount amount = new Amount(); //支付金额 amount.setTotal((int) wechatPayVo.getPayMoney()); amount.setCurrency(CNY); // 设置支付成功后的回调 request.setNotifyUrl(wechatPayConfig.getNotifyUrl()); request.setAmount(amount); //支付项目的名称 request.setAttach(wechatPayVo.getName()); request.setAppid(wechatPayConfig.getAppId()); request.setMchid(wechatPayConfig.getMchId()); //自定义设置支付成功后的商户单号32位字符 request.setOutTradeNo(outTradeNo); //订单描述 request.setDescription(wechatPayVo.getDetails()); Payer payer = new Payer(); //前端传递的openId payer.setOpenid(wechatPayVo.getOpenId()); request.setPayer(payer); // 调用接口 return jsapiServiceExtension.prepayWithRequestPayment(request); } }
6、自定义工具类(用于获取用户支付唯一标识的openId)
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.project.vo.IdCardInfo; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestBody; import java.util.HashMap; import java.util.Map; @Slf4j public class UtilClass { /** * 获取openid和session_key * * @param code 由前端传值 * @param appId * @param appSecret * @return */ public static JSONObject getOpenIdAndSessionKey(String code, String appId, String appSecret) { JSONObject jsonObject = null; try { // 请求微信接口获取openid和session_key String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code"; HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse response = httpClient.execute(httpGet); String jsonString = EntityUtils.toString(response.getEntity()); jsonObject = JSONObject.parseObject(jsonString); } catch (Exception e) { e.printStackTrace(); } return jsonObject; } }
前端调起支付传递的对象参数
import lombok.Data; /** * 微信支付vo */ @Data public class WechatPayVo { /** * 用户id */ private long userId; /** * 用户唯一标识 */ private String openId; /** * 订单描述 */ private String details; /** * 订单名称 */ private String name; /** * 支付金额 */ private double payMoney; /** * 支付时间(在支付成功的回调接口中获取数据后再赋值) */ private String payTime; }
**注意:微信官方调用自定义的回调接口时会传递一个JSON对象,里面包含基础的支付时间、订单描述、官方的提供的交易订单号、支付成功等信息;但需要通过解密得到,因此还需要一个解密的工具类 AesUtil **
import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONObject; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; /** * 解密支付回调工具类 */ public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; private final byte[] aesKey; /** * 小程序支付密钥 */ private final String APIv3Key = ""; public AesUtil(byte[] key) { if (key.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); } this.aesKey = key; } public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(aesKey, "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 | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } /** * 解密支付回调返回的resource对象 * 包含支付的所有信息 */ public JSONObject decryptResource(JSONObject jsonObject) { String json = jsonObject.toString(); //解密resource对象 String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data"); String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext"); String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce"); String decryptData; try { decryptData = new AesUtil(this.APIv3Key.getBytes(StandardCharsets.UTF_8)) .decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e); } return JSONObject.parseObject(decryptData, JSONObject.class); } }
import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONObject; import com.project.utils.UtilClass; import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; import com.project.config.WechatPayConfig; import com.project.service.impl.WechatPayServiceImpl; import com.project.utils.AesUtil; import com.project.vo.WechatPayVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.UUID; @RestController @Api(tags = "微信支付接口") @RequestMapping("wechatPay") public class WechatPayController { @Resource private WechatPayServiceImpl wechatPayServiceImpl; @Resource private WechatPayConfig wechatPayConfig; //前端首先调用此接口获取到用户唯一标识openId,用于后续调起支付的传参 @ApiOperation("获取openid和session_key") @GetMapping("getOpenIdAndSessionKey") public com.alibaba.fastjson.JSONObject getOpenIdAndSessionKey(String code) { return UtilClass.getOpenIdAndSessionKey(code, wechatPayConfig.getAppId(), wechatPayConfig.getAppSecret()); } @ApiOperation("调起支付") @PostMapping("pay") public synchronized PrepayWithRequestPaymentResponse prepayWithRequestPayment(@RequestBody WechatPayVo wechatPayVo) { String outTradeNo = UUID.randomUUID().toString().replaceAll("-", ""); return wechatPayServiceImpl.prepayWithRequestPayment(wechatPayVo, outTradeNo); } //支付成功后微信会自动调用此接口 @ApiOperation("支付成功的回调") @PostMapping("notify-pay") public Object callBackWeiXinPay(@RequestBody JSONObject jsonObject) { try { String key = wechatPayConfig.getApiV3Key(); String json = jsonObject.toString(); //解密 jsonObject 对象 String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data"); String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext"); String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce"); String decryptData = new AesUtil(key.getBytes(StandardCharsets.UTF_8)).decryptToString (associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class); if ("支付成功".equals(decryptDataObj.get("trade_state_desc"))) { //支付成功后的业务操作 } } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e); } return new Object(); } }
最后在本地测试时配置的内网穿透域名可通过 NATAPP内网穿透工具 设置,可在NATAPP官网学习使用
简单步骤:
1、注册登录
2、点击购买隧道——>免费隧道
3、根据教程下载以下文件并做简单配置后双击 natapp.exe
运行完成后如下,此时红圈内则是内网穿透的域名,拼接上我们的支付回调接口后 http://84cd47.natappfree.cc/wechatPay/notify-pay 即可使用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。