Commit de3de82d authored by dingzhiwei's avatar dingzhiwei
Browse files

初始化Jeepay项目

parent 40dcaf4a
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.bootstrap;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.jeequan.jeepay.pay.config.SystemYmlConfig;
import org.hibernate.validator.HibernateValidator;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Arrays;
/**
* @Author terrfly
* @Date 2019/11/7 15:19
* @Description spring-boot 主启动程序
**/
@SpringBootApplication
@EnableScheduling
@MapperScan("com.jeequan.jeepay.service.mapper") //Mybatis mapper接口路径
@ComponentScan(basePackages = "com.jeequan.jeepay.*") //由于MainApplication没有在项目根目录, 需要配置basePackages属性使得成功扫描所有Spring组件;
@Configuration
public class JeepayPayApplication {
@Autowired private SystemYmlConfig systemYmlConfig;
/** main启动函数 **/
public static void main(String[] args) {
//启动项目
SpringApplication.run(JeepayPayApplication.class, args);
}
/** fastJson 配置信息 **/
@Bean
public HttpMessageConverters fastJsonConfig(){
//新建fast-json转换器
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//fast-json 配置信息
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
converter.setFastJsonConfig(config);
//设置响应的 Content-Type
converter.setSupportedMediaTypes(Arrays.asList(new MediaType[]{MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8}));
return new HttpMessageConverters(converter);
}
/** Mybatis plus 分页插件 **/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
return paginationInterceptor;
}
/** 默认为 失败快速返回模式 **/
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
return validatorFactory.getValidator();
}
/** 允许跨域请求 **/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if(systemYmlConfig.getAllowCors()){
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); //带上cookie信息
// config.addAllowedOrigin(CorsConfiguration.ALL); //允许跨域的域名, *表示允许任何域名使用
config.addAllowedOriginPattern(CorsConfiguration.ALL); //使用addAllowedOriginPattern 避免出现 When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
config.addAllowedHeader(CorsConfiguration.ALL); //允许任何请求头
config.addAllowedMethod(CorsConfiguration.ALL); //允许任何方法(post、get等)
source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
}
return new CorsFilter(source);
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.beans.RequestKitBean;
import com.jeequan.jeepay.pay.util.ChannelCertConfigKitBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
/*
* 实现回调接口抽象类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:18
*/
public abstract class AbstractChannelNoticeService implements IChannelNoticeService {
@Autowired private RequestKitBean requestKitBean;
@Autowired private ChannelCertConfigKitBean channelCertConfigKitBean;
@Override
public ResponseEntity doNotifyOrderNotExists(HttpServletRequest request) {
return textResp("order not exists");
}
@Override
public ResponseEntity doNotifyOrderStateUpdateFail(HttpServletRequest request) {
return textResp("update status error");
}
/** 文本类型的响应数据 **/
protected ResponseEntity textResp(String text){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity(text, httpHeaders, HttpStatus.OK);
}
/** json类型的响应数据 **/
protected ResponseEntity jsonResp(Object body){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity(body, httpHeaders, HttpStatus.OK);
}
/**request.getParameter 获取参数 并转换为JSON格式 **/
protected JSONObject getReqParamJSON() {
return requestKitBean.getReqParamJSON();
}
/** 获取文件路径 **/
protected String getCertFilePath(String certFilePath) {
return channelCertConfigKitBean.getCertFilePath(certFilePath);
}
/** 获取文件File对象 **/
protected File getCertFile(String certFilePath) {
return channelCertConfigKitBean.getCertFile(certFilePath);
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.jeequan.jeepay.pay.util.ChannelCertConfigKitBean;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
/*
* 支付接口抽象类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:18
*/
public abstract class AbstractPaymentService implements IPaymentService{
@Autowired protected SysConfigService sysConfigService;
@Autowired protected ChannelCertConfigKitBean channelCertConfigKitBean;
protected String getNotifyUrl(){
return sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/pay/notify/" + getIfCode();
}
protected String getNotifyUrl(String payOrderId){
return sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/pay/notify/" + getIfCode() + "/" + payOrderId;
}
protected String getReturnUrl(){
return sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/pay/return/" + getIfCode();
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
/*
* 渠道侧的通知解析实现 【分为同步跳转(doReturn)和异步回调(doNotify) 】
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 15:14
*/
public interface IChannelNoticeService {
/** 通知类型 **/
enum NoticeTypeEnum {
DO_RETURN, //同步跳转
DO_NOTIFY //异步回调
}
/* 获取到接口code **/
String getIfCode();
/** 解析参数: 订单号 和 请求参数
* 异常需要自行捕捉,并返回null , 表示已响应数据。
* **/
MutablePair<String, Object> parseParams(HttpServletRequest request, String urlOrderId, NoticeTypeEnum noticeTypeEnum);
/** 返回需要更新的订单状态 和响应数据 **/
ChannelRetMsg doNotice(HttpServletRequest request,
Object params, PayOrder payOrder, MchConfigContext mchConfigContext, NoticeTypeEnum noticeTypeEnum);
/** 数据库订单 状态更新异常 (仅异步通知使用) **/
ResponseEntity doNotifyOrderStateUpdateFail(HttpServletRequest request);
/** 数据库订单数据不存在 (仅异步通知使用) **/
ResponseEntity doNotifyOrderNotExists(HttpServletRequest request);
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.pay.model.MchConfigContext;
/*
* @Description: 301方式获取渠道侧用户ID, 如微信openId 支付宝的userId等
* @author terrfly
* @date 2021/5/2 15:10
*/
public interface IChannelUserService {
/* 获取到接口code **/
String getIfCode();
/** 获取重定向地址 **/
String buildUserRedirectUrl(String callbackUrlEncode, MchConfigContext mchConfigContext);
/** 获取渠道用户ID **/
String getChannelUserId(JSONObject reqParams, MchConfigContext mchConfigContext);
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.model.MchConfigContext;
/*
* 查单(渠道侧)接口定义
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/19 15:16
*/
public interface IPayOrderQueryService {
/* 获取到接口code **/
String getIfCode();
/** 查询订单 **/
ChannelRetMsg query(PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception;
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.model.MchConfigContext;
/*
* 调起上游渠道侧支付接口
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 15:13
*/
public interface IPaymentService {
/* 获取到接口code **/
String getIfCode();
/** 是否支持该支付方式 */
boolean isSupport(String wayCode);
/** 前置检查如参数等信息是否符合要求, 返回错误信息或直接抛出异常即可 */
String preCheck(UnifiedOrderRQ bizRQ, PayOrder payOrder);
/** 调起支付接口,并响应数据; 内部处理普通商户和服务商模式 **/
AbstractRS pay(UnifiedOrderRQ bizRQ, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception;
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.internal.util.AlipaySignature;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.model.params.alipay.AlipayNormalMchParams;
import com.jeequan.jeepay.pay.channel.AbstractChannelNoticeService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/*
* 支付宝 回调接口实现类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:20
*/
@Service
@Slf4j
public class AlipayChannelNoticeService extends AbstractChannelNoticeService {
@Override
public String getIfCode() {
return CS.IF_CODE.ALIPAY;
}
@Override
public MutablePair<String, Object> parseParams(HttpServletRequest request, String urlOrderId, NoticeTypeEnum noticeTypeEnum) {
try {
JSONObject params = getReqParamJSON();
String payOrderId = params.getString("out_trade_no");
return MutablePair.of(payOrderId, params);
} catch (Exception e) {
log.error("error", e);
throw ResponseException.buildText("ERROR");
}
}
@Override
public ChannelRetMsg doNotice(HttpServletRequest request, Object params, PayOrder payOrder, MchConfigContext mchConfigContext, NoticeTypeEnum noticeTypeEnum) {
try {
//配置参数获取
Byte useCert = null;
String alipaySignType, alipayPublicCert, alipayPublicKey = null;
if(mchConfigContext.isIsvsubMch()){
// 获取支付参数
AlipayIsvParams alipayParams = mchConfigContext.getIsvConfigContext().getIsvParamsByIfCode(getIfCode(), AlipayIsvParams.class);
useCert = alipayParams.getUseCert();
alipaySignType = alipayParams.getSignType();
alipayPublicCert = alipayParams.getAlipayPublicCert();
alipayPublicKey = alipayParams.getAlipayPublicKey();
}else{
// 获取支付参数
AlipayNormalMchParams alipayParams = mchConfigContext.getNormalMchParamsByIfCode(getIfCode(), AlipayNormalMchParams.class);
useCert = alipayParams.getUseCert();
alipaySignType = alipayParams.getSignType();
alipayPublicCert = alipayParams.getAlipayPublicCert();
alipayPublicKey = alipayParams.getAlipayPublicKey();
}
// 获取请求参数
JSONObject jsonParams = (JSONObject) params;
boolean verifyResult;
if(useCert != null && useCert == CS.YES){ //证书方式
verifyResult = AlipaySignature.rsaCertCheckV1(jsonParams.toJavaObject(Map.class), getCertFilePath(alipayPublicCert),
AlipayConfig.CHARSET, alipaySignType);
}else{
verifyResult = AlipaySignature.rsaCheckV1(jsonParams.toJavaObject(Map.class), alipayPublicKey, AlipayConfig.CHARSET, alipaySignType);
}
//验签失败
if(!verifyResult){
throw ResponseException.buildText("ERROR");
}
//验签成功后判断上游订单状态
ResponseEntity okResponse = textResp("SUCCESS");
ChannelRetMsg result = new ChannelRetMsg();
result.setChannelOrderId(jsonParams.getString("trade_no")); //渠道订单号
result.setChannelUserId(jsonParams.getString("buyer_id")); //支付用户ID
result.setResponseEntity(okResponse); //响应数据
result.setChannelState(ChannelRetMsg.ChannelState.WAITING); // 默认支付中
if("TRADE_SUCCESS".equals(jsonParams.getString("trade_status"))){
result.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if("TRADE_CLOSED".equals(jsonParams.getString("trade_status"))){
result.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
}
return result;
} catch (Exception e) {
log.error("error", e);
throw ResponseException.buildText("ERROR");
}
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.model.params.alipay.AlipayNormalMchParams;
import com.jeequan.jeepay.pay.channel.IChannelUserService;
import com.jeequan.jeepay.pay.exception.ChannelException;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/*
* 支付宝: 获取用户ID实现类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:21
*/
@Service
@Slf4j
public class AlipayChannelUserService implements IChannelUserService {
@Override
public String getIfCode() {
return CS.IF_CODE.ALIPAY;
}
@Override
public String buildUserRedirectUrl(String callbackUrlEncode, MchConfigContext mchConfigContext) {
String oauthUrl = AlipayConfig.PROD_OAUTH_URL;
String appId = null;
if(mchConfigContext.isIsvsubMch()){
AlipayIsvParams isvParams = mchConfigContext.getIsvConfigContext().getIsvParamsByIfCode(getIfCode(), AlipayIsvParams.class);
appId = isvParams.getAppId();
}else{
//获取商户配置信息
AlipayNormalMchParams normalMchParams = mchConfigContext.getNormalMchParamsByIfCode(getIfCode(), AlipayNormalMchParams.class);
appId = normalMchParams.getAppId();
if(normalMchParams.getSandbox() != null && normalMchParams.getSandbox() == CS.YES){
oauthUrl = AlipayConfig.SANDBOX_OAUTH_URL;
}
}
return String.format(oauthUrl, appId, callbackUrlEncode);
}
@Override
public String getChannelUserId(JSONObject reqParams, MchConfigContext mchConfigContext) {
String authCode = reqParams.getString("auth_code");
//通过code 换取openId
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setCode(authCode); request.setGrantType("authorization_code");
try {
return mchConfigContext.getAlipayClientWrapper().execute(request).getUserId();
} catch (ChannelException e) {
e.printStackTrace();
return null;
}
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay;
import com.alipay.api.AlipayObject;
import com.alipay.api.AlipayRequest;
import com.alipay.api.domain.*;
import com.alipay.api.request.*;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvsubMchParams;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import org.apache.commons.lang3.StringUtils;
/*
* 【支付宝】支付通道工具包
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:19
*/
public class AlipayKit {
/** 放置 isv特殊信息 **/
public static void putApiIsvInfo(MchConfigContext mchConfigContext, AlipayRequest req, AlipayObject model){
//不是特约商户, 无需放置此值
if(!mchConfigContext.isIsvsubMch()){
return ;
}
// 获取支付参数
AlipayIsvParams isvParams = mchConfigContext.getIsvConfigContext().getIsvParamsByIfCode(CS.IF_CODE.ALIPAY, AlipayIsvParams.class);
AlipayIsvsubMchParams isvsubMchParams = mchConfigContext.getIsvsubMchParamsByIfCode(CS.IF_CODE.ALIPAY, AlipayIsvsubMchParams.class);
// 子商户信息
if(req instanceof AlipayTradePayRequest) ((AlipayTradePayRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradeAppPayRequest) ((AlipayTradeAppPayRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradeCreateRequest) ((AlipayTradeCreateRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradePagePayRequest) ((AlipayTradePagePayRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradePrecreateRequest) ((AlipayTradePrecreateRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradeWapPayRequest) ((AlipayTradeWapPayRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
else if(req instanceof AlipayTradeQueryRequest) ((AlipayTradeQueryRequest)req).putOtherTextParam("app_auth_token", isvsubMchParams.getAppAuthToken());
// 服务商信息
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId(isvParams.getPid());
if(model instanceof AlipayTradePayModel) ((AlipayTradePayModel)model).setExtendParams(extendParams);
else if(model instanceof AlipayTradeAppPayModel) ((AlipayTradeAppPayModel)model).setExtendParams(extendParams);
else if(model instanceof AlipayTradeCreateModel) ((AlipayTradeCreateModel)model).setExtendParams(extendParams);
else if(model instanceof AlipayTradePagePayModel) ((AlipayTradePagePayModel)model).setExtendParams(extendParams);
else if(model instanceof AlipayTradePrecreateModel) ((AlipayTradePrecreateModel)model).setExtendParams(extendParams);
else if(model instanceof AlipayTradeWapPayModel) ((AlipayTradeWapPayModel)model).setExtendParams(extendParams);
}
public static String appendErrCode(String code, String subCode){
return StringUtils.defaultIfEmpty(subCode, code); //优先: subCode
}
public static String appendErrMsg(String msg, String subMsg){
if(StringUtils.isNotEmpty(msg) && StringUtils.isNotEmpty(subMsg) ){
return msg + "【" + subMsg + "】";
}
return StringUtils.defaultIfEmpty(subMsg, msg);
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.channel.IPayOrderQueryService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import org.springframework.stereotype.Service;
/*
* 支付宝 查单接口实现类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:20
*/
@Service
public class AlipayPayOrderQueryService implements IPayOrderQueryService {
@Override
public String getIfCode() {
return CS.IF_CODE.ALIPAY;
}
@Override
public ChannelRetMsg query(PayOrder payOrder, MchConfigContext mchConfigContext){
AlipayTradeQueryRequest req = new AlipayTradeQueryRequest();
// 商户订单号,商户网站订单系统中唯一订单号,必填
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(payOrder.getPayOrderId());
req.setBizModel(model);
//通用字段
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
AlipayTradeQueryResponse resp = mchConfigContext.getAlipayClientWrapper().execute(req);
String result = resp.getTradeStatus();
if("TRADE_SUCCESS".equals(result)) {
return ChannelRetMsg.confirmSuccess(resp.getTradeNo()); //支付成功
}else if("WAIT_BUYER_PAY".equals(result)) {
return ChannelRetMsg.waiting(); //支付中
}
return ChannelRetMsg.waiting(); //支付中
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.channel.AbstractPaymentService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.util.PaywayUtil;
import org.springframework.stereotype.Service;
/*
* 支付接口: 支付宝官方
* 支付方式: 自适应
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:19
*/
@Service
public class AlipayPaymentService extends AbstractPaymentService {
@Override
public String getIfCode() {
return CS.IF_CODE.ALIPAY;
}
@Override
public boolean isSupport(String wayCode) {
return true;
}
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return PaywayUtil.getRealPaywayService(this, payOrder.getWayCode()).preCheck(rq, payOrder);
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception {
return PaywayUtil.getRealPaywayService(this, payOrder.getWayCode()).pay(rq, payOrder, mchConfigContext);
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.exception.ChannelException;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliAppOrderRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import org.springframework.stereotype.Service;
/*
* 支付宝 APP支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:20
*/
@Service("alipayPaymentByAliAppService") //Service Name需保持全局唯一性
public class AliApp extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext){
AlipayTradeAppPayRequest req = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
String payData = null;
// sdk方式需自行拦截接口异常信息
try {
payData = mchConfigContext.getAlipayClientWrapper().getAlipayClient().sdkExecute(req).getBody();
} catch (AlipayApiException e) {
throw ChannelException.sysError(e.getMessage());
}
// 构造函数响应数据
AliAppOrderRS res = ApiResBuilder.buildSuccess(AliAppOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelAttach(payData);
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
res.setPayData(payData);
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.domain.AlipayTradePayModel;
import com.alipay.api.request.AlipayTradePayRequest;
import com.alipay.api.response.AlipayTradePayResponse;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliBarOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliBarOrderRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/*
* 支付宝 条码支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:20
*/
@Service("alipayPaymentByAliBarService") //Service Name需保持全局唯一性
public class AliBar extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
AliBarOrderRQ bizRQ = (AliBarOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getAuthCode())){
throw new BizException("用户支付条码[authCode]不可为空");
}
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext){
AliBarOrderRQ bizRQ = (AliBarOrderRQ) rq;
AlipayTradePayRequest req = new AlipayTradePayRequest();
AlipayTradePayModel model = new AlipayTradePayModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setScene("bar_code"); //条码支付 bar_code ; 声波支付 wave_code
model.setAuthCode(bizRQ.getAuthCode()); //支付授权码
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
//调起支付宝 (如果异常, 将直接跑出 ChannelException )
AlipayTradePayResponse alipayResp = mchConfigContext.getAlipayClientWrapper().execute(req);
// 构造函数响应数据
AliBarOrderRS res = ApiResBuilder.buildSuccess(AliBarOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelAttach(alipayResp.getBody());
channelRetMsg.setChannelOrderId(alipayResp.getTradeNo());
// ↓↓↓↓↓↓ 调起接口成功后业务判断务必谨慎!! 避免因代码编写bug,导致不能正确返回订单状态信息 ↓↓↓↓↓↓
//当条码重复发起时,支付宝返回的code = 10003, subCode = null [等待用户支付], 此时需要特殊判断 = = 。
if("10000".equals(alipayResp.getCode()) && alipayResp.isSuccess()){ //支付成功, 更新订单成功 || 等待支付宝的异步回调接口
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if("10003".equals(alipayResp.getCode())){ //10003 表示为 处理中, 例如等待用户输入密码
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}else{ //其他状态, 表示下单失败
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(alipayResp.getCode(), alipayResp.getSubCode()));
channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(alipayResp.getMsg(), alipayResp.getSubMsg()));
}
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliJsapiOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliJsapiOrderRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/*
* 支付宝 jsapi支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:20
*/
@Service("alipayPaymentByJsapiService") //Service Name需保持全局唯一性
public class AliJsapi extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
AliJsapiOrderRQ bizRQ = (AliJsapiOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getBuyerUserId())){
throw new BizException("[buyerUserId]不可为空");
}
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception{
AliJsapiOrderRQ bizRQ = (AliJsapiOrderRQ) rq;
AlipayTradeCreateRequest req = new AlipayTradeCreateRequest();
AlipayTradeCreateModel model = new AlipayTradeCreateModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
model.setBuyerId(bizRQ.getBuyerUserId());
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
//调起支付宝 (如果异常, 将直接跑出 ChannelException )
AlipayTradeCreateResponse alipayResp = mchConfigContext.getAlipayClientWrapper().execute(req);
// 构造函数响应数据
AliJsapiOrderRS res = ApiResBuilder.buildSuccess(AliJsapiOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelAttach(alipayResp.getBody());
// ↓↓↓↓↓↓ 调起接口成功后业务判断务必谨慎!! 避免因代码编写bug,导致不能正确返回订单状态信息 ↓↓↓↓↓↓
res.setAlipayTradeNo(alipayResp.getTradeNo());
channelRetMsg.setChannelOrderId(alipayResp.getTradeNo());
if(alipayResp.isSuccess()){ //业务处理成功
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}else{
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(alipayResp.getCode(), alipayResp.getSubCode()));
channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(alipayResp.getMsg(), alipayResp.getSubMsg()));
}
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.exception.ChannelException;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliPcOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliPcOrderRS;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import org.springframework.stereotype.Service;
/*
* 支付宝 PC支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:21
*/
@Service("alipayPaymentByAliPcService") //Service Name需保持全局唯一性
public class AliPc extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext){
AliPcOrderRQ bizRQ = (AliPcOrderRQ) rq;
AlipayTradePagePayRequest req = new AlipayTradePagePayRequest();
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
model.setProductCode("FAST_INSTANT_TRADE_PAY");
model.setQrPayMode("2"); //订单码-跳转模式
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setReturnUrl(getReturnUrl()); // 同步跳转地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
// 构造函数响应数据
AliPcOrderRS res = ApiResBuilder.buildSuccess(AliPcOrderRS.class);
try {
if(CS.PAY_DATA_TYPE.FORM.equals(bizRQ.getPayDataType())){
res.setFormContent(mchConfigContext.getAlipayClientWrapper().getAlipayClient().pageExecute(req).getBody());
}else{
res.setPayUrl(mchConfigContext.getAlipayClientWrapper().getAlipayClient().pageExecute(req, "GET").getBody());
}
}catch (AlipayApiException e) {
throw ChannelException.sysError(e.getMessage());
}
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliQrOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliQrOrderRS;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import org.springframework.stereotype.Service;
/*
* 支付宝 QR支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:21
*/
@Service("alipayPaymentByAliQrService") //Service Name需保持全局唯一性
public class AliQr extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext){
AliQrOrderRQ aliQrOrderRQ = (AliQrOrderRQ)rq;
AlipayTradePrecreateRequest req = new AlipayTradePrecreateRequest();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
//调起支付宝 (如果异常, 将直接跑出 ChannelException )
AlipayTradePrecreateResponse alipayResp = mchConfigContext.getAlipayClientWrapper().execute(req);
// 构造函数响应数据
AliQrOrderRS res = ApiResBuilder.buildSuccess(AliQrOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelAttach(alipayResp.getBody());
// ↓↓↓↓↓↓ 调起接口成功后业务判断务必谨慎!! 避免因代码编写bug,导致不能正确返回订单状态信息 ↓↓↓↓↓↓
if(alipayResp.isSuccess()){ //处理成功
if(CS.PAY_DATA_TYPE.CODE_IMG_URL.equals(aliQrOrderRQ.getPayDataType())){ //二维码地址
res.setCodeImgUrl(sysConfigService.getDBApplicationConfig().genScanImgUrl(alipayResp.getQrCode()));
}else{ //默认都为跳转地址方式
res.setCodeUrl(alipayResp.getQrCode());
}
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}else{ //其他状态, 表示下单失败
res.setOrderState(PayOrder.STATE_FAIL); //支付失败
channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(alipayResp.getCode(), alipayResp.getSubCode()));
channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(alipayResp.getMsg(), alipayResp.getSubMsg()));
}
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.alipay.payway;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.pay.channel.alipay.AlipayKit;
import com.jeequan.jeepay.pay.channel.alipay.AlipayPaymentService;
import com.jeequan.jeepay.pay.exception.ChannelException;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliWapOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliWapOrderRS;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import org.springframework.stereotype.Service;
/*
* 支付宝 wap支付
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:21
*/
@Service("alipayPaymentByAliWapService") //Service Name需保持全局唯一性
public class AliWap extends AlipayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext){
AliWapOrderRQ bizRQ = (AliWapOrderRQ)rq;
AlipayTradeWapPayRequest req = new AlipayTradeWapPayRequest();
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(payOrder.getPayOrderId());
model.setSubject(payOrder.getSubject()); //订单标题
model.setBody(payOrder.getBody()); //订单描述信息
model.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount().toString())); //支付金额
model.setProductCode("QUICK_WAP_PAY");
req.setNotifyUrl(getNotifyUrl()); // 设置异步通知地址
req.setReturnUrl(getReturnUrl()); // 同步跳转地址
req.setBizModel(model);
//统一放置 isv接口必传信息
AlipayKit.putApiIsvInfo(mchConfigContext, req, model);
// 构造函数响应数据
AliWapOrderRS res = ApiResBuilder.buildSuccess(AliWapOrderRS.class);
try {
if(CS.PAY_DATA_TYPE.FORM.equals(bizRQ.getPayDataType())){ //表单方式
res.setFormContent(mchConfigContext.getAlipayClientWrapper().getAlipayClient().pageExecute(req).getBody());
}else if (CS.PAY_DATA_TYPE.CODE_IMG_URL.equals(bizRQ.getPayDataType())){ //二维码图片地址
String payUrl = mchConfigContext.getAlipayClientWrapper().getAlipayClient().pageExecute(req, "GET").getBody();
res.setCodeImgUrl(sysConfigService.getDBApplicationConfig().genScanImgUrl(payUrl));
}else{ // 默认都为 payUrl方式
res.setPayUrl(mchConfigContext.getAlipayClientWrapper().getAlipayClient().pageExecute(req, "GET").getBody());
}
}catch (AlipayApiException e) {
throw ChannelException.sysError(e.getMessage());
}
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
//放置 响应数据
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
return res;
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.wxpay;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier;
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
import com.github.binarywang.wxpay.v3.util.AesUtils;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.pay.channel.AbstractChannelNoticeService;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
/*
* 微信回调
*
* @author zhuxiao
* @site https://www.jeepay.vip
* @date 2021/6/8 18:10
*/
@Service
@Slf4j
public class WxpayChannelNoticeService extends AbstractChannelNoticeService {
@Autowired private ConfigContextService configContextService;
@Autowired private PayOrderService payOrderService;
@Override
public String getIfCode() {
return CS.IF_CODE.WXPAY;
}
@Override
public MutablePair<String, Object> parseParams(HttpServletRequest request, String urlOrderId, NoticeTypeEnum noticeTypeEnum) {
try {
if(StringUtils.isNotBlank(urlOrderId)){ // V3接口回调
// 获取订单信息
PayOrder payOrder = payOrderService.getById(urlOrderId);
if(payOrder == null){
throw new BizException("订单不存在");
}
//获取支付参数 (缓存数据) 和 商户信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(payOrder.getMchNo());
if(mchConfigContext == null){
throw new BizException("获取商户信息失败");
}
// 验签
if (!verifyNotifySign(request, mchConfigContext)) {
throw new BizException("验签失败");
}
// 获取加密信息
JSONObject params = getReqParamJSON();
JSONObject resource = params.getJSONObject("resource");
String cipherText = resource.getString("cipherText");
String associatedData = resource.getString("associatedData");
String nonce = resource.getString("nonce");
// 解密
String result = AesUtils.decryptToString(associatedData, nonce, cipherText, mchConfigContext.getWxServiceWrapper().getWxPayService().getConfig().getApiV3Key());
JSONObject decryptJSON = JSONObject.parseObject(result);
return MutablePair.of(decryptJSON.getString("out_trade_no"), decryptJSON);
} else { // V2接口回调
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
if(StringUtils.isEmpty(xmlResult)) {
return null;
}
WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlResult);
String payOrderId = result.getOutTradeNo();
return MutablePair.of(payOrderId, result);
}
} catch (Exception e) {
log.error("error", e);
throw ResponseException.buildText("ERROR");
}
}
@Override
public ChannelRetMsg doNotice(HttpServletRequest request, Object params, PayOrder payOrder, MchConfigContext mchConfigContext, NoticeTypeEnum noticeTypeEnum) {
try {
ChannelRetMsg channelResult = new ChannelRetMsg();
channelResult.setChannelState(ChannelRetMsg.ChannelState.WAITING); // 默认支付中
if (CS.PAY_IF_VERSION.WX_V2.equals(mchConfigContext.getWxServiceWrapper().getApiVersion())) { // V2
// 获取回调参数
WxPayOrderNotifyResult result = (WxPayOrderNotifyResult) params;
WxPayService wxPayService = mchConfigContext.getWxServiceWrapper().getWxPayService();
// 验证参数
verifyWxPayParams(wxPayService, result, payOrder);
channelResult.setChannelOrderId(result.getTransactionId()); //渠道订单号
channelResult.setChannelUserId(result.getOpenid()); //支付用户ID
channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if (CS.PAY_IF_VERSION.WX_V3.equals(mchConfigContext.getWxServiceWrapper().getApiVersion())) { // V3
// 获取回调参数
JSONObject resultJSON = (JSONObject) params;
// 验证参数
verifyWxPayParams(resultJSON, payOrder);
String channelState = resultJSON.getString("trade_state");
if ("SUCCESS".equals(channelState)) {
channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if("CLOSED".equals(channelState)
|| "REVOKED".equals(channelState)
|| "PAYERROR".equals(channelState)){ //CLOSED—已关闭, REVOKED—已撤销, PAYERROR--支付失败
channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL); //支付失败
}
channelResult.setChannelOrderId(resultJSON.getString("transaction_id")); //渠道订单号
JSONObject payer = resultJSON.getJSONObject("payer");
if (payer != null) {
channelResult.setChannelUserId(StringUtils.isNotBlank(payer.getString("openid")) ? payer.getString("openid") : payer.getString("sp_openid")); //支付用户ID
}
}else {
throw ResponseException.buildText("API_VERSION ERROR");
}
ResponseEntity okResponse = textResp("SUCCESS");
channelResult.setResponseEntity(okResponse); //响应数据
return channelResult;
} catch (Exception e) {
log.error("error", e);
throw ResponseException.buildText("ERROR");
}
}
/**
* V2接口验证微信支付通知参数
* @return
*/
public void verifyWxPayParams(WxPayService wxPayService, WxPayOrderNotifyResult result, PayOrder payOrder) {
try {
result.checkResult(wxPayService, WxPayConstants.SignType.MD5, true);
// 核对金额
Integer total_fee = result.getTotalFee(); // 总金额
long wxPayAmt = new BigDecimal(total_fee).longValue();
long dbPayAmt = payOrder.getAmount().longValue();
if (dbPayAmt != wxPayAmt) {
throw ResponseException.buildText("AMOUNT ERROR");
}
} catch (Exception e) {
throw ResponseException.buildText("ERROR");
}
}
/**
* V3校验通知签名
* @param request 请求信息
* @param mchConfigContext 商户配置
* @return true:校验通过 false:校验不通过
*/
private boolean verifyNotifySign(HttpServletRequest request, MchConfigContext mchConfigContext) throws Exception {
SignatureHeader header = new SignatureHeader();
header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
header.setNonce(request.getHeader("Wechatpay-Nonce"));
header.setSerialNo(request.getHeader("Wechatpay-Serial"));
header.setSigned(request.getHeader("Wechatpay-Signature"));
String beforeSign = String.format("%s\n%s\n%s\n",
header.getTimeStamp(),
header.getNonce(),
getReqParamJSON().toJSONString());
WxPayConfig wxPayConfig = mchConfigContext.getWxServiceWrapper().getWxPayService().getConfig();
// 自动获取微信平台证书
PrivateKey privateKey = PemUtils.loadPrivateKey(new FileInputStream(wxPayConfig.getPrivateKeyPath()));
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WxPayCredentials(wxPayConfig.getMchId(), new PrivateKeySigner(wxPayConfig.getCertSerialNo(), privateKey)),
wxPayConfig.getApiV3Key().getBytes("utf-8"));
return verifier.verify(header.getSerialNo(),
beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
}
/**
* V3接口验证微信支付通知参数
* @return
*/
public void verifyWxPayParams(JSONObject result, PayOrder payOrder) {
try {
// 核对金额
Integer total_fee = result.getInteger("total"); // 总金额
long wxPayAmt = new BigDecimal(total_fee).longValue();
long dbPayAmt = payOrder.getAmount().longValue();
if (dbPayAmt != wxPayAmt) {
throw ResponseException.buildText("AMOUNT ERROR");
}
} catch (Exception e) {
throw ResponseException.buildText("ERROR");
}
}
}
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.jeequan.jeepay.pay.channel.wxpay;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.params.wxpay.WxpayIsvParams;
import com.jeequan.jeepay.core.model.params.wxpay.WxpayNormalMchParams;
import com.jeequan.jeepay.pay.channel.IChannelUserService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/*
* 微信支付 获取微信openID实现类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:22
*/
@Service
@Slf4j
public class WxpayChannelUserService implements IChannelUserService {
/** 默认官方跳转地址 **/
private static final String DEFAULT_OAUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
@Override
public String getIfCode() {
return CS.IF_CODE.WXPAY;
}
@Override
public String buildUserRedirectUrl(String callbackUrlEncode, MchConfigContext mchConfigContext) {
String appId = null;
String oauth2Url = "";
if(mchConfigContext.isIsvsubMch()){
WxpayIsvParams wxpayIsvParams = mchConfigContext.getIsvConfigContext().getIsvParamsByIfCode(CS.IF_CODE.WXPAY, WxpayIsvParams.class);
appId = wxpayIsvParams.getAppId();
oauth2Url = wxpayIsvParams.getOauth2Url();
}else{
//获取商户配置信息
WxpayNormalMchParams normalMchParams = mchConfigContext.getNormalMchParamsByIfCode(CS.IF_CODE.WXPAY, WxpayNormalMchParams.class);
appId = normalMchParams.getAppId();
oauth2Url = normalMchParams.getOauth2Url();
}
if(StringUtils.isBlank(oauth2Url)){
oauth2Url = DEFAULT_OAUTH_URL;
}
return String.format(oauth2Url + "?appid=%s&scope=snsapi_base&state=&redirect_uri=%s", appId, callbackUrlEncode);
}
@Override
public String getChannelUserId(JSONObject reqParams, MchConfigContext mchConfigContext) {
String code = reqParams.getString("code");
try {
return mchConfigContext.getWxServiceWrapper().getWxMpService().getOAuth2Service().getAccessToken(code).getOpenId();
} catch (WxErrorException e) {
e.printStackTrace();
return null;
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment