فهرست منبع

集成微信支付

zhengqiang 5 سال پیش
والد
کامیت
f397fdb6c1

+ 17 - 0
pom.xml

@@ -181,6 +181,17 @@
 			<version>3.3.4.ALL</version>
 		</dependency>
 		<!--支付相关 end-->
+
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+			<version>5.0.6</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis</artifactId>
+		</dependency>
 	</dependencies>
 	<build>
 		<plugins>
@@ -241,5 +252,11 @@
 				<active.profile>test</active.profile>
 			</properties>
 		</profile>
+		<profile>
+			<id>production</id>
+			<properties>
+				<active.profile>production</active.profile>
+			</properties>
+		</profile>
 	</profiles>
 </project>

+ 127 - 0
src/main/java/com/jpsoft/epay/config/RedisConfig.java

@@ -0,0 +1,127 @@
+package com.jpsoft.epay.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.lang.reflect.Method;
+
+
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport {
+    public final static String WX_COMMON_ACCESS_TOKEN = "wx_common_accessToken";
+    public static final String JS_API_TICKET = "js_api_ticket";
+
+    /**
+     * 注入 RedisConnectionFactory,注意maven中要有redis.clients.jedis
+     */
+    @Autowired
+    RedisConnectionFactory redisConnectionFactory;
+
+    /**
+     * 实例化 RedisTemplate 对象
+     * @return
+     */
+    @Bean
+    public RedisTemplate<String, Object> getRedisTemplate() {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
+        return redisTemplate;
+    }
+
+    /**
+     * 设置数据存入 redis 的序列化方式
+     *
+     * @param redisTemplate
+     * @param factory
+     */
+    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+        redisTemplate.setConnectionFactory(factory);
+    }
+
+    @Bean
+    public KeyGenerator keyGenerator() {
+        return new KeyGenerator() {
+            @Override
+            public Object generate(Object target, Method method, Object... params) {
+                StringBuilder sb = new StringBuilder();
+                sb.append(target.getClass().getName());
+                sb.append(method.getName());
+                for (Object obj : params) {
+                    sb.append(obj.toString());
+                }
+
+                return sb.toString();
+            }
+        };
+    }
+
+    /**
+     * 实例化 HashOperations 对象,可以使用 Hash 类型操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForHash();
+    }
+
+    /**
+     * 实例化 ValueOperations 对象,可以使用 String 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForValue();
+    }
+
+    /**
+     * 实例化 ListOperations 对象,可以使用 List 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForList();
+    }
+
+    /**
+     * 实例化 SetOperations 对象,可以使用 Set 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForSet();
+    }
+
+    /**
+     * 实例化 ZSetOperations 对象,可以使用 ZSet 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForZSet();
+    }
+}

+ 215 - 11
src/main/java/com/jpsoft/epay/modules/business/controller/MobileApiController.java

@@ -1,12 +1,16 @@
 package com.jpsoft.epay.modules.business.controller;
 
-import com.jpsoft.epay.modules.base.entity.RechargeRecord;
-import com.jpsoft.epay.modules.base.entity.RoomInfo;
-import com.jpsoft.epay.modules.base.service.RechargeRecordService;
-import com.jpsoft.epay.modules.base.service.RoomInfoService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.jpsoft.epay.config.RedisConfig;
+import com.jpsoft.epay.modules.base.entity.*;
+import com.jpsoft.epay.modules.base.service.*;
 import com.jpsoft.epay.modules.business.service.RechargeService;
 import com.jpsoft.epay.modules.common.dto.MessageResult;
+import com.jpsoft.epay.modules.common.utils.StringUtils;
+import com.jpsoft.epay.modules.communication.server.ChannelWrapper;
 import com.jpsoft.epay.modules.communication.server.protocol.MeterReceivePacket;
+import com.jpsoft.epay.modules.communication.server.protocol.MeterSendPacket;
+import com.jpsoft.epay.modules.pay.properties.WxPayProperties;
 import com.jpsoft.epay.modules.sys.entity.DataDictionary;
 import com.jpsoft.epay.modules.sys.service.DataDictionaryService;
 import io.swagger.annotations.ApiImplicitParam;
@@ -14,13 +18,15 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.web.bind.annotation.*;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.text.DecimalFormat;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 @Slf4j
@@ -33,12 +39,17 @@ public class MobileApiController {
     @Autowired
     private RechargeService rechargeService;
 
+    @Autowired
+    private DataDictionaryService dataDictionaryService;
+
     @Autowired
     private RechargeRecordService rechargeRecordService;
 
+    @Autowired
+    ValueOperations<String, Object> valueOperations;
 
     @Autowired
-    private DataDictionaryService dataDictionaryService;
+    private WxPayProperties wxPayProperties;
 
     /**
      * 查询房间
@@ -153,4 +164,197 @@ public class MobileApiController {
 
         return msgResult;
     }
+
+    private RechargeRecord createRechargeRecord(String roomId, String buyType, Integer num, BigDecimal amount) throws Exception{
+        String serialNumber = StringUtils.getOutTradeNo();
+
+        RoomInfo room = roomInfoService.get(roomId);
+
+        //查询用电类型及价格
+        DataDictionary dd = dataDictionaryService.get(room.getUseType());
+
+        BigDecimal price = new BigDecimal(dd.getValue());
+
+        if(num<=0){
+            throw new Exception("充电度数只能为正整数!");
+        }
+
+        BigDecimal totalAmount = price.multiply(new BigDecimal(num));
+
+        DecimalFormat df = new DecimalFormat("#.##");
+
+        if(!totalAmount.equals(amount)){
+            throw  new Exception("支付金额应为:" + df.format(totalAmount) + "!");
+        }
+
+        RechargeRecord record = new RechargeRecord();
+        record.setId(UUID.randomUUID().toString());
+        record.setDelFlag(false);
+        record.setCreateTime(new Date());
+        record.setSerialNumber(serialNumber);
+        record.setProductTheme("电费充值(测试)");
+        record.setPaymentStatus("10");//10为未支付 20为支付成功
+        record.setChargingStatus("10");//10为未充电 20为充电成功
+        record.setRoomId(roomId);
+        record.setBuyElectricity(num);
+        record.setBuyAmount(totalAmount);
+        record.setBuyType(buyType); //weipay alipay cash
+
+        int affectCount = rechargeRecordService.insert(record);
+
+        if (affectCount>0){
+            return record;
+        }
+        else{
+            return null;
+        }
+    }
+
+    private String getCommonAccessToken() throws Exception{
+        String accessToken = null;
+
+        accessToken = (String)valueOperations.get(RedisConfig.WX_COMMON_ACCESS_TOKEN);
+
+        if(StringUtils.isEmpty(accessToken)){
+            URL realUrl = new URL(String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
+                    wxPayProperties.getAppId(),wxPayProperties.getAppSecret()));
+
+            HttpsURLConnection conn = (HttpsURLConnection) realUrl.openConnection();
+
+            conn.connect();
+
+            InputStream input = conn.getInputStream();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+
+            Map resultMap = objectMapper.readValue(input, Map.class);
+
+            accessToken = (String)resultMap.get("access_token");
+            Integer expiresIn = Integer.valueOf(resultMap.get("expires_in").toString()); //默认是7200秒过期
+
+            valueOperations.set(RedisConfig.WX_COMMON_ACCESS_TOKEN,accessToken,expiresIn,TimeUnit.SECONDS);
+        }
+
+        return accessToken;
+    }
+
+    private String getJSApiTicket() throws Exception{
+        String ticket = null;
+
+        ticket = (String)valueOperations.get(RedisConfig.JS_API_TICKET);
+
+        if(StringUtils.isEmpty(ticket)){
+            String accessToken = getCommonAccessToken();
+
+            URL realUrl = new URL(String.format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=%s",
+                    accessToken));
+
+            HttpsURLConnection conn = (HttpsURLConnection) realUrl.openConnection();
+
+            conn.connect();
+
+            InputStream input = conn.getInputStream();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+
+            Map resultMap = objectMapper.readValue(input, Map.class);
+
+            Integer errcode = (Integer)resultMap.get("errcode");
+
+            if(errcode.equals(0)){
+                ticket = (String)resultMap.get("ticket");
+                Integer expiresIn = Integer.valueOf(resultMap.get("expires_in").toString()); //默认是7200秒过期
+
+                valueOperations.set(RedisConfig.JS_API_TICKET,ticket,expiresIn,TimeUnit.SECONDS);
+            }
+        }
+
+        return ticket;
+    }
+
+    public Map getAuthAccessToken(String code) throws Exception{
+        String tokenUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
+                wxPayProperties.getAppId(),wxPayProperties.getAppSecret(),code);
+        String key = "AUTH_" + code;
+
+        Map tokenMap = (Map)valueOperations.get(key);
+
+        if(tokenMap==null){
+            URL realUrl = new URL(tokenUrl);
+
+            HttpsURLConnection conn = (HttpsURLConnection) realUrl.openConnection();
+
+            conn.connect();
+
+            InputStream input = conn.getInputStream();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+
+            tokenMap = objectMapper.readValue(input, Map.class);
+
+            valueOperations.set(key,tokenMap,5,TimeUnit.MINUTES);
+        }
+
+        return tokenMap;
+    }
+
+    @PostMapping(value="prepareWXPay")
+    public MessageResult<Map> prepareWXPay(String roomId, String buyType, Integer num, BigDecimal amount){
+        MessageResult<Map> msgResult = new MessageResult<>();
+
+        try {
+            Map<String,Object> dataMap = new HashMap<>();
+
+            dataMap.put("appId",wxPayProperties.getAppId());
+            RechargeRecord record = createRechargeRecord(roomId,buyType,num,amount);
+
+            if(record!=null){
+                dataMap.put("recordId",record.getId());
+            }
+
+            msgResult.setResult(true);
+
+            msgResult.setData(dataMap);
+        }
+        catch(Exception ex){
+            log.error(ex.getMessage(),ex);
+
+            msgResult.setMessage(ex.getMessage());
+            msgResult.setResult(false);
+        }
+
+        return msgResult;
+    }
+
+    @GetMapping(value="getWXConfigParam")
+    public MessageResult<Map> getWXConfigParam(String code){
+        MessageResult<Map> msgResult = new MessageResult<>();
+
+        try {
+            Map<String,Object> dataMap = new HashMap<>();
+
+            dataMap.put("appId",wxPayProperties.getAppId());
+            dataMap.put("ticket", getJSApiTicket());
+
+            Map tokenMap = getAuthAccessToken(code);
+
+            String accessToken = (String)tokenMap.get("access_token");
+            String openId = (String)tokenMap.get("openid");
+
+            dataMap.put("accessToken",accessToken);
+            dataMap.put("openId",openId);
+
+            msgResult.setResult(true);
+
+            msgResult.setData(dataMap);
+        }
+        catch(Exception ex){
+            log.error(ex.getMessage(),ex);
+
+            msgResult.setMessage(ex.getMessage());
+            msgResult.setResult(false);
+        }
+
+        return msgResult;
+    }
 }

+ 5 - 0
src/main/java/com/jpsoft/epay/modules/pay/properties/WxPayProperties.java

@@ -19,6 +19,11 @@ public class WxPayProperties {
      */
     private String appId;
 
