This commit is contained in:
felord.cn
2021-01-11 14:08:14 +08:00
parent 300df94858
commit b10e2702ce
350 changed files with 70645 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.3.RELEASE</version>
</parent>
<artifactId>payment-spring-boot-autoconfigure</artifactId>
<version>1.0.3.RELEASE</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,45 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class PayException extends RuntimeException {
public PayException() {
}
public PayException(String message) {
super(message);
}
public PayException(String message, Throwable cause) {
super(message, cause);
}
public PayException(Throwable cause) {
super(cause);
}
public PayException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,91 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.alipay;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import cn.felord.payment.PayException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "ali.pay", name = "v1.app-id")
@EnableConfigurationProperties(AliPayProperties.class)
public class AliPayConfiguration {
@Bean
public AlipayClient alipayClient(AliPayProperties aliPayProperties) throws AlipayApiException {
PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
AliPayProperties.V1 v1 = aliPayProperties.getV1();
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
propertyMapper.from(v1::getServerUrl).to(certAlipayRequest::setServerUrl);
propertyMapper.from(v1::getAppId).to(certAlipayRequest::setAppId);
propertyMapper.from(v1::getAppPrivateKeyPath).as(this::appRSAPrivateKey).to(certAlipayRequest::setPrivateKey);
propertyMapper.from(v1::getFormat).to(certAlipayRequest::setFormat);
propertyMapper.from(v1::getCharset).to(certAlipayRequest::setCharset);
propertyMapper.from(v1::getSignType).to(certAlipayRequest::setSignType);
propertyMapper.from(v1::getAppCertPublicKeyPath).as(this::getFileAbsolutePath).to(certAlipayRequest::setCertPath);
propertyMapper.from(v1::getAlipayPublicCertPath).as(this::getFileAbsolutePath).to(certAlipayRequest::setAlipayPublicCertPath);
propertyMapper.from(v1::getAlipayRootCertPath).as(this::getFileAbsolutePath).to(certAlipayRequest::setRootCertPath);
return new DefaultAlipayClient(certAlipayRequest);
}
private String getFileAbsolutePath(String classPath) {
try {
return new ClassPathResource(classPath).getFile().getAbsolutePath();
} catch (IOException e) {
log.error("ali pay cert path is not exist ,{}", e.getMessage());
throw new PayException("ali pay cert path is not exist");
}
}
private String appRSAPrivateKey(String classPath) {
try {
File file = new ClassPathResource(classPath).getFile();
return new BufferedReader(new FileReader(file)).readLine();
} catch (IOException e) {
log.error("ali pay app private key is required ,{}", e.getMessage());
throw new PayException("ali pay app private key is required");
}
}
}

View File

@@ -0,0 +1,89 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.alipay;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
* The type Ali pay properties.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
@ConfigurationProperties("ali.pay")
public class AliPayProperties {
/**
* alipay api version 1.0
*/
@NestedConfigurationProperty
private V1 v1;
/**
* The type V 1.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class V1 {
/**
* alipay server
*/
private String serverUrl = "https://openapi.alipay.com/gateway.do";
/**
* your app ID
*/
private String appId;
/**
* your app private key, which must be in a single line
*/
private String appPrivateKeyPath;
/**
* sign type default RSA2
*/
private String signType = "RSA2";
/**
* data format only json now
*/
private String format = "json";
/**
* charset default utf-8
*/
private String charset = "utf-8";
/**
* alipay public cert path
*/
private String alipayPublicCertPath;
/**
* alipay root cert path
*/
private String alipayRootCertPath;
/**
* appCertPublicKey
*/
private String appCertPublicKeyPath;
}
}

View File

@@ -0,0 +1,34 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.autoconfigure;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(PayConfiguration.class)
public @interface EnableMobilePay {
}

View File

@@ -0,0 +1,33 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.autoconfigure;
import cn.felord.payment.alipay.AliPayConfiguration;
import cn.felord.payment.wechat.WechatPayConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Configuration
@Import({WechatPayConfiguration.class, AliPayConfiguration.class})
public class PayConfiguration {
}

View File

@@ -0,0 +1,104 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat;
import cn.felord.payment.wechat.v3.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* The type Wechat pay configuration.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Configuration
@Conditional(WechatPayConfiguredCondition.class)
@EnableConfigurationProperties(WechatPayProperties.class)
public class WechatPayConfiguration {
/**
* The constant CERT_ALIAS.
*/
private static final String CERT_ALIAS = "Tenpay Certificate";
/**
* 微信支付公私钥 以及序列号等元数据.
*
* @param wechatPayProperties the wechat pay properties
* @return the wechat cert bean
*/
@Bean
@ConditionalOnMissingBean
WechatMetaContainer wechatMetaContainer(WechatPayProperties wechatPayProperties) {
Map<String, WechatPayProperties.V3> v3Map = wechatPayProperties.getV3();
WechatMetaContainer container = new WechatMetaContainer();
KeyPairFactory keyPairFactory = new KeyPairFactory();
v3Map.keySet().forEach(tenantId -> {
WechatPayProperties.V3 v3 = v3Map.get(tenantId);
String certPath = v3.getCertPath();
String mchId = v3.getMchId();
WechatMetaBean wechatMetaBean = keyPairFactory.createPKCS12(certPath, CERT_ALIAS, mchId);
wechatMetaBean.setV3(v3);
wechatMetaBean.setTenantId(tenantId);
container.addWechatMeta(tenantId, wechatMetaBean);
});
return container;
}
/**
* 微信支付V3签名工具.
*
* @param wechatMetaContainer the wechat meta container
* @return the signature provider
*/
@Bean
SignatureProvider signatureProvider(WechatMetaContainer wechatMetaContainer) {
return new SignatureProvider(wechatMetaContainer);
}
/**
* 微信支付V3 客户端.
*
* @param signatureProvider the signature provider
* @return the wechat pay service
*/
@Bean
public WechatPayClient wechatPayClient(SignatureProvider signatureProvider) {
return new WechatPayClient(signatureProvider);
}
/**
* 多租户接口Provider.
*
* @param wechatPayClient the wechat pay client
* @return the wechat api provider
*/
@Bean
public WechatApiProvider wechatApiProvider(WechatPayClient wechatPayClient) {
return new WechatApiProvider(wechatPayClient);
}
}

View File

@@ -0,0 +1,64 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
/**
* The type Wechat pay configured condition.
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
public class WechatPayConfiguredCondition extends SpringBootCondition {
/**
* The constant STRING_WECHAT_V3_MAP.
*/
private static final Bindable<Map<String, WechatPayProperties.V3>> STRING_WECHAT_V3_MAP = Bindable
.mapOf(String.class, WechatPayProperties.V3.class);
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Wechat Pay V3 Configured Condition");
Map<String, WechatPayProperties.V3> v3 = getV3(context.getEnvironment());
if (!v3.isEmpty()) {
return ConditionOutcome.match(message.foundExactly("registered wechat mchIds " + v3.values().stream()
.map(WechatPayProperties.V3::getMchId).collect(Collectors.joining(", "))));
}
return ConditionOutcome.noMatch(message.notAvailable("registered wechat pay configs"));
}
private Map<String, WechatPayProperties.V3> getV3(Environment environment) {
return Binder.get(environment).bind("wechat.pay.v3", STRING_WECHAT_V3_MAP)
.orElse(Collections.emptyMap());
}
}

View File

@@ -0,0 +1,78 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.Map;
/**
* The type Wechat pay properties.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
@ConfigurationProperties("wechat.pay")
public class WechatPayProperties {
/**
* wechat pay V3 properties
*/
private Map<String, V3> v3 = new HashMap<>();
/**
* wechat pay v3 properties.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class V3 {
/**
* app id for wechat pay is required
*/
private String appId;
/**
* app secret for wechat pay is required
*/
private String appSecret;
/**
* app V3 secret is required by wechat pay V3
*/
private String appV3Secret;
/**
* mchId for wechat pay is required
*/
private String mchId;
/**
* partnerKey for wechat pay is optional
*/
private String partnerKey;
/**
* wechat pay certificate Path
*/
private String certPath;
/**
* your pay server domain
*/
private String domain;
}
}

View File

@@ -0,0 +1,36 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import java.io.IOException;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatPayResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
}

View File

@@ -0,0 +1,332 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* 银行代码.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum BankCode {
/**
* 工商银行
*
* @since 1.0.0.RELEASE
*/
BK_1002("1002", "工商银行"),
/**
* 农业银行
*
* @since 1.0.0.RELEASE
*/
BK_1005("1005", "农业银行"),
/**
* 建设银行
*
* @since 1.0.0.RELEASE
*/
BK_1003("1003", "建设银行"),
/**
* 中国银行
*
* @since 1.0.0.RELEASE
*/
BK_1026("1026", "中国银行"),
/**
* 交通银行
*
* @since 1.0.0.RELEASE
*/
BK_1020("1020", "交通银行"),
/**
* 招商银行
*
* @since 1.0.0.RELEASE
*/
BK_1001("1001", "招商银行"),
/**
* 邮储银行
*
* @since 1.0.0.RELEASE
*/
BK_1066("1066", "邮储银行"),
/**
* 民生银行
*
* @since 1.0.0.RELEASE
*/
BK_1006("1006", "民生银行"),
/**
* 平安银行
*
* @since 1.0.0.RELEASE
*/
BK_1010("1010", "平安银行"),
/**
* 中信银行
*
* @since 1.0.0.RELEASE
*/
BK_1021("1021", "中信银行"),
/**
* 浦发银行
*
* @since 1.0.0.RELEASE
*/
BK_1004("1004", "浦发银行"),
/**
* 兴业银行
*
* @since 1.0.0.RELEASE
*/
BK_1009("1009", "兴业银行"),
/**
* 光大银行
*
* @since 1.0.0.RELEASE
*/
BK_1022("1022", "光大银行"),
/**
* 广发银行
*
* @since 1.0.0.RELEASE
*/
BK_1027("1027", "广发银行"),
/**
* 华夏银行
*
* @since 1.0.0.RELEASE
*/
BK_1025("1025", "华夏银行"),
/**
* 宁波银行
*
* @since 1.0.0.RELEASE
*/
BK_1056("1056", "宁波银行"),
/**
* 北京银行
*
* @since 1.0.0.RELEASE
*/
BK_4836("4836", "北京银行"),
/**
* 上海银行
*
* @since 1.0.0.RELEASE
*/
BK_1024("1024", "上海银行"),
/**
* 南京银行
*
* @since 1.0.0.RELEASE
*/
BK_1054("1054", "南京银行"),
/**
* 长子县融汇村镇银行
*
* @since 1.0.0.RELEASE
*/
BK_4755("4755", "长子县融汇村镇银行"),
/**
* 长沙银行
*
* @since 1.0.0.RELEASE
*/
BK_4216("4216", "长沙银行"),
/**
* 浙江泰隆商业银行
*
* @since 1.0.0.RELEASE
*/
BK_4051("4051", "浙江泰隆商业银行"),
/**
* 中原银行
*
* @since 1.0.0.RELEASE
*/
BK_4753("4753", "中原银行"),
/**
* 企业银行(中国)
*
* @since 1.0.0.RELEASE
*/
BK_4761("4761", "企业银行(中国)"),
/**
* 顺德农商银行
*
* @since 1.0.0.RELEASE
*/
BK_4036("4036", "顺德农商银行"),
/**
* 衡水银行
*
* @since 1.0.0.RELEASE
*/
BK_4752("4752", "衡水银行"),
/**
* 长治银行
*
* @since 1.0.0.RELEASE
*/
BK_4756("4756", "长治银行"),
/**
* 大同银行
*
* @since 1.0.0.RELEASE
*/
BK_4767("4767", "大同银行"),
/**
* 河南省农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4115("4115", "河南省农村信用社"),
/**
* 宁夏黄河农村商业银行
*
* @since 1.0.0.RELEASE
*/
BK_4150("4150", "宁夏黄河农村商业银行"),
/**
* 山西省农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4156("4156", "山西省农村信用社"),
/**
* 安徽省农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4166("4166", "安徽省农村信用社"),
/**
* 甘肃省农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4157("4157", "甘肃省农村信用社"),
/**
* 天津农村商业银行
*
* @since 1.0.0.RELEASE
*/
BK_4153("4153", "天津农村商业银行"),
/**
* 广西壮族自治区农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4113("4113", "广西壮族自治区农村信用社"),
/**
* 陕西省农村信用社
*
* @since 1.0.0.RELEASE
*/
BK_4108("4108", "陕西省农村信用社"),
/**
* 深圳农村商业银行
*
* @since 1.0.0.RELEASE
*/
BK_4076("4076", "深圳农村商业银行"),
/**
* 宁波鄞州农村商业银行
*
* @since 1.0.0.RELEASE
*/
BK_4052("4052", "宁波鄞州农村商业银行"),
/**
* 浙江省农村信用社联合社
*
* @since 1.0.0.RELEASE
*/
BK_4764("4764", "浙江省农村信用社联合社"),
/**
* 江苏省农村信用社联合社
*
* @since 1.0.0.RELEASE
*/
BK_4217("4217", "江苏省农村信用社联合社"),
/**
* 江苏紫金农村商业银行股份有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4072("4072", "江苏紫金农村商业银行"),
/**
* 北京中关村银行股份有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4769("4769", "北京中关村银行"),
/**
* 星展银行(中国)有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4778("4778", "星展银行(中国)"),
/**
* 枣庄银行股份有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4766("4766", "枣庄银行"),
/**
* 海口联合农村商业银行股份有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4758("4758", "海口联合农村商业银行"),
/**
* 南洋商业银行(中国)有限公司
*
* @since 1.0.0.RELEASE
*/
BK_4763("4763", "南洋商业银行(中国)");
private final String code;
private final String bankName;
BankCode(String code, String bankName) {
this.code = code;
this.bankName = bankName;
}
/**
* Code string.
*
* @return the string
*/
public String code() {
return this.code;
}
/**
* Bank name string.
*
* @return the string
*/
public String bankName() {
return this.bankName;
}
}

View File

@@ -0,0 +1,45 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 微信先享卡的守约状态
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
public enum ContractStatus {
/**
* 约定进行中,表示用户在约定有效期内,尚未完成所有目标时,守约状态为约定进行中。
*/
ONGOING,
/**
* 约定到期核对中,在约定有效期结束后的一段时间,商户可对卡记录进行校对并做必要调整,守约状态为约定到期核对调整中。
*/
SETTLING,
/**
* 已完成约定,表示用户在约定有效期内,已完成所有目标,守约状态为已完成约定。
*/
FINISHED,
/**
* 未完成约定,表示用户在约定有效期到期后,最终未完成所有约定目标,或用户提前退出约定,守约状态为未完成约定。
*/
UNFINISHED
}

View File

@@ -0,0 +1,35 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 优惠数量的类型标识
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
public enum CountType {
/**
* 不限数量
*/
COUNT_UNLIMITED,
/**
* 有限数量
*/
COUNT_LIMIT
}

View File

@@ -0,0 +1,92 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 优惠券背景色
* <p>
* 详见<a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/marketing/convention/chapter3_1.shtml#menu1">优惠券背景色参考</a>
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum CouponBgColor {
/**
* Color 010 coupon bg color.
*/
COLOR010("#63B359"),
/**
* Color 020 coupon bg color.
*/
COLOR020("#2C9F67"),
/**
* Color 030 coupon bg color.
*/
COLOR030("#509FC9"),
/**
* Color 040 coupon bg color.
*/
COLOR040("#5885CF"),
/**
* Color 050 coupon bg color.
*/
COLOR050("#9062C0"),
/**
* Color 060 coupon bg color.
*/
COLOR060("#D09A45"),
/**
* Color 070 coupon bg color.
*/
COLOR070("#E4B138"),
/**
* Color 080 coupon bg color.
*/
COLOR080("#EE903C"),
/**
* Color 090 coupon bg color.
*/
COLOR090("#DD6549"),
/**
* Color 100 coupon bg color.
*/
COLOR100("#CC463D");
/**
* The Color.
*/
private final String color;
/**
* Instantiates a new Coupon bg color.
*
* @param color the color
*/
CouponBgColor(String color) {
this.color = color;
}
/**
* Color string.
*
* @return the string
*/
public String color() {
return this.color;
}
}

View File

@@ -0,0 +1,46 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* 代金券状态.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum CouponStatus {
/**
* 可用.
*
* @since 1.0.0.RELEASE
*/
SENDED,
/**
* 已实扣.
*
* @since 1.0.0.RELEASE
*/
USED,
/**
* 已过期.
*
* @since 1.0.0.RELEASE
*/
EXPIRED
}

View File

@@ -0,0 +1,38 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 申请资金账单账户类型.
*
* @since 1.0.3.RELEASE
*/
public enum FundFlowAccountType {
/**
* 基本账户
*/
BASIC,
/**
* 运营账户
*/
OPERATION,
/**
* 手续费账户
*/
FEES
}

View File

@@ -0,0 +1,86 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* 代金券批次状态.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum StockStatus {
/**
* Unactivated stock status.
*
* @since 1.0.0.RELEASE
*/
UNACTIVATED("unactivated", "未激活"),
/**
* Audit stock status.
*
* @since 1.0.0.RELEASE
*/
AUDIT("audit", "审核中"),
/**
* Running stock status.
*
* @since 1.0.0.RELEASE
*/
RUNNING("running", "运行中"),
/**
* Stoped stock status.
*
* @since 1.0.0.RELEASE
*/
STOPED("stoped", "已停止"),
/**
* Paused stock status.
*
* @since 1.0.0.RELEASE
*/
PAUSED("paused", "暂停发放");
private final String value;
private final String description;
StockStatus(String value, String description) {
this.value = value;
this.description = description;
}
/**
* Value string.
*
* @return the string
* @since 1.0.0.RELEASE
*/
public String value() {
return this.value;
}
/**
* Description string.
*
* @return the string
* @since 1.0.0.RELEASE
*/
public String description() {
return this.description;
}
}

View File

@@ -0,0 +1,35 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 目标完成类型、优惠使用类型
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
public enum StrategyType {
/**
* 增加数量,表示用户发生了履约行为
*/
INCREASE,
/**
* 减少数量,表示取消用户的履约行为(例如用户取消购买、退货退款等)
*/
DECREASE
}

View File

@@ -0,0 +1,31 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 账单压缩类型
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
public enum TarType {
/**
* 格式为{@code .gzip}的压缩包账单
*/
GZIP
}

View File

@@ -0,0 +1,40 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 交易账单类型
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
public enum TradeBillType {
/**
* 返回当日所有订单信息(不含充值退款订单)
*/
ALL,
/**
* 返回当日成功支付的订单(不含充值退款订单)
*/
SUCCESS,
/**
* 返回当日退款订单(不含充值退款订单)
*/
REFUND
}

View File

