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