Browse Source

🐛 #1546 修复WxRedisOps问题, #1548 修复WxOpenInMemoryConfigStorage锁问题,#1305 增加商户电子发票功能

dev1
Mario Luo 4 years ago
committed by GitHub
parent
commit
058ce62a2b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1078 additions and 14 deletions
  1. +5
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java
  2. +5
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java
  3. +10
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java
  4. +51
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java
  5. +21
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java
  6. +26
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java
  7. +27
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java
  8. +85
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java
  9. +10
    -4
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
  10. +119
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java
  11. +50
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java
  12. +23
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java
  13. +66
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java
  14. +52
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java
  15. +22
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java
  16. +61
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java
  17. +30
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java
  18. +52
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java
  19. +200
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java
  20. +22
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java
  21. +16
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java
  22. +19
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java
  23. +15
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java
  24. +74
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java
  25. +17
    -6
      weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java

+ 5
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java View File

@@ -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);
}
}
}



+ 5
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java View File

@@ -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


+ 10
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java View File

@@ -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


+ 51
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java View File

@@ -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"));
}
}

+ 21
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java View File

@@ -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();
}
}

+ 26
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java View File

@@ -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() {
}
}

+ 27
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java View File

@@ -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();
}
}

+ 85
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMerchantInvoiceService.java View File

@@ -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.*;

/**
* 商户电子发票相关的接口
* <p>
* 重要!!!, 根据不同开票平台, 以下错误码可能开票成功(开票,冲红), 内部暂时未处理:
* 73105: 开票平台开票中,请使用相同的发票请求流水号重试开票
* 73107: 发票请求流水正在被处理,请通过查询接口获取结果
* 73100: 开票平台错误
* <p>
* 流程文档: 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;

/**
* 拒绝开票
* <p>
* 场景: 用户授权填写数据无效
* 结果: 用户会收到一条开票失败提示
*/
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;
}

+ 10
- 4
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java View File

@@ -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<H, P> 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<String, WxMpConfigStorage> configStorageMap;

private int retrySleepMillis = 1000;
@@ -359,11 +365,11 @@ public abstract class BaseWxMpServiceImpl<H, P> 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();


+ 119
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java View File

@@ -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> T doCommonInvoiceHttpPost(WxMpApiUrl url, Object data, Class<T> 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);
}
}

+ 50
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/ClearOutInvoiceRequest.java View File

@@ -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;

}
}

+ 23
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataRequest.java View File

@@ -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;

}

+ 66
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthDataResult.java View File

@@ -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<KeyValuePair> 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<KeyValuePair> customField;
}

@Data
public static class KeyValuePair implements Serializable {
private String key;
private String value;
}
}

+ 52
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageRequest.java View File

@@ -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;
}

+ 22
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageResult.java View File

@@ -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;
}

+ 61
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceAuthPageSetting.java View File

@@ -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<InvoiceAuthDataResult.KeyValuePair> 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<InvoiceAuthDataResult.KeyValuePair> customField;
}

@Data
public static class CustomField implements Serializable {
/**
* 字段名
*/
private String key;
/**
* 0:否,1:是, 默认为0
*/
private Integer isRequire;
/**
* 提示文案
*/
private String notice;
}
}

+ 30
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceRejectRequest.java View File

@@ -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;

}

+ 52
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/InvoiceResult.java View File

@@ -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;

}

}

+ 200
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MakeOutInvoiceRequest.java View File

@@ -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<InvoiceDetailItem> 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;

}

}

+ 22
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfo.java View File

@@ -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;

}

+ 16
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantContactInfoWrapper.java View File

@@ -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;


}

+ 19
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfo.java View File

@@ -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;
}

+ 15
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/invoice/merchant/MerchantInvoicePlatformInfoWrapper.java View File

@@ -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;

}

+ 74
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java View File

@@ -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);
}
}
}

+ 17
- 6
weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java View File

@@ -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<String, Token> cardApiTickets = new ConcurrentHashMap<>();
private Map<String, Lock> 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 getComponentAccessTokenLock() {
if (this.accessTokenLockInstance == null) {
synchronized (this) {
if (this.accessTokenLockInstance == null) {
this.accessTokenLockInstance = getLockByKey("componentAccessTokenLock");
}
}
}
return this.accessTokenLockInstance;
}

@Override
public Lock getLockByKey(String key){
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();


Loading…
Cancel
Save