@@ -0,0 +1,70 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* 微信侧返回交易状态
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum TradeState {
/**
* 支付成功
*
* @since 1.0.0.RELEASE
*/
SUCCESS,
/**
* 转入退款
*
* @since 1.0.0.RELEASE
*/
REFUND,
/**
* 未支付
*
* @since 1.0.0.RELEASE
*/
NOTPAY,
/**
* 已关闭
*
* @since 1.0.0.RELEASE
*/
CLOSED,
/**
* 已撤销(付款码支付)
*
* @since 1.0.0.RELEASE
*/
REVOKED,
/**
* 用户支付中(付款码支付)
*
* @since 1.0.0.RELEASE
*/
USERPAYING,
/**
* 支付失败(其他原因,如银行返回失败)
*
* @since 1.0.0.RELEASE
*/
PAYERROR,
}

View File

@@ -0,0 +1,64 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* 微信侧返回交易类型
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum TradeType {
/**
* 公众号支付
*
* @since 1.0.0.RELEASE
*/
JSAPI,
/**
* 扫码支付
*
* @since 1.0.0.RELEASE
*/
NATIVE,
/**
* APP支付
*
* @since 1.0.0.RELEASE
*/
APP,
/**
* 付款码支付
*
* @since 1.0.0.RELEASE
*/
MICROPAY,
/**
* H5支付
*
* @since 1.0.0.RELEASE
*/
MWEB,
/**
* 刷脸支付
*
* @since 1.0.0.RELEASE
*/
FACEPAY,
}

View File

@@ -0,0 +1,36 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.enumeration;
/**
* 未完成约定原因
* <p>
* 当订单守约状态为{@link ContractStatus#UNFINISHED},返回此字段
*
* @since 1.0.3.RELEASE
*/
public enum UnfinishedReason {
/**
* 到期未完成约
*/
DUE_TO_QUIT,
/**
* 提前退出约定
*/
EARLY_QUIT
}

View File

@@ -0,0 +1,92 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
/**
* The enum We chat server domain.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum WeChatServer {
/**
* 中国
*
* @since 1.0.0.RELEASE
*/
CHINA("https://api.mch.weixin.qq.com"),
/**
* 中国国内(备用域名)
*
* @since 1.0.0.RELEASE
*/
CHINA2("https://api2.mch.weixin.qq.com"),
/**
* 香港
*
* @since 1.0.0.RELEASE
*/
HK("https://apihk.mch.weixin.qq.com"),
/**
* 美国
*
* @since 1.0.0.RELEASE
*/
US("https://apius.mch.weixin.qq.com"),
/**
* 获取公钥
*
* @since 1.0.0.RELEASE
*/
FRAUD("https://fraud.mch.weixin.qq.com"),
/**
* 活动
*
* @since 1.0.0.RELEASE
*/
ACTION("https://action.weixin.qq.com");
/**
* 域名
*
* @since 1.0.0.RELEASE
*/
private final String domain;
WeChatServer(String domain) {
this.domain = domain;
}
/**
* Gets type.
*
* @return the type
* @since 1.0.0.RELEASE
*/
public String domain() {
return domain;
}
@Override
public String toString() {
return domain;
}
}

View File

@@ -0,0 +1,404 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.enumeration;
import org.springframework.http.HttpMethod;
/**
* 微信支付类型.
*
* @author felord.cn
* @see cn.felord.payment.wechat.v3.WechatPayClient
* @since 1.0.0.RELEASE
*/
public enum WechatPayV3Type {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 获取证书.
*
* @since 1.0.0.RELEASE
*/
CERT(HttpMethod.GET, "%s/v3/certificates"),
/**
* 文件下载
*
* @since 1.0.0.RELEASE
*/
FILE_DOWNLOAD(HttpMethod.GET, "%s/v3/billdownload/file"),
/**
* 申请交易账单API.
*
* @since 1.0.3.RELEASE
*/
TRADEBILL(HttpMethod.GET, "%s/v3/bill/tradebill"),
/**
* 申请资金账单API.
*
* @since 1.0.3.RELEASE
*/
FUNDFLOWBILL(HttpMethod.GET, "%s/v3/bill/fundflowbill"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 微信公众号支付或者小程序支付.
*
* @since 1.0.0.RELEASE
*/
JSAPI(HttpMethod.POST, "%s/v3/pay/transactions/jsapi"),
/**
* 微信扫码支付.
*
* @since 1.0.0.RELEASE
*/
NATIVE(HttpMethod.POST, "%s/v3/pay/transactions/native"),
/**
* 微信APP支付.
*
* @since 1.0.0.RELEASE
*/
APP(HttpMethod.POST, "%s/v3/pay/transactions/app"),
/**
* H5支付.
*
* @since 1.0.0.RELEASE
*/
MWEB(HttpMethod.POST, "%s/v3/pay/transactions/h5"),
/**
* 关闭订单.
*
* @since 1.0.0.RELEASE
*/
CLOSE(HttpMethod.POST, "%s/v3/pay/transactions/out-trade-no/{out_trade_no}/close"),
/**
* 微信支付订单号查询.
*
* @since 1.0.0.RELEASE
*/
TRANSACTION_TRANSACTION_ID(HttpMethod.GET, "%s/v3/pay/transactions/id/{transaction_id}"),
/**
* 商户订单号查询.
*
* @since 1.0.0.RELEASE
*/
TRANSACTION_OUT_TRADE_NO(HttpMethod.GET, "%s/v3/pay/transactions/out-trade-no/{out_trade_no}"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 合单下单-APP支付API.
*
* @since 1.0.0.RELEASE
*/
COMBINE_APP(HttpMethod.POST, "%s/v3/combine-transactions/app"),
/**
* 合单下单-微信公众号支付或者小程序支付.
*
* @since 1.0.0.RELEASE
*/
COMBINE_JSAPI(HttpMethod.POST, "%s/v3/combine-transactions/jsapi"),
/**
* 合单下单-H5支付API.
*
* @since 1.0.0.RELEASE
*/
COMBINE_MWEB(HttpMethod.POST, "%s/v3/combine-transactions/h5"),
/**
* 合单下单-Native支付API.
*
* @since 1.0.0.RELEASE
*/
COMBINE_NATIVE(HttpMethod.POST, "%s/v3/combine-transactions/native"),
/**
* 合单查询订单API.
*
* @since 1.0.0.RELEASE
*/
COMBINE_TRANSACTION_OUT_TRADE_NO(HttpMethod.GET, "%s/v3/combine-transactions/out-trade-no/{combine_out_trade_no}"),
/**
* 合单关闭订单API.
*
* @since 1.0.0.RELEASE
*/
COMBINE_CLOSE(HttpMethod.POST, "%s/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 商户预授权API.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_PERMISSIONS(HttpMethod.POST, "%s/v3/payscore/permissions"),
/**
* 创单结单合并API.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_DIRECT_COMPLETE(HttpMethod.POST, "%s/payscore/serviceorder/direct-complete"),
/**
* 查询与用户授权记录授权协议号API.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_PERMISSIONS_AUTH_CODE(HttpMethod.GET, "%s/v3/payscore/permissions/authorization-code/{authorization_code}"),
/**
* 解除用户授权关系授权协议号API.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_TERMINATE_PERMISSIONS_AUTH_CODE(HttpMethod.POST, "%s/v3/payscore/permissions/authorization-code/{authorization_code}/terminate"),
/**
* 查询与用户授权记录openidAPI.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_PERMISSIONS_OPENID(HttpMethod.GET, "%s/v3/payscore/permissions/openid/{openid}"),
/**
* 解除用户授权关系openidAPI.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_TERMINATE_PERMISSIONS_OPENID(HttpMethod.POST, "%s/v3/payscore/permissions/openid/{openid}/terminate"),
/**
* 查询用户授权状态API.
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_USER_SERVICE_STATE(HttpMethod.GET, "%s/v3/payscore/user-service-state?service_id={service_id}&appid={appid}&openid={openid}"),
/**
* 创建支付分订单API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_CREATE_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder"),
/**
* 查询支付分订单API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_QUERY_USER_SERVICE_ORDER(HttpMethod.GET, "%s/v3/payscore/serviceorder"),
/**
* 取消支付分订单API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_CANCEL_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/cancel"),
/**
* 修改订单金额API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_MODIFY_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/modify"),
/**
* 完结支付分订单API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_COMPLETE_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/complete"),
/**
* 商户发起催收扣款API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_PAY_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/pay"),
/**
* 同步服务订单信息API
*
* @since 1.0.2.RELEASE
*/
PAY_SCORE_SYNC_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/sync"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 微信先享卡预受理领卡请求API.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_PRE_REQUEST(HttpMethod.POST, "%s/v3/discount-card/cards"),
/**
* 微信先享卡增加用户记录API.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_ADD_USER_RECORDS(HttpMethod.POST, "%s/v3/discount-card/cards/{out_card_code}/add-user-records"),
/**
* 微信先享卡查询先享卡订单API.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_INFO(HttpMethod.POST, "%s/v3/discount-card/cards/{out_card_code}"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 创建代金券批次API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_COUPON_STOCKS(HttpMethod.POST, "%s/v3/marketing/favor/coupon-stocks"),
/**
* 激活代金券批次API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_START(HttpMethod.POST, "%s/v3/marketing/favor/stocks/{stock_id}/start"),
/**
* 暂停代金券批次API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_PAUSE(HttpMethod.POST, "%s/v3/marketing/favor/stocks/{stock_id}/pause"),
/**
* 发放代金券API、根据商户号查用户的券.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_USERS_COUPONS(HttpMethod.POST, "%s/v3/marketing/favor/users/{openid}/coupons"),
/**
* 重启代金券API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_RESTART(HttpMethod.POST, "%s/v3/marketing/favor/stocks/{stock_id}/restart"),
/**
* 条件查询批次列表API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS(HttpMethod.GET, "%s/v3/marketing/favor/stocks"),
/**
* 查询批次详情API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_DETAIL(HttpMethod.GET, "%s/v3/marketing/favor/stocks/{stock_id}"),
/**
* 查询代金券详情API
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_USERS_COUPONS_DETAIL(HttpMethod.GET, "%s/v3/marketing/favor/users/{openid}/coupons/{coupon_id}"),
/**
* 查询代金券可用商户API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_MERCHANTS(HttpMethod.GET, "%s/v3/marketing/favor/stocks/{stock_id}/merchants"),
/**
* 查询代金券可用单品API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_ITEMS(HttpMethod.GET, "%s/v3/marketing/favor/stocks/{stock_id}/items"),
/**
* 下载批次核销明细API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_USE_FLOW(HttpMethod.GET, "%s/v3/marketing/favor/stocks/{stock_id}/use-flow"),
/**
* 下载批次退款明细API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_STOCKS_REFUND_FLOW(HttpMethod.GET, "%s/v3/marketing/favor/stocks/{stock_id}/refund-flow"),
/**
* 营销图片上传API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_IMAGE_UPLOAD(HttpMethod.POST, "%s/v3/marketing/favor/media/image-upload"),
/**
* 设置核销回调通知API.
*
* @since 1.0.0.RELEASE
*/
MARKETING_FAVOR_CALLBACKS(HttpMethod.POST, "%s/v3/marketing/favor/callbacks");
/**
* The Pattern.
*
* @since 1.0.0.RELEASE
*/
private final String pattern;
/**
* The Method.
*
* @since 1.0.0.RELEASE
*/
private final HttpMethod method;
/**
* Instantiates a new Wechat pay v 3 type.
*
* @param method the method
* @param pattern the pattern
* @since 1.0.0.RELEASE
*/
WechatPayV3Type(HttpMethod method, String pattern) {
this.method = method;
this.pattern = pattern;
}
/**
* Method string.
*
* @return the string
* @since 1.0.0.RELEASE
*/
public HttpMethod method() {
return this.method;
}
/**
* Pattern string.
*
* @return the string
* @since 1.0.0.RELEASE
*/
public String pattern() {
return this.pattern;
}
/**
* 默认支付URI.
*
* @param weChatServer the we chat server
* @return the string
* @since 1.0.0.RELEASE
*/
public String uri(WeChatServer weChatServer) {
return String.format(this.pattern, weChatServer.domain());
}
}

View File

@@ -0,0 +1,266 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.PayException;
import cn.felord.payment.wechat.enumeration.*;
import cn.felord.payment.wechat.v3.model.FundFlowBillParams;
import cn.felord.payment.wechat.v3.model.TradeBillParams;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.http.RequestEntity;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.Optional;
/**
* The type Abstract api.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public abstract class AbstractApi {
/**
* The Mapper.
*/
private final ObjectMapper mapper;
/**
* The Wechat pay client.
*/
private final WechatPayClient wechatPayClient;
/**
* The Tenant id.
*/
private final String tenantId;
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public AbstractApi(WechatPayClient wechatPayClient, String tenantId) {
this.mapper = new ObjectMapper();
applyObjectMapper(this.mapper);
this.wechatPayClient = wechatPayClient;
Assert.hasText(tenantId, "tenantId is required");
if (!container().getTenantIds().contains(tenantId)) {
throw new PayException("tenantId is not in wechatMetaContainer");
}
this.tenantId = tenantId;
}
/**
* Apply object mapper.
*
* @param mapper the mapper
*/
private void applyObjectMapper(ObjectMapper mapper) {
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
SimpleModule module = new JavaTimeModule();
mapper.registerModule(module);
}
/**
* Gets mapper.
*
* @return the mapper
*/
public ObjectMapper getMapper() {
return mapper;
}
/**
* Client wechat pay client.
*
* @return the wechat pay client
*/
public WechatPayClient client() {
return wechatPayClient;
}
/**
* Tenant id string.
*
* @return the string
*/
public String tenantId() {
return tenantId;
}
/**
* Container wechat meta container.
*
* @return the wechat meta container
*/
public WechatMetaContainer container() {
return wechatPayClient.signatureProvider().wechatMetaContainer();
}
/**
* Wechat meta bean wechat meta bean.
*
* @return the wechat meta bean
*/
public WechatMetaBean wechatMetaBean() {
return container().getWechatMeta(tenantId);
}
/**
* 构建Post请求对象.
*
* @param uri the uri
* @param params the params
* @return the request entity
*/
protected RequestEntity<?> Post(URI uri, Object params) {
try {
return RequestEntity.post(uri).header("Pay-TenantId", tenantId)
.body(mapper.writeValueAsString(params));
} catch (JsonProcessingException e) {
throw new PayException("wechat app pay json failed");
}
}
/**
* 构建Get请求对象.
*
* @param uri the uri
* @return the request entity
*/
protected RequestEntity<?> Get(URI uri) {
return RequestEntity.get(uri).header("Pay-TenantId", tenantId)
.build();
}
/**
* 对账单下载。
*
* @param link the link
* @return 对账单内容,有可能为空字符 “”
*/
protected String billDownload(String link) {
return this.client().withType(WechatPayV3Type.FILE_DOWNLOAD, link)
.function((type, downloadUrl) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(downloadUrl)
.build()
.toUri();
return Get(uri);
})
.download();
}
/**
* 申请交易账单API
* <p>
* 微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址。文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况。
* <p>
* 注意:
* <ul>
* <li>微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致;</li>
* <li>对账单中涉及金额的字段单位为“元”;</li>
* <li>对账单接口只能下载三个月以内的账单。</li>
* <li>小微商户不单独提供对账单下载如有需要可在调取“下载对账单”API接口时不传sub_mch_id获取服务商下全量电商二级商户包括小微商户和非小微商户的对账单。</li>
* </ul>
*
* @param tradeBillParams tradeBillParams
* @since 1.0.3.RELEASE
*/
public final void downloadTradeBill(TradeBillParams tradeBillParams) {
this.client().withType(WechatPayV3Type.TRADEBILL, tradeBillParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
LocalDate billDate = params.getBillDate();
queryParams.add("bill_date", billDate.format(DateTimeFormatter.ISO_DATE));
String subMchid = params.getSubMchid();
if (StringUtils.hasText(subMchid)) {
queryParams.add("sub_mchid", subMchid);
}
TradeBillType tradeBillType = Optional.ofNullable(params.getBillType())
.orElse(TradeBillType.ALL);
queryParams.add("bill_type", tradeBillType.name());
TarType tarType = params.getTarType();
if (Objects.nonNull(tarType)) {
queryParams.add("tar_type", tarType.name());
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build().toUri();
return Get(uri);
})
.consumer(response -> this.billDownload(Objects.requireNonNull(response.getBody()).get("download_url").asText()))
.request();
}
/**
* 申请资金账单API
* <p>
* 微信支付按天提供微信支付账户的资金流水账单文件,商户可以通过该接口获取账单文件的下载地址。文件内包含该账户资金操作相关的业务单号、收支金额、记账时间等信息,供商户进行核对。
* <p>
* 注意:
* <ul>
* <li>资金账单中的数据反映的是商户微信支付账户资金变动情况;</li>
* <li>对账单中涉及金额的字段单位为“元”。</li>
* </ul>
*
* @param fundFlowBillParams fundFlowBillParams
* @since 1.0.3.RELEASE
*/
public final void downloadFundFlowBill(FundFlowBillParams fundFlowBillParams) {
this.client().withType(WechatPayV3Type.FUNDFLOWBILL, fundFlowBillParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
LocalDate billDate = params.getBillDate();
queryParams.add("bill_date", billDate.format(DateTimeFormatter.ISO_DATE));
FundFlowAccountType accountType = Optional.ofNullable(params.getAccountType())
.orElse(FundFlowAccountType.BASIC);
queryParams.add("account_type", accountType.name());
TarType tarType = params.getTarType();
if (Objects.nonNull(tarType)) {
queryParams.add("tar_type", tarType.name());
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build().toUri();
return Get(uri);
})
.consumer(response -> this.billDownload(Objects.requireNonNull(response.getBody()).get("download_url").asText()))
.request();
}
}

View File

@@ -0,0 +1,446 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import org.springframework.http.*;
import org.springframework.http.converter.*;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 用于微信支付处理上传的自定义消息转换器.
*
* @author felord.cn
* @see AllEncompassingFormHttpMessageConverter
* @since 1.0.0.RELEASE
*/
final class ExtensionFormHttpMessageConverter extends FormHttpMessageConverter {
/**
* The constant BOUNDARY.
*/
private static final String BOUNDARY = "boundary";
/**
* The constant jaxb2Present.
*/
private static final boolean jaxb2Present;
/**
* The constant jackson2Present.
*/
private static final boolean jackson2Present;
/**
* The constant jackson2XmlPresent.
*/
private static final boolean jackson2XmlPresent;
/**
* The constant jackson2SmilePresent.
*/
private static final boolean jackson2SmilePresent;
/**
* The constant gsonPresent.
*/
private static final boolean gsonPresent;
/**
* The constant jsonbPresent.
*/
private static final boolean jsonbPresent;
/**
* The Part converters.
*/
private final List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
static {
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
/**
* Instantiates a new Upload http message converter.
*/
public ExtensionFormHttpMessageConverter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
try {
this.partConverters.add(new SourceHttpMessageConverter<>());
} catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
if (jaxb2Present && !jackson2XmlPresent) {
this.partConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.partConverters.add(new MappingJackson2HttpMessageConverter());
} else if (gsonPresent) {
this.partConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
this.partConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.partConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
if (jackson2SmilePresent) {
this.partConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
this.setPartConverters(this.partConverters);
applyDefaultCharset();
}
/**
* Apply the configured charset as a default to registered part converters.
*/
private void applyDefaultCharset() {
for (HttpMessageConverter<?> candidate : this.partConverters) {
if (candidate instanceof AbstractHttpMessageConverter) {
AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
// Only override default charset if the converter operates with a charset to begin with...
if (converter.getDefaultCharset() != null) {
converter.setDefaultCharset(DEFAULT_CHARSET);
}
}
}
}
@Override
@SuppressWarnings("unchecked")
public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
} else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
}
}
/**
* Is multipart boolean.
*
* @param map the map
* @param contentType the content type
* @return the boolean
*/
private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType contentType) {
if (contentType != null) {
return MediaType.MULTIPART_FORM_DATA.includes(contentType);
}
for (List<?> values : map.values()) {
for (Object value : values) {
if (value != null && !(value instanceof String)) {
return true;
}
}
}
return false;
}
/**
* Write form.
*
* @param formData the form data
* @param contentType the content type
* @param outputMessage the output message
* @throws IOException the io exception
*/
private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException {
contentType = getMediaType(contentType);
outputMessage.getHeaders().setContentType(contentType);
Charset charset = contentType.getCharset();
Assert.notNull(charset, "No charset"); // should never occur
final byte[] bytes = serializeForm(formData, charset).getBytes(charset);
outputMessage.getHeaders().setContentLength(bytes.length);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(bytes, outputStream));
} else {
StreamUtils.copy(bytes, outputMessage.getBody());
}
}
/**
* Gets media type.
*
* @param mediaType the media type
* @return the media type
*/
private MediaType getMediaType(@Nullable MediaType mediaType) {
if (mediaType == null) {
return new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);
} else if (mediaType.getCharset() == null) {
return new MediaType(mediaType, DEFAULT_CHARSET);
} else {
return mediaType;
}
}
/**
* Write multipart.
*
* @param parts the parts
* @param outputMessage the output message
* @throws IOException the io exception
*/
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
throws IOException {
Map<String, String> parameters = new LinkedHashMap<>(1);
parameters.put(BOUNDARY, BOUNDARY);
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType);
byte[] boundaryBytes = BOUNDARY.getBytes();
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> {
writeParts(outputStream, parts, boundaryBytes);
writeEnd(outputStream, boundaryBytes);
});
} else {
writeParts(outputMessage.getBody(), parts, boundaryBytes);
writeEnd(outputMessage.getBody(), boundaryBytes);
}
}
/**
* Write parts.
*
* @param os the os
* @param parts the parts
* @param boundary the boundary
* @throws IOException the io exception
*/
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {
for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {
String name = entry.getKey();
for (Object part : entry.getValue()) {
if (part != null) {
writeBoundary(os, boundary);
writePart(name, getHttpEntity(part), os);
writeNewLine(os);
}
}
}
}
/**
* Write part.
*
* @param name the name
* @param partEntity the part entity
* @param os the os
* @throws IOException the io exception
*/
@SuppressWarnings("unchecked")
private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
Object partBody = partEntity.getBody();
if (partBody == null) {
throw new IllegalStateException("Empty body for part '" + name + "': " + partEntity);
}
Class<?> partType = partBody.getClass();
HttpHeaders partHeaders = partEntity.getHeaders();
MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters) {
if (messageConverter.canWrite(partType, partContentType)) {
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os, DEFAULT_CHARSET);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty()) {
multipartMessage.getHeaders().putAll(partHeaders);
}
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
return;
}
}
throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
"found for request type [" + partType.getName() + "]");
}
/**
* Write boundary.
*
* @param os the os
* @param boundary the boundary
* @throws IOException the io exception
*/
private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
writeNewLine(os);
}
/**
* Write end.
*
* @param os the os
* @param boundary the boundary
* @throws IOException the io exception
*/
private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
os.write('-');
os.write('-');
writeNewLine(os);
}
/**
* Write new line.
*
* @param os the os
* @throws IOException the io exception
*/
private static void writeNewLine(OutputStream os) throws IOException {
os.write('\r');
os.write('\n');
}
/**
* The type Multipart http output message.
*/
private static class MultipartHttpOutputMessage implements HttpOutputMessage {
/**
* The Output stream.
*/
private final OutputStream outputStream;
/**
* The Charset.
*/
private final Charset charset;
/**
* The Headers.
*/
private final HttpHeaders headers = new HttpHeaders();
/**
* The Headers written.
*/
private boolean headersWritten = false;
/**
* Instantiates a new Multipart http output message.
*
* @param outputStream the output stream
* @param charset the charset
*/
public MultipartHttpOutputMessage(OutputStream outputStream, Charset charset) {
this.outputStream = outputStream;
this.charset = charset;
}
@Override
public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
public OutputStream getBody() throws IOException {
writeHeaders();
return this.outputStream;
}
/**
* Write headers.
*
* @throws IOException the io exception
*/
private void writeHeaders() throws IOException {
if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
byte[] headerName = getBytes(entry.getKey());
for (String headerValueString : entry.getValue()) {
byte[] headerValue = getBytes(headerValueString);
this.outputStream.write(headerName);
this.outputStream.write(':');
this.outputStream.write(' ');
this.outputStream.write(headerValue);
writeNewLine(this.outputStream);
}
}
writeNewLine(this.outputStream);
this.headersWritten = true;
}
}
/**
* Get bytes byte [ ].
*
* @param name the name
* @return the byte [ ]
*/
private byte[] getBytes(String name) {
return name.getBytes(this.charset);
}
}
}

