一、準(zhǔn)備所需賬號(hào)以及配置信息
1、在 微信公眾平臺(tái) 注冊(cè)應(yīng)用,并保存好appId和appSecret
2、在微信支付商戶平臺(tái) 注冊(cè)一個(gè)商戶,保存好mchId(商戶id)、api_key(支付密鑰)、以及商戶證書序列號(hào)。還需要將支付商戶密鑰文件下載放到項(xiàng)目resources目錄中
項(xiàng)目結(jié)構(gòu)
(結(jié)構(gòu)中包含的其他內(nèi)容與支付無(wú)關(guān))
二、開發(fā)環(huán)境
1、導(dǎo)入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文件中配置參數(shù)
wechatpay:
#應(yīng)用編號(hào) 正式號(hào)id
appId: XXXXXX
# 小程序密鑰
appSecret: XXXXXX
#商戶號(hào)
mchId: XXXXXX
# APIv3密鑰(在微信支付平臺(tái)中自己設(shè)置)
apiV3Key: XXXXXX
# 支付成功后微信官方會(huì)自動(dòng)調(diào)用我們?cè)O(shè)定的接口:域名/接口名
# 支付通知回調(diào), 本地測(cè)試內(nèi)網(wǎng)穿透地址(僅用于做本地測(cè)試)
# notifyUrl: http://84cd47.natappfree.cc/wechatPay/notify-pay
# 支付通知回調(diào), 線上域名地址(正常使用服務(wù)器配置的域名即可),小程序上線后前后端訪問數(shù)據(jù)時(shí)只能用https不能用http,因此還需要為域名配置SSL證書,可根據(jù)使用的服務(wù)器查看如何配置
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
# 商戶證書序列號(hào)(可在微信支付平臺(tái)商戶信息中查看)
serialNo: XXXXXXXXXX
3、將參數(shù)注入到配置類中
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;
/**
* 商戶號(hào)(微信支付平臺(tái)設(shè)置)
*/
private String mchId;
/**
* APIv3密鑰(微信支付平臺(tái)設(shè)置)
*/
private String apiV3Key;
/**
* 支付成功回調(diào)地址
*/
private String notifyUrl;
/**
* 商戶證書序列號(hào)
*/
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);
}
/**
* 通過文件路徑獲取文件內(nèi)容
* ClassPathResource可以在jar包中運(yùn)行,但不能使用其中g(shù)etFile().getPath()
* @param path 文件路徑
* @return 文件內(nèi)容
* @throws Exception 報(bào)錯(cuò)信息
*/
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();
// 初始化服務(wù)
jsapiServiceExtension =
new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA") // 不填默認(rèn)為RSA
.build();
refundService = new RefundService.Builder().config(config).build();
}
/**
* JSAPI支付下單,并返回JSAPI調(diào)起支付數(shù)據(jù)
* <p>
* // * @param details 訂單描述
* // * @param outTradeNo id
* // * @param money 金額
* // * @param openId 用戶openid
* // * @param orderBean 購(gòu)買商品 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);
// 設(shè)置支付成功后的回調(diào)
request.setNotifyUrl(wechatPayConfig.getNotifyUrl());
request.setAmount(amount);
//支付項(xiàng)目的名稱
request.setAttach(wechatPayVo.getName());
request.setAppid(wechatPayConfig.getAppId());
request.setMchid(wechatPayConfig.getMchId());
//自定義設(shè)置支付成功后的商戶單號(hào)32位字符
request.setOutTradeNo(outTradeNo);
//訂單描述
request.setDescription(wechatPayVo.getDetails());
Payer payer = new Payer();
//前端傳遞的openId
payer.setOpenid(wechatPayVo.getOpenId());
request.setPayer(payer);
// 調(diào)用接口
return jsapiServiceExtension.prepayWithRequestPayment(request);
}
}
6、自定義工具類(用于獲取用戶支付唯一標(biāo)識(shí)的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 {
// 請(qǐng)求微信接口獲取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;
}
}
前端調(diào)起支付傳遞的對(duì)象參數(shù)
import lombok.Data;
/**
* 微信支付vo
*/
@Data
public class WechatPayVo {
/**
* 用戶id
*/
private long userId;
/**
* 用戶唯一標(biāo)識(shí)
*/
private String openId;
/**
* 訂單描述
*/
private String details;
/**
* 訂單名稱
*/
private String name;
/**
* 支付金額
*/
private double payMoney;
/**
* 支付時(shí)間(在支付成功的回調(diào)接口中獲取數(shù)據(jù)后再賦值)
*/
private String payTime;
}
**注意:微信官方調(diào)用自定義的回調(diào)接口時(shí)會(huì)傳遞一個(gè)JSON對(duì)象,里面包含基礎(chǔ)的支付時(shí)間、訂單描述、官方的提供的交易訂單號(hào)、支付成功等信息;但需要通過解密得到,因此還需要一個(gè)解密的工具類 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;
/**
* 解密支付回調(diào)工具類
*/
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("無(wú)效的ApiV3Key,長(zhǎng)度必須為32個(gè)字節(jié)");
}
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);
}
}
/**
* 解密支付回調(diào)返回的resource對(duì)象
* 包含支付的所有信息
*/
public JSONObject decryptResource(JSONObject jsonObject) {
String json = jsonObject.toString();
//解密resource對(duì)象
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);
}
}
7、controller層文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-851460.html
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;
//前端首先調(diào)用此接口獲取到用戶唯一標(biāo)識(shí)openId,用于后續(xù)調(diào)起支付的傳參
@ApiOperation("獲取openid和session_key")
@GetMapping("getOpenIdAndSessionKey")
public com.alibaba.fastjson.JSONObject getOpenIdAndSessionKey(String code) {
return UtilClass.getOpenIdAndSessionKey(code, wechatPayConfig.getAppId(), wechatPayConfig.getAppSecret());
}
@ApiOperation("調(diào)起支付")
@PostMapping("pay")
public synchronized PrepayWithRequestPaymentResponse prepayWithRequestPayment(@RequestBody WechatPayVo wechatPayVo) {
String outTradeNo = UUID.randomUUID().toString().replaceAll("-", "");
return wechatPayServiceImpl.prepayWithRequestPayment(wechatPayVo, outTradeNo);
}
//支付成功后微信會(huì)自動(dòng)調(diào)用此接口
@ApiOperation("支付成功的回調(diào)")
@PostMapping("notify-pay")
public Object callBackWeiXinPay(@RequestBody JSONObject jsonObject) {
try {
String key = wechatPayConfig.getApiV3Key();
String json = jsonObject.toString();
//解密 jsonObject 對(duì)象
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"))) {
//支付成功后的業(yè)務(wù)操作
}
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
return new Object();
}
}
最后在本地測(cè)試時(shí)配置的內(nèi)網(wǎng)穿透域名可通過 NATAPP內(nèi)網(wǎng)穿透工具 設(shè)置,可在NATAPP官網(wǎng)學(xué)習(xí)使用
簡(jiǎn)單步驟:
1、注冊(cè)登錄
2、點(diǎn)擊購(gòu)買隧道——>免費(fèi)隧道
3、根據(jù)教程下載以下文件并做簡(jiǎn)單配置后雙擊 natapp.exe
運(yùn)行完成后如下,此時(shí)紅圈內(nèi)則是內(nèi)網(wǎng)穿透的域名,拼接上我們的支付回調(diào)接口后 http://84cd47.natappfree.cc/wechatPay/notify-pay 即可使用文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-851460.html
到了這里,關(guān)于JAVA接入小程序微信支付的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!