生活随笔
收集整理的這篇文章主要介紹了
                                
springboot集成微信APP支付V3最新版
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
 
                                
                            
                            
                            
springboot集成微信APP支付V3最新版
- 初識微信支付(不明白微信文檔的請看,否則請跳過)
- 具體流程+代碼
- 第一步:申請證書并創建應用
- 第二步:下單支付(熟悉調用流程、封裝參數配置類、公用支付方法)
- 第三步:接收異步通知
 
 
- 結束總結:
 
初識微信支付(不明白微信文檔的請看,否則請跳過)
 
微信文檔地址
 眾所周知,看別人的文檔是個痛苦的事情,尤其是復雜的文檔,這里說一下自己總結看文檔的經驗,先不著急上去就看某一個功能,先把文檔的分類搞明白,然后是每個分類下的每個功能列表,切忌直接進去一頓操作跳轉,結果自己都暈了,感覺到處都是支付的信息,總結一句話,看文檔之前,先搞懂寫文檔人的套路,然后你會發現,真的是手到擒來,廢話不多說,直接進入正題。
 
第一步,進入文檔中心
 
 主要看這幾個,最重要的是API字典!
 
指引文檔
 
這里主要是告訴我們接入微信支付的前置條件以及滿足接入條件之后的開發步驟。這里建議大概看一下。
 
 
- 接入前的準備
 主要是準備一些支付所需要的資料,例如:appid,證書等,基本按照文檔一步步操作就能輕松搞定。
- 開發指引
 這里主要是講開發步驟,以及提供一些各語言的參考代碼,建議主看,初看的時候盡量先看一遍流程,先不要點里邊的鏈接跳轉過去看,搞明白了之后再回頭逐步去研究每個步驟的詳細介紹。
 總結一下,簡單的流程:
 1、前端請求后端支付接口(一般給個訂單號即可)
 2、后端加載證書,配置預支付所需要的參數
 3、請求微信支付下單接口,獲取預支付id,也就是所謂的 prepay_id
 4、組裝好前端拉起微信支付所需要的參數,返回給前端即可
 5、前端拉起微信支付,支付或取消,微信會以異步通知告知后端,這里需要后端提供一個接口給微信支付官方(文檔說是必須用https,我用的http也可以)
 6、接收微信異步通知,獲取相關參數,處理對應邏輯
 詳細流程及代碼邏輯,下邊會具體說!!!
API字典
 
這個比較簡單但也是最重要的,后邊我們會頻繁用到
 
 這個就不多說了,提醒一下,請求接口的時候一定要正確填寫接口地址和請求方式!!!
 
接口規則
 
這里主要講簽名,驗簽,敏感信息加解密(例如:異步通知返回的訂單信息),證書更新(微信支付證書有時間限制,需要定期更新)的介紹
 
 
具體流程+代碼
 
第一步:申請證書并創建應用
 
準備必要的材料,去微信開放平臺-管理中心,創建移動應用(應用沒有上線也可以申請,資料填好就行,沒有的先不填,基本可以通過審核),之后申請開通微信支付功能,得到所有配置所需要的參數。
 
注意,這里有個很容易配錯的地方:應用簽名,創建app應用的時候會讓你填寫應用簽名和應用包名,應用包名是你自己填寫的,保持跟安卓包名一致即可,應用簽名并不是你keystore文件里的MD5值,而是需要你用微信提供的工具安裝到手機上(同時安裝你的app),獲取你app的簽名。
 
 微信開發指引
 如果這里的連接下載不了簽名生成工具,可以去開放平臺安卓資源下載微信資源下載
 
 如果配錯應用簽名,你會發現app只能首次拉起微信支付,之后支付的時候,微信怎么都拉不起來!!!
 
第二步:下單支付(熟悉調用流程、封裝參數配置類、公用支付方法)
 
下載證書,配置微信支付。
 證書申請參考:證書申請
 
 配置微信支付config類:
 