View File

@@ -0,0 +1,76 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.PayException;
import org.springframework.core.io.ClassPathResource;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
/**
* 证书工具
*
* @author felord.cn
* @since 1.0.0.RELEASE
**/
public class KeyPairFactory {
private KeyStore store;
private final Object lock = new Object();
/**
* 获取公私钥.
*
* @param keyPath the key path
* @param keyAlias the key alias
* @param keyPass password
* @return the key pair
*/
public WechatMetaBean createPKCS12(String keyPath, String keyAlias, String keyPass) {
ClassPathResource resource = new ClassPathResource(keyPath);
char[] pem = keyPass.toCharArray();
try {
synchronized (lock) {
if (store == null) {
synchronized (lock) {
store = KeyStore.getInstance("PKCS12");
store.load(resource.getInputStream(), pem);
}
}
}
X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
certificate.checkValidity();
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
PublicKey publicKey = certificate.getPublicKey();
PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
WechatMetaBean wechatMetaBean = new WechatMetaBean();
wechatMetaBean.setKeyPair(new KeyPair(publicKey, storeKey));
wechatMetaBean.setSerialNumber(serialNumber);
return wechatMetaBean;
} catch (Exception e) {
throw new PayException("Cannot load keys from store: " + resource, e);
}
}
}

View File

@@ -0,0 +1,271 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams;
import cn.felord.payment.PayException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import org.springframework.http.*;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.Base64Utils;
import org.springframework.util.IdGenerator;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 签名 加签 验签
* <p>
* 我方请求微信服务器时需要根据我方的API证书对参数进行加签微信服务器会根据我方签名验签以确定请求来自我方服务器
* <p>
* 然后微信服务器响应我方请求并在响应报文中使用【微信平台证书】加签 我方需要根据规则验签是否响应来自微信支付服务器
* <p>
* 其中【微信平台证书】定期会进行更新,不受我方管控,我方需要适当的时候获取最新的证书列表。
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class SignatureProvider {
/**
* The constant ID_GENERATOR.
*/
private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator();
/**
* The constant SCHEMA.
*/
private static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048 ";
/**
* The constant TOKEN_PATTERN.
*/
public static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
/**
* 微信平台证书容器 key = 序列号 value = 证书对象
*/
private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();
/**
* The Rest operations.
*/
private final RestOperations restOperations = new RestTemplate();
/**
* The Wechat meta container.
*/
private final WechatMetaContainer wechatMetaContainer;
/**
* Instantiates a new Signature provider.
*
* @param wechatMetaContainer the wechat meta container
*/
public SignatureProvider(WechatMetaContainer wechatMetaContainer) {
this.wechatMetaContainer = wechatMetaContainer;
wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate);
}
/**
* 我方请求前用 SHA256withRSA 加签使用API证书.
*
* @param tenantId the properties key
* @param method the method
* @param canonicalUrl the canonical url
* @param body the body
* @return the string
*/
@SneakyThrows
public String requestSign(String tenantId, String method, String canonicalUrl, String body) {
Signature signer = Signature.getInstance("SHA256withRSA");
WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId);
signer.initSign(wechatMetaBean.getKeyPair().getPrivate());
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = ID_GENERATOR.generateId()
.toString()
.replaceAll("-", "");
final String signatureStr = createSign(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
String encode = Base64Utils.encodeToString(signer.sign());
// 序列号
String serialNo = wechatMetaBean.getSerialNumber();
// 生成token
String token = String.format(TOKEN_PATTERN,
wechatMetaBean.getV3().getMchId(),
nonceStr, timestamp, serialNo, encode);
return SCHEMA.concat(token);
}
/**
* 我方对响应验签,和应答签名做比较,使用微信平台证书.
*
* @param params the params
* @return the boolean
*/
@SneakyThrows
public boolean responseSignVerify(ResponseSignVerifyParams params) {
String wechatpaySerial = params.getWechatpaySerial();
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate);
}
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
final String signatureStr = createSign(params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody());
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initVerify(certificate);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return signer.verify(Base64Utils.decodeFromString(params.getWechatpaySignature()));
}
/**
* 当我方服务器不存在平台证书或者证书同当前响应报文中的证书序列号不一致时应当刷新 调用/v3/certificates
*
* @param tenantId tenantId
*/
@SneakyThrows
private synchronized void refreshCertificate(String tenantId) {
String url = WechatPayV3Type.CERT.uri(WeChatServer.CHINA);
UriComponents uri = UriComponentsBuilder.fromHttpUrl(url).build();
String canonicalUrl = uri.getPath();
String encodedQuery = uri.getQuery();
if (encodedQuery != null) {
canonicalUrl += "?" + encodedQuery;
}
// 签名
HttpMethod httpMethod = WechatPayV3Type.CERT.method();
String authorization = requestSign(tenantId, httpMethod.name(), canonicalUrl, "");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Authorization", authorization);
headers.add("User-Agent", "X-Pay-Service");
RequestEntity<?> requestEntity = new RequestEntity<>(headers, httpMethod, uri.toUri());
ResponseEntity<ObjectNode> responseEntity = restOperations.exchange(requestEntity, ObjectNode.class);
ObjectNode bodyObjectNode = responseEntity.getBody();
if (Objects.isNull(bodyObjectNode)) {
throw new PayException("cant obtain the response body");
}
ArrayNode certificates = bodyObjectNode.withArray("data");
if (certificates.isArray() && certificates.size() > 0) {
CERTIFICATE_MAP.clear();
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
certificates.forEach(objectNode -> {
JsonNode encryptCertificate = objectNode.get("encrypt_certificate");
String associatedData = encryptCertificate.get("associated_data").asText();
String nonce = encryptCertificate.get("nonce").asText();
String ciphertext = encryptCertificate.get("ciphertext").asText();
String publicKey = decryptResponseBody(tenantId, associatedData, nonce, ciphertext);
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
try {
Certificate certificate = certificateFactory.generateCertificate(inputStream);
String responseSerialNo = objectNode.get("serial_no").asText();
CERTIFICATE_MAP.put(responseSerialNo, certificate);
} catch (CertificateException e) {
throw new PayException("An error occurred while generating the wechat v3 certificate, reason : " + e.getMessage());
}
});
}
}
/**
* 解密响应体.
*
* @param tenantId the properties key
* @param associatedData the associated data
* @param nonce the nonce
* @param ciphertext the ciphertext
* @return the string
*/
public String decryptResponseBody(String tenantId, String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
String apiV3Key = wechatMetaContainer.getWechatMeta(tenantId).getV3().getAppV3Secret();
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new PayException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException |
InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new PayException(e);
}
}
/**
* Wechat meta container.
*
* @return the wechat meta container
*/
public WechatMetaContainer wechatMetaContainer() {
return wechatMetaContainer;
}
/**
* 请求时设置签名 组件
*
* @param components the components
* @return string string
*/
private static String createSign(String... components) {
return Arrays.stream(components)
.collect(Collectors.joining("\n", "", "\n"));
}
}

View File

@@ -0,0 +1,110 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
/**
* 微信支付工具.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatApiProvider {
/**
* The Wechat pay client.
*/
private final WechatPayClient wechatPayClient;
/**
* Instantiates a new Wechat api provider.
*
* @param wechatPayClient the wechat pay client
*/
public WechatApiProvider(WechatPayClient wechatPayClient) {
this.wechatPayClient = wechatPayClient;
}
/**
* 代金券.
*
* @param tenantId the tenant id
* @return the wechat marketing favor api
* @since 1.0.0.RELEASE
*/
public WechatMarketingFavorApi favorApi(String tenantId) {
return new WechatMarketingFavorApi(this.wechatPayClient, tenantId);
}
/**
* 普通支付-直连模式.
*
* @param tenantId the tenant id
* @return the wechat pay api
* @since 1.0.0.RELEASE
*/
public WechatDirectPayApi directPayApi(String tenantId) {
return new WechatDirectPayApi(wechatPayClient, tenantId);
}
/**
* 合单支付.
*
* @param tenantId the tenant id
* @return the wechat combine pay api
* @since 1.0.1.RELEASE
*/
public WechatCombinePayApi combinePayApi(String tenantId) {
return new WechatCombinePayApi(wechatPayClient, tenantId);
}
/**
* 微信支付分.
*
* @param tenantId the tenant id
* @return the wechat pay score api
* @since 1.0.2.RELEASE
*/
public WechatPayScoreApi payScoreApi(String tenantId) {
return new WechatPayScoreApi(wechatPayClient, tenantId);
}
/**
* 微信支付先享卡.
*
* @param tenantId the tenant id
* @return the wechat discount card api
* @since 1.0.2.RELEASE
*/
public WechatDiscountCardApi discountCardApi(String tenantId) {
return new WechatDiscountCardApi(wechatPayClient, tenantId);
}
/**
* 回调.
* <p>
* 需要处理白名单、幂等性问题。
*
* @param tenantId the tenant id
* @return the wechat pay callback
* @since 1.0.0.RELEASE
*/
public WechatPayCallback callback(String tenantId) {
return new WechatPayCallback(wechatPayClient.signatureProvider(), tenantId);
}
}

View File