+    /**
+     * 设置微信公众号或者小程序等的appSecret
+     */
+    private String appSecret;
+
     /**
      * 微信支付商户号
      */

+ 8 - 9
src/main/java/com/jpsoft/epay/modules/pay/weixin/WxPayController.java

@@ -53,13 +53,16 @@ public class WxPayController {
 
     @ApiOperation(value = "微信JSAPI支付")
     @GetMapping("/webPay")
-    public MessageResult webPay(@RequestParam("rechargeRecordId") String rechargeRecordId) {
-
-
+    public MessageResult webPay(String recordId,String openId) {
         MessageResult msgResult = new MessageResult<>();
         try {
+            RechargeRecord rechargeRecord = rechargeRecordService.get(recordId);
+
+            rechargeRecord.setOpenId(openId);
+            rechargeRecord.setUpdateTime(new Date());
+
+            rechargeRecordService.update(rechargeRecord);
 
-            RechargeRecord rechargeRecord = rechargeRecordService.get(rechargeRecordId);
             if (rechargeRecord == null) {
                 throw new Exception("订单不存在");
             }
@@ -73,7 +76,6 @@ public class WxPayController {
                 throw new Exception("订单已支付");
             }
 
-
             //金额处理
             int wxTotalTee = rechargeRecord.getBuyAmount().multiply(new BigDecimal(100)).intValue();
             Map<String, String> params = UnifiedOrderModel
@@ -132,7 +134,7 @@ public class WxPayController {
     @ResponseBody
     public String payNotify(HttpServletRequest request) {
         String xmlMsg = HttpKit.readData(request);
-        logger.info("支付通知=" + xmlMsg);
+        logger.warn("支付通知=" + xmlMsg);
         Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
 
         String returnCode = params.get("return_code");
@@ -140,9 +142,6 @@ public class WxPayController {
         String payTimeStr = params.get("time_end");
         Date payTime = DateUtil.parse(payTimeStr);
 
-
-
-
         // 注意此处签名方式需与统一下单的签名类型一致
         if (WxPayKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey(), SignType.HMACSHA256)) {
             if (WxPayKit.codeIsOk(returnCode)) {

+ 5 - 0
src/main/resources/application-dev.yml

@@ -19,3 +19,8 @@ logger:
 
 netty:
   port: 9966    #监听端口
+
+wx:
+  pay:
+    appId: wxc0ddd6a415c535d9
+    appSecret: 042fe6c9c970c1d9fe585dccfca89221

+ 22 - 5
src/main/resources/application-production.yml

@@ -1,20 +1,27 @@
 server:
-  port: 8080
+  port: 8086
   servlet:
     context-path: /epay-server
 
 spring:
   datasource:
-    url: jdbc:log4jdbc:mysql://192.168.33.20:3306/ipcps_test?autoReconnect=true&characterEncoding=utf8&serverTimezone=GMT%2B8
+    url: jdbc:log4jdbc:mysql://10.10.0.2:3306/electricity-payment?autoReconnect=true&characterEncoding=utf8&serverTimezone=GMT%2B8
     username: epay
     password: epay
   devtools:
     restart:
       enabled: true
+  redis:
+    # Redis数据库索引(默认为0)
+    database: 5
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
 
 logger:
-  level: INFO
-  dir: D:\\epay-server
+  level: WARN
+  dir: E:\\epay\\logs
 
 netty:
   port: 9966    #监听端口
@@ -23,4 +30,14 @@ springfox:
   documentation:
     swagger:
       v2:
-        host: 58.54.251.155:8088
+        host: www.wzgh.org
+
+wx:
+  pay:
+    appId: wxe598c699aa68cffe
+    appSecret: ea20d2e9a36aace26b4f7654218129af
+    mchId: 1500160622
+    subMchId: 1505070291
+    mchKey: jpsoft11111111111111111111111111
+    notifyUrl: http://www.wzgh.org/epay-server/wxPay/payNotify
+    ip: 122.228.31.242

+ 21 - 0
src/main/resources/application.yml

@@ -57,6 +57,26 @@ spring:
       enabled: true
   profiles:
     active: @active.profile@
+  redis:
+    # Redis数据库索引(默认为0)
+    database: 1
+    # Redis服务器地址
+    host: 192.168.33.21
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接池最大连接数(使用负值表示没有限制)
+    pool:
+      max-active: 8
+      # 连接池最大阻塞等待时间(使用负值表示没有限制)
+      max-wait: -1
+      # 连接池中的最大空闲连接
+      max-idle: 8
+      # 连接池中的最小空闲连接
+      min-idle: 0
+      # 连接超时时间(毫秒)
+      timeout: 0
 
 jwt:
   secret: WJgLLiAktNj/vCNEoz6mfAmE0btwluCTk/TnJiZOIkQ=
@@ -112,3 +132,4 @@ wx:
     ip: 101.37.31.116
 
 
+

+ 25 - 0
src/test/java/com/jpsoft/epay/HutoolTest.java

@@ -0,0 +1,25 @@
+package com.jpsoft.epay;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+
+public class HutoolTest {
+    public static synchronized String batchId(int tenantId, int module) {
+        String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN);
+        return prefix + tenantId + module + RandomUtil.randomNumbers(3);
+    }
+
+    public static void main(String[] args) {
+        for (int i = 0; i < 3; i++) {
+//            Snowflake snowflake = IdUtil.createSnowflake(1,1);
+//
+//            System.out.println(snowflake.nextId());
+//            System.out.println(snowflake.nextIdStr());
+
+            System.out.println(batchId(2019,1));
+        }
+    }
+}