public class WxPayInfoConfig {public final static String mchId 
= "********";public final static String appid 
= "********";public final static String apiV3Key 
= "********";public final static String refund_notify_url 
= "http://api.wx.com/refundNotify/refunded/wxNotify";public final static String pay_notify_url 
= "http://api.wx.com/payNotify/payed/wxNotify";public final static String privateKeyPath 
= "wx/apiclient_key.pem";public final static String singleUrl 
= "https://api.mch.weixin.qq.com/v3/pay/transactions/app";public final static String refundUrl 
= "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";public final static String queryUrl 
= "https://api.mch.weixin.qq.com/v3/pay/transactions/id/%s?mchid=%s";public final static String closeUrl 
= "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close";public static String mchSerialNo
;public static PrivateKey privateKey
;static {privateKey 
= WxPayUtil.getPrivateKey(privateKeyPath
);X509Certificate certificate 
= WxPayUtil.getX509Certificate();mchSerialNo 
= certificate
.getSerialNumber().toString(16).toUpperCase();}
}
 
提取微信支付工具類:
 這里注意,微信證書會過期,所以需要你定期更新證書,可手動更新,也可以自動更新,這里說下自動更新,自動更新,微信提供了java庫wechatpay-apache-httpclient(不僅封裝了證書相關發放,還封裝了下單,加解密的相關方法,可以細細研究一下)
 在我們往下看之前,請務必了解微信支付請求流程:
 以下單支付流程為例:
 
 
<dependency><groupId>com
.github
.wechatpay
-apiv3
</groupId
><artifactId>wechatpay
-apache
-httpclient
</artifactId
><version>0.2.3</version
>
</dependency
>
 
詳細介紹
 工具類配置
 
public class WxPayUtil {private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap 
= new ConcurrentHashMap<>();static AutoUpdateCertificatesVerifier getVerifier(String serialNumber
) {AutoUpdateCertificatesVerifier verifier 
= null;if (verifierMap
.isEmpty() || !verifierMap
.containsKey(serialNumber
)) {verifierMap
.clear();try {PrivateKeySigner signer 
= new PrivateKeySigner(WxPayInfoConfig.mchSerialNo
, WxPayInfoConfig.privateKey
);WechatPay2Credentials credentials 
= new WechatPay2Credentials(WxPayInfoConfig.mchId
, signer
);verifier 
= new AutoUpdateCertificatesVerifier(credentials
, WxPayInfoConfig.apiV3Key
.getBytes("utf-8"));verifierMap
.put(verifier
.getValidCertificate().getSerialNumber()+"", verifier
);} catch (UnsupportedEncodingException e
) {e
.printStackTrace();}} else {verifier 
= verifierMap
.get(serialNumber
);}return verifier
;}static CloseableHttpClient getHttpClient(String serialNumber
) {AutoUpdateCertificatesVerifier verifier 
= getVerifier(serialNumber
);if (verifier 
== null) {ExceptionCast.cast(CommonCode.OPERATION_ERROR
);}CloseableHttpClient httpClient 
= WechatPayHttpClientBuilder.create().withMerchant(WxPayInfoConfig.mchId
, WxPayInfoConfig.mchSerialNo
, WxPayInfoConfig.privateKey
).withValidator(new WechatPay2Validator(verifier
)).build();return httpClient
;}public static PrivateKey getPrivateKey(String fileName
) {try {ClassPathResource resource 
= new ClassPathResource(fileName
);String content 
= IOUtils.toString(resource
.getInputStream(), "utf-8");String privateKey 
= content
.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf 
= KeyFactory.getInstance("RSA");PrivateKey key 
= kf
.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey
)));return key
;} catch (NoSuchAlgorithmException e
) {throw new RuntimeException("當前Java環境不支持RSA", e
);} catch (InvalidKeySpecException e
) {throw new RuntimeException("無效的密鑰格式");} catch (UnsupportedEncodingException e
) {e
.printStackTrace();} catch (IOException e
) {e
.printStackTrace();}return null;}@SneakyThrowspublic static String appPaySign(String prepayid
, String nonceStr
, String timestamp
) {String signatureStr 
= Stream.of(WxPayInfoConfig.appid
, timestamp
, nonceStr
, prepayid
).collect(Collectors.joining("\n", "", "\n"));Signature sign 
= Signature.getInstance("SHA256withRSA");sign
.initSign(WxPayInfoConfig.privateKey
);sign
.update(signatureStr
.getBytes(StandardCharsets.UTF_8
));return Base64Utils.encodeToString(sign
.sign());}@SneakyThrowspublic static boolean verifySign(HttpServletRequest request
,String body
) {boolean verify 
= false;try {String wechatPaySignature 
= request
.getHeader("Wechatpay-Signature");String wechatPayTimestamp 
= request
.getHeader("Wechatpay-Timestamp");String wechatPayNonce 
= request
.getHeader("Wechatpay-Nonce");String wechatPaySerial 
= request
.getHeader("Wechatpay-Serial");String signStr 
= Stream.of(wechatPayTimestamp
, wechatPayNonce
, body
).collect(Collectors.joining("\n", "", "\n"));AutoUpdateCertificatesVerifier verifier 
= getVerifier(wechatPaySerial
);if (verifier 
!= null) {Signature signature 
= Signature.getInstance("SHA256withRSA");signature
.initVerify(verifier
.getValidCertificate());signature
.update(signStr
.getBytes(StandardCharsets.UTF_8
));verify 
= signature
.verify(Base64.getDecoder().decode(wechatPaySignature
.getBytes()));}} catch (InvalidKeyException e
) {e
.printStackTrace();} catch (NoSuchAlgorithmException e
) {e
.printStackTrace();}return verify
;}public static X509Certificate getX509Certificate() {ClassPathResource resource 
= new ClassPathResource("wx/apiclient_cert.p12");KeyStore keyStore
;X509Certificate certificate 
= null;try {keyStore 
= KeyStore.getInstance("PKCS12");keyStore
.load(resource
.getInputStream(), WxPayInfoConfig.mchId
.toCharArray());certificate 
= (X509Certificate) keyStore
.getCertificate("Tenpay Certificate");certificate
.checkValidity();} catch (KeyStoreException | IOException e
) {e
.printStackTrace();} catch (CertificateNotYetValidException e
) {e
.printStackTrace();} catch (CertificateExpiredException e
) {e
.printStackTrace();} catch (CertificateException e
) {e
.printStackTrace();} catch (NoSuchAlgorithmException e
) {e
.printStackTrace();}return certificate
;}public static String decryptToString(String associatedData
, String nonce
, String ciphertext
) {String cert 
= null;try {AesUtil aesUtil 
= new AesUtil(WxPayInfoConfig.apiV3Key
.getBytes(StandardCharsets.UTF_8
));cert 
= aesUtil
.decryptToString(associatedData
.getBytes(StandardCharsets.UTF_8
), nonce
.getBytes(StandardCharsets.UTF_8
), ciphertext
);} catch (GeneralSecurityException e
) {e
.printStackTrace();} catch (IOException e
) {e
.printStackTrace();}return cert
;}public static String rsaDecryptOAEP(String ciphertext
) {String text 
= null;try {text 
= RsaCryptoUtil.decryptOAEP(ciphertext
, WxPayInfoConfig.privateKey
);} catch (BadPaddingException e
) {e
.printStackTrace();}return text
;}public static String rsaEncryptOAEP(String message
) {String ciphertext 
= null;try {AutoUpdateCertificatesVerifier verifier 
= getVerifier(null);X509Certificate certificate 
= verifier
.getValidCertificate();ciphertext 
= RsaCryptoUtil.encryptOAEP(message
, certificate
);} catch (IllegalBlockSizeException e
) {e
.printStackTrace();}return ciphertext
;}public static <T> T wxHttpRequest(String method
, String url
, String reqdata
, Class<T> t
) {T resultBean 
= null;T instance 
= null;CloseableHttpResponse response
;CloseableHttpClient httpClient 
= null;try {if (!verifierMap
.isEmpty()) {String bigInteger 
= verifierMap
.keys().nextElement();httpClient 
= getHttpClient(bigInteger
);} else {httpClient 
= getHttpClient("1");}instance 
= t
.newInstance();if ("get".equalsIgnoreCase(method
)) {HttpGet httpGet 
= new HttpGet(url
);httpGet
.setHeader("Accept", "application/json");response 
= httpClient
.execute(httpGet
);} else {StringEntity entity 
= new StringEntity(reqdata
, StandardCharsets.UTF_8
);entity
.setContentType("application/json");HttpPost httpPost 
= new HttpPost(url
);httpPost
.setEntity(entity
);httpPost
.setHeader("Accept", "application/json");response 
= httpClient
.execute(httpPost
);}int statusCode 
= response
.getStatusLine().getStatusCode();if (statusCode 
== 200) {String result 
= EntityUtils.toString(response
.getEntity(), StandardCharsets.UTF_8
);JSONObject jsonObject 
= JSON
.parseObject(result
);if (instance 
instanceof JSONObject) {resultBean 
= (T) jsonObject
;} else {resultBean 
= JSONObject.parseObject(jsonObject
.toString(), t
);}} else if (statusCode 
== 204) {log
.info("關單成功");String code 
= "204";resultBean 
= (T) code
;} else {log
.error("錯誤狀態碼 = " + statusCode 
+ ",返回的錯誤信息 = " + EntityUtils.toString(response
.getEntity()));ExceptionCast.cast(CommonCode.WX_ORDER_REAUEST_FAIL
);}} catch (InstantiationException e
) {e
.printStackTrace();} catch (IllegalAccessException e
) {e
.printStackTrace();} catch (ClientProtocolException e
) {e
.printStackTrace();} catch (IOException e
) {e
.printStackTrace();} finally {try {if (httpClient 
!= null) {httpClient
.close();}} catch (IOException e
) {e
.printStackTrace();}}return resultBean
;}public static Map<String, String> createOrder(Integer totalPrice
, String out_trade_no
, Stringdescription
) {WxOnePayVo wxOnePayVo 
= new WxOnePayVo(totalPrice
);wxOnePayVo
.setAppid(WxPayInfoConfig.appid
);wxOnePayVo
.setMchid(WxPayInfoConfig.mchId
);wxOnePayVo
.setNotify_url(WxPayInfoConfig.pay_notify_url
);wxOnePayVo
.setDescription(description
);wxOnePayVo
.setAttach(CommonConstant.pay_order
);wxOnePayVo
.setOut_trade_no(out_trade_no
);wxOnePayVo
.setTime_expire(CommonUtil.getTimeExpire());String reqdata 
= JSON
.toJSONString(wxOnePayVo
);Map<String, String> returnMap 
= getPayReturnMap("POST", reqdata
, WxPayInfoConfig.singleUrl
);return returnMap
;}public static Map<String, String> getPayReturnMap(String method
, String reqdata
, String url
) {HashMap<String, String> returnMap 
= new HashMap<String, String>();JSONObject jsonObject 
= wxHttpRequest(method
, url
, reqdata
, JSONObject.class);String prepay_id 
= (String) jsonObject
.get("prepay_id");String nonceStr 
= UUID
.randomUUID().toString(true);String timestamp 
= String.valueOf(System.currentTimeMillis() / 1000);String signature 
= appPaySign(prepay_id
, nonceStr
, timestamp
);returnMap
.put("appid", WxPayInfoConfig.appid
);returnMap
.put("partnerid", WxPayInfoConfig.mchId
);returnMap
.put("prepayid", prepay_id
);returnMap
.put("package", "Sign=WXPay");returnMap
.put("noncestr", nonceStr
);returnMap
.put("timestamp", timestamp
);returnMap
.put("sign", signature
);return returnMap
;}public static WxRefundReturnInfoVo refundOrder(WxRefundInfoVo wxRefundInfoVo
) {wxRefundInfoVo
.setNotify_url(WxPayInfoConfig.refund_notify_url
);String reqdata 
= JSON
.toJSONString(wxRefundInfoVo
);WxRefundReturnInfoVo wxRefundReturnInfoVo 
= wxHttpRequest("POST", WxPayInfoConfig.refundUrl
, reqdata
, WxRefundReturnInfoVo.class);return wxRefundReturnInfoVo
;}public static NotifyResourceVO QueryOrder(String transaction_id
) {String url 
= String.format(WxPayInfoConfig.queryUrl
, transaction_id
, WxPayInfoConfig.mchId
);NotifyResourceVO notifyResourceVO 
= wxHttpRequest("GET", url
, null, NotifyResourceVO.class);return notifyResourceVO
;}public static String CloseOrder(String out_trade_no
) {String url 
= String.format(WxPayInfoConfig.closeUrl
, out_trade_no
);String reqdata 
= "{\"mchid\": \"" + WxPayInfoConfig.mchId 
+ "\"}";String code 
= wxHttpRequest("POST", url
, reqdata
, String.class);return code
;}} 
實體類:
 WxOnePayVo:請求預付單標識的關鍵參數
 