@@ -0,0 +1,214 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.combine.CombineCloseParams;
import cn.felord.payment.wechat.v3.model.combine.CombineH5PayParams;
import cn.felord.payment.wechat.v3.model.combine.CombinePayParams;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.RequestEntity;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
/**
* 微信合单支付.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatCombinePayApi extends AbstractApi {
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatCombinePayApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 合单下单-APP支付API
* <p>
* 使用合单支付接口用户只输入一次密码即可完成多个订单的支付。目前最多一次可支持50笔订单进行合单支付。
* <p>
* 注意:
* • 订单如果需要进行抽佣等需要在合单中指定需要进行分账profit_sharing为true指定后交易资金进入二级商户账户处于冻结状态可在后续使用分账接口进行分账利用分账完结进行资金解冻实现抽佣和对二级商户的账期。
*
* @param combinePayParams the combine pay params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> appPay(CombinePayParams combinePayParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_APP, combinePayParams)
.function(this::combinePayFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 合单下单-JSAPI支付/小程序支付API
* <p>
* 使用合单支付接口用户只输入一次密码即可完成多个订单的支付。目前最多一次可支持50笔订单进行合单支付。
* <p>
* 注意:
* • 订单如果需要进行抽佣等需要在合单中指定需要进行分账profit_sharing为true指定后交易资金进入二级商户账户处于冻结状态可在后续使用分账接口进行分账利用分账完结进行资金解冻实现抽佣和对二级商户的账期。
*
* @param combinePayParams the combine pay params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> jsPay(CombinePayParams combinePayParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_JSAPI, combinePayParams)
.function(this::combinePayFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Combine pay function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> combinePayFunction(WechatPayV3Type type, CombinePayParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
params.setCombineAppid(v3.getAppId());
params.setCombineMchid(v3.getMchId());
String notifyUrl = v3.getDomain().concat(params.getNotifyUrl());
params.setNotifyUrl(notifyUrl);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
}
/**
* 合单下单-H5支付API.
* <p>
* 使用合单支付接口用户只输入一次密码即可完成多个订单的支付。目前最多一次可支持50笔订单进行合单支付。
* <p>
* 注意:
* • 订单如果需要进行抽佣等需要在合单中指定需要进行分账profit_sharing为true指定后交易资金进入二级商户账户处于冻结状态可在后续使用分账接口进行分账利用分账完结进行资金解冻实现抽佣和对二级商户的账期。
*
* @param combineH5PayParams the combine h 5 pay params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> h5Pay(CombineH5PayParams combineH5PayParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_MWEB, combineH5PayParams)
.function(this::combinePayFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Combine pay function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> combinePayFunction(WechatPayV3Type type, CombineH5PayParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
params.setCombineAppid(v3.getAppId());
params.setCombineMchid(v3.getMchId());
String notifyUrl = v3.getDomain().concat(params.getNotifyUrl());
params.setNotifyUrl(notifyUrl);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
}
/**
* 合单下单-Native支付API.
* <p>
* 使用合单支付接口用户只输入一次密码即可完成多个订单的支付。目前最多一次可支持50笔订单进行合单支付。
* <p>
* 注意:
* • 订单如果需要进行抽佣等需要在合单中指定需要进行分账profit_sharing为true指定后交易资金进入二级商户账户处于冻结状态可在后续使用分账接口进行分账利用分账完结进行资金解冻实现抽佣和对二级商户的账期。
*
* @param combinePayParams the combine pay params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> nativePay(CombinePayParams combinePayParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_NATIVE, combinePayParams)
.function(this::combinePayFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 合单查询订单API.
*
* @param combineOutTradeNo the combine out trade no
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryTransactionByOutTradeNo(String combineOutTradeNo) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_TRANSACTION_OUT_TRADE_NO, combineOutTradeNo)
.function((wechatPayV3Type, outTradeNo) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(outTradeNo)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 合单关闭订单API.
* <p>
* 合单支付订单只能使用此合单关单api完成关单。
* <p>
* 微信服务器返回 204。
*
* @param combineCloseParams the combine close params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> close(CombineCloseParams combineCloseParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.COMBINE_CLOSE, combineCloseParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build().toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -0,0 +1,197 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.PayParams;
import cn.felord.payment.wechat.v3.model.TransactionQueryParams;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.RequestEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
/**
* 普通支付-直连模式.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatDirectPayApi extends AbstractApi {
/**
* Instantiates a new Wechat pay api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatDirectPayApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* APP下单API
*
* @param payParams the pay params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> appPay(PayParams payParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.APP, payParams)
.function(this::payFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* JSAPI/小程序下单API
*
* @param payParams the pay params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> jsPay(PayParams payParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.JSAPI, payParams)
.function(this::payFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Native下单API
*
* @param payParams the pay params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> nativePay(PayParams payParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.NATIVE, payParams)
.function(this::payFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* H5下单API
*
* @param payParams the pay params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> h5Pay(PayParams payParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MWEB, payParams)
.function(this::payFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
private RequestEntity<?> payFunction(WechatPayV3Type type, PayParams payParams) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
payParams.setAppid(v3.getAppId());
payParams.setMchid(v3.getMchId());
String notifyUrl = v3.getDomain().concat(payParams.getNotifyUrl());
payParams.setNotifyUrl(notifyUrl);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, payParams);
}
/**
* 微信支付订单号查询API
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryTransactionById(TransactionQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.TRANSACTION_TRANSACTION_ID, params)
.function(this::queryTransactionFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 商户订单号查询API
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryTransactionByOutTradeNo(TransactionQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.TRANSACTION_OUT_TRADE_NO, params)
.function(this::queryTransactionFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
private RequestEntity<?> queryTransactionFunction(WechatPayV3Type type, TransactionQueryParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("mchid", v3.getMchId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(params.getTransactionIdOrOutTradeNo())
.toUri();
return Get(uri);
}
/**
* 关单API
*
* @param outTradeNo the out trade no
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> close(String outTradeNo) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.CLOSE, outTradeNo)
.function(this::closeByOutTradeNoFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
private RequestEntity<?> closeByOutTradeNoFunction(WechatPayV3Type type, String outTradeNo) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("mchid", v3.getMchId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(outTradeNo)
.toUri();
return Post(uri, queryParams);
}
}

View File

@@ -0,0 +1,120 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.discountcard.DiscountCardPreRequestParams;
import cn.felord.payment.wechat.v3.model.discountcard.UserRecordsParams;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
/**
* 微信支付先享卡.
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
public class WechatDiscountCardApi extends AbstractApi {
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatDiscountCardApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 预受理领卡请求API
* <p>
* 商户在引导用户跳转先享卡领卡前,需要请求先享卡预受理领卡请求接口,再根据返回数据引导用户跳转领卡。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> preRequest(DiscountCardPreRequestParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.DISCOUNT_CARD_PRE_REQUEST, params)
.function((wechatPayV3Type, requestParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
requestParams.setAppid(v3.getAppId());
requestParams.setNotifyUrl(v3.getDomain().concat(requestParams.getNotifyUrl()));
return Post(uri, requestParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 增加用户记录API
* <p>
* 当用户在商户侧消费时,用户完成了微信先享卡的目标或者获取使用优惠时,商户需要把这个信息同步给微信先享卡平台,用于在微信先享卡小程序展示及先享卡到期后的用户结算。
*
* @param params the params
* @return 返回http状态码 204 处理成功,应答无内容
*/
public WechatResponseEntity<ObjectNode> addUserRecords(UserRecordsParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.DISCOUNT_CARD_ADD_USER_RECORDS, params)
.function((wechatPayV3Type, recordsParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(recordsParams.getOutCardCode())
.toUri();
recordsParams.setOutCardCode(null);
return Post(uri, recordsParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询先享卡订单API
* <p>
* 商户可以通过商户领卡号查询指定的先享卡,可用于对账或者界面展示。
*
* @param outCardCode 商户领卡号
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> addUserRecords(String outCardCode) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.DISCOUNT_CARD_INFO, outCardCode)
.function((wechatPayV3Type, cardCode) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(cardCode)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -0,0 +1,637 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.StockStatus;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import org.bouncycastle.jcajce.provider.digest.SHA256;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
/**
* 微信支付营销代金券API
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatMarketingFavorApi extends AbstractApi {
/**
* Instantiates a new Wechat marketing favor api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatMarketingFavorApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 创建代金券批次API
* <p>
* 通过调用此接口可创建代金券批次,包括 <strong>预充值</strong> 和 <strong>免充值</strong> 类型。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> createStock(StocksCreateParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_COUPON_STOCKS, params)
.function(this::createStocksFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Create stocks function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> createStocksFunction(WechatPayV3Type type, StocksCreateParams params) {
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
String mchId = v3.getMchId();
params.setBelongMerchant(mchId);
return Post(uri, params);
}
/**
* 激活代金券批次API
* <p>
* 制券成功后,通过调用此接口激活批次,如果是预充值代金券,激活时会从商户账户余额中锁定本批次的营销资金。
*
* @param stockId the stock id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> startStock(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_START, stockId)
.function(this::startAndRestartAndPauseStockFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 发放代金券API
* <p>
* 商户侧开发时建议增加发放流水记录。
* <p>
* 微信支付文档所要求的微信公众号服务号不是必须的只要你有一个绑定了微信支付商户平台和开放平台的appid即可。
* <p>
* 流程为:
* 1. appid 请求授权微信登录。
* 2. 登录成功后,开发者在商户侧保存用户 <strong>对应此appid的openid</strong>。
* 3. 通过 appid - openid 进行发券。
* <p>
* 商户平台/API完成制券后可使用发放代金券接口发券。通过调用此接口可发放指定批次给指定用户发券场景可以是小程序、H5、APP等。
* <p>
* 注意:
* • 商户可在H5活动页面、商户小程序、商户APP等自有场景内调用该接口完成发券商户默认只允许发放本商户号调用发券接口的商户号创建的代金券如需发放其他商户商户创建的代金券请参考常见问题Q1。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> sendStock(StocksSendParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_USERS_COUPONS, params)
.function(this::sendStocksFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Send stocks function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> sendStocksFunction(WechatPayV3Type type, StocksSendParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
params.setAppid(v3.getAppId());
params.setStockCreatorMchid(v3.getMchId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(params.getOpenid())
.toUri();
params.setOpenid(null);
return Post(uri, params);
}
/**
* 暂停代金券批次API
* <p>
* 通过此接口可暂停指定代金券批次。暂停后,该代金券批次暂停发放,用户无法通过任何渠道再领取该批次的券。
*
* @param stockId the stock id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> pauseStock(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_PAUSE, stockId)
.function(this::startAndRestartAndPauseStockFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 重启代金券批次API
* <p>
* 通过此接口可重启指定代金券批次。重启后,该代金券批次可以再次发放。
*
* @param stockId the stock id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> restartStock(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_RESTART, stockId)
.function(this::startAndRestartAndPauseStockFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Start and restart and pause stock function request entity.
*
* @param type the type
* @param stockId the stock id
* @return the request entity
*/
private RequestEntity<?> startAndRestartAndPauseStockFunction(WechatPayV3Type type, String stockId) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
String mchId = v3.getMchId();
Map<String, String> body = new HashMap<>(1);
body.put("stock_creator_mchid", mchId);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(stockId)
.toUri();
return Post(uri, body);
}
/**
* 条件查询批次列表API
* <p>
* 通过此接口可查询多个批次的信息,包括批次的配置信息以及批次概况数据。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryStocksByMch(StocksQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS, params)
.function(this::queryStocksByMchFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Query stocks function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> queryStocksByMchFunction(WechatPayV3Type type, StocksQueryParams params) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("offset", String.valueOf(params.getOffset()));
queryParams.add("limit", String.valueOf(params.getLimit()));
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
queryParams.add("stock_creator_mchid", v3.getMchId());
OffsetDateTime createStartTime = params.getCreateStartTime();
if (Objects.nonNull(createStartTime)) {
//rfc 3339 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
queryParams.add("create_start_time", createStartTime
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
OffsetDateTime createEndTime = params.getCreateEndTime();
if (Objects.nonNull(createEndTime)) {
//rfc 3339 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
queryParams.add("create_end_time", createEndTime
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
StockStatus status = params.getStatus();
if (Objects.nonNull(status)) {
queryParams.add("status", status.value());
}
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build().toUri();
return Get(uri);
}
/**
* 查询批次详情API
* <p>
* 通过此接口可查询批次信息,包括批次的配置信息以及批次概况数据。
*
* @param stockId the stock id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryStockDetail(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_DETAIL, stockId)
.function(this::stockDetailFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Stock detail function request entity.
*
* @param type the type
* @param stockId the stock id
* @return the request entity
*/
private RequestEntity<?> stockDetailFunction(WechatPayV3Type type, String stockId) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("stock_creator_mchid", v3.getMchId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(stockId)
.toUri();
return Get(uri);
}
/**
* 查询代金券详情API
* <p>
* 通过此接口可查询代金券信息,包括代金券的基础信息、状态。如代金券已核销,会包括代金券核销的订单信息(订单号、单品信息等)。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryCouponDetails(CouponDetailsQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_USERS_COUPONS_DETAIL, params)
.function(this::couponDetailFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Coupon detail function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> couponDetailFunction(WechatPayV3Type type, CouponDetailsQueryParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("appid", v3.getAppId());
MultiValueMap<String, String> pathParams = new LinkedMultiValueMap<>();
pathParams.add("openid", params.getOpenId());
pathParams.add("coupon_id", params.getCouponId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(pathParams)
.toUri();
return Get(uri);
}
/**
* 查询代金券可用商户API
* <p>
* 通过调用此接口可查询批次的可用商户号,判断券是否在某商户号可用,来决定是否展示。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryMerchantsByStockId(MchQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_MERCHANTS, params)
.function(this::queryMerchantsByStockIdFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Query stocks function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> queryMerchantsByStockIdFunction(WechatPayV3Type type, MchQueryParams params) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("offset", String.valueOf(params.getOffset()));
queryParams.add("limit", String.valueOf(params.getLimit()));
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
queryParams.add("stock_creator_mchid", v3.getMchId());
String stockId = params.getStockId();
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(stockId)
.toUri();
return Get(uri);
}
/**
* 查询代金券可用单品API
* <p>
* 通过此接口可查询批次的可用商品编码,判断券是否可用于某些商品,来决定是否展示。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryStockItems(MchQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_ITEMS, params)
.function(this::queryMerchantsByStockIdFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 根据商户号查用户的券API
* <p>
* 可通过该接口查询用户在某商户号可用的全部券,可用于商户的小程序/H5中用户"我的代金券"或"提交订单页"展示优惠信息。无法查询到微信支付立减金。本接口查不到用户的微信支付立减金(又称“全平台通用券”),即在所有商户都可以使用的券,例如:摇摇乐红包;当按可用商户号查询时,无法查询用户已经核销的券
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryUserCouponsByMchId(UserCouponsQueryParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_USERS_COUPONS, params)
.function(this::queryUserCouponsFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Query user coupons function request entity.
*
* @param type the type
* @param params the params
* @return the request entity
*/
private RequestEntity<?> queryUserCouponsFunction(WechatPayV3Type type, UserCouponsQueryParams params) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("appid", v3.getAppId());
queryParams.add("creator_mchid", v3.getMchId());
String senderMchId = params.getSenderMchId();
if (StringUtils.hasText(senderMchId)) {
queryParams.add("sender_mchid", senderMchId);
}
String availableMchId = params.getAvailableMchId();
if (StringUtils.hasText(availableMchId)) {
queryParams.add("available_mchid", availableMchId);
} else {
String offset = Objects.isNull(params.getOffset()) ? null : params.getOffset().toString();
queryParams.add("offset", offset);
String limit = Objects.isNull(params.getLimit()) ? null : params.getLimit().toString();
queryParams.add("limit", limit);
String status = Objects.nonNull(params.getStatus()) ? params.getStatus().name() : null;
queryParams.add("status", status);
String stockId = params.getStockId();
if (StringUtils.hasText(stockId)) {
queryParams.add("stock_id", stockId);
}
}
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(params.getOpenId())
.toUri();
return Get(uri);
}
/**
* 下载批次核销明细API
* <p>
* 数据结果包含在响应体的 <strong>csv</strong> 字段中
* <p>
* 可获取到某批次的核销明细数据,包括订单号、单品信息、银行流水号等,用于对账/数据分析。
*
* @param stockId the stock id
* @return the wechat response entity
* @see AbstractApi#billDownload(String) 对账单下载api
*/
public WechatResponseEntity<ObjectNode> downloadStockUseFlow(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_USE_FLOW, stockId)
.function(this::downloadFlowFunction)
.consumer(wechatResponseEntity::convert)
.request();
String csv = this.billDownload(wechatResponseEntity.getBody().get("url").asText());
wechatResponseEntity.getBody().put("csv", csv);
return wechatResponseEntity;
}
/**
* 下载批次退款明细API
* <p>
* 数据结果包含在响应体的 <strong>csv</strong> 字段中
* <p>
* 可获取到某批次的退款明细数据,包括订单号、单品信息、银行流水号等,用于对账/数据分析。
*
* @param stockId the stock id
* @return the wechat response entity
* @see AbstractApi#billDownload(String) 对账单下载api
*/
public WechatResponseEntity<ObjectNode> downloadStockRefundFlow(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_STOCKS_REFUND_FLOW, stockId)
.function(this::downloadFlowFunction)
.consumer(wechatResponseEntity::convert)
.request();
String csv = this.billDownload(wechatResponseEntity.getBody().get("url").asText());
wechatResponseEntity.getBody().put("csv", csv);
return wechatResponseEntity;
}
/**
* Download flow function request entity.
*
* @param type the type
* @param stockId the stock id
* @return the request entity
*/
private RequestEntity<?> downloadFlowFunction(WechatPayV3Type type, String stockId) {
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(stockId)
.toUri();
return Get(uri);
}
/**
* 营销图片上传API
* <p>
* 媒体图片只支持JPG、BMP、PNG格式文件大小不能超过2M。
* <p>
* 通过本接口上传图片后可获得图片url地址。图片url可在微信支付营销相关的API使用包括商家券、代金券、支付有礼等。
*
* @param file the file
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> marketingImageUpload(MultipartFile file) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_IMAGE_UPLOAD, file)
.function(this::marketingImageUploadFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Marketing image upload function request entity.
*
* @param type the type
* @param file the file
* @return the request entity
*/
@SneakyThrows
private RequestEntity<?> marketingImageUploadFunction(WechatPayV3Type type, MultipartFile file) {
Map<String, Object> meta = new LinkedHashMap<>(2);
String originalFilename = file.getOriginalFilename();
String filename = StringUtils.hasText(originalFilename) ? originalFilename : file.getName();
meta.put("filename", filename);
byte[] digest = SHA256.Digest.getInstance("SHA-256").digest(file.getBytes());
meta.put("sha256", Hex.toHexString(digest));
MultiValueMap<Object, Object> body = new LinkedMultiValueMap<>();
body.add("meta", meta);
body.add("file", file.getResource());
// 签名
String metaStr = this.getMapper().writeValueAsString(meta);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return RequestEntity.post(uri)
.header("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE)
.header("Meta-Info", metaStr)
.header("Pay-TenantId", tenantId())
.body(body);
}
/**
* 代金券核销回调通知API。
* <p>
* 设置核销回调通知的{@code notifyUrl},{@code notifyUrl}需要设置应用白名单。开发者应该对代金券的核销结果进行流水记录。
*
* @param notifyUrl the notify url
* @return the wechat response entity
* @see WechatPayCallback#couponCallback(ResponseSignVerifyParams, Consumer) 核销回调
*/
public WechatResponseEntity<ObjectNode> setMarketingFavorCallback(String notifyUrl) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.MARKETING_FAVOR_CALLBACKS, notifyUrl)
.function(this::setMarketingFavorCallbackFunction)
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* Sets marketing favor callback function.
*
* @param type the type
* @param notifyUrl the notify url
* @return the marketing favor callback function
*/
private RequestEntity<?> setMarketingFavorCallbackFunction(WechatPayV3Type type, String notifyUrl) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
Map<String, Object> body = new HashMap<>(3);
body.put("mchid", v3.getMchId());
body.put("notify_url", notifyUrl);
body.put("switch", true);
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, body);
}
}

View File

@@ -0,0 +1,52 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import lombok.Data;
import java.security.KeyPair;
/**
* 微信支付元数据Bean.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class WechatMetaBean {
/**
* The Key pair.
*/
private KeyPair keyPair;
/**
* The Serial number.
*/
private String serialNumber;
/**
* The Tenant id.
*/
private String tenantId;
/**
* The V3.
*/
private WechatPayProperties.V3 v3;
}

View File

@@ -0,0 +1,76 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import java.util.*;
/**
* 配置容器
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatMetaContainer {
private final Map<String, WechatMetaBean> wechatMetaBeanMap = new HashMap<>();
private final Set<String> tenantIds = new HashSet<>();
/**
* Add wechat meta boolean.
*
* @param tenantId the tenantId
* @param wechatMetaBean the wechat meta bean
* @return the boolean
*/
public WechatMetaBean addWechatMeta(String tenantId, WechatMetaBean wechatMetaBean) {
tenantIds.add(tenantId);
return this.wechatMetaBeanMap.put(tenantId, wechatMetaBean);
}
/**
* Remove wechat meta wechat meta bean.
*
* @param tenantId the tenantId
* @return the wechat meta bean
*/
public WechatMetaBean removeWechatMeta(String tenantId) {
tenantIds.remove(tenantId);
return this.wechatMetaBeanMap.remove(tenantId);
}
/**
* Gets wechat meta.
*
* @param tenantId the tenantId
* @return the wechat meta
*/
public WechatMetaBean getWechatMeta(String tenantId) {
return Objects.requireNonNull(this.wechatMetaBeanMap.get(tenantId));
}
/**
* Gets properties keys.
*
* @return the properties keys
*/
public Set<String> getTenantIds() {
return tenantIds;
}
}

View File

@@ -0,0 +1,381 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.PayException;
import cn.felord.payment.wechat.v3.model.CallbackParams;
import cn.felord.payment.wechat.v3.model.CouponConsumeData;
import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams;
import cn.felord.payment.wechat.v3.model.TransactionConsumeData;
import cn.felord.payment.wechat.v3.model.combine.CombineTransactionConsumeData;
import cn.felord.payment.wechat.v3.model.discountcard.*;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreConsumer;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserConfirmConsumeData;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserPaidConsumeData;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserPermissionConsumeData;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
/**
* 微信支付回调工具.
* <p>
* 注意:<strong>开发者应该保证回调调用的幂等性</strong>
* <p>
* 支付通知http应答码为200或204才会当作正常接收当回调处理异常时应答的HTTP状态码应为500或者4xx。
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Slf4j
public class WechatPayCallback {
/**
* The constant MAPPER.
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* The Signature provider.
*/
private final SignatureProvider signatureProvider;
/**
* The Tenant id.
*/
private final String tenantId;
static {
MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
/**
* Instantiates a new Wechat pay callback.
*
* @param signatureProvider the signature provider
* @param tenantId the tenant id
*/
public WechatPayCallback(SignatureProvider signatureProvider, String tenantId) {
this.signatureProvider = signatureProvider;
Assert.hasText(tenantId, "tenantId is required");
this.tenantId = tenantId;
}
/**
* 微信支付代金券核销回调.
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
* @return the map
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> couponCallback(ResponseSignVerifyParams params, Consumer<CouponConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.COUPON);
CouponConsumeData consumeData = MAPPER.readValue(data, CouponConsumeData.class);
consumeDataConsumer.accept(consumeData);
Map<String, Object> responseBody = new HashMap<>(2);
responseBody.put("code", 200);
responseBody.put("message", "SUCCESS");
return responseBody;
}
/**
* 微信支付成功回调.
* <p>
* 无需开发者判断,只有扣款成功微信才会回调此接口
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
* @return the map
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> transactionCallback(ResponseSignVerifyParams params, Consumer<TransactionConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.TRANSACTION);
TransactionConsumeData consumeData = MAPPER.readValue(data, TransactionConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
}
/**
* 微信合单支付成功回调.
* <p>
* 无需开发者判断,只有扣款成功微信才会回调此接口
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
* @return the map
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> combineTransactionCallback(ResponseSignVerifyParams params, Consumer<CombineTransactionConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.TRANSACTION);
CombineTransactionConsumeData consumeData = MAPPER.readValue(data, CombineTransactionConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
}
/**
* 微信支付分确认订单、支付成功回调通知.
* <p>
* 该链接是通过商户 <a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml">创建支付分订单</a> 提交notify_url参数必须为https协议。如果链接无法访问商户将无法接收到微信通知。 通知url必须为直接可访问的url不能携带参数。示例 “https://pay.weixin.qq.com/wxpay/pay.action”
*
* @param params the params
* @param payScoreConsumer the pay score consumer
* @return the map
* @since 1.0.2.RELEASE
*/
@SneakyThrows
public Map<String, ?> payscoreUserOrderCallback(ResponseSignVerifyParams params, PayScoreConsumer payScoreConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
if (Objects.equals(eventType, EventType.PAYSCORE_USER_CONFIRM.event)) {
String data = this.decrypt(callbackParams);
PayScoreUserConfirmConsumeData confirmConsumeData = MAPPER.readValue(data, PayScoreUserConfirmConsumeData.class);
payScoreConsumer.getConfirmConsumeDataConsumer().accept(confirmConsumeData);
} else if (Objects.equals(eventType, EventType.PAYSCORE_USER_PAID.event)) {
String data = this.decrypt(callbackParams);
PayScoreUserPaidConsumeData paidConsumeData = MAPPER.readValue(data, PayScoreUserPaidConsumeData.class);
payScoreConsumer.getPaidConsumeDataConsumer().accept(paidConsumeData);
} else {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
return Collections.singletonMap("code", "SUCCESS");
}
/**
* 授权/解除授权服务回调通知API.
* <p>
* 微信支付分通过授权/解除授权服务通知接口将用户过授权/解除授权服务消息通知给商户
* <p>
* 普通授权模式是通过[商户入驻配置申请表]提交service_notify_url设置预授权模式是通过[商户预授权]提交的notify_url设置必须为https协议。如果链接无法访问商户将无法接收到微信通知。 通知url必须为直接可访问的url不能携带参数。示例 “https://pay.weixin.qq.com/wxpay/pay.action”
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
* @return the map
*/
@SneakyThrows
public Map<String, ?> permissionCallback(ResponseSignVerifyParams params, Consumer<PayScoreUserPermissionConsumeData> consumeDataConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
boolean closed;
if (Objects.equals(eventType, EventType.PAYSCORE_USER_OPEN.event)) {
closed = false;
} else if (Objects.equals(eventType, EventType.PAYSCORE_USER_CLOSE.event)) {
closed = true;
} else {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
String data = this.decrypt(callbackParams);
PayScoreUserPermissionConsumeData consumeData = MAPPER.readValue(data, PayScoreUserPermissionConsumeData.class);
consumeData.setClosed(closed);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
}
/**
* 用户领卡、守约状态变化、扣费状态变化通知API
* <p>
* 用户领取优惠卡后或者用户守约状态发生变更后或扣费状态变化后,微信会把对应信息发送给商户。
* <p>
* 该链接是通过商户<a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/discount-card/chapter3_1.shtml">预受理领卡请求</a>中提交notify_url参数必须为https协议。如果链接无法访问商户将无法接收到微信通知。 通知url必须为直接可访问的url不能携带参数。示例 “https://pay.weixin.qq.com/wxpay/pay.action”
*
* @param params the params
* @param discountCardConsumer the discount card consumer
* @return the map
*/
@SneakyThrows
public Map<String, ?> discountCardCallback(ResponseSignVerifyParams params, DiscountCardConsumer discountCardConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
if (Objects.equals(eventType, EventType.DISCOUNT_CARD_AGREEMENT_ENDED.event)) {
String data = this.decrypt(callbackParams);
DiscountCardAgreementEndConsumeData agreementEndConsumeData = MAPPER.readValue(data, DiscountCardAgreementEndConsumeData.class);
discountCardConsumer.getAgreementEndConsumeDataConsumer().accept(agreementEndConsumeData);
} else if (Objects.equals(eventType, EventType.DISCOUNT_CARD_USER_ACCEPTED.event)) {
String data = this.decrypt(callbackParams);
DiscountCardAcceptedConsumeData acceptedConsumeData = MAPPER.readValue(data, DiscountCardAcceptedConsumeData.class);
discountCardConsumer.getAcceptedConsumeDataConsumer().accept(acceptedConsumeData);
} else if (Objects.equals(eventType, EventType.DISCOUNT_CARD_USER_PAID.event)) {
String data = this.decrypt(callbackParams);
DiscountCardUserPaidConsumeData paidConsumeData = MAPPER.readValue(data, DiscountCardUserPaidConsumeData.class);
discountCardConsumer.getCardUserPaidConsumeDataConsumer().accept(paidConsumeData);
} else {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
return Collections.singletonMap("code", "SUCCESS");
}
/**
* Callback.
*
* @param params the params
* @param eventType the event type
* @return the string
*/
@SneakyThrows
private String callback(ResponseSignVerifyParams params, EventType eventType) {
CallbackParams callbackParams = this.resolve(params);
if (!Objects.equals(callbackParams.getEventType(), eventType.event)) {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
return this.decrypt(callbackParams);
}
/**
* Resolve callback params.
*
* @param params the params
* @return the callback params
* @throws JsonProcessingException the json processing exception
* @since 1.0.2.RELEASE
*/
private CallbackParams resolve(ResponseSignVerifyParams params) throws JsonProcessingException {
if (signatureProvider.responseSignVerify(params)) {
return MAPPER.readValue(params.getBody(), CallbackParams.class);
}
throw new PayException("invalid wechat pay callback");
}
/**
* Decrypt.
*
* @param callbackParams the callback params
* @return the string
* @since 1.0.2.RELEASE
*/
private String decrypt(CallbackParams callbackParams) {
CallbackParams.Resource resource = callbackParams.getResource();
String associatedData = resource.getAssociatedData();
String nonce = resource.getNonce();
String ciphertext = resource.getCiphertext();
String data = signatureProvider.decryptResponseBody(tenantId, associatedData, nonce, ciphertext);
Assert.hasText(data, "decryptData is required");
return data;
}
/**
* 事件类型用于处理回调.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
enum EventType {
/**
* 微信支付分确认订单事件.
*
* @since 1.0.2.RELEASE
*/
PAYSCORE_USER_CONFIRM("PAYSCORE.USER_CONFIRM"),
/**
* 微信支付分用户支付成功订单事件.
*
* @since 1.0.2.RELEASE
*/
PAYSCORE_USER_PAID("PAYSCORE.USER_PAID"),
/**
* 微信支付分授权事件.
*
* @since 1.0.2.RELEASE
*/
PAYSCORE_USER_OPEN("PAYSCORE.USER_OPEN_SERVICE"),
/**
* 微信支付分解除授权事件.
*
* @since 1.0.2.RELEASE
*/
PAYSCORE_USER_CLOSE("PAYSCORE.USER_CLOSE_SERVICE"),
/**
* 用户领取微信先享卡事件.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_USER_ACCEPTED("DISCOUNT_CARD.USER_ACCEPTED"),
/**
* 微信先享卡守约状态变化事件.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_AGREEMENT_ENDED("DISCOUNT_CARD.AGREEMENT_ENDED"),
/**
* 微信先享卡扣费状态变化事件.
*
* @since 1.0.2.RELEASE
*/
DISCOUNT_CARD_USER_PAID("DISCOUNT_CARD.USER_PAID"),
/**
* 优惠券核销事件.
*
* @since 1.0.0.RELEASE
*/
COUPON("COUPON.USE"),
/**
* 支付成功事件.
*
* @since 1.0.0.RELEASE
*/
TRANSACTION("TRANSACTION.SUCCESS");
/**
* The Event.
*/
private final String event;
/**
* Instantiates a new Event type.
*
* @param event the event
*/
EventType(String event) {
this.event = event;
}
}
}

View File

@@ -0,0 +1,314 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.PayException;
import cn.felord.payment.wechat.WechatPayResponseErrorHandler;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
* The type Wechat pay client.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public class WechatPayClient {
/**
* The Signature provider.
*/
private final SignatureProvider signatureProvider;
/**
* The Rest operations.
*/
private RestOperations restOperations;
/**
* Instantiates a new Wechat pay service.
*
* @param signatureProvider the signature provider
*/
public WechatPayClient(SignatureProvider signatureProvider) {
this.signatureProvider = signatureProvider;
applyDefaultRestTemplate();
}
/**
* 构造 {@link WechatRequestEntity}.
*
* @param <M> the type parameter
* @param wechatPayV3Type the v 3 pay type
* @param m the m
* @return the executor
*/
public <M> Executor<M> withType(WechatPayV3Type wechatPayV3Type, M m) {
return new Executor<>(wechatPayV3Type, m, this.signatureProvider, this.restOperations);
}
/**
* The type Executor.
*
* @param <M> the type parameter
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public static class Executor<M> {
/**
* The V3 pay type.
*/
private final WechatPayV3Type wechatPayV3Type;
/**
* The Rest operations.
*/
private final RestOperations restOperations;
/**
* The Signature provider.
*/
private final SignatureProvider signatureProvider;
/**
* The Model.
*/
private final M model;
/**
* The Request entity bi function.
*/
private BiFunction<WechatPayV3Type, M, RequestEntity<?>> requestEntityBiFunction;
/**
* The Response body consumer.
*/
private Consumer<ResponseEntity<ObjectNode>> responseBodyConsumer;
/**
* Instantiates a new Executor.
*
* @param wechatPayV3Type the v 3 pay type
* @param model the model
* @param signatureProvider the signature provider
* @param restOperations the rest operations
*/
Executor(WechatPayV3Type wechatPayV3Type,
M model,
SignatureProvider signatureProvider, RestOperations restOperations) {
this.wechatPayV3Type = wechatPayV3Type;
this.model = model;
this.signatureProvider = signatureProvider;
this.restOperations = restOperations;
}
/**
* Function executor.
*
* @param requestEntityBiFunction the request entity bifunction
* @return the executor
*/
public Executor<M> function(BiFunction<WechatPayV3Type, M, RequestEntity<?>> requestEntityBiFunction) {
this.requestEntityBiFunction = requestEntityBiFunction;
return this;
}
/**
* Consumer executor.
*
* @param responseBodyConsumer the response body consumer
* @return the executor
*/
public Executor<M> consumer(Consumer<ResponseEntity<ObjectNode>> responseBodyConsumer) {
this.responseBodyConsumer = responseBodyConsumer;
return this;
}
/**
* Request.
*/
public void request() {
RequestEntity<?> requestEntity = this.requestEntityBiFunction.apply(this.wechatPayV3Type, this.model);
WechatRequestEntity<?> wechatRequestEntity = WechatRequestEntity.of(requestEntity, this.responseBodyConsumer);
this.doExecute(this.header(wechatRequestEntity));
}
/**
* Download string.
*
* @return the string
*/
public String download() {
RequestEntity<?> requestEntity = this.requestEntityBiFunction.apply(this.wechatPayV3Type, this.model);
WechatRequestEntity<?> wechatRequestEntity = WechatRequestEntity.of(requestEntity, this.responseBodyConsumer);
return this.doDownload(this.header(wechatRequestEntity));
}
/**
* 构造私钥签名.
*
* @param <T> the type parameter
* @param requestEntity the request entity
* @return the wechat request entity
*/
private <T> WechatRequestEntity<T> header(WechatRequestEntity<T> requestEntity) {
UriComponents uri = UriComponentsBuilder.fromUri(requestEntity.getUrl()).build();
String canonicalUrl = uri.getPath();
String encodedQuery = uri.getQuery();
Assert.notNull(canonicalUrl, "canonicalUrl is required");
if (encodedQuery != null) {
canonicalUrl += "?" + encodedQuery;
}
// 签名
HttpMethod httpMethod = requestEntity.getMethod();
Assert.notNull(httpMethod, "httpMethod is required");
HttpHeaders headers = requestEntity.getHeaders();
String body = requestEntity.hasBody() ? Objects.requireNonNull(requestEntity.getBody()).toString() : "";
if (WechatPayV3Type.MARKETING_IMAGE_UPLOAD.pattern().contains(canonicalUrl)) {
body = Objects.requireNonNull(headers.get("Meta-Info")).get(0);
}
String tenantId = Objects.requireNonNull(headers.get("Pay-TenantId")).get(0);
String authorization = signatureProvider.requestSign(tenantId, httpMethod.name(), canonicalUrl, body);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.addAll(headers);
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// for upload
if (Objects.isNull(httpHeaders.getContentType())) {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
}
httpHeaders.add("Authorization", authorization);
httpHeaders.add("User-Agent", "X-Pay-Service");
httpHeaders.remove("Meta-Info");
httpHeaders.remove("Pay-TenantId");
return requestEntity.headers(httpHeaders);
}
/**
* Do execute.
*
* @param <T> the type parameter
* @param requestEntity the request entity
*/
private <T> void doExecute(WechatRequestEntity<T> requestEntity) {
ResponseEntity<ObjectNode> responseEntity = restOperations.exchange(requestEntity, ObjectNode.class);
HttpHeaders headers = responseEntity.getHeaders();
ObjectNode body = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
// 微信请求id
String requestId = headers.getFirst("Request-ID");
if (!statusCode.is2xxSuccessful()) {
throw new PayException("wechat pay server error, Request-ID " + requestId + " , statusCode " + statusCode + ",result : " + body);
}
ResponseSignVerifyParams params = new ResponseSignVerifyParams();
// 微信平台证书序列号 用来取微信平台证书
params.setWechatpaySerial(headers.getFirst("Wechatpay-Serial"));
//获取应答签名
params.setWechatpaySignature(headers.getFirst("Wechatpay-Signature"));
//构造验签名串
params.setWechatpayTimestamp(headers.getFirst("Wechatpay-Timestamp"));
params.setWechatpayNonce(headers.getFirst("Wechatpay-Nonce"));
String content = Objects.isNull(body) ? "" : body.toString();
params.setBody(content);
// 验证微信服务器签名
if (signatureProvider.responseSignVerify(params)) {
Consumer<ResponseEntity<ObjectNode>> responseConsumer = requestEntity.getResponseBodyConsumer();
if (Objects.nonNull(responseConsumer)) {
// 验证通过消费
responseConsumer.accept(responseEntity);
}
} else {
throw new PayException("wechat pay signature failed, Request-ID " + requestId);
}
}
/**
* Do download string.
*
* @param <T> the type parameter
* @param requestEntity the request entity
* @return the string
*/
private <T> String doDownload(WechatRequestEntity<T> requestEntity) {
ResponseEntity<String> responseEntity = restOperations.exchange(requestEntity, String.class);
HttpStatus statusCode = responseEntity.getStatusCode();
// 微信请求id
String requestId = requestEntity.getHeaders().getFirst("Request-ID");
if (!statusCode.is2xxSuccessful()) {
throw new PayException("wechat pay server error, Request-ID " + requestId + " , statusCode " + statusCode + ",result : " + responseEntity);
}
return Optional.ofNullable(responseEntity.getBody()).orElse("");
}
}
/**
* Signature provider signature provider.
*
* @return the signature provider
*/
public SignatureProvider signatureProvider() {
return signatureProvider;
}
/**
* Apply default rest template.
*/
private void applyDefaultRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
DefaultResponseErrorHandler errorHandler = new WechatPayResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.removeIf(httpMessageConverter -> httpMessageConverter instanceof AllEncompassingFormHttpMessageConverter);
messageConverters.add(new ExtensionFormHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
this.restOperations = restTemplate;
}
}

View File

@@ -0,0 +1,467 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.payscore.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
/**
* 微信支付分API.
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
public class WechatPayScoreApi extends AbstractApi {
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatPayScoreApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 微信支付分-查询用户授权状态API.
* <p>
* 免确认订单起始接口,【免确认订单模式】是高级接口权限,参见:<a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter2_5.shtml">业务流程说明</a>
* <p>
* 用户申请使用服务时,商户可通过此接口查询用户是否“已授权”本服务。在“已授权”状态下的服务,用户才可以申请使用。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> userServiceState(UserServiceStateParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_USER_SERVICE_STATE, params)
.function((wechatPayV3Type, userServiceStateParams) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> expandParams = new LinkedMultiValueMap<>();
expandParams.add("appid", v3.getAppId());
expandParams.add("service_id", params.getServiceId());
expandParams.add("openid", params.getOpenId());
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(expandParams)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 创建支付分订单API
* <p>
* 用户申请使用服务时,商户可通过此接口申请创建微信支付分订单。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> createServiceOrder(UserServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_CREATE_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setNotifyUrl(v3.getDomain().concat(orderParams.getNotifyUrl()));
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询支付分订单API
* <p>
* 用于查询单笔微信支付分订单详细信息。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryServiceOrder(QueryServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_QUERY_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
String outOrderNo = orderParams.getOutOrderNo();
if (StringUtils.hasText(outOrderNo)) {
queryParams.add("out_order_no", outOrderNo);
}
String queryId = orderParams.getQueryId();
if (StringUtils.hasText(queryId)) {
queryParams.add("query_id", queryId);
}
queryParams.add("service_id", orderParams.getServiceId());
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
queryParams.add("appid", v3.getAppId());
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 取消支付分订单API
* <p>
* 微信支付分订单创建之后,由于某些原因导致订单不能正常支付时,可使用此接口取消订单。
* <p>
* 订单为以下状态时可以取消订单CREATED已创单、DOING进行中包括商户完结支付分订单后且支付分订单收款状态为待支付USER_PAYING)
* <p>
* 注意:
* • DOING状态包含了订单用户确认、已完结-待支付USER_PAYING的状态因此这种状态下订单也是可以被取消的请确认当前操作是否正确防止误操作将完结后需要支付分收款的单据取消。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> cancelServiceOrder(CancelServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_CANCEL_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOutOrderNo())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOutOrderNo(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 修改订单金额API
* <p>
* 完结订单总金额与实际金额不符时,可通过该接口修改订单金额。
* 例如充电宝场景由于机器计费问题导致商户完结订单时扣除用户99元用户客诉成功后商户需要按照实际的消费金额如10元扣费当服务订单支付状态处于“待支付”时商户可使用此能力修改订单金额。
* <p>
* 注意:
* • 若此笔订单已收款成功,商户直接使用退款能力,将差价退回用户即可。
* <p>
* • 修改次数&gt;=1第n次修改后金额 &lt;第n-1次修改后金额
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> modifyServiceOrder(ModifyServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_MODIFY_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOutOrderNo())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOutOrderNo(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 完结支付分订单API
* <p>
* 前置条件:服务订单状态为“进行中”且订单状态说明需为[USER_CONFIRM:用户确认]
* <p>
* 完结微信支付分订单。用户使用服务完成后,商户可通过此接口完结订单。
* <p>
* 特别说明:
* • 完结接口调用成功后,微信支付将自动发起免密代扣。 若扣款失败,微信支付将自动再次发起免密代扣(按照一定频次),直到扣成功为止。
*
* @param params the params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> completeServiceOrder(CompleteServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_COMPLETE_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOutOrderNo())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOutOrderNo(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 商户发起催收扣款API
* <p>
* 前置条件:服务订单支付状态处于“待支付”状态
* <p>
* 当微信支付分订单支付状态处于“待支付”时,商户可使用该接口向用户发起收款。
* <p>
* 注意:
* • 此能力不影响微信支付分代商户向用户发起收款的策略。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> payServiceOrder(PayServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_PAY_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOutOrderNo())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOutOrderNo(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 同步服务订单信息API
* <p>
* 前提条件:同步商户渠道收款成功信息时,即场景类型=“Order_Paid”订单的状态需为[MCH_COMPLETE:商户完结订单]
* <p>
* 由于收款商户进行的某些“线下操作”会导致微信支付侧的订单状态与实际情况不符。例如,用户通过线下付款的方式已经完成支付,而微信支付侧并未支付成功,此时可能导致用户重复支付。因此商户需要通过订单同步接口将订单状态同步给微信支付,修改订单在微信支付系统中的状态。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> syncServiceOrder(SyncServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_SYNC_USER_SERVICE_ORDER, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOutOrderNo())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOutOrderNo(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 创单结单合并API
* <p>
* 相对需确认模式免确认模式减少了用户确认授权这步操作因此在免确认模式下商户无法获取用户的授权状态为了解决商户的困扰我们为免确认模式特别提供了查询授权状态和调起授权页面的api接口这些接口仅在免确认模式下需要调用且必须调用。
* <p>
* 该接口适用于无需微信支付分做订单风控判断的业务场景,在服务完成后,通过该接口对用户进行免密代扣。
* <p>
* 注意:
* • 限制条件:【免确认订单模式】,用户已授权状态下,可调用该接口。
* <p>
* 特别提醒:创单结单合并接口暂未对外开放,如有需要请咨询对接的微信支付运营人员,申请开通调用权限。
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> directCompleteServiceOrder(DirectCompleteServiceOrderParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_DIRECT_COMPLETE, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
String notifyUrl = orderParams.getNotifyUrl();
if (StringUtils.hasText(notifyUrl)) {
orderParams.setNotifyUrl(v3.getDomain().concat(notifyUrl));
}
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 商户预授权API
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> permissions(ServiceOrderPermissionParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_PERMISSIONS, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
String notifyUrl = orderParams.getNotifyUrl();
orderParams.setAppid(v3.getAppId());
if (StringUtils.hasText(notifyUrl)) {
orderParams.setNotifyUrl(v3.getDomain().concat(notifyUrl));
}
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询与用户授权记录授权协议号API
* <p>
* 通过authorization_code商户查询与用户授权关系
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryPermissionsByAuthCode(PermissionsAuthCodeParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_PERMISSIONS_AUTH_CODE, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParam("service_id", orderParams.getServiceId())
.build()
.expand(orderParams.getAuthorizationCode())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 解除用户授权关系授权协议号API
* <p>
* 通过authorization_code商户解除用户授权关系
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> terminatePermissionsByAuthCode(PermissionsAuthCodeParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_TERMINATE_PERMISSIONS_AUTH_CODE, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getAuthorizationCode())
.toUri();
orderParams.setAuthorizationCode(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询与用户授权记录openidAPI
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryPermissionsByOpenId(PermissionsOpenIdParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_PERMISSIONS_OPENID, params)
.function((wechatPayV3Type, orderParams) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParam("appid", v3.getAppId())
.queryParam("service_id", orderParams.getServiceId())
.build()
.expand(orderParams.getOpenid())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 解除用户授权关系openidAPI
*
* @param params the params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> terminatePermissionsByOpenId(PermissionsOpenIdParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PAY_SCORE_TERMINATE_PERMISSIONS_OPENID, params)
.function((wechatPayV3Type, orderParams) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(orderParams.getOpenid())
.toUri();
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
orderParams.setAppid(v3.getAppId());
orderParams.setOpenid(null);
return Post(uri, orderParams);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -0,0 +1,94 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.function.Consumer;
/**
* The type Wechat request entity.
*
* @param <T> the type parameter
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Getter
public class WechatRequestEntity<T> extends RequestEntity<T> {
/**
* The Response body consumer.
*/
private final Consumer<ResponseEntity<ObjectNode>> responseBodyConsumer;
/**
* Instantiates a new Wechat request entity.
*
* @param body the body
* @param headers the headers
* @param method the method
* @param url the url
* @param type the type
* @param responseBodyConsumer the response body consumer
*/
public WechatRequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url, Type type, Consumer<ResponseEntity<ObjectNode>> responseBodyConsumer) {
super(body, headers, method, url, type);
this.responseBodyConsumer = responseBodyConsumer;
}
/**
* Headers wechat request entity.
*
* @param httpHeaders the http headers
* @return the wechat request entity
*/
public WechatRequestEntity<T> headers(HttpHeaders httpHeaders) {
return new WechatRequestEntity<>(this.getBody(),
httpHeaders,
this.getMethod(),
this.getUrl(),
this.getType(),
this.responseBodyConsumer);
}
/**
* Of wechat request entity.
*
* @param requestEntity the request entity
* @param responseBodyConsumer the response body consumer
* @return the wechat request entity
*/
public static WechatRequestEntity<?> of(RequestEntity<?> requestEntity, Consumer<ResponseEntity<ObjectNode>> responseBodyConsumer) {
return new WechatRequestEntity<>(requestEntity.getBody(),
requestEntity.getHeaders(),
requestEntity.getMethod(),
requestEntity.getUrl(),
requestEntity.getType(),
responseBodyConsumer);
}
}

View File

@@ -0,0 +1,126 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.Objects;
/**
* The type Wechat response entity.
*
* @param <T> the type parameter
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Slf4j
@Data
public class WechatResponseEntity<T> {
/**
* The Http status.
*/
private int httpStatus;
/**
* The Body.
*/
private T body;
/**
* Convert {@link ResponseEntity} to {@link WechatResponseEntity}.
*
* @param responseEntity the response entity
*/
public void convert(ResponseEntity<T> responseEntity) {
if (log.isDebugEnabled()) {
log.debug("wechat response {}", responseEntity);
}
if (Objects.nonNull(responseEntity)) {
this.httpStatus = responseEntity.getStatusCodeValue();
this.body = responseEntity.getBody();
} else {
this.httpStatus = HttpStatus.REQUEST_TIMEOUT.value();
this.body = null;
}
}
/**
* Is 1 xx informational boolean.
*
* @return the boolean
*/
public boolean is1xxInformational() {
if (log.isDebugEnabled()) {
log.debug("wechat httpStatus {}", this.httpStatus);
}
return HttpStatus.valueOf(this.httpStatus).is1xxInformational();
}
/**
* Is 2xx successful boolean.
*
* @return the boolean
*/
public boolean is2xxSuccessful() {
if (log.isDebugEnabled()) {
log.debug("wechat httpStatus {}", this.httpStatus);
}
return HttpStatus.valueOf(this.httpStatus).is2xxSuccessful();
}
/**
* Is 3xx redirection boolean.
*
* @return the boolean
*/
public boolean is3xxRedirection() {
if (log.isDebugEnabled()) {
log.debug("wechat httpStatus {}", this.httpStatus);
}
return HttpStatus.valueOf(this.httpStatus).is3xxRedirection();
}
/**
* Is 4xx client error boolean.
*
* @return the boolean
*/
public boolean is4xxClientError() {
if (log.isDebugEnabled()) {
log.debug("wechat httpStatus {}", this.httpStatus);
}
return HttpStatus.valueOf(this.httpStatus).is4xxClientError();
}
/**
* Is 5xx server error boolean.
*
* @return the boolean
*/
public boolean is5xxServerError() {
if (log.isDebugEnabled()) {
log.debug("wechat httpStatus {}", this.httpStatus);
}
return HttpStatus.valueOf(this.httpStatus).is5xxServerError();
}
}

View File

@@ -0,0 +1,39 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 支付金额 货币单位【分】默认使用人民币标识CNY
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class Amount {
/**
* The Total.
*/
private int total;
/**
* The Currency.
*/
private String currency = "CNY";
}

View File

@@ -0,0 +1,84 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 微信支付回调请求参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CallbackParams {
/**
* The Id.
*/
private String id;
/**
* The Create time.
*/
private String createTime;
/**
* The Event type.
*/
private String eventType;
/**
* The Resource type.
*/
private String resourceType;
/**
* The Summary.
*/
private String summary;
/**
* The Resource.
*/
private Resource resource;
/**
* The type Resource.
*/
@Data
public static class Resource {
/**
* The Algorithm.
*/
private String algorithm;
/**
* The Ciphertext.
*/
private String ciphertext;
/**
* The Associated data.
*/
private String associatedData;
/**
* The Nonce.
*/
private String nonce;
/**
* The Original type.
*/
private String originalType;
}
}

View File

@@ -0,0 +1,54 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
import java.util.List;
/**
* 已实扣代金券信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class ConsumeInformation {
/**
* The Consume mchid.
*/
private String consumeMchid;
/**
* The Consume time.
*/
private String consumeTime;
/**
* The Goods detail.
*/
private List<GoodsDetail> goodsDetail;
/**
* The Transaction id.
*/
private String transactionId;
}

View File

@@ -0,0 +1,43 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* The type Coupon available time.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CouponAvailableTime {
/**
* The Available time after receive.
*/
private Long availableTimeAfterReceive;
/**
* The Fix available time.
*/
private FixAvailableTime fixAvailableTime;
/**
* The Second day available.
*/
private Boolean secondDayAvailable;
}

View File

@@ -0,0 +1,112 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 微信代金券核销通知参数
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CouponConsumeData {
/**
* The Available begin time.
*/
private String availableBeginTime;
/**
* The Available end time.
*/
private String availableEndTime;
/**
* The Consume information.
*/
private ConsumeInformation consumeInformation;
/**
* The Coupon id.
*/
private String couponId;
/**
* The Coupon name.
*/
private String couponName;
/**
* The Coupon type.
*/
private CouponType couponType;
/**
* The Create time.
*/
private String createTime;
/**
* The Description.
*/
private String description;
/**
* The Discount to.
*/
private DiscountTo discountTo;
/**
* The No cash.
*/
private Boolean noCash;
/**
* The Normal coupon information.
*/
private NormalCouponInformation normalCouponInformation;
/**
* The Singleitem.
*/
private Boolean singleitem;
/**
* The Singleitem discount off.
*/
private SingleitemDiscountOff singleitemDiscountOff;
/**
* The Status.
*/
private String status;
/**
* The Stock creator mchid.
*/
private String stockCreatorMchid;
/**
* The Stock id.
*/
private String stockId;
/**
* 券类型
*
* @since 1.0.2.RELEASE
*/
public enum CouponType{
/**
* 满减券
*/
NORMAL,
/**
* 减至券
*/
CUT_TO
}
}

View File

@@ -0,0 +1,41 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 查询代金券详情API参数
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CouponDetailsQueryParams {
/**
* 用户在appid下授权得到的openid
* <p>
* 参考发券
*/
private String openId;
/**
* 代金券id
*/
private String couponId;
}

View File

@@ -0,0 +1,109 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
import java.util.List;
/**
* 核销规则.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CouponUseRule {
/**
* 可核销商品编码
*/
private List<String> availableItems;
/**
* 可用商户
*/
private List<String> availableMerchants;
/**
* 是否可以叠加使用
*/
private Boolean combineUse;
/**
* 券生效时间
*/
private CouponAvailableTime couponAvailableTime;
/**
* 固定面额满减券使用规则
*/
private FixedNormalCoupon fixedNormalCoupon;
/**
* 订单优惠标记
*/
private List<String> goodsTag;
/**
* 指定支付方式
*/
private List<String> limitPay;
/**
* 指定银行卡BIN
*/
private LimitCard limitCard;
/**
* 支付方式,枚举值:
*
* <ul>
* <li>MICROAPP小程序支付</li>
* <li>APPPAYAPP支付</li>
* <li>PPAY免密支付</li>
* <li>CARD刷卡支付</li>
* <li>FACE人脸支付</li>
* <li>OTHER其他支付</li>
* </ul>
*/
private String tradeType;
/**
* 指定银行卡BIN
* <p>
* 限定该批次核销的指定银行卡BIN当批次限定了指定银行卡时方可生效
*/
@Data
public static class LimitCard {
/**
* 银行卡名称
* <p>
* 将在微信支付收银台向用户展示最多4个中文字
*/
private String name;
/**
* 指定卡BIN
* <p>
* 使用指定卡BIN的银行卡支付方可享受优惠按json格式
* 特殊规则单个卡BIN的字符长度为【6,9】,条目个数限制为【1,10】。
* 示例值:['62123456','62123457']
*/
private List<String> bin;
}
}

View File

@@ -0,0 +1,45 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
import java.util.List;
/**
* The type Detail.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class Detail {
/**
* 订单原价
*/
private int costPrice;
/**
* 商品小票ID
*/
private String invoiceId;
/**
* 单品列表
*/
private List<Goods> goodsDetail;
}

View File

@@ -0,0 +1,41 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 减至优惠限定字段,仅减至优惠场景有返回
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class DiscountTo {
/**
* The Cut to price.
*/
private Long cutToPrice;
/**
* The Max price.
*/
private Long maxPrice;
}

View File

@@ -0,0 +1,47 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
import java.util.List;
/**
* The type Fix available time.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class FixAvailableTime {
/**
* The Available week day.
*/
private List<Long> availableWeekDay;
/**
* The Begin time.
*/
private Long beginTime;
/**
* The End time.
*/
private Long endTime;
}

View File

@@ -0,0 +1,44 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 固定面额满减券使用规则, stock_type为NORMAL时必填
* <p>
* 满transactionMinimum 减少 couponAmount
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class FixedNormalCoupon {
/**
* 面额,单位分
*/
private Long couponAmount;
/**
* 门槛 满M元可用
*/
private Long transactionMinimum;
}

View File

@@ -0,0 +1,52 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.FundFlowAccountType;
import cn.felord.payment.wechat.enumeration.TarType;
import lombok.Data;
import java.time.LocalDate;
/**
* 申请资金账单请求参数
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
@Data
public class FundFlowBillParams {
/**
* 账单日期,必传。
* <p>
* 格式YYYY-MM-DD仅支持三个月内的账单下载申请。
*/
private LocalDate billDate;
/**
* 资金账户类型,不填则默认值为{@link FundFlowAccountType#BASIC}
*
* @see FundFlowAccountType
*/
private FundFlowAccountType accountType;
/**
* 压缩类型,不填默认值为数据流
*
* @see TarType
*/
private TarType tarType;
}

View File

@@ -0,0 +1,49 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class Goods {
/**
* 商户侧商品编码
*/
private String merchantGoodsId;
/**
* 微信侧商品编码
*/
private String wechatpayGoodsId;
/**
* 商品名称
*/
private String goodsName;
/**
* 商品数量
*/
private int quantity;
/**
* 商品单价
*/
private int unitPrice;
}

View File

@@ -0,0 +1,49 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 商户下单接口传的单品信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class GoodsDetail {
/**
* The Discount amount.
*/
private Long discountAmount;
/**
* The Goods id.
*/
private String goodsId;
/**
* The Price.
*/
private Long price;
/**
* The Quantity.
*/
private Long quantity;
}

View File

@@ -0,0 +1,51 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* H5 信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class H5Info {
/**
* 场景类型
*/
private String type;
/**
* 应用名称
*/
private String appName;
/**
* 网站URL
*/
private String appUrl;
/**
* IOS 平台 BundleID
*/
private String bundleId;
/**
* Android 平台 PackageName
*/
private String packageName;
}

View File

@@ -0,0 +1,47 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 查询代金券可用商户.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class MchQueryParams {
/**
* 必填
* <p>
* 查询代金券可用商户API 分页页码最大1000。
*/
private Integer offset = 0;
/**
* 必填
* <p>
* 查询代金券可用商户API 最大50。
*/
private Integer limit = 10;
/**
* 批次ID
*/
private String stockId;
}

View File

@@ -0,0 +1,41 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 普通满减券面额、门槛信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class NormalCouponInformation {
/**
* The Coupon amount.
*/
private Long couponAmount;
/**
* The Transaction minimum.
*/
private Long transactionMinimum;
}

View File

@@ -0,0 +1,54 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.CouponBgColor;
import lombok.Data;
/**
* 优惠券样式
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class PatternInfo {
/**
* 背景色
*/
private CouponBgColor backgroundColor;
/**
* 券详情图片
*/
private String couponImage;
/**
* 使用说明
*/
private String description;
/**
* 商户logo
*/
private String merchantLogo;
/**
* 商户名称
*/
private String merchantName;
}

View File

@@ -0,0 +1,84 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 支付请求参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class PayParams {
/**
* The Appid.
*/
private String appid;
/**
* The Mchid.
*/
private String mchid;
/**
* 商品描述
* Image形象店-深圳腾大-QQ公仔
*/
private String description;
/**
* 商户系统内部订单号只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
* 示例值1217752501201407033233368018
*/
private String outTradeNo;
/**
* 订单失效时间 YYYY-MM-DDTHH:mm:ss+TIMEZONE
*/
private String timeExpire;
/**
* 附加数据在查询API和支付通知中原样返回可作为自定义参数使用
*/
private String attach;
/**
* 通知URL必须为直接可访问的URL不允许携带查询串。
*/
private String notifyUrl;
/**
* 订单优惠标记
*/
private String goodsTag;
/**
* 支付金额
*/
private Amount amount;
/**
* 支付者 JSAPI/小程序下单 必传
*/
private Payer payer;
/**
* 优惠功能
*/
private Detail detail;
/**
* 场景信息
*/
private SceneInfo sceneInfo;
}

View File

@@ -0,0 +1,43 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* The type Payer.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class Payer {
/**
* 用户标识
*/
private String openid;
/**
* 用户服务标识
*/
private String spOpenid;
/**
* 用户子标识
*/
private String subOpenid;
}

View File

