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.channel.ysfpay.payway;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.pay.channel.ysfpay.YsfpayPaymentService;
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 pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 18:11
*/
@Service("ysfpayPaymentByAliJsapiService") //Service Name需保持全局唯一性
public class AliJsapi extends YsfpayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
AliJsapiOrderRQ bizRQ = (AliJsapiOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getBuyerUserId())){
throw new BizException("[buyerUserId]不可为空");
}
return null;
}
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception {
String logPrefix = "【云闪付(alipayJs)jsapi支付】";
JSONObject reqParams = new JSONObject();
AliJsapiOrderRS res = ApiResBuilder.buildSuccess(AliJsapiOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
// 请求参数赋值
jsapiParamsSet(reqParams, payOrder, getNotifyUrl(), getReturnUrl());
AliJsapiOrderRQ bizRQ = (AliJsapiOrderRQ) rq;
//云闪付扫一扫支付, 需要传入buyerUserId参数
reqParams.put("userId", bizRQ.getBuyerUserId()); // buyerUserId
//客户端IP
reqParams.put("customerIp", StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1"));
// 发送请求并返回订单状态
JSONObject resJSON = packageParamAndReq("/gateway/api/pay/unifiedorder", reqParams, logPrefix, mchConfigContext.getIsvConfigContext(), mchConfigContext);
//请求 & 响应成功, 判断业务逻辑
String respCode = resJSON.getString("respCode"); //应答码
String respMsg = resJSON.getString("respMsg"); //应答信息
try {
//00-交易成功, 02-用户支付中 , 12-交易重复, 需要发起查询处理 其他认为失败
if("00".equals(respCode)){
//付款信息
JSONObject payDataJSON = JSONObject.parseObject(resJSON.getString("payData"));
String tradeNo = "";
if(StringUtils.isNotBlank(payDataJSON.getString("tradeNo"))){
tradeNo = payDataJSON.getString("tradeNo");
}else{
String prepayId = payDataJSON.getString("prepayId");
if(prepayId != null && prepayId.length() > 2){
tradeNo = prepayId.substring(2);
}
}
res.setAlipayTradeNo(tradeNo);
res.setPayData(payDataJSON.toJSONString());
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}
}catch (Exception e) {
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
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.ysfpay.payway;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.pay.channel.ysfpay.YsfpayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.WxBarOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.WxBarOrderRS;
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;
/*
* 云闪付 微信bar
*
* @author pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 18:11
*/
@Service("ysfPaymentByWxBarService") //Service Name需保持全局唯一性
public class WxBar extends YsfpayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
WxBarOrderRQ bizRQ = (WxBarOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getAuthCode())){
throw new BizException("用户支付条码[authCode]不可为空");
}
return null;
}
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception {
String logPrefix = "【云闪付条码(wechat)支付】";
WxBarOrderRQ bizRQ = (WxBarOrderRQ) rq;
WxBarOrderRS res = ApiResBuilder.buildSuccess(WxBarOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
JSONObject reqParams = new JSONObject();
reqParams.put("authCode", bizRQ.getAuthCode()); //付款码: 用户 APP 展示的付款条码或二维码
// 云闪付 bar 统一参数赋值
barParamsSet(reqParams, payOrder);
//客户端IP
reqParams.put("termInfo", "{\"ip\": \""+StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1")+"\"}"); //终端信息
// 发送请求
JSONObject resJSON = packageParamAndReq("/gateway/api/pay/micropay", reqParams, logPrefix, mchConfigContext.getIsvConfigContext(), mchConfigContext);
//请求 & 响应成功, 判断业务逻辑
String respCode = resJSON.getString("respCode"); //应答码
String respMsg = resJSON.getString("respMsg"); //应答信息
try {
//00-交易成功, 02-用户支付中 , 12-交易重复, 需要发起查询处理 其他认为失败
if("00".equals(respCode)){
res.setPayData(resJSON.getString("payData"));
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if("02".equals(respCode) || "12".equals(respCode) || "99".equals(respCode)){
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
channelRetMsg.setNeedQuery(true); // 开启轮询查单
}else{
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
}catch (Exception e) {
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
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.ysfpay.payway;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.pay.channel.ysfpay.YsfpayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.WxJsapiOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.WxJsapiOrderRS;
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 pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 18:11
*/
@Service("ysfpayPaymentByWxJsapiService") //Service Name需保持全局唯一性
public class WxJsapi extends YsfpayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
WxJsapiOrderRQ bizRQ = (WxJsapiOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getOpenid())){
throw new BizException("[openId]不可为空");
}
return null;
}
@Override
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception {
String logPrefix = "【云闪付(wechatJs)jsapi支付】";
JSONObject reqParams = new JSONObject();
WxJsapiOrderRS res = ApiResBuilder.buildSuccess(WxJsapiOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
// 请求参数赋值
jsapiParamsSet(reqParams, payOrder, getNotifyUrl(), getReturnUrl());
WxJsapiOrderRQ bizRQ = (WxJsapiOrderRQ) rq;
//云闪付扫一扫支付, 需要传入openId参数
reqParams.put("userId", bizRQ.getOpenid()); // openId
//客户端IP
reqParams.put("customerIp", StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1"));
// 发送请求并返回订单状态
JSONObject resJSON = packageParamAndReq("/gateway/api/pay/unifiedorder", reqParams, logPrefix, mchConfigContext.getIsvConfigContext(), mchConfigContext);
//请求 & 响应成功, 判断业务逻辑
String respCode = resJSON.getString("respCode"); //应答码
String respMsg = resJSON.getString("respMsg"); //应答信息
try {
//00-交易成功, 02-用户支付中 , 12-交易重复, 需要发起查询处理 其他认为失败
if("00".equals(respCode)){
//付款信息
res.setPayInfo(resJSON.getString("payData"));
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}else{
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
}catch (Exception e) {
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
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.ysfpay.payway;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.pay.channel.ysfpay.YsfpayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfBarOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfBarOrderRS;
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 pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 18:11
*/
@Service("ysfPaymentByYsfBarService") //Service Name需保持全局唯一性
public class YsfBar extends YsfpayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
YsfBarOrderRQ bizRQ = (YsfBarOrderRQ) rq;
if(StringUtils.isEmpty(bizRQ.getAuthCode())){
throw new BizException("用户支付条码[authCode]不可为空");
}
return null;
}
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, MchConfigContext mchConfigContext) throws Exception {
String logPrefix = "【云闪付条码(unionpay)支付】";
YsfBarOrderRQ bizRQ = (YsfBarOrderRQ) rq;
YsfBarOrderRS res = ApiResBuilder.buildSuccess(YsfBarOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
JSONObject reqParams = new JSONObject();
reqParams.put("authCode", bizRQ.getAuthCode()); //付款码: 用户 APP 展示的付款条码或二维码
// 云闪付 bar 统一参数赋值
barParamsSet(reqParams, payOrder);
//客户端IP
reqParams.put("termInfo", "{\"ip\": \""+StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1")+"\"}"); //终端信息
// 发送请求
JSONObject resJSON = packageParamAndReq("/gateway/api/pay/micropay", reqParams, logPrefix, mchConfigContext.getIsvConfigContext(), mchConfigContext);
//请求 & 响应成功, 判断业务逻辑
String respCode = resJSON.getString("respCode"); //应答码
String respMsg = resJSON.getString("respMsg"); //应答信息
try {
//00-交易成功, 02-用户支付中 , 12-交易重复, 需要发起查询处理 其他认为失败
if("00".equals(respCode)){
res.setPayData(resJSON.getString("payData"));
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
}else if("02".equals(respCode) ||"12".equals(respCode) || "99".equals(respCode)){
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
channelRetMsg.setNeedQuery(true); // 开启轮询查单
}else{
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
}catch (Exception e) {
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
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.ysfpay.payway;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.pay.channel.ysfpay.YsfpayPaymentService;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfJsapiOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfJsapiOrderRS;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.util.ApiResBuilder;
import com.jeequan.jeepay.pay.model.IsvConfigContext;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/*
* 云闪付 jsapi
*
* @author pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 18:11
*/
@Service("ysfpayPaymentByJsapiService") //Service Name需保持全局唯一性
public class YsfJsapi extends YsfpayPaymentService {
@Override
public String preCheck(UnifiedOrderRQ rq, PayOrder payOrder) {
return null;
}
public AbstractRS pay(UnifiedOrderRQ rq, PayOrder payOrder, IsvConfigContext isvConfigContext, MchConfigContext mchConfigContext) throws Exception {
String logPrefix = "【云闪付(unionpay)jsapi支付】";
JSONObject reqParams = new JSONObject();
YsfJsapiOrderRS res = ApiResBuilder.buildSuccess(YsfJsapiOrderRS.class);
ChannelRetMsg channelRetMsg = new ChannelRetMsg();
res.setChannelRetMsg(channelRetMsg);
YsfJsapiOrderRQ bizRQ = (YsfJsapiOrderRQ) rq;
// 请求参数赋值
jsapiParamsSet(reqParams, payOrder, getNotifyUrl(), getReturnUrl());
//云闪付扫一扫支付, 需要传入termInfo参数
reqParams.put("termInfo", "{\"ip\": \""+StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1")+"\"}");
//客户端IP
reqParams.put("customerIp", StringUtils.defaultIfEmpty(payOrder.getClientIp(), "127.0.0.1"));
// 发送请求并返回订单状态
JSONObject resJSON = packageParamAndReq("/gateway/api/pay/unifiedorder", reqParams, logPrefix, isvConfigContext, mchConfigContext);
//请求 & 响应成功, 判断业务逻辑
String respCode = resJSON.getString("respCode"); //应答码
String respMsg = resJSON.getString("respMsg"); //应答信息
try {
//00-交易成功, 02-用户支付中 , 12-交易重复, 需要发起查询处理 其他认为失败
if("00".equals(respCode)){
//付款信息
res.setPayData(resJSON.getString("payData"));
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING);
}else{
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
}catch (Exception e) {
channelRetMsg.setChannelErrCode(respCode);
channelRetMsg.setChannelErrMsg(respMsg);
}
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.ysfpay.utils;
import com.alibaba.fastjson.JSON;
import com.jeequan.jeepay.core.constants.CS;
import javax.activation.MimetypesFileTypeMap;
import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/*
* 参考云闪付demo实现的 httputils
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
public class YsfHttpUtil {
private static final String DEFAULT_CHARSET = "UTF-8";
private static final int DEFAULT_TIMEOUT = 60 * 1000; // 60 秒超时
public static String doGet(String url) throws Exception {
return doGet(url, null);
}
public static String doGet(String url, Map<String, Object> params) throws Exception {
return doGet(url, params, null);
}
public static String doGet(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {
String getUrl = buildGetUrl(url, params);
return doRequest(getUrl, "GET", headers, null);
}
public static String doPost(String url) throws Exception {
return doPost(url, null);
}
public static String doPost(String url, Map<String, Object> params) throws Exception {
return doPost(url, null, params);
}
public static String doPost(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {
return doPostStr(url, headers, buildQueryParams(params));
}
public static String doPostStr(String url, Map<String, Object> headers, String data) throws Exception {
return doRequest(url, "POST", headers, data);
}
public static String doPostJson(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {
if (headers == null) {
headers = new HashMap<String, Object>();
}
if (!headers.containsKey("Content-Type")) {
headers.put("Content-Type", "application/json; charset=" + DEFAULT_CHARSET);
}
return doPostStr(url, headers, JSON.toJSONString(params));
}
public static String doPostFile(String url, Map<String, Object> headers, Map<String, Object> params, Map<String, FileItem> fileParams) throws Exception {
HttpURLConnection http = null;
InputStream in = null;
OutputStream out = null;
try {
String boundary = String.valueOf(System.currentTimeMillis()); // 随机分隔线
http = getHttpConnection(url, "POST");
http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary + ";charset=" + DEFAULT_CHARSET);
// 添加HTTP请求头
if (headers != null && !headers.isEmpty()) {
for (Entry<String, Object> entry : headers.entrySet()) {
http.setRequestProperty(entry.getKey(), entry.getValue().toString());
}
}
out = http.getOutputStream();
// 组装文本请求参数
byte[] entryBoundaryBytes = ("\r\n--" + boundary + "\r\n").getBytes(DEFAULT_CHARSET);
if (params != null && !params.isEmpty()) {
for (Entry<String, Object> textEntry : params.entrySet()) {
byte[] textBytes = getTextEntry(textEntry.getKey(), String.valueOf(textEntry.getValue()));
out.write(entryBoundaryBytes);
out.write(textBytes);
}
}
// 组装文件请求参数
if (fileParams != null && !fileParams.isEmpty()) {
for (Entry<String, FileItem> fileEntry : fileParams.entrySet()) {
FileItem fileItem = fileEntry.getValue();
byte[] fileBytes = getFileEntry(fileEntry.getKey(), fileItem.getFileName(), fileItem.getMimeType());
out.write(entryBoundaryBytes);
out.write(fileBytes);
out.write(fileItem.getContent());
}
}
// 添加请求结束标志
byte[] endBoundaryBytes = ("\r\n--" + boundary + "--\r\n").getBytes(DEFAULT_CHARSET);
out.write(endBoundaryBytes);
out.flush();
in = http.getInputStream();
return getStreamAsString(in);
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (http != null) {
http.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String doRequest(String url, String method, Map<String, Object> headers, String data) throws Exception {
HttpURLConnection http = null;
InputStream in = null;
OutputStream out = null;
try {
http = getHttpConnection(url, method);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, Object> entry : headers.entrySet()) {
http.setRequestProperty(entry.getKey(), entry.getValue().toString());
}
}
if (data != null && !data.trim().isEmpty()) {
out = http.getOutputStream();
out.write(data.getBytes(DEFAULT_CHARSET));
out.flush();
}
in = http.getInputStream();
return getStreamAsString(in);
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (http != null) {
http.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static HttpURLConnection getHttpConnection(String url, String method) throws Exception {
boolean isSSL = url.startsWith("https");
if (isSSL) {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(new KeyManager[0], new TrustManager[] { new SimpleTrustManager() }, new SecureRandom());
SSLSocketFactory sslf = sslContext.getSocketFactory();
HttpsURLConnection https = (HttpsURLConnection) new URL(url).openConnection();
https.setHostnameVerifier(new SimpleHostnameVerifier());
https.setSSLSocketFactory(sslf);
https.setRequestMethod(method);
https.setDoOutput(true);
https.setDoInput(true);
https.setUseCaches(false);
https.setConnectTimeout(DEFAULT_TIMEOUT);
https.setReadTimeout(DEFAULT_TIMEOUT);
return https;
} else {
HttpURLConnection http = (HttpURLConnection) new URL(url).openConnection();
http.setRequestMethod(method);
http.setDoOutput(true);
http.setDoInput(true);
http.setUseCaches(false);
http.setConnectTimeout(DEFAULT_TIMEOUT);
http.setReadTimeout(DEFAULT_TIMEOUT);
return http;
}
}
private static String getStreamAsString(InputStream in) throws Exception {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(in, DEFAULT_CHARSET));
StringBuilder buffer = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
} finally {
if (reader != null) {
reader.close();
}
}
}
private static String buildGetUrl(String url, Map<String, Object> params) throws Exception {
String queryParams = buildQueryParams(params);
if (null == queryParams || queryParams.isEmpty()) {
return url;
}
StringBuilder sb = new StringBuilder();
if (url.endsWith("?")) {
sb.append(url).append(queryParams);
} else {
sb.append(url).append("?").append(queryParams);
}
return sb.toString();
}
private static String buildQueryParams(Map<String, Object> params) throws Exception {
if (null == params || params.isEmpty()) {
return null;
}
StringBuilder query = new StringBuilder();
boolean hasParam = false;
for (Entry<String, Object> entry : params.entrySet()) {
Object v = entry.getValue();
if (v == null) {
continue;
}
if (hasParam) {
query.append("&");
} else {
hasParam = true;
}
query.append(entry.getKey()).append("=").append(URLEncoder.encode(v.toString(), DEFAULT_CHARSET));
}
return query.toString();
}
private static byte[] getTextEntry(String fieldName, String fieldValue) throws Exception {
StringBuilder entry = new StringBuilder();
entry.append("Content-Disposition:form-data; name=\"");
entry.append(fieldName);
entry.append("\"\r\nContent-Type:text/plain\r\n\r\n");
entry.append(fieldValue);
return entry.toString().getBytes(DEFAULT_CHARSET);
}
private static byte[] getFileEntry(String fieldName, String fileName, String mimeType) throws Exception {
StringBuilder entry = new StringBuilder();
entry.append("Content-Disposition:form-data; name=\"");
entry.append(fieldName);
entry.append("\"; filename=\"");
entry.append(fileName);
entry.append("\"\r\nContent-Type:");
entry.append(mimeType);
entry.append("\r\n\r\n");
return entry.toString().getBytes(DEFAULT_CHARSET);
}
private static class SimpleHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
private static class SimpleTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
}
public static class FileItem {
private String fileName;
private String mimeType;
private byte[] content;
public FileItem() {
}
public FileItem(String fileName, byte[] content) {
this.fileName = fileName;
this.content = content;
}
public FileItem(String fileName, byte[] content, String mimeType) {
this.fileName = fileName;
this.content = content;
this.mimeType = mimeType;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
if (mimeType == null) {
mimeType = getContentType(fileName);
}
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
private String getContentType(String fileName) {
String defaultType = "application/octet-stream";
if (fileName == null || fileName.isEmpty()) {
return defaultType;
}
String contentType = null;
try {
Path path = Paths.get(fileName);
contentType = Files.probeContentType(path);
} catch (Exception e) {
e.printStackTrace();
}
if (contentType == null || contentType.isEmpty()) {
contentType = new MimetypesFileTypeMap().getContentType(fileName);
}
return contentType;
}
}
/** 云闪付条码付 封装参数,orderType **/
public static String getOrderTypeByBar(String wayCode){
if(CS.PAY_WAY_CODE.ALI_BAR.equals(wayCode)){
return "alipay";
}else if(CS.PAY_WAY_CODE.WX_BAR.equals(wayCode)){
return "wechat";
}else if(CS.PAY_WAY_CODE.YSF_BAR.equals(wayCode)){
return "unionpay";
}
return null;
}
/** 云闪付jsapi对应的订单类型 **/
public static String getOrderTypeByJSapi(String wayCode){
if(CS.PAY_WAY_CODE.ALI_JSAPI.equals(wayCode)){
return "alipayJs";
}else if(CS.PAY_WAY_CODE.WX_JSAPI.equals(wayCode)){
return "wechatJs";
}else if(CS.PAY_WAY_CODE.YSF_JSAPI.equals(wayCode)){
return "upJs";
}
return null;
}
/** 云闪付通用订单类型, 如查单 **/
public static String getOrderTypeByCommon(String wayCode){
if(CS.PAY_WAY_CODE.ALI_JSAPI.equals(wayCode) || CS.PAY_WAY_CODE.ALI_BAR.equals(wayCode)){
return "alipay";
}else if(CS.PAY_WAY_CODE.WX_JSAPI.equals(wayCode) || CS.PAY_WAY_CODE.WX_BAR.equals(wayCode)){
return "wechat";
}else if(CS.PAY_WAY_CODE.YSF_JSAPI.equals(wayCode) || CS.PAY_WAY_CODE.YSF_BAR.equals(wayCode)){
return "unionpay";
}
return null;
}
}
\ No newline at end of file
/*
* 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.ysfpay.utils;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;
import java.util.TreeMap;
/**
* 银联接口签名工具类
*
* @author terrfly
* @modify pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021-06-07 07:15
*/
public class YsfSignUtils {
private static final String KEYSTORE_TYPE_PKCS12 = "PKCS12"; //私钥类型
private static final String KEYSTORE_PROVIDER_BC = "BC"; //提供商
private static final String ALGORITHM_SHA256WITHRSA = "SHA256withRSA"; //签名算法 sha256
private static final String CERTIFICATE_TYPE_X509 = "X.509"; //公钥证书类型
private static final Logger logger = LoggerFactory.getLogger(YsfSignUtils.class);
static {
try {
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
logger.error("addProvider Error", e);
}
}
/** 签名
* 注意事项: 签名需商户申请 5.1.0版本证书;
* 文档: https://open.unionpay.com/tjweb/acproduct/list?apiSvcId=468&index=2
* 1. 排序并拼接为[key=value]格式;
* 2. 对原始签名串使用SHA-256算法做摘要
* 3. 使用商户私钥做签名(使用 SHA-256)
* 4. 进行Base64处理
* **/
public static String signBy256(JSONObject params, String privateKeyFilePath, String certPwd) {
try {
//0. 将请求参数 转换成key1=value1&key2=value2的形式
String stringSign = convertSignString(params);
//1. 通过SHA256进行摘要并转16进制
byte[] signDigest = sha256X16(stringSign, "UTF-8");
//2. /获取私钥证书的key
PrivateKey privateKey = getSignCertPrivateKey(privateKeyFilePath, certPwd);
//3. 使用 SHA-256算法 进行签名
Signature st = Signature.getInstance(ALGORITHM_SHA256WITHRSA, KEYSTORE_PROVIDER_BC);
st.initSign(privateKey);
st.update(signDigest);
byte[] result = st.sign();
//4. 做base64 处理
byte[] byteSign = Base64.encodeBase64(result);
return new String(byteSign);
} catch (Exception e) {
logger.error("银联签名失败", e);
return null;
}
}
/** 验签 **/
public static boolean validate(JSONObject params, String ysfpayPublicKey){
//签名串
String signature = params.getString("signature");
// 将请求参数信息转换成key1=value1&key2=value2的形式
String stringData = convertSignString(params);
try {
//1. 通过SHA256进行摘要并转16进制
byte[] signDigest = sha256X16(stringData, "UTF-8");
//构造公钥证书
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.decodeBase64(ysfpayPublicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature st = Signature.getInstance(ALGORITHM_SHA256WITHRSA);
st.initVerify(pubKey); //公钥
st.update(signDigest);
return st.verify(Base64.decodeBase64(signature.getBytes("UTF-8")));
} catch (Exception e) {
logger.error("验签失败!", e);
}
return false;
}
/** 进件验签 **/
public static boolean applyValidate(JSONObject params, String ysfpayPublicKey){
//签名串
String signature = params.getString("signature");
// 将请求参数信息转换成key1=value1&key2=value2的形式
String stringData = convertSignApplyNotifyString(params);
try {
//1. 通过SHA256进行摘要并转16进制
byte[] signDigest = sha256X16(stringData, "UTF-8");
//构造公钥证书
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.decodeBase64(ysfpayPublicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature st = Signature.getInstance(ALGORITHM_SHA256WITHRSA);
st.initVerify(pubKey); //公钥
st.update(signDigest);
return st.verify(Base64.decodeBase64(signature.getBytes("UTF-8")));
} catch (Exception e) {
logger.error("验签失败!", e);
}
return false;
}
/** 进件回调 将JSON中的数据转换成key1=value1&key2=value2的形式,忽略null内容 和 signature字段* */
private static String convertSignApplyNotifyString(JSONObject params) {
TreeMap<String, Object> tree = new TreeMap<>();
//1. 所有参数进行排序
params.keySet().stream().forEach( key -> tree.put(key, params.get(key)));
//2. 拼接为 key=value&形式
StringBuffer stringBuffer = new StringBuffer();
tree.keySet().stream().forEach( key -> {
if (tree.get(key) == null) {
return ;
}
if("signature".equals(key)){ //签名串, 不参与签名
return ;
}
stringBuffer.append(key).append("=").append(tree.get(key).toString()).append("&");
});
//3. 去掉最后一个&
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
/** 将JSON中的数据转换成key1=value1&key2=value2的形式, 忽略空内容 和 signature字段 **/
private static String convertSignString(JSONObject params) {
TreeMap<String, Object> tree = new TreeMap<>();
//1. 所有参数进行排序
params.keySet().stream().forEach( key -> tree.put(key, params.get(key)));
//2. 拼接为 key=value&形式
StringBuffer stringBuffer = new StringBuffer();
tree.keySet().stream().forEach( key -> {
if (tree.get(key) == null) {
return ;
}
if(StringUtils.isAnyEmpty(key, tree.get(key).toString())){ //空值, 不参与签名
return ;
}
if("signature".equals(key)){ //签名串, 不参与签名
return ;
}
stringBuffer.append(key).append("=").append(tree.get(key).toString()).append("&");
});
//3. 去掉最后一个&
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
/** 通过SHA256进行摘要并转16进制 **/
private static byte[] sha256X16(String data, String encoding) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(data.getBytes(encoding));
byte[] bytes = md.digest();
StringBuilder sha256StrBuff = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
sha256StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
} else {
sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
}
}
return sha256StrBuff.toString().toLowerCase().getBytes(encoding);
}
/** 获取证书私钥 **/
private static PrivateKey getSignCertPrivateKey(String pfxkeyfile, String keypwd) {
FileInputStream fis = null;
try {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12, KEYSTORE_PROVIDER_BC);
fis = new FileInputStream(pfxkeyfile);
char[] nPassword = null == keypwd || "".equals(keypwd.trim()) ? null: keypwd.toCharArray();
if (null != keyStore) {
keyStore.load(fis, nPassword);
}
Enumeration<String> aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keypwd.toCharArray());
return privateKey;
} catch (Exception e) {
logger.error("获取证书私钥失败!", e);
return null;
}finally {
if(null!=fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/*
* 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.config;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
/*
* RedisConfig
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Value("${spring.redis.database}")
private Integer defaultDatabase;
@Value("${spring.redis.password}")
private String password;
/** 当前系统的redis缓存操作对象 (主对象) **/
@Primary
@Bean(name = "defaultStringRedisTemplate")
public StringRedisTemplate sysStringRedisTemplate() {
StringRedisTemplate template = new StringRedisTemplate();
LettuceConnectionFactory jedisConnectionFactory = new LettuceConnectionFactory();
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setTimeout(timeout);
if (!StringUtils.isEmpty(password)) {
jedisConnectionFactory.setPassword(password);
}
if (defaultDatabase != 0) {
jedisConnectionFactory.setDatabase(defaultDatabase);
}
jedisConnectionFactory.afterPropertiesSet();
template.setConnectionFactory(jedisConnectionFactory);
return template;
}
}
/*
* 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.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component;
/**
* 系统Yml配置参数定义Bean
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021-04-27 15:50
*/
@Component
@ConfigurationProperties(prefix="isys")
@Data
public class SystemYmlConfig {
/** 是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时,应开启此配置或在nginx统一配置允许跨域] **/
private Boolean allowCors;
@NestedConfigurationProperty //指定该属性为嵌套值, 否则默认为简单值导致对象为空(外部类不存在该问题, 内部static需明确指定)
private OssFile ossFile;
/** 系统oss配置信息 **/
@Data
public static class OssFile{
/** 存储根路径 **/
private String rootPath;
/** 公共读取块 **/
private String publicPath;
/** 私有读取块 **/
private String privatePath;
}
}
\ No newline at end of file
/*
* 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.ctrl;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.AbstractRQ;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.service.ValidateService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
/*
* api 抽象接口, 公共函数
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:28
*/
public abstract class ApiController extends AbstractCtrl {
@Autowired private ValidateService validateService;
@Autowired private ConfigContextService configContextService;
/** 获取请求参数并转换为对象,通用验证 **/
protected <T extends AbstractRQ> T getRQ(Class<T> cls){
T bizRQ = getObject(cls);
// [1]. 验证通用字段规则
validateService.validate(bizRQ);
return bizRQ;
}
/** 获取请求参数并转换为对象,商户通用验证 **/
protected <T extends AbstractRQ> T getRQByWithMchSign(Class<T> cls){
//获取请求RQ, and 通用验证
T bizRQ = getRQ(cls);
// 转换为 JSON
JSONObject bizReqJSON = (JSONObject)JSONObject.toJSON(bizRQ);
// [2]. 业务校验, 包括: 验签, 商户状态是否可用, 是否支持该支付方式下单等。
String mchNo = bizReqJSON.getString("mchNo");
String sign = bizRQ.getSign();
if(StringUtils.isAnyEmpty(mchNo, sign)){
throw new BizException("参数有误!");
}
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(mchNo);
MchInfo mchInfo = mchConfigContext == null ? null : mchConfigContext.getMchInfo();
if(mchInfo == null || mchInfo.getState() != CS.YES){
throw new BizException("商户不存在或商户状态不可用");
}
// 验签
String privateKey = mchInfo.getPrivateKey();
bizReqJSON.remove("sign");
if(!sign.equalsIgnoreCase(JeepayKit.getSign(bizReqJSON, privateKey))){
throw new BizException("验签失败");
}
return bizRQ;
}
}
/*
* 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.ctrl.payorder;
import cn.hutool.core.date.DateUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.entity.MchPayPassage;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.channel.IPaymentService;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.exception.ChannelException;
import com.jeequan.jeepay.pay.model.IsvConfigContext;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.mq.queue.MqQueue4ChannelOrderQuery;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.QrCashierOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.QrCashierOrderRS;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.service.impl.MchPayPassageService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
/*
* 创建支付订单抽象类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:26
*/
@Slf4j
public abstract class AbstractPayOrderController extends ApiController {
@Autowired private MchPayPassageService mchPayPassageService;
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextService configContextService;
@Autowired private PayMchNotifyService payMchNotifyService;
@Autowired private SysConfigService sysConfigService;
@Autowired private MqQueue4ChannelOrderQuery mqChannelOrderQueryQueue;
/** 统一下单 (新建订单模式) **/
protected ApiRes unifiedOrder(String wayCode, UnifiedOrderRQ bizRQ){
return unifiedOrder(wayCode, bizRQ, null);
}
/** 统一下单 **/
protected ApiRes unifiedOrder(String wayCode, UnifiedOrderRQ bizRQ, PayOrder payOrder){
// 响应数据
UnifiedOrderRS bizRS = null;
//是否新订单模式 [ 一般接口都为新订单模式, 由于QR_CASHIER支付方式,需要先 在DB插入一个新订单, 导致此处需要特殊判断下。 如果已存在则直接更新,否则为插入。 ]
boolean isNewOrder = payOrder == null;
try {
if(payOrder != null){ //当订单存在时,封装公共参数。
if(payOrder.getState() != PayOrder.STATE_INIT){
throw new BizException("订单状态异常");
}
payOrder.setWayCode(wayCode); // 需要将订单更新 支付方式
bizRQ.setMchNo(payOrder.getMchNo());
bizRQ.setMchOrderNo(payOrder.getMchOrderNo());
bizRQ.setWayCode(wayCode);
bizRQ.setAmount(payOrder.getAmount());
bizRQ.setCurrency(payOrder.getCurrency());
bizRQ.setClientIp(payOrder.getClientIp());
bizRQ.setSubject(payOrder.getSubject());
bizRQ.setNotifyUrl(payOrder.getNotifyUrl());
bizRQ.setReturnUrl(payOrder.getReturnUrl());
bizRQ.setChannelExtra(payOrder.getChannelExtra());
bizRQ.setChannelUser(payOrder.getChannelUser());
bizRQ.setExtParam(payOrder.getExtParam());
}
String mchNo = bizRQ.getMchNo();
// 只有新订单模式,进行校验
if(isNewOrder && payOrderService.count(PayOrder.gw().eq(PayOrder::getMchNo, mchNo).eq(PayOrder::getMchOrderNo, bizRQ.getMchOrderNo())) > 0){
throw new BizException("商户订单["+bizRQ.getMchOrderNo()+"]已存在");
}
//获取支付参数 (缓存数据) 和 商户信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(mchNo);
if(mchConfigContext == null){
throw new BizException("获取商户信息失败");
}
MchInfo mchInfo = mchConfigContext.getMchInfo();
//收银台支付并且只有新订单需要走这里, 收银台二次下单的wayCode应该为实际支付方式。
if(isNewOrder && CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
//生成订单
payOrder = genPayOrder(bizRQ, mchInfo, null);
String payOrderId = payOrder.getPayOrderId();
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
payOrderService.save(payOrder);
QrCashierOrderRS qrCashierOrderRS = new QrCashierOrderRS();
QrCashierOrderRQ qrCashierOrderRQ = (QrCashierOrderRQ)bizRQ;
String payUrl = sysConfigService.getDBApplicationConfig().genUniJsapiPayUrl(payOrderId);
if(CS.PAY_DATA_TYPE.CODE_IMG_URL.equals(qrCashierOrderRQ.getPayDataType())){ //二维码地址
qrCashierOrderRS.setCodeImgUrl(sysConfigService.getDBApplicationConfig().genScanImgUrl(payUrl));
}else{ //默认都为跳转地址方式
qrCashierOrderRS.setPayUrl(payUrl);
}
return packageApiResByPayOrder(bizRQ, qrCashierOrderRS, payOrder);
}
//获取支付接口
IPaymentService paymentService = checkMchWayCodeAndGetService(mchConfigContext, wayCode);
String ifCode = paymentService.getIfCode();
//生成订单
if(isNewOrder){
payOrder = genPayOrder(bizRQ, mchInfo, ifCode);
}else{
payOrder.setIfCode(ifCode);
}
//预先校验
String errMsg = paymentService.preCheck(bizRQ, payOrder);
if(StringUtils.isNotEmpty(errMsg)){
throw new BizException(errMsg);
}
if(isNewOrder){
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
payOrderService.save(payOrder);
}
//调起上游支付接口
bizRS = (UnifiedOrderRS) paymentService.pay(bizRQ, payOrder, mchConfigContext);
//处理上游返回数据
this.processChannelMsg(bizRS.getChannelRetMsg(), payOrder);
return packageApiResByPayOrder(bizRQ, bizRS, payOrder);
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (ChannelException e) {
//处理上游返回数据
this.processChannelMsg(e.getChannelRetMsg(), payOrder);
if(e.getChannelRetMsg().getChannelState() == ChannelRetMsg.ChannelState.SYS_ERROR ){
return ApiRes.customFail(e.getMessage());
}
return this.packageApiResByPayOrder(bizRQ, bizRS, payOrder);
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
private PayOrder genPayOrder(UnifiedOrderRQ rq, MchInfo mchInfo, String ifCode){
PayOrder payOrder = new PayOrder();
payOrder.setPayOrderId(SeqKit.genPayOrderId()); //生成订单ID
payOrder.setMchNo(mchInfo.getMchNo()); //商户号
payOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
payOrder.setMchName(mchInfo.getMchShortName()); //商户名称(简称)
payOrder.setMchType(mchInfo.getType()); //商户类型
payOrder.setMchOrderNo(rq.getMchOrderNo()); //商户订单号
payOrder.setIfCode(ifCode); //接口代码
payOrder.setWayCode(rq.getWayCode()); //支付方式
payOrder.setAmount(rq.getAmount()); //订单金额
payOrder.setCurrency(rq.getCurrency()); //币种
payOrder.setState(PayOrder.STATE_INIT); //订单状态, 默认订单生成状态
payOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), getClientIp())); //客户端IP
payOrder.setSubject(rq.getSubject()); //商品标题
payOrder.setBody(rq.getBody()); //商品描述信息
// payOrder.setChannelExtra(rq.getChannelExtra()); //特殊渠道发起的附件额外参数, 是否应该删除该字段了?? 比如authCode不应该记录, 只是在传输阶段存在的吧? 之前的为了在payOrder对象需要传参。
payOrder.setChannelUser(rq.getChannelUser()); //渠道用户标志
payOrder.setDivisionFlag(CS.NO); //分账标志, 默认为: 0-否
payOrder.setExtParam(rq.getExtParam()); //商户扩展参数
payOrder.setNotifyUrl(rq.getNotifyUrl()); //异步通知地址
payOrder.setReturnUrl(rq.getReturnUrl()); //页面跳转地址
Date nowDate = new Date();
payOrder.setExpiredTime(DateUtil.offsetHour(nowDate, 2)); //订单过期时间 默认两个小时
payOrder.setCreatedAt(nowDate); //订单创建时间
return payOrder;
}
/**
* 校验: 商户的支付方式是否可用
* 返回: 支付接口
* **/
private IPaymentService checkMchWayCodeAndGetService(MchConfigContext mchConfigContext, String wayCode){
// 根据支付方式, 查询出 该商户 可用的支付接口
MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchConfigContext.getMchNo(), wayCode);
if(mchPayPassage == null){
throw new BizException("该支付方式商户未开通");
}
// 接口代码
String ifCode = mchPayPassage.getIfCode();
IPaymentService paymentService = SpringBeansUtil.getBean(ifCode + "PaymentService", IPaymentService.class);
if(paymentService == null){
throw new BizException("无此支付通道接口");
}
if(!paymentService.isSupport(ifCode)){
throw new BizException("接口不支持该支付方式");
}
if(mchConfigContext.getMchType() == MchInfo.TYPE_NORMAL){ //普通商户
if(mchConfigContext == null || mchConfigContext.getNormalMchParamsByIfCode(ifCode) == null){
throw new BizException("商户参数未配置");
}
}else if(mchConfigContext.getMchType() == MchInfo.TYPE_ISVSUB){ //特约商户
mchConfigContext = configContextService.getMchConfigContext(mchConfigContext.getMchNo());
if(mchConfigContext == null || mchConfigContext.getIsvsubMchParamsByIfCode(ifCode) == null){
throw new BizException("特约商户参数未配置");
}
IsvConfigContext isvConfigContext = configContextService.getIsvConfigContext(mchConfigContext.getMchInfo().getIsvNo());
if(isvConfigContext == null || isvConfigContext.getIsvParamsByIfCode(ifCode) == null){
throw new BizException("服务商参数未配置");
}
}
return paymentService;
}
/** 处理返回的渠道信息,并更新订单状态
* payOrder将对部分信息进行 赋值操作。
* **/
private void processChannelMsg(ChannelRetMsg channelRetMsg, PayOrder payOrder){
//对象为空 || 上游返回状态为空, 则无需操作
if(channelRetMsg == null || channelRetMsg.getChannelState() == null){
return ;
}
String payOrderId = payOrder.getPayOrderId();
//明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(PayOrder.STATE_SUCCESS, payOrder, channelRetMsg);
payMchNotifyService.payOrderNotify(payOrder);
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(PayOrder.STATE_FAIL, payOrder, channelRetMsg);
// 上游处理中 || 未知 || 上游接口返回异常 订单为支付中状态
}else if( ChannelRetMsg.ChannelState.WAITING == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.UNKNOWN == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.API_RET_ERROR == channelRetMsg.getChannelState()
){
this.updateInitOrderStateThrowException(PayOrder.STATE_ING, payOrder, channelRetMsg);
// 系统异常: 订单不再处理。 为: 生成状态
}else if( ChannelRetMsg.ChannelState.SYS_ERROR == channelRetMsg.getChannelState()){
}else{
throw new BizException("ChannelState 返回异常!");
}
//判断是否需要轮询查单
if(channelRetMsg.isNeedQuery()){
mqChannelOrderQueryQueue.send(MqQueue4ChannelOrderQuery.buildMsg(payOrderId, 1), 5 * 1000);
}
}
/** 更新订单状态 --》 订单生成--》 其他状态 (向外抛出异常) **/
private void updateInitOrderStateThrowException(byte orderState, PayOrder payOrder, ChannelRetMsg channelRetMsg){
payOrder.setState(orderState);
payOrder.setChannelOrderNo(channelRetMsg.getChannelOrderId());
payOrder.setErrCode(channelRetMsg.getChannelErrCode());
payOrder.setErrMsg(channelRetMsg.getChannelErrMsg());
boolean isSuccess = payOrderService.updateInit2Ing(payOrder.getPayOrderId(), payOrder.getIfCode(), payOrder.getWayCode());
if(!isSuccess){
throw new BizException("更新订单异常!");
}
isSuccess = payOrderService.updateIng2SuccessOrFail(payOrder.getPayOrderId(), payOrder.getState(),
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelErrCode(), channelRetMsg.getChannelErrMsg());
if(!isSuccess){
throw new BizException("更新订单异常!");
}
}
/** 统一封装订单数据 **/
private ApiRes packageApiResByPayOrder(UnifiedOrderRQ bizRQ, UnifiedOrderRS bizRS, PayOrder payOrder){
// 返回接口数据
bizRS.setPayOrderId(payOrder.getPayOrderId());
bizRS.setOrderState(payOrder.getState());
bizRS.setMchOrderNo(payOrder.getMchOrderNo());
if(payOrder.getState() == PayOrder.STATE_FAIL){
bizRS.setErrCode(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrCode() : null);
bizRS.setErrMsg(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrMsg() : null);
}
return ApiRes.okWithSign(bizRS, configContextService.getMchConfigContext(bizRQ.getMchNo()).getMchInfo().getPrivateKey());
}
}
/*
* 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.ctrl.payorder;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
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.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.channel.IChannelNoticeService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
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.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/*
* 渠道侧的通知入口Controller 【分为同步跳转(doReturn)和异步回调(doNotify) 】
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:26
*/
@Slf4j
@Controller
public class ChannelNoticeController extends AbstractCtrl {
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextService configContextService;
@Autowired private PayMchNotifyService payMchNotifyService;
/** 同步通知入口 **/
@RequestMapping(value= {"/api/pay/return/{ifCode}", "/api/pay/return/{ifCode}/{payOrderId}"})
public String doReturn(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "payOrderId", required = false) String urlOrderId){
String payOrderId = null;
String logPrefix = "进入[" +ifCode+ "]支付同步跳转:urlOrderId:["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return this.toReturnPage("ifCode is empty");
}
//查询支付接口是否存在
IChannelNoticeService payNotifyService = SpringBeansUtil.getBean(ifCode + "ChannelNoticeService", IChannelNoticeService.class);
// 支付通道接口实现不存在
if(payNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return this.toReturnPage("[" + ifCode + "] interface not exists");
}
// 解析订单号 和 请求参数
MutablePair<String, Object> mutablePair = payNotifyService.parseParams(request, urlOrderId, IChannelNoticeService.NoticeTypeEnum.DO_NOTIFY);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
payOrderId = mutablePair.left;
log.info("{}, 解析数据为:payOrderId:{}, params:{}", logPrefix, payOrderId, mutablePair.getRight());
if(StringUtils.isNotEmpty(urlOrderId) && !urlOrderId.equals(payOrderId)){
log.error("{}, 订单号不匹配. urlOrderId={}, payOrderId={} ", logPrefix, urlOrderId, payOrderId);
throw new BizException("订单号不匹配!");
}
//获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(payOrderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. payOrderId={} ", logPrefix, payOrderId);
return this.toReturnPage("支付订单不存在");
}
//查询出商户的配置信息
String mchNo = payOrder.getMchNo();
//查询出商户配置参数
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(mchNo);
//调起接口的回调判断
ChannelRetMsg notifyResult = payNotifyService.doNotice(request, mutablePair.getRight(), payOrder, mchConfigContext, IChannelNoticeService.NoticeTypeEnum.DO_RETURN);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//判断订单状态
if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
payOrder.setState(PayOrder.STATE_SUCCESS);
}else if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL) {
payOrder.setState(PayOrder.STATE_FAIL);
}
boolean hasReturnUrl = StringUtils.isNotBlank(payOrder.getReturnUrl());
log.info("===== {}, 订单通知完成。 payOrderId={}, parseState = {}, hasReturnUrl={} =====", logPrefix, payOrderId, notifyResult.getChannelState(), hasReturnUrl);
//包含通知地址时
if(hasReturnUrl){
// 重定向
response.sendRedirect(payMchNotifyService.createReturnUrl(payOrder, mchConfigContext.getMchInfo().getPrivateKey()));
return null;
}else{
//跳转到支付成功页面
return this.toReturnPage(null);
}
} catch (BizException e) {
log.error("{}, payOrderId={}, BizException", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
} catch (ResponseException e) {
log.error("{}, payOrderId={}, ResponseException", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
} catch (Exception e) {
log.error("{}, payOrderId={}, 系统异常", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
}
}
/** 异步回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/pay/notify/{ifCode}", "/api/pay/notify/{ifCode}/{payOrderId}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "payOrderId", required = false) String urlOrderId){
String payOrderId = null;
String logPrefix = "进入[" +ifCode+ "]支付回调:urlOrderId:["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
IChannelNoticeService payNotifyService = SpringBeansUtil.getBean(ifCode + "ChannelNoticeService", IChannelNoticeService.class);
// 支付通道接口实现不存在
if(payNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析订单号 和 请求参数
MutablePair<String, Object> mutablePair = payNotifyService.parseParams(request, urlOrderId, IChannelNoticeService.NoticeTypeEnum.DO_NOTIFY);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
payOrderId = mutablePair.left;
log.info("{}, 解析数据为:payOrderId:{}, params:{}", logPrefix, payOrderId, mutablePair.getRight());
if(StringUtils.isNotEmpty(urlOrderId) && !urlOrderId.equals(payOrderId)){
log.error("{}, 订单号不匹配. urlOrderId={}, payOrderId={} ", logPrefix, urlOrderId, payOrderId);
throw new BizException("订单号不匹配!");
}
//获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(payOrderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. payOrderId={} ", logPrefix, payOrderId);
return payNotifyService.doNotifyOrderNotExists(request);
}
//查询出商户的配置信息
String mchNo = payOrder.getMchNo();
//查询出商户配置参数
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(mchNo);
//调起接口的回调判断
ChannelRetMsg notifyResult = payNotifyService.doNotice(request, mutablePair.getRight(), payOrder, mchConfigContext, IChannelNoticeService.NoticeTypeEnum.DO_NOTIFY);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
boolean updateOrderSuccess = true; //默认更新成功
// 订单是 【支付中状态】
if(payOrder.getState() == PayOrder.STATE_ING) {
//明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == notifyResult.getChannelState()) {
updateOrderSuccess = payOrderService.updateIng2Success(payOrderId, notifyResult.getChannelOrderId(), notifyResult.getChannelUserId());
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == notifyResult.getChannelState()) {
updateOrderSuccess = payOrderService.updateIng2Fail(payOrderId, notifyResult.getChannelOrderId(), notifyResult.getChannelErrCode(), notifyResult.getChannelErrMsg());
}
}
// 更新订单 异常
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
return payNotifyService.doNotifyOrderStateUpdateFail(request);
}
//订单支付成功 需要MQ通知下游商户
if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
payOrder.setState(PayOrder.STATE_SUCCESS);
payMchNotifyService.payOrderNotify(payOrder);
}
log.info("===== {}, 订单通知完成。 payOrderId={}, parseState = {} =====", logPrefix, payOrderId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, payOrderId={}, BizException", logPrefix, payOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, payOrderId={}, ResponseException", logPrefix, payOrderId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, payOrderId={}, 系统异常", logPrefix, payOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
/* 跳转到支付成功页面 **/
private String toReturnPage(String errInfo){
return "cashier/returnPage";
}
}
/*
* 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.ctrl.payorder;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.rqrs.QueryPayOrderRQ;
import com.jeequan.jeepay.pay.rqrs.QueryPayOrderRS;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 商户查单controller
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:26
*/
@Slf4j
@RestController
public class QueryOrderController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextService configContextService;
/**
* 查单接口
* **/
@RequestMapping("/api/pay/query")
public ApiRes queryOrder(){
//获取参数 & 验签
QueryPayOrderRQ rq = getRQByWithMchSign(QueryPayOrderRQ.class);
if(StringUtils.isAllEmpty(rq.getMchOrderNo(), rq.getPayOrderId())){
throw new BizException("mchOrderNo 和 payOrderId不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(rq.getMchNo(), rq.getPayOrderId(), rq.getMchOrderNo());
if(payOrder == null){
throw new BizException("订单不存在");
}
QueryPayOrderRS bizRes = QueryPayOrderRS.buildByPayOrder(payOrder);
return ApiRes.okWithSign(bizRes, configContextService.getMchConfigContext(rq.getMchNo()).getMchInfo().getPrivateKey());
}
}
/*
* 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.ctrl.payorder;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.entity.PayWay;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AutoBarOrderRQ;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.service.impl.PayWayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 统一下单 controller
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:27
*/
@Slf4j
@RestController
public class UnifiedOrderController extends AbstractPayOrderController {
@Autowired private PayWayService payWayService;
@Autowired private ConfigContextService configContextService;
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/unifiedOrder")
public ApiRes unifiedOrder(){
//获取参数 & 验签
UnifiedOrderRQ rq = getRQByWithMchSign(UnifiedOrderRQ.class);
UnifiedOrderRQ bizRQ = buildBizRQ(rq);
//实现子类的res
ApiRes apiRes = unifiedOrder(bizRQ.getWayCode(), bizRQ);
if(apiRes.getData() == null){
return apiRes;
}
UnifiedOrderRS bizRes = (UnifiedOrderRS)apiRes.getData();
//聚合接口,返回的参数
UnifiedOrderRS res = new UnifiedOrderRS();
BeanUtils.copyProperties(bizRes, res);
//只有 订单生成(QR_CASHIER) || 支付中 || 支付成功返回该数据
if(bizRes.getOrderState() != null && (bizRes.getOrderState() == PayOrder.STATE_INIT || bizRes.getOrderState() == PayOrder.STATE_ING || bizRes.getOrderState() == PayOrder.STATE_SUCCESS) ){
res.setPayDataType(bizRes.buildPayDataType());
res.setPayData(bizRes.buildPayData());
}
return ApiRes.okWithSign(res, configContextService.getMchConfigContext(rq.getMchNo()).getMchInfo().getPrivateKey());
}
private UnifiedOrderRQ buildBizRQ(UnifiedOrderRQ rq){
//支付方式 比如: ali_bar
String wayCode = rq.getWayCode();
//jsapi 收银台聚合支付场景 (不校验是否存在payWayCode)
if(CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
return rq.buildBizRQ();
}
//如果是自动分类条码
if(CS.PAY_WAY_CODE.AUTO_BAR.equals(wayCode)){
AutoBarOrderRQ bizRQ = (AutoBarOrderRQ)rq.buildBizRQ();
wayCode = JeepayKit.getPayWayCodeByBarCode(bizRQ.getAuthCode());
rq.setWayCode(wayCode);
}
if(payWayService.count(PayWay.gw().eq(PayWay::getWayCode, wayCode)) <= 0){
throw new BizException("不支持的支付方式");
}
//转换为 bizRQ
return rq.buildBizRQ();
}
}
/*
* 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.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliBarOrderRQ;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 支付宝 条码支付 controller
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class AliBarOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/aliBarOrder")
public ApiRes aliBarOrder(){
//获取参数 & 验证
AliBarOrderRQ bizRQ = getRQByWithMchSign(AliBarOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.ALI_BAR, bizRQ);
}
}
/*
* 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.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliJsapiOrderRQ;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 支付宝 jspai controller
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class AliJsapiOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/aliJsapiOrder")
public ApiRes aliJsapiOrder(){
//获取参数 & 验证
AliJsapiOrderRQ bizRQ = getRQByWithMchSign(AliJsapiOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.ALI_JSAPI, bizRQ);
}
}
/*
* 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.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfBarOrderRQ;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 云闪付 条码支付 controller
*
* @author pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class YsfBarOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/ysfBarOrder")
public ApiRes aliBarOrder(){
//获取参数 & 验证
YsfBarOrderRQ bizRQ = getRQByWithMchSign(YsfBarOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.YSF_BAR, bizRQ);
}
}
/*
* 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.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.YsfJsapiOrderRQ;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 云闪付 jsapi支付 controller
*
* @author pangxiaoyu
* @site https://www.jeepay.vip
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class YsfJsapiOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/ysfJsapiOrder")
public ApiRes aliJsapiOrder(){
//获取参数 & 验证
YsfJsapiOrderRQ bizRQ = getRQByWithMchSign(YsfJsapiOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.YSF_JSAPI, bizRQ);
}
}
/*
* 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.ctrl.qr;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.channel.IChannelUserService;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.ChannelUserIdRQ;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URLEncoder;
/*
* 商户获取渠道用户ID接口
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:27
*/
@RestController
@RequestMapping("/api/channelUserId")
public class ChannelUserIdController extends AbstractPayOrderController {
@Autowired private ConfigContextService configContextService;
@Autowired private SysConfigService sysConfigService;
/** 重定向到微信地址 **/
@RequestMapping("/jump")
public void jump() throws Exception {
//获取请求数据
ChannelUserIdRQ rq = getRQByWithMchSign(ChannelUserIdRQ.class);
String ifCode = "AUTO".equalsIgnoreCase(rq.getIfCode()) ? getIfCodeByUA() : rq.getIfCode();
// 获取接口
IChannelUserService channelUserService = SpringBeansUtil.getBean(ifCode + "ChannelUserService", IChannelUserService.class);
if(channelUserService == null){
throw new BizException("不支持的客户端");
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("mchNo", rq.getMchNo());
jsonObject.put("ifCode", ifCode);
jsonObject.put("redirectUrl", rq.getRedirectUrl());
//回调地址
String callbackUrl = sysConfigService.getDBApplicationConfig().genMchChannelUserIdApiOauth2RedirectUrlEncode(jsonObject);
//获取商户配置信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(rq.getMchNo());
String redirectUrl = channelUserService.buildUserRedirectUrl(callbackUrl, mchConfigContext);
response.sendRedirect(redirectUrl);
}
/** 回调地址 **/
@RequestMapping("/oauth2Callback/{aesData}")
public void oauth2Callback(@PathVariable("aesData") String aesData) throws Exception {
JSONObject callbackData = JSON.parseObject(JeepayKit.aesDecode(aesData));
String mchNo = callbackData.getString("mchNo");
String ifCode = callbackData.getString("ifCode");
String redirectUrl = callbackData.getString("redirectUrl");
// 获取接口
IChannelUserService channelUserService = SpringBeansUtil.getBean(ifCode + "ChannelUserService", IChannelUserService.class);
if(channelUserService == null){
throw new BizException("不支持的客户端");
}
//获取商户配置信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(mchNo);
String channelUserId = channelUserService.getChannelUserId(getReqParamJSON(), mchConfigContext);
response.sendRedirect(redirectUrl + "?channelId=" + URLEncoder.encode(channelUserId));
}
/** 根据UA获取支付接口 */
private String getIfCodeByUA() {
String ua = request.getHeader("User-Agent");
// 无法识别扫码客户端
if (StringUtils.isBlank(ua)) return null;
if(ua.contains("Alipay")) {
return CS.IF_CODE.ALIPAY; //支付宝服务窗支付
}else if(ua.contains("MicroMessenger")) {
return CS.IF_CODE.WXPAY;
}
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.ctrl.qr;
import com.alipay.api.AlipayApiException;
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.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.channel.IChannelUserService;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.AliJsapiOrderRQ;
import com.jeequan.jeepay.pay.rqrs.payorder.payway.WxJsapiOrderRQ;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.model.MchConfigContext;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 聚合码支付二维码收银台controller
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/6/8 17:27
*/
@RestController
@RequestMapping("/api/cashier")
public class QrCashierController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextService configContextService;
@Autowired private SysConfigService sysConfigService;
@Autowired private PayMchNotifyService payMchNotifyService;
/**
* 返回 oauth2【获取uerId跳转地址】
* **/
@PostMapping("/redirectUrl")
public ApiRes redirectUrl(){
//查询订单
PayOrder payOrder = getPayOrder();
//回调地址
String redirectUrlEncode = sysConfigService.getDBApplicationConfig().genOauth2RedirectUrlEncode(payOrder.getPayOrderId());
//获取商户配置信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(payOrder.getMchNo());
//获取接口并返回数据
IChannelUserService channelUserService = getServiceByWayCode(getWayCode(), "ChannelUserService", IChannelUserService.class);
return ApiRes.ok(channelUserService.buildUserRedirectUrl(redirectUrlEncode, mchConfigContext));
}
/**
* 获取userId
* **/
@PostMapping("/channelUserId")
public ApiRes channelUserId() throws Exception {
//查询订单
PayOrder payOrder = getPayOrder();
String wayCode = getWayCode();
//获取商户配置信息
MchConfigContext mchConfigContext = configContextService.getMchConfigContext(payOrder.getMchNo());
IChannelUserService channelUserService = getServiceByWayCode(wayCode, "ChannelUserService", IChannelUserService.class);
return ApiRes.ok(channelUserService.getChannelUserId(getReqParamJSON(), mchConfigContext));
}
/**
* 获取订单支付信息
* **/
@PostMapping("/payOrderInfo")
public ApiRes payOrderInfo() throws Exception {
//查询订单
PayOrder payOrder = getPayOrder();
PayOrder resOrder = new PayOrder();
resOrder.setPayOrderId(payOrder.getPayOrderId());
resOrder.setMchOrderNo(payOrder.getMchOrderNo());
resOrder.setMchName(payOrder.getMchName());
resOrder.setAmount(payOrder.getAmount());
resOrder.setReturnUrl(payMchNotifyService.createReturnUrl(payOrder, configContextService.getMchConfigContext(payOrder.getMchNo()).getMchInfo().getPrivateKey()));
return ApiRes.ok(resOrder);
}
/** 调起下单接口, 返回支付数据包 **/
@PostMapping("/pay")
public ApiRes pay() throws Exception {
//查询订单
PayOrder payOrder = getPayOrder();
String wayCode = getWayCode();
ApiRes apiRes = null;
if(wayCode.equals(CS.PAY_WAY_CODE.ALI_JSAPI)){
apiRes = packageAlipayPayPackage(payOrder);
}else if(wayCode.equals(CS.PAY_WAY_CODE.WX_JSAPI)){
apiRes = packageWxpayPayPackage(payOrder);
}
return ApiRes.ok(apiRes);
}
/** 获取支付宝的 支付参数 **/
private ApiRes packageAlipayPayPackage(PayOrder payOrder) throws AlipayApiException {
String channelUserId = getValStringRequired("channelUserId");
AliJsapiOrderRQ rq = new AliJsapiOrderRQ();
rq.setBuyerUserId(channelUserId);
return this.unifiedOrder(getWayCode(), rq, payOrder);
}
/** 获取微信的 支付参数 **/
private ApiRes packageWxpayPayPackage(PayOrder payOrder) throws AlipayApiException {
String openId = getValStringRequired("channelUserId");
WxJsapiOrderRQ rq = new WxJsapiOrderRQ();
rq.setOpenid(openId);
return this.unifiedOrder(getWayCode(), rq, payOrder);
}
private String getToken(){
return getValStringRequired("token");
}
private String getWayCode(){
return getValStringRequired("wayCode");
}
private PayOrder getPayOrder(){
String payOrderId = JeepayKit.aesDecode(getToken()); //解析token
PayOrder payOrder = payOrderService.getById(payOrderId);
if(payOrder == null || payOrder.getState() != PayOrder.STATE_INIT){
throw new BizException("订单不存在或状态不正确");
}
return payOrderService.getById(payOrderId);
}
private <T> T getServiceByWayCode(String wayCode, String serviceSuffix, Class<T> cls){
if(CS.PAY_WAY_CODE.ALI_JSAPI.equals(wayCode)){
return SpringBeansUtil.getBean(CS.IF_CODE.ALIPAY + serviceSuffix, cls);
}else if(CS.PAY_WAY_CODE.WX_JSAPI.equals(wayCode)){
return SpringBeansUtil.getBean(CS.IF_CODE.WXPAY + serviceSuffix, cls);
}
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