@Data
public class WxOnePayVo {private String out_trade_no
;private String mchid
;private String appid
;private String attach
;private String description
;private String notify_url
;private String time_expire
;private Amount amount
;@Dataclass Amount {@ApiModelProperty("總金額")private Integer total
;@ApiModelProperty("CNY")private String currency 
= "CNY";}public WxOnePayVo(Integer total
){Amount amount 
= new Amount();amount
.setTotal(total
);this.amount 
= amount
;}
} 
代碼里都有對應的方法,順序可能有些亂,不過參照上邊的流程圖看,應該沒問題,代碼里的加解密敏感信息,我暫時我用到,只用了解密報文(decryptToString)。
 
第三步:接收異步通知
 
接收異步通知(按照第二步完成支付信息的封裝并返回給前端之后,接下來就是默默等待異步通知了,所有支付的后續工作都在這里完成)
 流程:定義異步通知接口-接收通知并解析處理
 關鍵代碼:
 
	@ApiOperation(value 
= "微信異步通知", notes 
= "微信異步通知")@PostMapping(value 
= "/wxNotify")public Map<String, String> getTenPayNotify(HttpServletRequest request
) throws IOException {HashMap<String, String> returnMap 
= new HashMap<>(2);String body 
= request
.getReader().lines().collect(Collectors.joining());WxPayAsyncVo wxPayAsyncVo 
= JSONObject.parseObject(body
, WxPayAsyncVo.class);boolean verifySign 
= WxPayUtil.verifySign(request
, body
);if (verifySign
) {com.ls.modules.common.vo.wx.pay.Resource resource 
= wxPayAsyncVo
.getResource();String notifyResourceStr 
= WxPayUtil.decryptToString(resource
.getAssociated_data(), resource
.getNonce(), resource
.getCiphertext());NotifyResourceVO notifyResourceVO 
= JSONObject.parseObject(notifyResourceStr
, NotifyResourceVO.class);}}
 
