New file |
| | |
| | | package com.yeshi.buwan.controller; |
| | | |
| | | import com.yeshi.buwan.util.StringUtil; |
| | | import net.sf.json.JSONObject; |
| | | import org.apache.commons.io.IOUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.stereotype.Controller; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.yeshi.utils.entity.wx.WXAPPInfo; |
| | | import org.yeshi.utils.wx.WXPayV3Util; |
| | | |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import javax.crypto.spec.GCMParameterSpec; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.IOException; |
| | | import java.security.GeneralSecurityException; |
| | | import java.security.InvalidAlgorithmParameterException; |
| | | import java.security.InvalidKeyException; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.util.Base64; |
| | | |
| | | @Controller |
| | | @RequestMapping("wx") |
| | | public class WXController { |
| | | Logger logger = LoggerFactory.getLogger(WXController.class); |
| | | |
| | | private static String decryptToString(String apiV3Key, String associatedData, String nonce, String ciphertext) |
| | | throws GeneralSecurityException, IOException { |
| | | try { |
| | | Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); |
| | | |
| | | SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes("UTF-8"), "AES"); |
| | | GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8")); |
| | | |
| | | cipher.init(Cipher.DECRYPT_MODE, key, spec); |
| | | cipher.updateAAD(associatedData.getBytes("UTF-8")); |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理通知结果 |
| | | * |
| | | * @param request |
| | | * @throws Exception |
| | | */ |
| | | private void process(HttpServletRequest request) throws Exception { |
| | | WXAPPInfo wxappInfo = new WXAPPInfo(); |
| | | //验证证书序列号 |
| | | String mchSerialNo = request.getHeader("Wechatpay-Serial"); |
| | | if (!mchSerialNo.equalsIgnoreCase(wxappInfo.getMchSerialNo())) { |
| | | throw new Exception("证书序列号不一致"); |
| | | } |
| | | |
| | | String timeStamp = request.getHeader("Wechatpay-Timestamp"); |
| | | String nonce = request.getHeader("Wechatpay-Nonce"); |
| | | String signature = request.getHeader("Wechatpay-Signature"); |
| | | String data = null; |
| | | |
| | | try { |
| | | if (request.getInputStream() != null) { |
| | | String entity = IOUtils.toString(request.getInputStream(), "UTF-8"); |
| | | data = entity; |
| | | logger.error("微信支付回调entity:" + entity); |
| | | } |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | |
| | | if (StringUtil.isNullOrEmpty(data)) { |
| | | throw new Exception("通知的内容为空"); |
| | | } |
| | | |
| | | JSONObject resultJson = JSONObject.fromObject(data); |
| | | String eventType = resultJson.optString("event_type"); |
| | | switch (eventType) { |
| | | //支付成功 |
| | | case "TRANSACTION.SUCCESS": |
| | | JSONObject resource = resultJson.optJSONObject("resource"); |
| | | String ciphertext = resource.optString("ciphertext"); |
| | | String r = decryptToString(wxappInfo.getApiV3Key(), resource.optString("associated_data"), resource.optString("nonce"), ciphertext); |
| | | //解密格式如下 {"mchid":"1520950211","appid":"wxa99686bb65a9f466","out_trade_no":"buwan-vip-8","transaction_id":"4200000796202101259681241680","trade_type":"MWEB","trade_state":"SUCCESS","trade_state_desc":"支付成功","bank_type":"OTHERS","attach":"","success_time":"2021-01-25T16:18:33+08:00","payer":{"openid":"oq7R20lxhKF8qSnkszxFJHViyKEY"},"amount":{"total":10,"payer_total":10,"currency":"CNY","payer_currency":"CNY"}} |
| | | JSONObject decript = JSONObject.fromObject(r); |
| | | String outTradeNo = decript.optString("out_trade_no"); |
| | | String appId = decript.optString("appid"); |
| | | String mchId = decript.optString("mchid"); |
| | | String tradeState = decript.optString("trade_state"); |
| | | //支付成功 |
| | | if (tradeState.equalsIgnoreCase("SUCCESS")) { |
| | | boolean isPaySuccess = WXPayV3Util.isPaySuccess(outTradeNo, wxappInfo); |
| | | if (isPaySuccess) { |
| | | //TODO 支付成功 |
| | | } |
| | | } |
| | | |
| | | break; |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 微信支付结果通知 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @RequestMapping("pay/vip") |
| | | public void vipPay(HttpServletRequest request, HttpServletResponse response) throws IOException { |
| | | try { |
| | | process(request); |
| | | JSONObject data = new JSONObject(); |
| | | data.put("code", "SUCCESS"); |
| | | data.put("message", "处理成功"); |
| | | response.sendError(200, data.toString()); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | JSONObject data = new JSONObject(); |
| | | data.put("code", "FAIL"); |
| | | data.put("message", e.getMessage()); |
| | | response.sendError(500, data.toString()); |
| | | } |
| | | |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | |
| | | String result = "{\n" + |
| | | " \"id\":\"c86a0485-0b56-5c6a-b449-d2a17fbb98ea\",\n" + |
| | | " \"create_time\":\"2021-01-25T16:18:33+08:00\",\n" + |
| | | " \"resource_type\":\"encrypt-resource\",\n" + |
| | | " \"event_type\":\"TRANSACTION.SUCCESS\",\n" + |
| | | " \"summary\":\"支付成功\",\n" + |
| | | " \"resource\":{\n" + |
| | | " \"original_type\":\"transaction\",\n" + |
| | | " \"algorithm\":\"AEAD_AES_256_GCM\",\n" + |
| | | " \"ciphertext\":\"UG6kFmJ4z+zCyOldYZLa/y+jzWwyLLMjc8usFqum0Yh08qR0nGPhLYyXCyIGi6DvnG33Ju7t1grKBA5VP0nLqw8NrITwajCK+Ph6avkxSz6lv7ObenIGp6tg+SJe/UxS9rToLH0sDVUB4L6XvawHw5ite/40G0lrdVCbZlJZCm4NxfnH3iRMnBZBw3QaUlySRoKugMIc2WzloHueq7K4bc7tCln4uro3eNDEHe++p2QWuiHmDm6s+aMLHKdQj723aVPhmZLc1kWqUAua6QJx77Jn3eYy8I0PksTKh1EdK3DV4+Fl4+UBZ86aqHdIHy+7gj0vRyDXXIUdfHYHQPMkYr46KfjoVSbA/Jop+2uH610a8jBRP4aP/3YJ2XBJbvw/DSupTJ7z8k1e9xCNJUfN3SeycvFIyaQI/mYi8jpe2F6ek2wexGUAXB5bDmoNGMGkIbbLE9NjJazF8O1WtQilqMIeJ/CiW6Pnrn0N/BibK1EuJxyr2dXmHt+aA+jpvwI8pTWBXn8ZeYG9CeMM0Ml+mBQcPzYdPx1ZsrKylJoIlvwSZTy4cRGSQlC/0w==\",\n" + |
| | | " \"associated_data\":\"transaction\",\n" + |
| | | " \"nonce\":\"2pIZSvqoRYYu\"\n" + |
| | | " }\n" + |
| | | "}"; |
| | | WXAPPInfo wxappInfo = new WXAPPInfo("wxa99686bb65a9f466", "1520950211", "454328C324C6CC21355D064B44D6524CD7506DD0", "", "XYJkJ2018FAfaodCCx899mLl138rfGVd"); |
| | | |
| | | JSONObject resultJson = JSONObject.fromObject(result); |
| | | JSONObject resource = resultJson.optJSONObject("resource"); |
| | | String ciphertext = resource.optString("ciphertext"); |
| | | try { |
| | | String r = decryptToString(wxappInfo.getApiV3Key(), resource.optString("associated_data"), resource.optString("nonce"), ciphertext); |
| | | System.out.println(r); |
| | | } catch (GeneralSecurityException e) { |
| | | e.printStackTrace(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | } |