JAVA开发(后端):微信小程序API调用详细分析及步骤

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u013289808/article/details/82896971
            </div>
                                                <!--一个博主专栏付费入口-->
         
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
            <div class="htmledit_views" id="content_views">
                                        <p>&nbsp;</p>

关键词:微信登录、统一下单(支付)、统一下单通知(回调)、统一下单查询、企业付款至零钱、支付查询、获取ACCESS_Token、获取小程序二维码

 

因为做项目涉及到微信这些接口的调用,尽管看了很多博客,比对了官方文档,仍还是踩了很多很多的坑,这里做一个记录及分享,提醒自己,帮助他人。文章如果有讲的不对得地方,欢迎指正。

 

首先根据官方文档分析流程,工具类见最后:

一、登录

官方时序图如下:

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

图里其实说的很清楚了,清理下流程:

1.前端调用wx.login()获取code值

2.前端通过调用wx.getUserInfo获取iv、rawData、signature、encryptedData等加密数据,传递给后端

3.服务器通过code请求api换回session_key和openid

4.服务器通过前端给的rawData 加获取的session_key使用sha1加密,计算出signature1

5.比对前端传的signature和自己算出来的signature1是否一致(防止数据不一致)

6.用AES算法解密encryptedData里的敏感数据

7.拿着敏感数据后做自己的逻辑

8.通知前端登陆成功

 

** 这里如果你只是想拿到用户的openid,则直接1,3就可以做到了。如下:

 


 
 
  1. public WeChatUserInfo loginSimple(String code) throws Exception {
  2. String url = new StringBuilder().append(WeChatAPIInfo.loginUrl)
  3. .append( "?appid="+ WechatInfo.appid)
  4. .append( "&secret="+WechatInfo.SECRET)
  5. .append( "&js_code="+code)
  6. .append( "&grant_type=authorization_code")
  7. .toString();
  8. String result = HttpClientHelper.doGet(url, null);
  9. if(result == null ) { //请求失败
  10. throw new UnExceptedException( "获取会话失败");
  11. }
  12. JSONObject jsonObj = JSON.parseObject(result);
  13. String openId = jsonObj.getString( "openid");
  14. WeChatUserInfo weUser = new WeChatUserInfo();
  15. weUser.setOpenId(openId);
  16. return weUser;
  17. }

 


 
 
  1. /**
  2. * 登录并验证:验证数据完整性
  3. * @param req
  4. * @return
  5. */
  6. public WeChatUserInfo loginAndSign(WeChatAppLoginReq req) throws Exception {
  7. //获取 session_key 和 openId
  8. String url = new StringBuilder().append(WeChatAPIInfo.loginUrl)
  9. .append( "?appid="+ WechatInfo.appid)
  10. .append( "&secret="+WechatInfo.SECRET)
  11. .append( "&js_code="+req.getCode())
  12. .append( "&grant_type=authorization_code")
  13. .toString();
  14. String result = HttpClientHelper.doGet(url, null);
  15. if(result == null ) { //请求失败
  16. throw new UnExceptedException( "获取会话失败");
  17. }
  18. JSONObject jsonObj = JSON.parseObject(result);
  19. String sessionKey = jsonObj.getString( "session_key");
  20. String str = req.getRawData()+sessionKey;
  21. String signature = Algorithm.useSHA1(str); //用SHA-1算法计算签名
  22. if(!signature.equals(req.getSignature())){
  23. logger.info( " req signature="+req.getSignature()+ "\n\t\n"+ " java signature="+signature);
  24. throw new CheckSignatureFailException( "签名无法解析,或被篡改,无法登录");
  25. }
  26. byte[] resultByte = null;
  27. try { //解密敏感数据
  28. resultByte = WeChatUtil.decrypt(Base64.decodeBase64(req.getEncryptedData()),
  29. Base64.decodeBase64(sessionKey),
  30. Base64.decodeBase64(req.getIv()));
  31. } catch (Exception e) {
  32. throw new DecryptFailedException( "数据无法解析!");
  33. }
  34. if( null != resultByte && resultByte.length > 0){
  35. try {
  36. String userInfoStr = new String(resultByte, "UTF-8");
  37. WeChatUserInfo weUser = JSON.parseObject(userInfoStr,WeChatUserInfo.class);
  38. return weUser;
  39. } catch (UnsupportedEncodingException e){
  40. logger.error( "对象转换错误",e);
  41. }
  42. }
  43. return null;
  44. }

 

二、

⑴统一下单

官方api: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

 

同样清理下流程:

1.计算signature:把这个文档中提及的必填参数及你需要的通过字典顺序连接(字典顺序就是你查字典时,单词的顺序),然后在最后拼接&key=商户key,然后用MD5计算signature,微信服务器会用这个signature及自己生成的作比对,防止数据被篡改。

2.把你请求的参数转换为xml格式:腾讯接口采用的xml通讯,这个是他要求的格式,没办法,老老实实转吧。内容就是你上面请求的参数+你刚生成的signature,signature腾讯服务器要用来比对的。

3.发起请求,获取prepay_id:这里状态真的炒鸡多的,不过你怎么处理就看自己需要了。