@@ -0,0 +1,111 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
import java.util.List;
/**
* The type Promotion detail.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class PromotionDetail {
/**
* The Amount.
*/
private Long amount;
/**
* The Coupon id.
*/
private String couponId;
/**
* The Currency.
*/
private String currency;
/**
* The Goods detail.
*/
private List<GoodsDetail> goodsDetail;
/**
* The Merchant contribute.
*/
private Long merchantContribute;
/**
* The Name.
*/
private String name;
/**
* The Other contribute.
*/
private Long otherContribute;
/**
* The Scope.
*/
private String scope;
/**
* The Stock id.
*/
private String stockId;
/**
* The Type.
*/
private String type;
/**
* The Wechatpay contribute.
*/
private Long wechatpayContribute;
/**
* The type Goods detail.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class GoodsDetail {
/**
* The Goods id.
*/
private String goodsId;
/**
* The Quantity.
*/
private Long quantity;
/**
* The Unit price.
*/
private Long unitPrice;
/**
* The Discount amount.
*/
private Long discountAmount;
/**
* The Goods remark.
*/
private String goodsRemark;
}
}

View File

@@ -0,0 +1,53 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.v3.SignatureProvider;
import lombok.Data;
/**
* 微信的响应签名校验参数
*
* @author felord.cn
* @see SignatureProvider#responseSignVerify(ResponseSignVerifyParams) SignatureProvider#responseSignVerify(ResponseSignVerifyParams)SignatureProvider#responseSignVerify(ResponseSignVerifyParams)
* @since 1.0.0.RELEASE
*/
@Data
public class ResponseSignVerifyParams {
/**
* response.headers['Wechatpay-Serial'] 当前使用的微信平台证书序列号
*/
private String wechatpaySerial;
/**
* response.headers['Wechatpay-Signature'] 微信平台签名
*/
private String wechatpaySignature;
/**
* response.headers['Wechatpay-Timestamp'] 微信服务器的时间戳
*/
private String wechatpayTimestamp;
/**
* response.headers['Wechatpay-Nonce'] 微信服务器提供的随机串
*/
private String wechatpayNonce;
/**
* response.body 微信服务器的响应体
*/
private String body;
}

View File

@@ -0,0 +1,47 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 场景信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class SceneInfo {
/**
* 用户终端IP
*/
private String payerClientIp;
/**
* 商户端设备号
*/
private String deviceId;
/**
* 商户门店信息
*/
private StoreInfo storeInfo;
/**
* H5 场景信息
*/
private H5Info h5Info;
}

View File

@@ -0,0 +1,37 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class SettleInfo {
/**
* 是否指定分账
*/
private Boolean profitSharing;
/**
* 补差金额
*/
private Integer subsidyAmount;
}

View File

@@ -0,0 +1,34 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 单品优惠特定信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class SingleitemDiscountOff {
private Long singlePriceMax;
}

View File

@@ -0,0 +1,57 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 批次使用规则
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class StockUseRule {
/**
* 总消耗金额,单位:分。
* max_amount需要等于coupon_amount面额 * max_coupons发放总上限
*/
private Long maxAmount;
/**
* 单天最高消耗金额,单位:分
*/
private Long maxAmountByDay;
/**
* 最大发券数
*/
private Long maxCoupons;
/**
* 单个用户可领个数每个用户最多60张券
*/
private Long maxCouponsPerUser;
/**
* 是否开启自然人限制
*/
private Boolean naturalPersonLimit;
/**
* api发券防刷
*/
private Boolean preventApiAbuse;
}

View File

@@ -0,0 +1,88 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.OffsetDateTime;
/**
* 创建优惠券批次参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class StocksCreateParams {
/**
* 批次名称
*/
private String stockName;
/**
* 仅配置商户可见,用于自定义信息
*/
private String comment;
/**
* 批次归属商户号
*/
private String belongMerchant;
/**
* 批次开始时间 rfc 3339 yyyy-mm-ddthh:mm:ss.sss+timezone
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime availableBeginTime;
/**
* 批次结束时间 rfc 3339 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime availableEndTime;
/**
* 可创建代金券的类型包含预充值和免充值两种类型。此字段用来标识制券 <strong>是否无资金流</strong>
* <p>
* ● 预充值代金券适用于第三方出资策划的活动例如满100减10. 指订单金额100元用户实付90元商户实收100元。设置为{@link Boolean#FALSE}
* <p>
* ● 免充值适用于商户策划的活动例如满100减10。 指订单金额100元用户实付90元用户领券后在支付中直接核销10元商户实收90元。设置为{@link Boolean#TRUE}
*/
private Boolean noCash;
/**
* 批次类型
*/
private String stockType = "NORMAL";
/**
* 商户单据号
*/
private String outRequestNo;
/**
* 扩展属性
*/
private String extInfo;
/**
* 批次使用规则
*/
private StockUseRule stockUseRule;
/**
* 核销规则
*/
private CouponUseRule couponUseRule;
/**
* 代金券样式
*/
private PatternInfo patternInfo;
}

View File

@@ -0,0 +1,65 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.StockStatus;
import lombok.Data;
import java.time.OffsetDateTime;
/**
* 查询参数,适用以下接口:
* <p>
* 条件查询批次列表API
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class StocksQueryParams {
/**
* 必填
* <p>
* 条件查询批次列表API 页码从0开始默认第0页传递1可能出错。
*/
private Integer offset = 0;
/**
* 必填
* <p>
* 条件查询批次列表API 分页大小最大10。
*/
private Integer limit = 10;
/**
* 选填
* <p>
* 起始时间 最终满足格式 {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
*/
private OffsetDateTime createStartTime;
/**
* 选填
* <p>
* 终止时间 最终满足格式 {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
*/
private OffsetDateTime createEndTime;
/**
* 根据API而定
* <p>
* 批次状态
*/
private StockStatus status;
}

View File

@@ -0,0 +1,61 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* 代金券发放接口请求参数
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class StocksSendParams {
/**
* 批次号 必须为代金券(全场券或单品券)批次号,不支持立减与折扣。
*/
private String stockId;
/**
* 用户openid 该openid需要与接口传入中的appid有对应关系。
*/
private String openid;
/**
* 商户单据号
*/
private String outRequestNo;
/**
* 公众账号ID
*/
private String appid;
/**
* 创建批次的商户号
*/
private String stockCreatorMchid;
/**
* 指定面额发券场景,券面额,其他场景不需要填,单位:分。
* 校验规则:仅在发券时指定面额及门槛的场景才生效,常规发券场景请勿传入该信息。
*/
private Long couponValue;
/**
* 指定面额发券批次门槛,其他场景不需要,单位:分。
* 校验规则:仅在发券时指定面额及门槛的场景才生效,常规发券场景请勿传入该信息。
*/
private Long couponMinimum;
}

View File

@@ -0,0 +1,45 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class StoreInfo {
/**
* 门店编号
*/
private String id;
/**
* 门店名称
*/
private String name;
/**
* 地区编码
*/
private String areaCode;
/**
* 详细地址
*/
private String address;
}

View File

@@ -0,0 +1,70 @@
/*
*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.TarType;
import cn.felord.payment.wechat.enumeration.TradeBillType;
import lombok.Data;
import java.time.LocalDate;
/**
* 申请交易账单请求参数
*
* @author felord.cn
* @since 1.0.3.RELEASE
*/
@Data
public class TradeBillParams {
/**
* 账单日期,必传。
* <p>
* 格式YYYY-MM-DD仅支持三个月内的账单下载申请。
*/
private LocalDate billDate;
/**
* 二级商户号,选填。
*
* <ol>
* <li>若商户是直连商户:无需填写该字段。</li>
* <li>若商户是服务商:
* <ul>
* <li>不填则默认返回服务商下的交易或退款数据。</li>
* <li>如需下载某个子商户下的交易或退款数据,则该字段必填。</li>
* </ul>
* </li>
* </ol>
* <p>
* 特殊规则最小字符长度为8
* <p>
* 注意:仅适用于电商平台 服务商
*/
private String subMchid;
/**
* 账单类型,不填则默认值为{@link TradeBillType#ALL}
*
* @see TradeBillType
*/
private TradeBillType billType;
/**
* 压缩类型,不填默认值为数据流
*
* @see TarType
*/
private TarType tarType;
}

View File

@@ -0,0 +1,149 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.TradeState;
import cn.felord.payment.wechat.enumeration.TradeType;
import lombok.Data;
import java.util.List;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class TransactionConsumeData {
/**
* The Amount.
*/
private Amount amount;
/**
* The Appid.
*/
private String appid;
/**
* The Attach.
*/
private String attach;
/**
* The Bank type.
*/
private String bankType;
/**
* The Mchid.
*/
private String mchid;
/**
* The Out trade no.
*/
private String outTradeNo;
/**
* The Payer.
*/
private Payer payer;
/**
* The Promotion detail.
*/
private List<PromotionDetail> promotionDetail;
/**
* The Scene info.
*/
private SceneInfo sceneInfo;
/**
* The Success time.
*/
private String successTime;
/**
* 在 1.0.0.RELEASE 直接返回了枚举字符串1.0.2.RELEASE 中变更为枚举
* @since 1.0.0.RELEASE
*/
private TradeState tradeState;
/**
* The Trade state desc.
*/
private String tradeStateDesc;
/**
* 在 1.0.0.RELEASE 直接返回了枚举字符串1.0.2.RELEASE 中变更为枚举
* @since 1.0.0.RELEASE
*/
private TradeType tradeType;
/**
* The Transaction id.
*/
private String transactionId;
/**
* The type Payer.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class Payer {
/**
* The Openid.
*/
private String openid;
}
/**
* The type Scene info.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class SceneInfo {
/**
* The Device id.
*/
private String deviceId;
}
/**
* The type Amount.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class Amount {
/**
* The Total.
*/
private Integer total;
/**
* The Payer total.
*/
private Integer payerTotal;
/**
* The Currency.
*/
private String currency;
/**
* The Payer currency.
*/
private String payerCurrency;
}
}

View File

@@ -0,0 +1,31 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class TransactionQueryParams {
private String mchId;
private String transactionIdOrOutTradeNo;
}

View File

@@ -0,0 +1,86 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model;
import lombok.Data;
/**
* The type User coupons query params.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class UserCouponsQueryParams {
/**
* 用户公众号服务号标识
*/
private String openId;
/**
* 公众服务号ID
*/
private String appId;
/**
* 批次号
*/
private String stockId;
/**
* 券状态 null 不生效
*/
private Status status;
/**
* 创建批次的商户号
*/
private String creatorMchId;
/**
* 批次发放商户号
*/
private String senderMchId;
/**
* 可用商户号
*/
private String availableMchId;
/**
* 分页页码
*/
private Integer offset = 0;
/**
* 分页大小
*/
private Integer limit = 20;
/**
* The enum Status.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
public enum Status {
/**
* Sended status.
*/
SENDED,
/**
* Used status.
*/
USED
}
}

View File

@@ -0,0 +1,46 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import lombok.Data;
/**
* 合单支付订单金额信息.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombineAmount {
/**
* 符合ISO 4217标准的三位字母代码必填人民币CNY 。
*
* @since 1.0.0.RELEASE
*/
private String currency = "CNY";
/**
* 子单金额,单位为分,必填
* <p>
* 境外场景下标价金额要超过商户结算币种的最小单位金额例如结算币种为美元则标价金额必须大于1美分
*
* @since 1.0.0.RELEASE
*/
private Long totalAmount;
}

View File

@@ -0,0 +1,74 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import lombok.Data;
import java.util.List;
/**
* 合单支付 关单参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombineCloseParams {
/**
* 合单商户appid必填
*/
private String combineAppid;
/**
* 合单商户订单号,必填,商户侧需要保证同一商户下唯一
*/
private String combineOutTradeNo;
/**
* 子单信息必填最多50单
*/
private List<ClosingSubOrder> subOrders;
/**
* 关单-子单信息最多50单.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class ClosingSubOrder {
/**
* 子单发起方商户号必填必须与发起方appid有绑定关系。
*/
private String mchid;
/**
* 子单商户订单号必填商户系统内部订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
*/
private String outTradeNo;
/**
* 二级商户商户号,由微信支付生成并下发。
* <p>
* 服务商子商户的商户号,被合单方。
* <p>
* 直连商户不用传二级商户号。
*/
private String subMchid;
}
}

View File

@@ -0,0 +1,77 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.OffsetDateTime;
import java.util.List;
/**
* 合单支付 H5支付参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombineH5PayParams {
/**
* 合单商户appid必填
*/
private String combineAppid;
/**
* 合单商户号,必填
*/
private String combineMchid;
/**
* 合单商户订单号,必填,商户侧需要保证同一商户下唯一
*/
private String combineOutTradeNo;
/**
* 合单支付者信息,选填
*/
private CombinePayerInfo combinePayerInfo;
/**
* 通知地址必填接收微信支付异步通知回调地址通知url必须为直接可访问的URL不能携带参数。
* <p>
* <strong>合单支付需要独立的通知地址。</strong>
*/
private String notifyUrl;
/**
* 合单支付场景信息描述,选填
*/
private CombineH5SceneInfo sceneInfo;
/**
* 子单信息必填最多50单
*/
private List<SubOrder> subOrders;
/**
* 交易起始时间,选填
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime timeStart;
/**
* 交易结束时间,选填
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime timeExpire;
}

View File

@@ -0,0 +1,37 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.combine;
import cn.felord.payment.wechat.v3.model.H5Info;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 合单支付 H5场景信息.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CombineH5SceneInfo extends CombineSceneInfo {
/**
* H5 支付信息.
*/
private H5Info h5Info;
}

View File

@@ -0,0 +1,77 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.OffsetDateTime;
import java.util.List;
/**
* 合单支付 APP支付、JSAPI支付、小程序支付、Native支付参数.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombinePayParams {
/**
* 合单商户appid必填
*/
private String combineAppid;
/**
* 合单商户号,必填
*/
private String combineMchid;
/**
* 合单商户订单号,必填,商户侧需要保证同一商户下唯一
*/
private String combineOutTradeNo;
/**
* 合单支付者信息,选填
*/
private CombinePayerInfo combinePayerInfo;
/**
* 通知地址必填接收微信支付异步通知回调地址通知url必须为直接可访问的URL不能携带参数。
* <p>
* <strong>合单支付需要独立的通知地址。</strong>
*/
private String notifyUrl;
/**
* 合单支付场景信息描述,选填
*/
private CombineSceneInfo sceneInfo;
/**
* 子单信息必填最多50单
*/
private List<SubOrder> subOrders;
/**
* 交易起始时间,选填
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime timeStart;
/**
* 交易结束时间,选填
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "GMT+8")
private OffsetDateTime timeExpire;
}

View File

@@ -0,0 +1,36 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import lombok.Data;
/**
* 合单支付,支付者信息.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombinePayerInfo {
/**
* 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
*/
private String openid;
}

View File

@@ -0,0 +1,39 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import lombok.Data;
/**
* 合单支付场景信息.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombineSceneInfo {
/**
* 商户设备号,选填。
*/
private String deviceId;
/**
* 用户终端ip,必填。
*/
private String payerClientIp;
}

View File

@@ -0,0 +1,156 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.combine;
import cn.felord.payment.wechat.enumeration.TradeState;
import cn.felord.payment.wechat.enumeration.TradeType;
import cn.felord.payment.wechat.v3.model.SceneInfo;
import lombok.Data;
import java.util.List;
/**
* 合单支付回调解密数据.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class CombineTransactionConsumeData {
/**
* 合单商户appid即合单发起方的appid
*/
private String combineAppid;
/**
* 合单商户号.
*/
private String combineMchid;
/**
* 合单商户订单号.
*/
private String combineOutTradeNo;
/**
* 支付者.
*/
private CombinePayerInfo combinePayerInfo;
/**
* 场景信息合单支付回调只返回device_id
*/
private SceneInfo sceneInfo;
/**
* 合单支付回调子订单.
*/
private List<SubOrderCallback> subOrders;
/**
* 合单支付回调子订单.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class SubOrderCallback {
/**
* 订单金额信息
*/
private CombineAmount amount;
/**
* 附加数据在查询API和支付通知中原样返回可作为自定义参数使用。
*/
private String attach;
/**
* 付款银行类型,参见<a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/terms_definition/chapter1_1_3.shtml#part-4">开户银行对照表</a>
*/
private String bankType;
/**
* 子单发起方商户号必须与发起方Appid有绑定关系。即电商平台mchid
*/
private String mchid;
/**
* 子单商户侧订单号
*/
private String outTradeNo;
/**
* 二级商户商户号,由微信支付生成并下发。
* 服务商子商户的商户号,被合单方。直连商户不用传二级商户号。
*/
private String subMchid;
/**
* 支付完成时间
*/
private String successTime;
/**
* 交易状态
*/
private TradeState tradeState;
/**
* 交易类型
*/
private TradeType tradeType;
/**
* 微信支付侧订单号
*/
private String transactionId;
}
/**
* 订单金额信息.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class CombineAmount {
/**
* 标价金额,单位为分.
*/
private Long totalAmount;
/**
* 标价币种.
*/
private String currency;
/**
* 现金支付金额.
*/
private Long payerAmount;
/**
* 现金支付币种.
*/
private String payerCurrency;
}
}

View File

@@ -0,0 +1,71 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.combine;
import cn.felord.payment.wechat.v3.model.SettleInfo;
import lombok.Data;
/**
* 子单信息最多50单.
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public class SubOrder {
/**
* 合单支付订单金额信息,必填。
*/
private CombineAmount amount;
/**
* 附加数据必填在查询API和支付通知中原样返回可作为自定义参数使用。
*/
private String attach;
/**
* 商品描述必填需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。
*/
private String description;
/**
* 子单发起方商户号必填必须与发起方appid有绑定关系。
*/
private String mchid;
/**
* 子单商户订单号必填商户系统内部订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
*/
private String outTradeNo;
/**
* 二级商户商户号,由微信支付生成并下发。
* <p>
* 服务商子商户的商户号,被合单方。
* <p>
* 直连商户不用传二级商户号。
*/
private String subMchid;
/**
* 结算信息,选填
*/
private SettleInfo settleInfo;
}

View File

@@ -0,0 +1,185 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import cn.felord.payment.wechat.enumeration.ContractStatus;
import cn.felord.payment.wechat.enumeration.CountType;
import lombok.Data;
import java.util.List;
/**
* 用户领取微信先享卡通知解密
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DiscountCardAcceptedConsumeData {
/**
* 应用appid需要绑定微信商户平台
*/
private String appid;
/**
* 先享卡ID唯一标识一个先享卡
*/
private String cardId;
/**
* 先享卡模板ID唯一定义此资源的标识。创建模板后可获得
*/
private String cardTemplateId;
/**
* 创建先享卡的时间
*/
private String createTime;
/**
* 商户号
*/
private String mchid;
/**
* 用户先享卡目标列表
*/
private List<Objective> objectives;
/**
* 用户标识,用户在{@code appid}下的唯一标识
*/
private String openid;
/**
* 商户领卡号商户在请求领卡预受理接口时传入的领卡请求号同一个商户号下必须唯一要求32个字符内只能是数字、大小写字母_-|*
*/
private String outCardCode;
/**
* 用户先享卡优惠列表
*/
private List<Reward> rewards;
/**
* 邀请者用户标识
* <p>
* 微信用户在商户对应appid下的唯一标识。
* 仅当此卡是通过“邀请有礼”渠道领卡时,会返回此字段;指此先享卡是通过此[邀请者]邀请领卡成功的。当此先享卡完成约定时,商户可给此[邀请者]下发应邀请有礼的奖励
*/
private String sharerOpenid;
/**
* 先享卡的守约状态
*/
private ContractStatus state;
/**
* 约定时间期限
*/
private TimeRange timeRange;
/**
* 用户先享卡目标列表
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class Objective {
/**
* 目标数量
* <p>
* 履约目标需要完成的数量必须大于0。
*/
private Long count;
/**
* 目标描述
*/
private String description;
/**
* 目标名称
*/
private String name;
/**
* 目标id
*/
private String objectiveId;
/**
* 目标单位
* <p>
* 示例值:次
*/
private String unit;
}
/**
* 用户先享卡优惠列表
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class Reward {
/**
* 优惠金额
* <p>
* 1、优惠金额此项优惠对应的优惠总金额单位必须大于0。
* 2、此项优惠已享累计金额≤创建模板时配置的此项奖励的奖励金额
* 例如优惠为【满10元减3元优惠券4张】时用户一次消费使用了2张优惠券优惠金额为本次优惠总金额6元优惠数量为本次使用优惠的优惠券数量2张
*/
private Long amount;
/**
* 优惠数量
*/
private Long count;
/**
* 优惠数量类型
*/
private CountType countType;
/**
* 优惠描述
*/
private String description;
/**
* 优惠名称
*/
private String name;
/**
* 优惠ID
*/
private String rewardId;
/**
* 优惠单位,例如 “个”
*/
private String unit;
}
/**
* 先享卡约定时间期限
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class TimeRange {
/**
* 开始时间
*/
private String betinTime;
/**
* 结束时间
*/
private String endTime;
}
}

View File

@@ -0,0 +1,194 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import cn.felord.payment.wechat.enumeration.ContractStatus;
import cn.felord.payment.wechat.enumeration.CountType;
import cn.felord.payment.wechat.enumeration.UnfinishedReason;
import lombok.Data;
import java.util.List;
/**
* 微信支付先享卡用户守约状态变化通知解密
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DiscountCardAgreementEndConsumeData {
/**
* 应用appid需要绑定微信商户平台
*/
private String appid;
/**
* 先享卡ID唯一标识一个先享卡
*/
private String cardId;
/**
* 先享卡模板ID唯一定义此资源的标识。创建模板后可获得
*/
private String cardTemplateId;
/**
* 创建先享卡的时间
*/
private String createTime;
/**
* 商户号
*/
private String mchid;
/**
* 用户先享卡目标列表
*/
private List<Objective> objectives;
/**
* 用户标识,用户在{@code appid}下的唯一标识
*/
private String openid;
/**
* 商户领卡号商户在请求领卡预受理接口时传入的领卡请求号同一个商户号下必须唯一要求32个字符内只能是数字、大小写字母_-|*
*/
private String outCardCode;
/**
* 用户先享卡优惠列表
*/
private List<Reward> rewards;
/**
* 先享卡的守约状态
*/
private ContractStatus state;
/**
* 先享卡约定时间期限
*/
private TimeRange timeRange;
/**
* 享受优惠总金额,单位为 “分”
*/
private Long totalAmount;
/**
* 未完成约定原因
*/
private UnfinishedReason unfinishedReason;
/**
* 目标列表属性
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class Objective {
/**
* 目标数量
* <p>
* 履约目标需要完成的数量必须大于0。
*/
private Long count;
/**
* 目标描述
*/
private String description;
/**
* 目标名称
*/
private String name;
/**
* 用户先享卡目标完成纪录
*/
private List<ObjectiveCompletionRecord> objectiveCompletionRecords;
/**
* 目标id
*/
private String objectiveId;
/**
* 目标单位
* <p>
* 示例值:次
*/
private String unit;
}
/**
* 先享卡约定时间期限
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class TimeRange {
/**
* 开始时间
*/
private String betinTime;
/**
* 结束时间
*/
private String endTime;
}
/**
* 优惠列表属性
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class Reward {
/**
* 优惠金额
* <p>
* 1、优惠金额此项优惠对应的优惠总金额单位必须大于0。
* 2、此项优惠已享累计金额≤创建模板时配置的此项奖励的奖励金额
* 例如优惠为【满10元减3元优惠券4张】时用户一次消费使用了2张优惠券优惠金额为本次优惠总金额6元优惠数量为本次使用优惠的优惠券数量2张
*/
private Long amount;
/**
* 优惠数量
*/
private Long count;
/**
* 优惠数量类型
*/
private CountType countType;
/**
* 优惠描述
*/
private String description;
/**
* 优惠名称
*/
private String name;
/**
* 优惠ID
*/
private String rewardId;
/**
* 优惠单位,例如 “个”
*/
private String unit;
/**
* 优惠使用记录列表
*/
private List<RewardUsageRecord> rewardUsageRecords;
}
}

View File

@@ -0,0 +1,44 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import lombok.Data;
import java.util.function.Consumer;
/**
* 先享卡回调消费复合消费器
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DiscountCardConsumer {
/**
* 用户领取微信先享卡通知解密
*/
private Consumer<DiscountCardAcceptedConsumeData> acceptedConsumeDataConsumer;
/**
* 微信支付先享卡用户守约状态变化通知解密
*/
private Consumer<DiscountCardAgreementEndConsumeData> agreementEndConsumeDataConsumer;
/**
* 先享卡扣费状态变化通知解密
*/
private Consumer<DiscountCardUserPaidConsumeData> cardUserPaidConsumeDataConsumer;
}

View File

@@ -0,0 +1,50 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import lombok.Data;
/**
* 预受理领卡请求参数
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DiscountCardPreRequestParams {
/**
* 商户领卡号,必传
*/
private String outCardCode;
/**
* 先享卡模板ID必传
*/
private String cardTemplateId;
/**
* APPID必传自动从配置中注入
*/
private String appid;
/**
* 通知商户URL必传
*/
private String notifyUrl;
}

View File

@@ -0,0 +1,114 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import cn.felord.payment.wechat.enumeration.ContractStatus;
import cn.felord.payment.wechat.enumeration.UnfinishedReason;
import lombok.Data;
/**
* 先享卡扣费状态变化通知解密
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DiscountCardUserPaidConsumeData {
/**
* 应用appid需要绑定微信商户平台
*/
private String appid;
/**
* 先享卡ID唯一标识一个先享卡
*/
private String cardId;
/**
* 先享卡模板ID唯一定义此资源的标识。创建模板后可获得
*/
private String cardTemplateId;
/**
* 商户号
*/
private String mchid;
/**
* 用户标识,用户在{@code appid}下的唯一标识
*/
private String openid;
/**
* 商户领卡号商户在请求领卡预受理接口时传入的领卡请求号同一个商户号下必须唯一要求32个字符内只能是数字、大小写字母_-|*
*/
private String outCardCode;
/**
* 先享卡的守约状态
*/
private ContractStatus state;
/**
* 享受优惠总金额,单位为 “分”
*/
private Long totalAmount;
/**
* 未完成约定原因
*/
private UnfinishedReason unfinishedReason;
/**
* 用户退回优惠的付款信息
*/
private PayInformation payInformation;
/**
* 用户退回优惠的付款信息
* <p>
* 当状态为{@link ContractStatus#UNFINISHED}(用户未完成约定)时,且需要退回已享受的优惠金额时,返回此字段;
*/
@Data
public static class PayInformation {
/**
* 付款金额,用户需要退回优惠而付款的金额,单位为:分;
*/
private Long payAmount;
/**
* 用户付款状态,
*/
private PayState payState;
/**
* 付款时间
*/
private String payTime;
/**
* 微信支付订单号,仅在订单成功收款时才返回
*/
private String transactionId;
}
/**
* 付款状态
* @since 1.0.3.RELEASE
*/
public enum PayState {
/**
* 付款中
*/
PAYING,
/**
* 已付款
*/
PAID
}
}

View File

@@ -0,0 +1,61 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import cn.felord.payment.wechat.enumeration.StrategyType;
import lombok.Data;
/**
* 微信先享卡目标完成纪录
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class ObjectiveCompletionRecord {
/**
* 目标完成数量
*/
private Long completionCount;
/**
* 目标完成时间
*/
private String completionTime;
/**
* 目标完成类型
*/
private StrategyType completionType;
/**
* 目标完成描述
*/
private String description;
/**
* 目标完成流水号
*/
private String objectiveCompletionSerialNo;
/**
* 目标id
*/
private String objectiveId;
/**
* 备注说明
*/
private String remark;
}

View File

@@ -0,0 +1,70 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import cn.felord.payment.wechat.enumeration.StrategyType;
import lombok.Data;
/**
* 优惠使用纪录列表对象
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class RewardUsageRecord {
/**
* 优惠金额
*
* <ol>
* <li>优惠金额用户此项本次享受的优惠对应的优惠总金额单位必须大于0。</li>
* <li>子优惠已享金额累计≤创建模板时配置的此子优惠的价值金额 例如优惠为【满10元减3元优惠券4张】时用户一次消费使用了2张优惠券优惠金额为本次优惠总金额6元优惠数量为本次使用优惠的优惠券数量2张</li>
* </ol>
*/
private Long amount;
/**
* 优惠使用描述
*/
private String description;
/**
* 备注说明
*/
private String remark;
/**
* 优惠Id
*/
private String rewardId;
/**
* 优惠使用纪录流水号
*/
private String rewardUsageSerialNo;
/**
* 优惠使用数量
*/
private Long usageCount;
/**
* 优惠使用时间
*/
private String usageTime;
/**
* 优惠使用类型
*/
private StrategyType usageType;
}

View File

@@ -0,0 +1,49 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.discountcard;
import lombok.Data;
import java.util.List;
/**
* 增加用户记录请求参数
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class UserRecordsParams {
/**
* 商户领卡号商户在请求领卡预受理接口时传入的领卡请求号同一个商户号下必须唯一要求32个字符内只能是数字、大小写字母_-|*
*/
private String outCardCode;
/**
* 先享卡模板ID唯一定义此资源的标识。创建模板后可获得
*/
private String cardTemplateId;
/**
* 微信先享卡目标完成纪录
*/
private List<ObjectiveCompletionRecord> objectiveCompletionRecords;
/**
* 优惠使用纪录
*/
private List<RewardUsageRecord> rewardUsageRecords;
}

View File

@@ -0,0 +1,47 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
/**
* 取消支付分订单请求参数
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class CancelServiceOrderParams {
/**
* 商户服务订单号,必填
*/
private String outOrderNo;
/**
* 与传入的商户号建立了支付绑定关系的appid必填
*/
private String appid;
/**
* 服务ID必填
*/
private String serviceId;
/**
* 取消原因最长50个字符必填
*/
private String reason;
}

View File

@@ -0,0 +1,111 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.List;
/**
* 完结支付分订单请求参数
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class CompleteServiceOrderParams {
/**
* 商户服务订单号,必填
* <p>
* 商户系统内部服务订单号不是交易单号要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
*/
private String outOrderNo;
/**
* 与传入的商户号建立了支付绑定关系的appid必填
*/
private String appid;
/**
* 服务ID必填
* <p>
* 该服务ID有本接口对应产品的权限。
*/
private String serviceId;
/**
* 后付费项目,必填
*/
private List<PostPayment> postPayments;
/**
* 后付费商户优惠,选填
*/
private List<PostDiscount> postDiscounts;
/**
* 总金额,单位分,必填
* <p>
* 不能超过完结订单时候的总金额,只能为整数,详见 <a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_2">支付金额</a>。此参数需满足:总金额 =修改后付费项目1…+修改后完结付费项目n-(修改 后付费商户优惠项目1…+修改后付费商户优惠项目n
*/
private Long totalAmount;
/**
* 服务时间段,条件选填
* <p>
* 服务时间范围,创建订单未填写服务结束时间,则完结的时候,服务结束时间必填
* 如果传入,用户侧则显示此参数。
*/
private TimeRange timeRange;
/**
* 服务位置,选填
*/
private CompleteLocation location;
/**
* 微信支付服务分账标记,选填
* <p>
* 完结订单分账接口标记。分账开通流程,详见 <a target = "_blank" href = "https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=26_2">分账</a>
* false不分账默认false
* true分账。
*/
private Boolean profitSharing = Boolean.TRUE;
/**
* 订单优惠标记,选填
* <p>
* 代金券或立减金优惠的参数,说明详见代金券或立减金优惠
*/
private String goodsTag;
/**
* 服务位置信息
* <p>
* 如果传入,用户侧则显示此参数。
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public static class CompleteLocation {
/**
* 预计服务结束地点,条件选填。
* <p>
* 结束使用服务的地点不超过50个字符超出报错处理 。 创建订单传入了【服务开始地点】,此项才能填写
* 【建议】
* 1、预计结束地点为空时实际结束地点与开始地点相同不填写
* 2、预计结束地点不为空时实际结束地点与预计结束地点相同不填写
*/
private String endLocation;
}
}

View File

@@ -0,0 +1,89 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.List;
/**
* 创单结单合并API请求参数
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class DirectCompleteServiceOrderParams {
/**
* The Appid.
*/
private String appid;
/**
* The Attach.
*/
private String attach;
/**
* The Goods tag.
*/
private String goodsTag;
/**
* The Location.
*/
private Location location;
/**
* The Notify url.
*/
private String notifyUrl;
/**
* The Openid.
*/
private String openid;
/**
* The Out order no.
*/
private String outOrderNo;
/**
* The Post discounts.
*/
private List<PostDiscount> postDiscounts;
/**
* The Post payments.
*/
private List<PostPayment> postPayments;
/**
* The Profit sharing.
*/
private Boolean profitSharing;
/**
* The Service id.
*/
private String serviceId;
/**
* The Service introduction.
*/
private String serviceIntroduction;
/**
* The Time range.
*/
private TimeRange timeRange;
/**
* The Total amount.
*/
private Long totalAmount;
}

View File

@@ -0,0 +1,51 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
/**
* 服务位置信息
* <p>
* 如果传入,用户侧则显示此参数。
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class Location {
/**
* 服务开始地点,选填。
* <p>
* 开始使用服务的地点不超过50个字符超出报错处理。
* 【建议】
* 1、用户下单时【未确定】服务结束地点不填写。
* 2、服务在同一地点开始和结束不填写。
* 3、用户下单时【已确定】服务结束地点填写。
*/
private String startLocation;
/**
* 预计服务结束地点,有开始地点时为必填。
*
* 1、结束使用服务的地点不超过50个字符超出报错处理 。
* 2、填写了服务开始地点才能填写服务结束地点。
*/
private String endLocation;
}

View File

@@ -0,0 +1,70 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.List;
/**
* 修改微信支付分订单金额请求参数.
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class ModifyServiceOrderParams {
/**
* 商户服务订单号,必填
* <p>
* 商户系统内部服务订单号不是交易单号要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
*/
private String outOrderNo;
/**
* 与传入的商户号建立了支付绑定关系的appid必填
*/
private String appid;
/**
* 服务ID必填
* <p>
* 该服务ID有本接口对应产品的权限。需要与创建订单时保持一致。
*/
private String serviceId;
/**
* 后付费项目,必填
*/
private List<PostPayment> postPayments;
/**
* 后付费商户优惠,选填
*/
private List<PostDiscount> postDiscounts;
/**
* 总金额,单位分,必填
*
* 不能超过完结订单时候的总金额,只能为整数,详见 <a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_2">支付金额</a>。此参数需满足:总金额 =修改后付费项目1…+修改后完结付费项目n-(修改 后付费商户优惠项目1…+修改后付费商户优惠项目n
*/
private Long totalAmount;
/**
* 取消原因最长50个字符必填
*/
private String reason;
}

View File

@@ -0,0 +1,40 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.function.Consumer;
/**
* 支付分回调复合消费器
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class PayScoreConsumer {
/**
* 用户确认回调消费接口
*/
private Consumer<PayScoreUserConfirmConsumeData> confirmConsumeDataConsumer;
/**
* 用户支付回调消费接口
*/
private Consumer<PayScoreUserPaidConsumeData> paidConsumeDataConsumer;
}

View File

@@ -0,0 +1,98 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.List;
/**
* 微信支付分用户确认订单回调解密.
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class PayScoreUserConfirmConsumeData {
/**
* The Appid.
*/
private String appid;
/**
* The Attach.
*/
private String attach;
/**
* The Location.
*/
private Location location;
/**
* The Mchid.
*/
private String mchid;
/**
* The Openid.
*/
private String openid;
/**
* The Order id.
*/
private String orderId;
/**
* The Out order no.
*/
private String outOrderNo;
/**
* The Post discounts.
*/
private List<PostDiscount> postDiscounts;
/**
* The Post payments.
*/
private List<PostPayment> postPayments;
/**
* The Risk fund.
*/
private RiskFund riskFund;
/**
* The Service id.
*/
private String serviceId;
/**
* The Service introduction.
*/
private String serviceIntroduction;
/**
* The State.
*/
private String state;
/**
* The State description.
*/
private String stateDescription;
/**
* The Time range.
*/
private TimeRange timeRange;
/**
* The Total amount.
*/
private String totalAmount;
}

View File

@@ -0,0 +1,105 @@
/*
*
* Copyright 2019-2020 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cn.felord.payment.wechat.v3.model.payscore;
import lombok.Data;
import java.util.List;
/**
* 微信支付分支付成功回调解密
*
* @author felord.cn
* @since 1.0.2.RELEASE
*/
@Data
public class PayScoreUserPaidConsumeData {
/**
* The Appid.
*/
private String appid;
/**
* The Attach.
*/
private String attach;
/**
* The Collection.
*/
private PaymentCollection collection;
/**
* The Location.
*/
private Location location;
/**
* The Mchid.
*/
private String mchid;
/**
* The Need collection.
*/
private Boolean needCollection;
/**
* The Notify url.
*/
private String notifyUrl;
/**
* The Openid.
*/
private String openid;
/**
* The Order id.
*/
private String orderId;
/**
* The Out order no.
*/
private String outOrderNo;
/**
* The Post discounts.
*/
private List<PostDiscount> postDiscounts;
/**
* The Post payments.
*/
private List<PostPayment> postPayments;
/**
* The Risk fund.
*/
private RiskFund riskFund;
/**
* The Service id.
*/
private String serviceId;
/**
* The Service introduction.
*/
private String serviceIntroduction;
/**
* The State.
*/
private String state;
/**
* The Time range.
*/
private TimeRange timeRange;
/**
* The Total amount.
*/
private String totalAmount;
}

Some files were not shown because too many files have changed in this diff Show More