From 058ce62a2b932633931e30762f44c561481dde5f Mon Sep 17 00:00:00 2001 From: Mario Luo Date: Tue, 12 May 2020 18:17:17 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20#1546=20=20=E4=BF=AE=E5=A4=8DWxRedisOps?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=20#1548=20=E4=BF=AE=E5=A4=8DWxOpenI?= =?UTF-8?q?nMemoryConfigStorage=E9=94=81=E9=97=AE=E9=A2=98=EF=BC=8C#1305?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E5=95=86=E6=88=B7=E7=94=B5=E5=AD=90?= =?UTF-8?q?=E5=8F=91=E7=A5=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/common/redis/JedisWxRedisOps.java | 6 +- .../common/redis/RedisTemplateWxRedisOps.java | 6 +- .../common/redis/RedissonWxRedisOps.java | 12 +- .../common/redis/CommonWxRedisOpsTest.java | 51 +++++ .../common/redis/JedisWxRedisOpsTest.java | 21 ++ .../redis/RedisTemplateWxRedisOpsTest.java | 26 +++ .../common/redis/RedissonWxRedisOpsTest.java | 27 +++ .../mp/api/WxMpMerchantInvoiceService.java | 85 ++++++++ .../mp/api/impl/BaseWxMpServiceImpl.java | 14 +- .../impl/WxMpMerchantInvoiceServiceImpl.java | 119 +++++++++++ .../merchant/ClearOutInvoiceRequest.java | 50 +++++ .../merchant/InvoiceAuthDataRequest.java | 23 ++ .../merchant/InvoiceAuthDataResult.java | 66 ++++++ .../merchant/InvoiceAuthPageRequest.java | 52 +++++ .../merchant/InvoiceAuthPageResult.java | 22 ++ .../merchant/InvoiceAuthPageSetting.java | 61 ++++++ .../merchant/InvoiceRejectRequest.java | 30 +++ .../bean/invoice/merchant/InvoiceResult.java | 52 +++++ .../merchant/MakeOutInvoiceRequest.java | 200 ++++++++++++++++++ .../invoice/merchant/MerchantContactInfo.java | 22 ++ .../merchant/MerchantContactInfoWrapper.java | 16 ++ .../merchant/MerchantInvoicePlatformInfo.java | 19 ++ .../MerchantInvoicePlatformInfoWrapper.java | 15 ++ .../chanjar/weixin/mp/enums/WxMpApiUrl.java | 74 +++++++ .../api/impl/WxOpenInMemoryConfigStorage.java | 23 +- 25 files changed, 1078 insertions(+), 14 deletions(-) create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java index ede2fcd8b..b42142943 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java @@ -23,7 +23,11 @@ public class JedisWxRedisOps implements WxRedisOps { @Override public void setValue(String key, String value, int expire, TimeUnit timeUnit) { try (Jedis jedis = this.jedisPool.getResource()) { - jedis.psetex(key, timeUnit.toMillis(expire), value); + if (expire <= 0) { + jedis.set(key, value); + } else { + jedis.psetex(key, timeUnit.toMillis(expire), value); + } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java index 068703287..652cec84a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java @@ -19,7 +19,11 @@ public class RedisTemplateWxRedisOps implements WxRedisOps { @Override public void setValue(String key, String value, int expire, TimeUnit timeUnit) { - redisTemplate.opsForValue().set(key, value, expire, timeUnit); + if (expire <= 0) { + redisTemplate.opsForValue().set(key, value); + } else { + redisTemplate.opsForValue().set(key, value, expire, timeUnit); + } } @Override diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java index e06db302e..d51cd3e1a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java @@ -19,12 +19,20 @@ public class RedissonWxRedisOps implements WxRedisOps { @Override public void setValue(String key, String value, int expire, TimeUnit timeUnit) { - redissonClient.getBucket(key).set(value, expire, timeUnit); + if (expire <= 0) { + redissonClient.getBucket(key).set(value); + } else { + redissonClient.getBucket(key).set(value, expire, timeUnit); + } } @Override public Long getExpire(String key) { - return redissonClient.getBucket(key).remainTimeToLive(); + long expire = redissonClient.getBucket(key).remainTimeToLive(); + if (expire > 0) { + expire = expire / 1000; + } + return expire; } @Override diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java new file mode 100644 index 000000000..96ba20ba2 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +public class CommonWxRedisOpsTest { + + protected WxRedisOps wxRedisOps; + private String key = "access_token"; + private String value = String.valueOf(System.currentTimeMillis()); + + @Test + public void testGetValue() { + wxRedisOps.setValue(key, value, 3, TimeUnit.SECONDS); + Assert.assertEquals(wxRedisOps.getValue(key), value); + } + + @Test + public void testSetValue() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 0, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 1, TimeUnit.SECONDS); + } + + @Test + public void testGetExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + Assert.assertTrue(wxRedisOps.getExpire(key) < 0); + wxRedisOps.setValue(key, value, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.expire(key, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testGetLock() { + Assert.assertNotNull(wxRedisOps.getLock("access_token_lock")); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java new file mode 100644 index 000000000..2ff2c37b8 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import redis.clients.jedis.JedisPool; + +public class JedisWxRedisOpsTest extends CommonWxRedisOpsTest { + + JedisPool jedisPool; + + @BeforeTest + public void init() { + this.jedisPool = new JedisPool("127.0.0.1", 6379); + this.wxRedisOps = new JedisWxRedisOps(jedisPool); + } + + @AfterTest + public void destroy() { + this.jedisPool.close(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java new file mode 100644 index 000000000..bf3b35a7c --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.common.redis; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedisTemplateWxRedisOpsTest extends CommonWxRedisOpsTest { + + StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + this.redisTemplate = redisTemplate; + this.wxRedisOps = new RedisTemplateWxRedisOps(this.redisTemplate); + } + + @AfterTest + public void destroy() { + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java new file mode 100644 index 000000000..48cf7b29b --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedissonWxRedisOpsTest extends CommonWxRedisOpsTest { + + RedissonClient redissonClient; + + @BeforeTest + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); + config.setTransportMode(TransportMode.NIO); + this.redissonClient = Redisson.create(config); + this.wxRedisOps = new RedissonWxRedisOps(this.redissonClient); + } + + @AfterTest + public void destroy() { + this.redissonClient.shutdown(); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java new file mode 100644 index 000000000..294fba85b --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java @@ -0,0 +1,85 @@ +package me.chanjar.weixin.mp.api; + + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.invoice.merchant.*; + +/** + * 商户电子发票相关的接口 + *

+ * 重要!!!, 根据不同开票平台, 以下错误码可能开票成功(开票,冲红), 内部暂时未处理: + * 73105: 开票平台开票中,请使用相同的发票请求流水号重试开票 + * 73107: 发票请求流水正在被处理,请通过查询接口获取结果 + * 73100: 开票平台错误 + *

+ * 流程文档: https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_and_Invoicing_Platform_Mode_Instruction.html + * 接口文档: https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_API_List.html + */ +public interface WxMpMerchantInvoiceService { + + /** + * 获取开票授权页链接 + */ + InvoiceAuthPageResult getAuthPageUrl(InvoiceAuthPageRequest params) throws WxErrorException; + + /** + * 获得用户授权数据 + */ + InvoiceAuthDataResult getAuthData(InvoiceAuthDataRequest params) throws WxErrorException; + + /** + * 拒绝开票 + *

+ * 场景: 用户授权填写数据无效 + * 结果: 用户会收到一条开票失败提示 + */ + void rejectInvoice(InvoiceRejectRequest params) throws WxErrorException; + + /** + * 开具电子发票 + */ + void makeOutInvoice(MakeOutInvoiceRequest params) throws WxErrorException; + + /** + * 发票冲红 + */ + void clearOutInvoice(ClearOutInvoiceRequest params) throws WxErrorException; + + /** + * 查询发票信息 + * + * @param fpqqlsh 发票请求流水号 + * @param nsrsbh 纳税人识别号 + */ + InvoiceResult queryInvoiceInfo(String fpqqlsh, String nsrsbh) throws WxErrorException; + + /** + * 设置商户联系方式, 获取授权链接前需要设置商户联系信息 + */ + void setMerchantContactInfo(MerchantContactInfo contact) throws WxErrorException; + + /** + * 获取商户联系方式 + */ + MerchantContactInfo getMerchantContactInfo() throws WxErrorException; + + /** + * 配置授权页面字段 + */ + void setAuthPageSetting(InvoiceAuthPageSetting authPageSetting) throws WxErrorException; + + /** + * 获取授权页面配置 + */ + InvoiceAuthPageSetting getAuthPageSetting() throws WxErrorException; + + /** + * 设置商户开票平台信息 + */ + void setMerchantInvoicePlatform(MerchantInvoicePlatformInfo merchantInvoicePlatformInfo) throws WxErrorException; + + /** + * 获取商户开票平台信息 + */ + MerchantInvoicePlatformInfo getMerchantInvoicePlatform(MerchantInvoicePlatformInfo merchantInvoicePlatformInfo) throws WxErrorException; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java index fb4ae5d0d..d6a625c9b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java @@ -6,10 +6,13 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.bean.WxNetCheckResult; +import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.session.StandardSessionManager; @@ -26,7 +29,6 @@ import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.config.WxMpConfigStorage; -import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.mp.enums.WxMpApiUrl; import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; import org.apache.commons.lang3.StringUtils; @@ -70,6 +72,10 @@ public abstract class BaseWxMpServiceImpl implements WxMpService, RequestH private WxMpOcrService ocrService = new WxMpOcrServiceImpl(this); private WxMpImgProcService imgProcService = new WxMpImgProcServiceImpl(this); + @Getter + @Setter + private WxMpMerchantInvoiceService merchantInvoiceService = new WxMpMerchantInvoiceServiceImpl(this, this.cardService); + private Map configStorageMap; private int retrySleepMillis = 1000; @@ -359,11 +365,11 @@ public abstract class BaseWxMpServiceImpl implements WxMpService, RequestH // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token Lock lock = this.getWxMpConfigStorage().getAccessTokenLock(); lock.lock(); - try{ - if(StringUtils.equals(this.getWxMpConfigStorage().getAccessToken(), accessToken)){ + try { + if (StringUtils.equals(this.getWxMpConfigStorage().getAccessToken(), accessToken)) { this.getWxMpConfigStorage().expireAccessToken(); } - } catch (Exception ex){ + } catch (Exception ex) { this.getWxMpConfigStorage().expireAccessToken(); } finally { lock.unlock(); diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java new file mode 100644 index 000000000..ffae3ddf1 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java @@ -0,0 +1,119 @@ +package me.chanjar.weixin.mp.api.impl; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpCardService; +import me.chanjar.weixin.mp.api.WxMpMerchantInvoiceService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.invoice.merchant.*; +import me.chanjar.weixin.mp.enums.WxMpApiUrl; + +import java.util.HashMap; +import java.util.Map; + +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Invoice.*; + + +@AllArgsConstructor +public class WxMpMerchantInvoiceServiceImpl implements WxMpMerchantInvoiceService { + + private WxMpService wxMpService; + private WxMpCardService wxMpCardService; + + private final static Gson gson; + + static { + gson = new GsonBuilder() + .disableHtmlEscaping() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + } + + @Override + public InvoiceAuthPageResult getAuthPageUrl(InvoiceAuthPageRequest params) throws WxErrorException { + String ticket = wxMpCardService.getCardApiTicket(); + params.setTicket(ticket); + return doCommonInvoiceHttpPost(GET_AUTH_URL, params, InvoiceAuthPageResult.class); + } + + @Override + public InvoiceAuthDataResult getAuthData(InvoiceAuthDataRequest params) throws WxErrorException { + return doCommonInvoiceHttpPost(GET_AUTH_DATA, params, InvoiceAuthDataResult.class); + } + + @Override + public void rejectInvoice(InvoiceRejectRequest params) throws WxErrorException { + doCommonInvoiceHttpPost(REJECT_INSERT, params, null); + } + + @Override + public void makeOutInvoice(MakeOutInvoiceRequest params) throws WxErrorException { + doCommonInvoiceHttpPost(MAKE_OUT_INVOICE, params, null); + } + + @Override + public void clearOutInvoice(ClearOutInvoiceRequest params) throws WxErrorException { + doCommonInvoiceHttpPost(CLEAR_OUT_INVOICE, params, null); + } + + @Override + public InvoiceResult queryInvoiceInfo(String fpqqlsh, String nsrsbh) throws WxErrorException { + Map data = new HashMap(); + data.put("fpqqlsh", fpqqlsh); + data.put("nsrsbh", nsrsbh); + return doCommonInvoiceHttpPost(QUERY_INVOICE_INFO, data, InvoiceResult.class); + } + + @Override + public void setMerchantContactInfo(MerchantContactInfo contact) throws WxErrorException { + MerchantContactInfoWrapper data = new MerchantContactInfoWrapper(); + data.setContact(contact); + doCommonInvoiceHttpPost(SET_CONTACT_SET_BIZ_ATTR, data, null); + } + + @Override + public MerchantContactInfo getMerchantContactInfo() throws WxErrorException { + MerchantContactInfoWrapper merchantContactInfoWrapper = doCommonInvoiceHttpPost(GET_CONTACT_SET_BIZ_ATTR, null, MerchantContactInfoWrapper.class); + return merchantContactInfoWrapper == null ? null : merchantContactInfoWrapper.getContact(); + } + + @Override + public void setAuthPageSetting(InvoiceAuthPageSetting authPageSetting) throws WxErrorException { + doCommonInvoiceHttpPost(SET_AUTH_FIELD_SET_BIZ_ATTR, authPageSetting, null); + } + + @Override + public InvoiceAuthPageSetting getAuthPageSetting() throws WxErrorException { + return doCommonInvoiceHttpPost(GET_AUTH_FIELD_SET_BIZ_ATTR, new JsonObject(), InvoiceAuthPageSetting.class); + } + + @Override + public void setMerchantInvoicePlatform(MerchantInvoicePlatformInfo paymchInfo) throws WxErrorException { + MerchantInvoicePlatformInfoWrapper data = new MerchantInvoicePlatformInfoWrapper(); + data.setPaymchInfo(paymchInfo); + doCommonInvoiceHttpPost(SET_PAY_MCH_SET_BIZ_ATTR, data, null); + } + + @Override + public MerchantInvoicePlatformInfo getMerchantInvoicePlatform(MerchantInvoicePlatformInfo merchantInvoicePlatformInfo) throws WxErrorException { + MerchantInvoicePlatformInfoWrapper result = doCommonInvoiceHttpPost(GET_PAY_MCH_SET_BIZ_ATTR, new JsonObject(), MerchantInvoicePlatformInfoWrapper.class); + return result == null ? null : result.getPaymchInfo(); + } + + /** + * 电子发票公用post请求方法 + */ + private T doCommonInvoiceHttpPost(WxMpApiUrl url, Object data, Class resultClass) throws WxErrorException { + String json = ""; + if (data != null) { + json = gson.toJson(data); + } + String responseText = wxMpService.post(url, json); + if (resultClass == null) return null; + return gson.fromJson(responseText, resultClass); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java new file mode 100644 index 000000000..108e76ca2 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 发票充红请求参数 + */ +@Data +public class ClearOutInvoiceRequest implements Serializable { + + + private ClearOutInvoiceInfo invoiceinfo; + + @Data + public static class ClearOutInvoiceInfo implements Serializable { + + /** + * 用户的openid 用户知道是谁在开票 + */ + private String wxopenid; + + /** + * 发票请求流水号,唯一查询发票的流水号 + */ + private String fpqqlsh; + + /** + * 纳税人识别码 + */ + private String nsrsbh; + + /** + * 纳税人名称 + */ + private String nsrmc; + + /** + * 原发票代码,即要冲红的蓝票的发票代码 + */ + private String yfpdm; + + /** + * 原发票号码,即要冲红的蓝票的发票号码 + */ + private String yfphm; + + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java new file mode 100644 index 000000000..092e16410 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 获取电子开票用户授权数据 + */ +@Data +public class InvoiceAuthDataRequest implements Serializable { + + /** + * 开票平台在微信的标识号,商户需要找开票平台提供 + */ + private String sPappid; + + /** + * 订单id,在商户内单笔开票请求的唯一识别号 + */ + private String orderId; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java new file mode 100644 index 000000000..99d8f57cc --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 用户开票认证信息返回结果DTO + */ +@Data +public class InvoiceAuthDataResult implements Serializable { + + /** + * 订单授权状态,当errcode为0时会出现 + */ + private String invoiceStatus; + + /** + * 授权时间,为十位时间戳(utc+8),当errcode为0时会出现 + */ + private Long authTime; + + /** + * 用户授权信息 + */ + private UserAuthInfo userAuthInfo; + + @Data + public static class UserAuthInfo implements Serializable { + /** + * 个人抬头 + */ + private UserField userField; + + /** + * 单位抬头 + */ + private BizField bizField; + } + + @Data + public static class UserField implements Serializable { + private String title; + private String phone; + private String email; + private List customField; + } + + @Data + public static class BizField implements Serializable { + private String title; + private String taxNo; + private String addr; + private String phone; + private String bankType; + private String bankNo; + private List customField; + } + + @Data + public static class KeyValuePair implements Serializable { + private String key; + private String value; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java new file mode 100644 index 000000000..07a8a24e5 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 获取授权地址的输入参数 + */ +@Data +public class InvoiceAuthPageRequest implements Serializable { + + /** + * 开票平台在微信的标识号,商户需要找开票平台提供 + */ + private String sPappid; + + /** + * 订单id,在商户内单笔开票请求的唯一识别号 + */ + private String orderId; + + /** + * 订单金额,以分为单位 + */ + private Long money; + + /** + * 开票来源 + */ + private String source; + + /** + * 授权成功后跳转页面。本字段只有在source为H5的时候需要填写,引导用户在微信中进行下一步流程。app开票因为从外部app拉起微信授权页,授权完成后自动回到原来的app,故无需填写。 + */ + private String redirectUrl; + + /** + * 授权类型,0:开票授权,1:填写字段开票授权,2:领票授权 + */ + private Integer type; + + /** + * 时间戳单位s + */ + private Long timestamp; + + /** + * 内部填充(请务设置) + */ + private String ticket; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java new file mode 100644 index 000000000..d137adb49 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 获取授权链接返回结果DTO + */ +@Data +public class InvoiceAuthPageResult implements Serializable { + + /** + * 授权页地址 + */ + private String authUrl; + + /** + * 当发起端为小程序时, 返回 + */ + private String appid; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java new file mode 100644 index 000000000..dbc04816e --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class InvoiceAuthPageSetting implements Serializable { + + private AuthField authField; + + @Data + public static class AuthField implements Serializable { + private UserField userField; + private BizField bizField; + } + + @Data + public static class UserField implements Serializable { + private Integer showTitle; + private Integer showPhone; + private Integer showEmail; + private Integer requirePhone; + private Integer requireEmail; + private List customField; + } + + @Data + public static class BizField implements Serializable { + private Integer showTitle; + private Integer showTaxNo; + private Integer showAddr; + private Integer showPhone; + private Integer showBankType; + private Integer showBankNo; + + private Integer requireTaxNo; + private Integer requireAddr; + private Integer requirePhone; + private Integer requireBankType; + private Integer requireBankNo; + private List customField; + } + + @Data + public static class CustomField implements Serializable { + /** + * 字段名 + */ + private String key; + /** + * 0:否,1:是, 默认为0 + */ + private Integer isRequire; + /** + * 提示文案 + */ + private String notice; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java new file mode 100644 index 000000000..9048ceb05 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import java.io.Serializable; + +/** + * 拒绝开票请求参数 + */ +public class InvoiceRejectRequest implements Serializable { + + /** + * 开票平台标示 + */ + private String sPappid; + + /** + * 订单id + */ + private String orderId; + + /** + * 拒绝原因 + */ + private String reason; + + /** + * 引导用户跳转url + */ + private String url; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java new file mode 100644 index 000000000..6f4da63a2 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 电子发票信息查询结果 + */ +@Data +public class InvoiceResult implements Serializable { + + /** + * 发票相关信息 + */ + private InvoiceDetail invoicedetail; + + @Data + public static class InvoiceDetail implements Serializable { + /** + * 发票流水号 + */ + private String fpqqlsh; + + /** + * 检验码 + */ + private String jym; + + /** + * 校验码 + */ + private String kprq; + + /** + * 发票代码 + */ + private String fpdm; + + /** + * 发票号码 + */ + private String fphm; + + /** + * 发票url + */ + private String pdfurl; + + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java new file mode 100644 index 000000000..d9336eac1 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java @@ -0,0 +1,200 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 开票信息请求参数 + */ +@Data +public class MakeOutInvoiceRequest implements Serializable { + + private InvoiceInfo invoiceinfo; + + /** + * 发票信息 + */ + @Data + public static class InvoiceInfo implements Serializable { + /** + * 维修openid + */ + private String wxopenid; + + /** + * 订单号 + */ + private String ddh; + + /** + * 发票请求流水号,唯一识别开票请求的流水号 + */ + private String fpqqlsh; + + /** + * 纳税人识别码 + */ + private String nsrsbh; + + /** + * 纳税人名称 + */ + private String nsrmc; + + /** + * 纳税人地址 + */ + private String nsrdz; + + /** + * 纳税人电话 + */ + private String nsrdh; + + /** + * 纳税人开户行 + */ + private String nsrbank; + + /** + * 纳税人银行账号 + */ + private String nsrbankid; + + /** + * 购货方名称 + */ + private String ghfnsrsbh; + + /** + * 购货方识别号 + */ + private String ghfmc; + + /** + * 购货方地址 + */ + private String ghfdz; + + /** + * 购货方电话 + */ + private String ghfdh; + + /** + * 购货方开户行 + */ + private String ghfbank; + + /** + * 购货方银行帐号 + */ + private String ghfbankid; + + /** + * 开票人 + */ + private String kpr; + + /** + * 收款人 + */ + private String skr; + + /** + * 复核人 + */ + private String fhr; + + /** + * 价税合计 + */ + private String jshj; + + /** + * 合计金额 + */ + private String hjje; + + /** + * 合计税额 + */ + private String hjse; + + /** + * 备注 + */ + private String bz; + + /** + * 行业类型 0 商业 1其它 + */ + private String hylx; + + /** + * 发票商品条目 + */ + private List invoicedetailList; + + } + + /** + * 发票条目 + */ + @Data + public static class InvoiceDetailItem implements Serializable { + /** + * 发票性质 + */ + private String fphxz; + + /** + * 19位税收分类编码 + */ + private String spbm; + + /** + * 项目名称 + */ + private String xmmc; + + /** + * 计量单位 + */ + private String dw; + + /** + * 规格型号 + */ + private String ggxh; + + /** + * 项目数量 + */ + private String xmsl; + + /** + * 项目单价 + */ + private String xmdj; + + /** + * 项目金额 不含税,单位元 两位小数 + */ + private String xmje; + + /** + * 税率 精确到两位小数 如0.01 + */ + private String sl; + + /** + * 税额 单位元 两位小数 + */ + private String se; + + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java new file mode 100644 index 000000000..569fffa6b --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 商户联系信息 + */ +@Data +public class MerchantContactInfo implements Serializable { + /** + * 联系电话 + */ + private String phone; + + /** + * 开票超时时间 + */ + private Integer timeout; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java new file mode 100644 index 000000000..3ceed2b6d --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 设置商户联系信息和发票过时时间参数 + */ +@Data +public class MerchantContactInfoWrapper implements Serializable { + + private MerchantContactInfo contact; + + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java new file mode 100644 index 000000000..f7a64edfc --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import java.io.Serializable; + +/** + * 商户的开票平台信息 + */ +public class MerchantInvoicePlatformInfo implements Serializable { + + /** + * 微信支付商户号 + */ + private String mchid; + + /** + * 为该商户提供开票服务的开票平台 id ,由开票平台提供给商户 + */ + private String sPappid; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java new file mode 100644 index 000000000..035146616 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java @@ -0,0 +1,15 @@ +package me.chanjar.weixin.mp.bean.invoice.merchant; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 设置商户联系信息和发票过时时间参数 + */ +@Data +public class MerchantInvoicePlatformInfoWrapper implements Serializable { + + private MerchantInvoicePlatformInfo paymchInfo; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java index cbe3d4df9..9bc0e1de0 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java @@ -1082,4 +1082,78 @@ public interface WxMpApiUrl { } } + @AllArgsConstructor + enum Invoice implements WxMpApiUrl { + + /** + * 获取用户开票授权地址 + */ + GET_AUTH_URL(API_DEFAULT_HOST_URL, "/card/invoice/getauthurl"), + + /** + * 获取用户开票授权信息 + */ + GET_AUTH_DATA(API_DEFAULT_HOST_URL, "/card/invoice/getauthdata"), + + /** + * 拒绝为用户开票 + */ + REJECT_INSERT(API_DEFAULT_HOST_URL, "/card/invoice/rejectinsert"), + + /** + * 开票 + */ + MAKE_OUT_INVOICE(API_DEFAULT_HOST_URL, "/card/invoice/makeoutinvoice"), + + /** + * 发票冲红 + */ + CLEAR_OUT_INVOICE(API_DEFAULT_HOST_URL, "/card/invoice/clearoutinvoice"), + + /** + * 查询发票信息 + */ + QUERY_INVOICE_INFO(API_DEFAULT_HOST_URL, "/card/invoice/queryinvoceinfo"), + + /** + * 设置商户信息联系 + */ + SET_CONTACT_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_contact"), + + /** + * 获取商户联系信息 + */ + GET_CONTACT_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_contact"), + + /** + * 设置授权页面字段 + */ + SET_AUTH_FIELD_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_auth_field"), + + /** + * 获取授权页面字段 + */ + GET_AUTH_FIELD_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_auth_field"), + + /** + * 设置关联商户 + */ + SET_PAY_MCH_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_pay_mch"), + + /** + * 获取关联商户 + */ + GET_PAY_MCH_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_pay_mch"), + ; + private String prefix; + private String path; + + @Override + public String getUrl(WxMpConfigStorage config) { + if (null == config) { + return buildUrl(null, prefix, path); + } + return buildUrl(config.getHostConfig(), prefix, path); + } + } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java index bcd6001e5..723ec3806 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java @@ -4,10 +4,10 @@ package me.chanjar.weixin.open.api.impl; import cn.binarywang.wx.miniapp.config.WxMaConfig; import lombok.Data; import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.mp.bean.WxMpHostConfig; import me.chanjar.weixin.mp.config.WxMpConfigStorage; -import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.open.api.WxOpenConfigStorage; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; @@ -46,9 +46,6 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { private Map cardApiTickets = new ConcurrentHashMap<>(); private Map locks = new ConcurrentHashMap<>(); - private Lock componentAccessTokenLock = getLockByKey("componentAccessTokenLock"); - - @Override public boolean isComponentAccessTokenExpired() { return System.currentTimeMillis() > componentExpiresTime; @@ -64,11 +61,25 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { updateComponentAccessToken(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); } + private Lock accessTokenLockInstance; + @Override - public Lock getLockByKey(String key){ + public Lock getComponentAccessTokenLock() { + if (this.accessTokenLockInstance == null) { + synchronized (this) { + if (this.accessTokenLockInstance == null) { + this.accessTokenLockInstance = getLockByKey("componentAccessTokenLock"); + } + } + } + return this.accessTokenLockInstance; + } + + @Override + public Lock getLockByKey(String key) { Lock lock = locks.get(key); if (lock == null) { - synchronized (WxOpenInMemoryConfigStorage.class){ + synchronized (this) { lock = locks.get(key); if (lock == null) { lock = new ReentrantLock();