4.返回前段调用支付接口需要的参数,并根据这些参数生成一个新的signature,然后返回给前端,这里的signature是下一次请求腾讯服务器时的签名,和第一个一个作用,一次请求一个。

5.前端拿着返回的参数,发起wx.requestPayment(Object object)

6.微信服务器会进行回调,回调地址是前面请求参数的notify_url,用来通知你支付成功了,然后你可以做相应的逻辑操作,然后告诉微信服务器你知道了,不然他会通知很多次(9次)。

7.支付成功,前端收到通知,继续其他逻辑。


 
 
  1. /**
  2. * 统一下单
  3. * @param orderParams
  4. * @param resultParse
  5. * @return
  6. * @throws UnsupportedEncodingException
  7. * @throws NoSuchAlgorithmException
  8. */
  9. public FlyResponse pay(CreateOrderParams orderParams, WeChatResultParseAbstract resultParse)
  10. throws UnsupportedEncodingException, NoSuchAlgorithmException {
  11. //解析参数
  12. String urlParam = WeChatUtil.concatOrderParams(orderParams); //参数按字典顺序连接起来
  13. String sign = WeChatUtil.getSign(urlParam); //MD5加密形成签名sign,官方文档固定格式
  14. orderParams.setSign(sign); //将生成的签名放入
  15. String xmlStr = WeChatUtil.transToXML(orderParams); //转为xml
  16. logger.info( "\t\n微信下单参数转换为xml:\n"+xmlStr);
  17. resultParse.setUrl(WeChatAPIInfo.Create_Order_Prefix_Url);
  18. resultParse.setXmlStr(xmlStr);
  19. resultParse.setApiDesc( "<< 统一下单 >>");
  20. return resultParse.ResultParse();
  21. }

 


 
 
  1. /**
  2. * @ClassName: WeChatResultParseAbstract
  3. * @Description: 结果解析抽象类
  4. * @Version: 1.0
  5. */
  6. public abstract class WeChatResultParseAbstract {
  7. private static Logger logger = LoggerFactory.getLogger(WeChatResultParseAbstract.class);
  8. /**
  9. * 调用api的描述,如:统一下单
  10. */
  11. private String apiDesc;
  12. /**
  13. * 调用api的url
  14. */
  15. private String url;
  16. /**
  17. * 调用APi需要的xml格式参数
  18. */
  19. private String xmlStr;
  20. public WeChatResultParseAbstract(String apiDesc, String url, String xmlStr) {
  21. this.apiDesc = apiDesc;
  22. this.url = url;
  23. this.xmlStr = xmlStr;
  24. }
  25. public WeChatResultParseAbstract(String apiDesc, String xmlStr) {
  26. this.apiDesc = apiDesc;
  27. this.xmlStr = xmlStr;
  28. }
  29. public WeChatResultParseAbstract() {
  30. }
  31. public FlyResponse ResultParse(){
  32. FlyResponse flyResponse = null;
  33. RestTemplate template = new RestTemplate();
  34. template.getMessageConverters().set( 1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
  35. ResponseEntity<String> resp = template.postForEntity(url, xmlStr, String.class);
  36. if(resp == null || resp.getStatusCode() != HttpStatus.OK) {
  37. throw new UnExceptedException( "连接通信失败");
  38. }
  39. Map<String,String> map = null;
  40. try{
  41. map = WeChatUtil.transXMLStrToMap(resp.getBody());
  42. } catch (ParserConfigurationException | IOException | SAXException e) {
  43. logger.error(apiDesc+ "xml解析异常:"+e.getMessage()+ "\n");
  44. return FlyResponse.Fail( 501,apiDesc+ "失败", null,apiDesc+ "xml解析异常!");
  45. }
  46. if ( "SUCCESS".equals(map.get( "return_code"))) {
  47. if( "SUCCESS".equals(map.get( "result_code"))){
  48. flyResponse = onSuccess(map);
  49. } else{
  50. flyResponse = onFail(map);
  51. }
  52. } else{
  53. flyResponse = onLinkFail(map);
  54. }
  55. return flyResponse;
  56. }
  57. /**
  58. * 响应成功,业务状态成功后要做的业务逻辑
  59. * @param resultMap
  60. * @return
  61. */
  62. protected abstract FlyResponse onSuccess(Map<String,String> resultMap);
  63. /**
  64. * 业务失败,业务码失败后的逻辑
  65. * @param resultMap
  66. * @return
  67. */
  68. protected abstract FlyResponse onFail(Map<String,String> resultMap);
  69. /**
  70. * 响应失败,业务码失败后的逻辑
  71. * @param resultMap
  72. * @return
  73. */
  74. protected FlyResponse onLinkFail(Map<String,String> resultMap){
  75. return FlyResponse.Fail( 505, "通信失败",resultMap, "通信失败");
  76. }

 

⑵下单回调

官方api: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8

 

流程:

1.接收微信服务器发送的数据,xml格式

2.根据响应结果,完成自己的业务逻辑

3.通知微信服务器接收到通知

 

 


 
 
  1. /**
  2. * 支付回调
  3. *
  4. * @param request
  5. * @param response
  6. * @return
  7. */
  8. public void payback(HttpServletRequest request, HttpServletResponse response) throws Exception {
  9. logger.info( "\n--------------<><><><>开始回调<><><>----------\n");
  10. BufferedReader br = new BufferedReader( new InputStreamReader(request.getInputStream()));
  11. String line = null;
  12. StringBuilder sb = new StringBuilder();
  13. while((line = br.readLine()) != null){
  14. sb.append(line);
  15. }
  16. br.close();
  17. String notityXml = sb.toString();
  18. String resXml = "";
  19. Map<String,String> map = WeChatUtil.transXMLStrToMap(notityXml);
  20. String returnCode = (String) map.get( "return_code");
  21. if( "SUCCESS".equals(returnCode)){
  22. SortedMap<String,String> payInfo = new TreeMap<>(map);
  23. String sign = WeChatUtil.getSignFromMap(payInfo);
  24. if(sign.equals(map.get( "sign"))){ //比对签名防止数据篡改
  25. //这里写自己的逻辑
  26. }
  27. //通知微信服务器已经支付成功
  28. resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
  29. + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
  30. logger.info( "回调成功!!!!!!!");
  31. } else {
  32. resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
  33. + "<return_msg><![CDATA[签名不一致]]></return_msg>" + "</xml> ";
  34. logger.warn( "\n\t微信支付回调失败!签名不一致\n\t");
  35. }
  36. } else{
  37. resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
  38. + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
  39. logger.warn( "回调失败!!!!!!!");
  40. }
  41. BufferedOutputStream out = new BufferedOutputStream(
  42. response.getOutputStream());
  43. out.write(resXml.getBytes());
  44. out.flush();
  45. out.close();
  46. }

 

⑶下单查询

官方api: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2

 

流程:

1.根据请求参数生成signature

2.请求参数转为xml

3.发起请求,判断结果

 

 


 
 
  1. /**
  2. * 查询订单情况
  3. * @param orderNo
  4. * @return
  5. */
  6. public FlyResponse checkOrder(String orderNo) throws UnsupportedEncodingException,NoSuchAlgorithmException {
  7. CheckOrderVO check = WechatVOFactory.createCheck(orderNo);
  8. String sign = WeChatUtil.getSign(WeChatUtil.concatOrderParams(check)); //参数按字典顺序连接起来
  9. check.setSign(sign);
  10. String xml = WeChatUtil.transToXML(check);
  11. //调用统计下单接口验证
  12. WeChatResultParseAbstract reParse = new WeChatResultParseAbstract( "查询订单",Order_check_Url,xml) {
  13. @Override
  14. protected FlyResponse onSuccess(Map<String, String> resultMap) {
  15. if( "SUCCESS".equalsIgnoreCase(resultMap.get( "trade_state"))){
  16. return FlyResponse.Success( 200, "支付成功",resultMap);
  17. } else{
  18. return FlyResponse.Fail( 501, "支付失败",resultMap, "支付失败");
  19. }
  20. }
  21. @Override
  22. protected FlyResponse onFail(Map<String, String> resultMap) {
  23. return FlyResponse.Fail( 500, "【查询订单】失败",resultMap, "微信API调用Fail");
  24. }
  25. };
  26. return reParse.ResultParse();
  27. }

三、企业付款至零钱

官方api: https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2

 

企业付款有支付到零钱和银行卡,这里我使用的是支付到零钱。签名什么的大同小异,直接上代码。


 
 
  1. /**
  2. * 企业付款至用户零钱
  3. * @param request
  4. * @param fee
  5. * @param openid
  6. * @param payDesc
  7. * @return
  8. * @throws UnsupportedEncodingException
  9. * @throws NoSuchAlgorithmException
  10. */
  11. public synchronized FlyResponse CompanyPayToUser(HttpServletRequest request,String fee, String openid,String payDesc)
  12. throws Exception {
  13. FlyResponse flyResponse = null;
  14. final String orderno = createAnTransferOrderNum(); //创建一个唯一的订单号,这个看自己的情况实现
  15. CompanyPayVO payVO = WechatVOFactory.createCompanyPayVO(request, orderno, openid, fee, payDesc);
  16. String sign = WeChatUtil.getSign(WeChatUtil.concatOrderParams(payVO));
  17. payVO.setSign(sign);
  18. String xmlStr = WeChatUtil.transToXML(payVO);
  19. String result = WeChatUtil.HttpsPost(WeChatAPIInfo.Company_Transfer_Url,xmlStr);
  20. if(result != null && result.length()> 0 ){
  21. Map<String, String> resultMap = WeChatUtil.transXMLStrToMap(result);
  22. if ( "SUCCESS".equals(resultMap.get( "return_code"))) {
  23. if( "SUCCESS".equals(resultMap.get( "result_code"))){
  24. flyResponse = FlyResponse.Success( 200, "提现成功",resultMap);
  25. } else{
  26. flyResponse = FlyResponse.Fail( 500, "提现失败",resultMap, "提现失败");
  27. }
  28. } else{
  29. if(TransgerErrorCode.isUnsureState(resultMap.get( "err_code"))){ //不确定状态,再查一次
  30. try {
  31. flyResponse = checkTransfer(orderno);
  32. } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
  33. e.printStackTrace(); //已经解析过
  34. }
  35. } else{
  36. flyResponse = FlyResponse.Fail( 500, "提现失败",resultMap, "提现失败");
  37. }
  38. }
  39. } else{
  40. flyResponse = FlyResponse.Fail( 505, "通信失败", null, "通信失败");
  41. }
  42. if(flyResponse.getCode() == 200){ //成功
  43. //写自己要的逻辑
  44. } else if(flyResponse.getCode() == 201){ //处理中
  45. //写自己要的逻辑
  46. }
  47. logger.info( "返回结果:"+JSON.toJSON(flyResponse));
  48. return flyResponse;
  49. }

 
 
  1. /**
  2. * 查询企业付款情况
  3. * @param orderNo
  4. * @return
  5. * @throws UnsupportedEncodingException
  6. * @throws NoSuchAlgorithmException
  7. */
  8. public synchronized FlyResponse checkTransfer(String orderNo) throws Exception {
  9. FlyResponse flyResponse = null;
  10. TransferCheckVO check = WechatVOFactory.createTransferCheckVO(orderNo);
  11. String sign = WeChatUtil.getSign(WeChatUtil.concatOrderParams(check));
  12. check.setSign(sign);
  13. String xmlStr = WeChatUtil.transToXML(check);
  14. String result = WeChatUtil.HttpsPost(WeChatAPIInfo.Transfer_Check_Url,xmlStr);
  15. if(result != null && result.length()> 0 ){
  16. Map<String, String> resultMap = WeChatUtil.transXMLStrToMap(result);
  17. if ( "SUCCESS".equals(resultMap.get( "return_code"))) {
  18. if( "SUCCESS".equalsIgnoreCase(resultMap.get( "status"))){
  19. return FlyResponse.Success( 200, "提现成功",resultMap);
  20. }
  21. if( "PROCESSING".equalsIgnoreCase(resultMap.get( "status"))){ //处理中
  22. return FlyResponse.Success( 201, "提现处理中",resultMap);
  23. } else{
  24. return FlyResponse.Fail( 501, "提现失败",resultMap, "提现失败");
  25. }
  26. } else{
  27. return FlyResponse.Fail( 500, "【提现查询】失败",resultMap, "微信API调用Fail");
  28. }
  29. } else{
  30. flyResponse = FlyResponse.Fail( 505, "提现查询通信失败", null, "通信失败");
  31. }
  32. if(flyResponse.getCode() == 200){
  33. //查询支付成功了,写自己逻辑
  34. }
  35. return flyResponse;
  36. }

 

四、获取小程序二维码

小程序二维码获取之前得获取accesstoken,然后调用接口获取二维码,我是保存在项目本地,然后返回地址供请求访问,代码如下:

 


 
 
  1. /**
  2. * 获取小程序二维码
  3. * @param qrCode
  4. * @param imageName
  5. * @return
  6. * @throws Exception
  7. */
  8. public String getQRCode(QRCodeVO qrCode,String imageName) throws Exception {
  9. imageName += ".jpg";
  10. String url = new StringBuilder().append(WeChatAPIInfo.QRcode)
  11. .append( "?access_token="+getAccessToken().getAccess_token())
  12. .toString();
  13. //获取web目录
  14. String rootpath = ContextLoader.getCurrentWebApplicationContext().getServletContext().getRealPath( "/");
  15. Path path = Paths.get(rootpath,WechatInfo.QRImgRootAddress,imageName);
  16. File file = path.toFile();
  17. File result = HttpClientHelper.doPostToFileSSL_unSafe(url,JSON.toJSONString(qrCode),file);
  18. return WechatInfo.SourceUrl +imageName;
  19. }

 


 
 
  1. /**
  2. *获取全局Access_Token验证凭证
  3. * @return
  4. */
  5. public AccessResult getAccessToken() throws Exception {
  6. String url = new StringBuilder().append(WeChatAPIInfo.ACCESS_TOKEN)
  7. .append( "&appid="+ WechatInfo.appid)
  8. .append( "&secret="+WechatInfo.SECRET)
  9. .toString();
  10. String str = HttpClientHelper.doGet(url, null);
  11. if(str == null ) { //请求失败
  12. throw new UnExceptedException( "获取会话失败");
  13. }
  14. AccessResult result = JSON.parseObject(str,AccessResult.class);
  15. if(result.getAccess_token() == null){
  16. throw new UnExceptedException( "获取accessToken失败:"+result.getErrcode()+ ";"+result.getErrmsg());
  17. }
  18. if(result.getErrcode()== null){ //没有错误码,成功,替换新的accesstoken
  19. //这里可以把accesstoken保存下来,官方说有2小时有效期,我是采用的redis,怎么实现看个人喜好了
  20. }
  21. return result;
  22. }

 


 
 
  1. /**
  2. * AccessToken结果内部类
  3. */
  4. public static class AccessResult{
  5. private String access_token;
  6. private String expires_in;
  7. private String errcode;
  8. private String errmsg;
  9. //get、set
  10. 、、、、
  11. }

 

嗯。。。基本是这样,接下来是自建的工具类:

*ps: 因为参考过很多人的代码,也修改过很多次,但是 有些地方并没有去调整。比如http相关的工具类的东西,可能有些乱。不过因为可以用,而且很懒,所以就那样吧先。。。。


 
 
  1. import com.thoughtworks.xstream.XStream;
  2. import com.thoughtworks.xstream.XStreamException;
  3. import org.apache.http.HttpEntity;
  4. import org.apache.http.client.methods.CloseableHttpResponse;
  5. import org.apache.http.client.methods.HttpPost;
  6. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  7. import org.apache.http.entity.StringEntity;
  8. import org.apache.http.impl.client.CloseableHttpClient;
  9. import org.apache.http.impl.client.HttpClients;
  10. import org.apache.http.ssl.SSLContexts;
  11. import org.apache.http.util.EntityUtils;
  12. import org.apache.log4j.Logger;
  13. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  14. import org.springframework.util.StringUtils;
  15. import org.w3c.dom.Node;
  16. import org.w3c.dom.NodeList;
  17. import org.xml.sax.SAXException;
  18. import javax.crypto.BadPaddingException;
  19. import javax.crypto.Cipher;
  20. import javax.crypto.IllegalBlockSizeException;
  21. import javax.crypto.NoSuchPaddingException;
  22. import javax.crypto.spec.IvParameterSpec;
  23. import javax.crypto.spec.SecretKeySpec;
  24. import javax.net.ssl.SSLContext;
  25. import javax.servlet.http.HttpServletRequest;
  26. import javax.xml.parsers.DocumentBuilder;
  27. import javax.xml.parsers.DocumentBuilderFactory;
  28. import javax.xml.parsers.ParserConfigurationException;
  29. import java.io.*;
  30. import java.lang.reflect.Field;
  31. import java.lang.reflect.InvocationTargetException;
  32. import java.lang.reflect.Method;
  33. import java.security.*;
  34. import java.util.*;
  35. /**
  36. * @ClassName: WeChatUtil
  37. * @Description: 微信工具类
  38. * @Author: totors
  39. * @Version: 1.0
  40. */
  41. public class WeChatUtil {
  42. private static Logger logger = Logger.getLogger(WeChatUtil.class);
  43. public static boolean initialized = false;
  44. /**
  45. * 获取一个32位的随机字符串
  46. *
  47. * @return
  48. */
  49. public static String getOneRandomString() {
  50. String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  51. Random random = new Random();
  52. StringBuilder sb = new StringBuilder();
  53. for ( int i = 0; i < 31; i++) {
  54. int number = random.nextInt( 31);
  55. sb.append(base.charAt(number));
  56. }
  57. return sb.toString();
  58. }
  59. /**
  60. * 获取真实的ip地址
  61. *
  62. * @param request
  63. * @return
  64. */
  65. public static String getIpAddr(HttpServletRequest request) {
  66. Enumeration<String> headers = request.getHeaderNames();
  67. String ip = request.getHeader( "X-Forwarded-For");
  68. if (!StringUtils.isEmpty(ip) && ! "unKnown".equalsIgnoreCase(ip)) {
  69. int index = ip.indexOf( ",");
  70. if (index != - 1) {
  71. return ip.substring( 0, index);
  72. } else {
  73. return ip;
  74. }
  75. }
  76. ip = request.getHeader( "X-Real-IP");
  77. if (!StringUtils.isEmpty(ip) && ! "unKnown".equalsIgnoreCase(ip)) {
  78. return ip;
  79. }
  80. return request.getRemoteAddr();
  81. }
  82. /**
  83. * 连接订单参数,空则忽略,连接符&
  84. * 使用详解:符合条件的参数按字段名称由小到大(字典顺序)排序,并连接
  85. *
  86. * @param createOrderParams
  87. * @return
  88. */
  89. public static String concatOrderParams(Object createOrderParams) throws UnExceptedException {
  90. TreeMap<String, String> tree = new TreeMap<>(); //用于排序
  91. Class clazz = createOrderParams.getClass();
  92. Field[] fields = clazz.getDeclaredFields();
  93. //查找字段
  94. for (Field field : fields) {
  95. field.setAccessible( true);
  96. String fieldName = field.getName();
  97. String methodName = getFiledMethodName(fieldName);
  98. try {
  99. Method method = clazz.getMethod(methodName);
  100. Object value = method.invoke(createOrderParams);
  101. if (value != null) { //不为空
  102. tree.put(fieldName, value.toString());
  103. }
  104. } catch (NoSuchMethodException e) {
  105. logger.error(e.getMessage());
  106. } catch (IllegalAccessException e) {
  107. logger.error(e.getMessage());
  108. } catch (InvocationTargetException e) {
  109. logger.error(e.getMessage());
  110. }
  111. }
  112. if (tree.size() == 0) {
  113. throw new UnExceptedException( "No field can be linked ! ");
  114. }
  115. String str = linkMapKeyValue(tree, "&");
  116. return str.substring( 1); //截取第一个&符号之后的内容
  117. }
  118. /**
  119. * 从map创建签名
  120. * @param parameters
  121. * @return
  122. */
  123. public static String getSignFromMap(SortedMap<String, String> parameters){
  124. StringBuffer sb = new StringBuffer();
  125. Set es = parameters.entrySet();
  126. Iterator it = es.iterator();
  127. while (it.hasNext())
  128. {
  129. Map.Entry entry = (Map.Entry) it.next();
  130. String k = (String) entry.getKey();
  131. Object v = entry.getValue();
  132. if ( null != v && ! "".equals(v) && ! "sign".equals(k) && ! "key".equals(k)){
  133. sb.append(k + "=" + v + "&");
  134. }
  135. }
  136. sb.append( "key=" + WechatInfo.key);
  137. logger.info( "\t\n由MAP生产的字符串:"+sb.toString());
  138. String sign = null;
  139. try {
  140. sign = Algorithm.MD5(sb.toString()).toUpperCase();
  141. } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
  142. logger.error( "MD5加密失败:"+e.getClass()+ ">>>>"+e.getMessage());
  143. }
  144. return sign;
  145. }
  146. /**
  147. * 连接字符串:
  148. * * 将map值连接为key = value & key = value……形式
  149. * @param map
  150. * @param character 连接符号,如:&
  151. * @return
  152. */
  153. public static String linkMapKeyValue(TreeMap<String, String> map, String character) {
  154. if (map == null || map.size() == 0) {
  155. return null;
  156. }
  157. StringBuilder sb = new StringBuilder();
  158. Set<String> keys = map.keySet();
  159. Iterator<String> it = keys.iterator();
  160. while (it.hasNext()) {
  161. String key = it.next();
  162. sb.append(character + key + "=" + map.get(key));
  163. }
  164. return sb.toString();
  165. }
  166. /**
  167. * 获取字段方法名称
  168. *
  169. * @param fieldName
  170. * @return
  171. */
  172. public static String getFiledMethodName(String fieldName) {
  173. char firstChar = fieldName.toCharArray()[ 0];
  174. return "get" + String.valueOf(firstChar).toUpperCase() + fieldName.substring( 1, fieldName.length());
  175. }
  176. /**
  177. * 将对象非空参数转化为XML
  178. *
  179. * @param obj
  180. * @return
  181. */
  182. public static String transToXML(Object obj) {
  183. //解决XStream对出现双下划线的bug
  184. // XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
  185. XStream xstream = XStreamFactory.getXStream();
  186. xstream.alias( "xml", obj.getClass());
  187. return xstream.toXML(obj);
  188. }
  189. /**
  190. * 将字符串格式的xml内容转化为对象
  191. * 注意:该方法存在一个不可避免风险,即:当微信官方文档反馈字段增加或改变时,该方法将不能映射进pojo里。
  192. * 因为本地pojo(OrderResult)可能没有做对应调整。
  193. * @param str
  194. * @return
  195. */
  196. public static OrderResult transToObject(String str) throws XStreamException {
  197. str = str.replaceAll( "xml", "OrderResult"); //将返回结果的<xml>标签替换为返回结果类
  198. XStream xstream = new XStream();
  199. xstream.alias( "OrderResult", OrderResult.class);
  200. OrderResult orderResult = new OrderResult();
  201. return (OrderResult) xstream.fromXML(str,orderResult);
  202. }
  203. /**
  204. * 将xml字符串解析为map集合,兼容性高
  205. * @param xmlStr
  206. * @return
  207. * @throws ParserConfigurationException
  208. */
  209. public static Map<String,String> transXMLStrToMap(String xmlStr) throws ParserConfigurationException,
  210. SAXException, IOException {
  211. Map<String, String> data = new HashMap<String, String>();
  212. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  213. DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
  214. try(InputStream stream = new ByteArrayInputStream(xmlStr.getBytes( "UTF-8"));) {
  215. org.w3c.dom.Document doc = documentBuilder.parse(stream);
  216. doc.getDocumentElement().normalize();
  217. NodeList nodeList = doc.getDocumentElement().getChildNodes();
  218. for ( int idx = 0; idx < nodeList.getLength(); ++idx) {
  219. Node node = nodeList.item(idx);
  220. if (node.getNodeType() == Node.ELEMENT_NODE) {
  221. org.w3c.dom.Element element = (org.w3c.dom.Element) node;
  222. data.put(element.getNodeName(), element.getTextContent());
  223. }
  224. }
  225. } catch (UnsupportedEncodingException e) {
  226. logger.error( "解析xml结果失败!字符编码不匹配!");
  227. throw e;
  228. } catch (IOException e) {
  229. logger.error( "解析xml结果失败!无法读入流");
  230. throw e;
  231. }
  232. return data;
  233. }
  234. /**
  235. * 获取签名
  236. * @param signStr
  237. * @return
  238. */
  239. public static String getSign(String signStr) throws UnsupportedEncodingException, NoSuchAlgorithmException {
  240. return Algorithm.MD5(signStr + "&key="+ WechatInfo.key).toUpperCase();
  241. }
  242. /**
  243. * 敏感数据对称解密
  244. * @param content ——被加密的数据
  245. * @param keyByte ——加密密匙
  246. * @param ivByte ——偏移量
  247. * @return
  248. * @throws InvalidAlgorithmParameterException
  249. */
  250. public static byte[] decrypt( byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
  251. initialize();
  252. try {
  253. Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS7Padding");
  254. Key sKeySpec = new SecretKeySpec(keyByte, "AES");
  255. cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte)); // 初始化
  256. byte[] result = cipher.doFinal(content);
  257. return result;
  258. }
  259. return null;
  260. }
  261. /**
  262. * 添加算法
  263. */
  264. public static void initialize(){
  265. if (initialized) return;
  266. Security.addProvider( new BouncyCastleProvider());
  267. initialized = true;
  268. }
  269. /**
  270. * @Description 生成iv
  271. * @Param [iv]
  272. * @return java.security.AlgorithmParameters
  273. **/
  274. public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
  275. AlgorithmParameters params = AlgorithmParameters.getInstance( "AES");
  276. params.init( new IvParameterSpec(iv));
  277. return params;
  278. }
  279. public static String HttpsPost(String url,String xmlStr) throws Exception {
  280. KeyStore keyStore = KeyStore.getInstance( "PKCS12");
  281. try (FileInputStream instream = new FileInputStream( new File(WechatInfo.CERTIFICATE_ADDRESS));){
  282. keyStore.load(instream, WechatInfo.mch_id.toCharArray());
  283. }
  284. // Trust own CA and all self-signed certs
  285. SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, WechatInfo.mch_id.toCharArray()).build();
  286. // Allow TLSv1 protocol only
  287. SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);
  288. // SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
  289. CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
  290. HttpPost httpPost = new HttpPost(url);
  291. httpPost.addHeader( "Connection", "keep-alive");
  292. httpPost.addHeader( "Accept", "*/*");
  293. httpPost.addHeader( "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  294. httpPost.addHeader( "Host", "api.mch.weixin.qq.com");
  295. httpPost.addHeader( "X-Requested-With", "XMLHttpRequest");
  296. httpPost.addHeader( "Cache-Control", "max-age=0");
  297. // httpPost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
  298. httpPost.setEntity( new StringEntity(xmlStr, "UTF-8"));
  299. logger.info( "执行请求" + httpPost.getRequestLine());
  300. CloseableHttpResponse response = httpclient.execute(httpPost);
  301. StringBuffer sbf = new StringBuffer();
  302. try {
  303. HttpEntity entity = response.getEntity();
  304. if (entity != null) {
  305. BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(entity.getContent(), "UTF-8"));
  306. String text;
  307. while ((text = bufferedReader.readLine()) != null) {
  308. sbf.append(text);
  309. }
  310. }
  311. EntityUtils.consume(entity);
  312. } finally {
  313. response.close();
  314. httpclient.close();
  315. }
  316. return sbf.toString();
  317. }
  318. }

 

 算法工具类:


 
 
  1. public class Algorithm {
  2. private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
  3. '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  4. public static String useSHA1(String str) {
  5. if (str == null) {
  6. return null;
  7. }
  8. try {
  9. MessageDigest messageDigest = MessageDigest.getInstance( "SHA1");
  10. messageDigest.update(str.getBytes( "utf-8"));
  11. String result = getFormattedText(messageDigest.digest());
  12. return result;
  13. } catch (Exception e) {
  14. throw new RuntimeException(e);
  15. }
  16. }
  17. private static String getFormattedText(byte[] bytes) {
  18. int len = bytes.length;
  19. StringBuilder buf = new StringBuilder(len * 2);
  20. // 把密文转换成十六进制的字符串形式
  21. for ( int j = 0; j < len; j++) {
  22. buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
  23. buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
  24. }
  25. return buf.toString();
  26. }
  27. /**
  28. * MD5加密
  29. * @param text
  30. * @return
  31. * @throws NoSuchAlgorithmException
  32. * @throws UnsupportedEncodingException
  33. */
  34. public static String MD5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
  35. return MD5.MD5Encode(text);
  36. }
  37. }

 

 

 


 
 
  1. public class MD5 {
  2. private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7",
  3. "8", "9", "a", "b", "c", "d", "e", "f"};
  4. /**
  5. * 转换字节数组为16进制字串
  6. * @param b 字节数组
  7. * @return 16进制字串
  8. */
  9. public static String byteArrayToHexString(byte[] b) {
  10. StringBuilder resultSb = new StringBuilder();
  11. for ( byte aB : b) {
  12. resultSb.append(byteToHexString(aB));
  13. }
  14. return resultSb.toString();
  15. }
  16. /**
  17. * 转换byte到16进制
  18. * @param b 要转换的byte
  19. * @return 16进制格式
  20. */
  21. private static String byteToHexString(byte b) {
  22. int n = b;
  23. if (n < 0) {
  24. n = 256 + n;
  25. }
  26. int d1 = n / 16;
  27. int d2 = n % 16;
  28. return hexDigits[d1] + hexDigits[d2];
  29. }
  30. /**
  31. * MD5编码
  32. * @param origin 原始字符串
  33. * @return 经过MD5加密之后的结果
  34. */
  35. public static String MD5Encode(String origin) {
  36. String resultString = null;
  37. try {
  38. resultString = origin;
  39. MessageDigest md = MessageDigest.getInstance( "MD5");
  40. resultString = byteArrayToHexString(md.digest(resultString.getBytes( "UTF-8")));
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. return resultString;
  45. }
  46. }

 

微信小程序及商户的配置信息,你也可以写在配置文件来引用:

 


 
 
  1. public interface WeChatAPIInfo {
  2. /**
  3. * 获取access_token
  4. */
  5. String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
  6. /**
  7. * 登录地址
  8. */
  9. String loginUrl = "https://api.weixin.qq.com/sns/jscode2session";
  10. /**
  11. * 统一下单url
  12. */
  13. String Create_Order_Prefix_Url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  14. /**
  15. * 订单情况查询url
  16. */
  17. String Order_check_Url = "https://api.mch.weixin.qq.com/pay/orderquery";
  18. /**
  19. * 企业付款到零钱
  20. */
  21. String Company_Transfer_Url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
  22. /**
  23. * 企业付款查询url
  24. */
  25. String Transfer_Check_Url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
  26. /**
  27. * 二维码url
  28. */
  29. String QRcode = "https://api.weixin.qq.com/wxa/getwxacodeunlimit";
  30. String SendTemplateMsg_Url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send";
  31. }

 

 


 
 
  1. public interface WechatInfo {
  2. /**
  3. * 小程序appid
  4. */
  5. String appid = "";
  6. /**
  7. * 商户号的Appid
  8. */
  9. String mch_appid = "";
  10. /**
  11. *商户号
  12. */
  13. String mch_id = "";
  14. /**
  15. *回调地址
  16. */
  17. String notify_url = "";
  18. /**
  19. *交易类型
  20. */
  21. String trade_type = "JSAPI";
  22. /**
  23. * 签名类型
  24. */
  25. String sign_type = "MD5";
  26. /**
  27. * 商户密匙
  28. */
  29. String key = "";
  30. /**
  31. * 小程序ApiSecret
  32. */
  33. String SECRET = "";
  34. /**
  35. * 证书地址
  36. */
  37. String CERTIFICATE_ADDRESS = "";
  38. /**
  39. * 二维码图片地址
  40. */
  41. String QRImgRootAddress = "";
  42. /**
  43. * 静态资源
  44. */
  45. String SourceUrl = "";
  46. }

 

HTTP方法封装:

 


 
 
  1. import org.apache.http.HttpEntity;
  2. import org.apache.http.HttpStatus;
  3. import org.apache.http.NameValuePair;
  4. import org.apache.http.client.config.RequestConfig;
  5. import org.apache.http.client.entity.UrlEncodedFormEntity;
  6. import org.apache.http.client.methods.CloseableHttpResponse;
  7. import org.apache.http.client.methods.HttpGet;
  8. import org.apache.http.client.methods.HttpPost;
  9. import org.apache.http.client.utils.URIUtils;
  10. import org.apache.http.client.utils.URLEncodedUtils;
  11. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  12. import org.apache.http.conn.ssl.TrustStrategy;
  13. import org.apache.http.entity.ContentType;
  14. import org.apache.http.entity.StringEntity;
  15. import org.apache.http.entity.mime.MultipartEntityBuilder;
  16. import org.apache.http.entity.mime.content.FileBody;
  17. import org.apache.http.entity.mime.content.StringBody;
  18. import org.apache.http.impl.client.CloseableHttpClient;
  19. import org.apache.http.impl.client.HttpClients;
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java后端可以通过调用微信支付的API来实现微信小程序支付功能。 首先,我们需要在微信支付商户平台注册并获取到商户号、appid、appsecret等必要的信息。然后,在Java后端,我们可以使用SDK或者自行封装HTTP请求来调用微信支付的API。 在用户选择微信支付后,前端会发送支付请求到后端后端收到请求后,需要进行以下操作: 1. 生成订单:根据业务需求,后端需要生成一个唯一的商户订单号,并保存相关订单信息到数据库中。同时,需要计算订单的总金额、描述等参数。 2. 生成签名:根据微信支付要求,在生成订单后,后端需要根据商户号、appid、订单号、总金额等参数,生成一个签名值,确保请求的合法性和安全性。 3. 调用支付API:将生成的订单信息、签名等参数通过HTTP请求发送给微信支付后端API。此时,我们可以使用HttpClient等工具发送HTTP POST请求,并将结果解析为JSON格式。 4. 处理支付结果:微信支付后端API返回的结果包含支付所需的各种参数,例如预支付交易会话标识prepay_id等。后端需要将这些参数返回给前端,前端根据这些参数发起小程序支付。 此外,为了确保支付的安全性,后端还应该对微信支付回调进行处理。微信支付成功后,会异步回调后端提供的回调接口,我们需要验证回调接口的合法性,判断回调的结果,并根据业务逻辑进行相应的处理。 总的来说,Java后端可以通过调用微信支付的API来实现小程序支付功能,包括生成订单、生成签名、调用支付API和处理支付回调等步骤。这样,用户在小程序中选择微信支付后,后端可以实现支付逻辑,并与微信支付后端进行交互,最终完成支付流程。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值