封裝的實體類對象:
 WxPayAsyncVo:通知結果大對象
 
@ToString
@Data
public class WxPayAsyncVo {private String id
;private String create_time
;private String event_type
;private String resource_type
;private String summary
;private Resource resource
;
} 
NotifyResourceVO:大對象中包含的資源對象
 
@Data
public class NotifyResourceVO {private String appid
;private String mchid
;private String out_trade_no
;private String transaction_id
;private String trade_type
;private String trade_state
;private String trade_state_desc
;private String bank_type
;private String success_time
;private String attach
;private Payer payer
;private NotifyAmount amount
;@Datapublic class Payer {private String openid
;}
}
 
結束總結:
 
了解清楚app下單,那么其他的功能,比如:關單、查單、退款等等操作都基本相同,直接在公共類中增加對應的方法即可。
 配置證書和各種id,key的時候一定要仔細,最容易出錯的就是路徑問題,id或者密鑰等配反了、錯了。
 增加新功能的時候多查看微信提供的示例代碼,另外注意的是,微信的示例代碼有些是V2的或者自己手寫實現的,最好看完示例代碼,去這里java類庫看看,里邊封裝了很多方法,能夠節省很多工作量。
                            總結
                            
                                以上是生活随笔為你收集整理的springboot集成微信APP支付V3最新版的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。