银联支付之在线网关支付
准备下载SDK下载证书 SDK示例Demo修改acp_sdk.properties启动项目测试 项目集成添加依赖集成SDK支付类型与支付信息对象Mapper接口支付接口支付信息真
SEO靠我正支付接口回调接口加载配置信息添加OR修改配置添加数据库表添加支付类型信息 执行测试生成支付信息开始支付支付状态查询商户测试中心查询
准备
中国银联.开放平台 https://open.unionpay.SEO靠我com/tjweb/index
选择业务产品.全渠道.在线网关支付
下载SDK
在线网关支付页面下载SDK
选择合适的SDK,导入开发工具
下载证书
在线网关支付页面选择网关测试进入商户入网测试中心,在测试参数项SEO靠我下载证书
SDK示例Demo
SDK示例Demo导入开发工具 ,并进行相应配置
修改acp_sdk.properties
配置项目访问路径配置4个证书存放路径
#前台通知地址,填写银联前台通
SEO靠我知的地址,必须外网能访问
acpsdk.frontUrl=http://localhost:9999/ACPSample_B2C/frontRcvResponse###########
SEO靠我##############入网测试环境签名证书配置 ################################
# 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自
SEO靠我行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。
acpsdk.signCert.path=D:/certs/acp_test_
SEO靠我sign.pfx##########################加密证书配置################################
# 敏感信息加密证书路径(商户号开通了
SEO靠我商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用)
acpsdk.encryptCert.
SEO靠我path=d:/certs/acp_test_enc.cer##########################验签证书配置################################
SEO靠我 # 验签中级证书路径(银联提供)
acpsdk.middleCert.path=D:/certs/acp_test_middle.cer# 验签根证书路径(银联提供)
SEO靠我 acpsdk.rootCert.path=D:/certs/acp_test_root.cer
启动项目
配置后启动项目,示例中包含了各种Demo,后续项目集成可参考
测试
测试消费样例,卡号与支付密码SEO靠我在测试参数处
返回商户
项目集成
参考示例Demo集成项目即可
添加依赖
<!-- 集成web--><dependency><groupId>org.springframework.boot</groupId>
SEO靠我<artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframewor
SEO靠我k.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><!--
SEO靠我 集成redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-d
SEO靠我ata-redis</artifactId></dependency><!-- 集成mysql--><dependency><groupId>mysql</groupId><artifactId>my
SEO靠我sql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 集成lombok框架 --><dependency><gr
SEO靠我oupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependenc
SEO靠我y><!-- 集成mybatis-plus框架 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-
SEO靠我starter</artifactId><version>3.4.2</version></dependency><!-- 集成fastjson框架 --><dependency><groupId>c
SEO靠我om.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency><!-- 集成co
SEO靠我mmons工具类 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><
SEO靠我version>3.10</version></dependency><!-- SDK中SecureUtil类需要 --><dependency><groupId>org.bouncycastle</
SEO靠我groupId><artifactId>bcprov-jdk15on</artifactId><version>1.68</version></dependency>
集成SDK
将SDK,如Java VSEO靠我ersion SDK (通用版)\ACPSample_B2C\src\com\unionpay\acp\sdk目录下的类拷贝至项目
支付类型与支付信息对象
@Getter
@Setter
SEO靠我 public class PaymentType {/*** 主键*/@TableId(type = IdType.AUTO)private Long id;/*** 支付平台*/pr
SEO靠我ivate String typeName;/*** 同步通知*/private String frontUrl;/*** 异步通知*/private String backUrl;/*** 商户id
SEO靠我*/private String merchantId;/*** 创建时间*/private Timestamp createDate;/*** 修改时间*/private Timestamp upd
SEO靠我ateDate;
}
@Getter
@Setter
public class PaymentInfo {/*** 主键*/@Tabl
SEO靠我eId(type = IdType.AUTO)private Long id;/*** 支付类型*/private Long typeId;/*** 订单编号*/private String orde
SEO靠我rId;/*** 第三方平台支付id*/private String platformorderId;/*** 价格 以分为单位*/private Long price;/*** 支付来源*/priv
SEO靠我ate String source;/*** 支付*/private Integer state;/*** 支付报文*/private String payMessage;/*** 创建时间*/pri
SEO靠我vate Timestamp createDate;/*** 修改时间*/private Timestamp updateDate;
}
Mapper接口
@Mapper
SEO靠我public interface PaymentInfoDao extends BaseMapper<PaymentInfo> {}
@Mapper
public i
SEO靠我nterface PaymentTypeDao extends BaseMapper<PaymentType> {}
支付接口
@Controller
public class PayCo
SEO靠我ntroller {@Autowiredprivate PaymentInfoServiceImpl paymentInfoService;@Autowiredprivate PayImplServi
SEO靠我ce payService;/*** 生成订单支付信息,返回与此支付信息关联的随机token** @param paymentInfo* @return*/@RequestMapping("/gene
SEO靠我ratePaymentInfo")@ResponseBodypublic Object generatePaymentInfo(@RequestBody PaymentInfo paymentInfo
SEO靠我) {return paymentInfoService.generatePaymentInfo(paymentInfo);}/*** 跳转支付网关** @param request* @param
SEO靠我token 订单支付信息关联的token* @param resp* @throws IOException*/@RequestMapping("/payGateway")public void pa
SEO靠我yGateway(HttpServletRequest request, String token, HttpServletResponse resp) {paymentInfoService.get
SEO靠我PaymentInfoToken(token,request,resp);}/*** 查询订单支付状态* @param orderId* @param txnTime* @param resp*/@R
SEO靠我equestMapping("/queryPayStatus")public void queryPayStatus(String orderId, String txnTime, HttpServl
SEO靠我etResponse resp) {payService.queryPayStatus(orderId, txnTime, resp);}}
public interface Pay
SEO靠我Service {/*** 支付接口* @param paymentInfo* @return*/public String pay(PaymentInfo paymentInfo);/*** 查询订
SEO靠我单支付状态* @param orderId* @param txnTime* @param resp*/void queryPayStatus(String orderId,String txnTim
SEO靠我e, HttpServletResponse resp);
}
@Slf4j
@Service
public class PayImp
SEO靠我lService implements PayService {@Autowiredprivate PaymentTypeDao paymentTypeDao;@Autowiredprivate Yi
SEO靠我nLianPay yinLianPay;@Overridepublic String pay(PaymentInfo paymentInfo) {Long typeId = paymentInfo.g
SEO靠我etTypeId();PaymentType paymentType = paymentTypeDao.selectById(typeId);if (paymentType == null) {log
SEO靠我.error("PaymentType is null");return null;}String typeName = paymentType.getTypeName();PayAdaptServi
SEO靠我ce payAdaptService = null;//依据支付类型分发支付接口switch (typeName) {case "yinlianPay":payAdaptService = yinLi
SEO靠我anPay;break;
// case "zhifubaoPay":
// payAdaptService = zhifubaoPay;
// bre
SEO靠我ak;default:break;}return payAdaptService.pay(paymentInfo, paymentType);}@Overridepublic void queryPa
SEO靠我yStatus(String orderId, String txnTime, HttpServletResponse resp) {Map<String, String> data = new Ha
SEO靠我shMap<String, String>();/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/data.put("version", DemoBase.versi
SEO靠我on); //版本号data.put("encoding", DemoBase.encoding); //字符集编码 可以使用UTF-8,GBK两种方式data.put("signMethod", S
SEO靠我DKConfig.getConfig().getSignMethod()); //签名方法data.put("txnType", "00"); //交易类型 00-默认data.put("txnSub
SEO靠我Type", "00"); //交易子类型 默认00data.put("bizType", "000201"); //业务类型 B2C网关支付,手机wap支付/***商户接入参数***/data.pu
SEO靠我t("merId", "777290058110048"); //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试data.put("accessType", "0"); //
SEO靠我接入类型,商户接入固定填0,不需修改/***要调通交易以下字段必须修改***/data.put("orderId", orderId); //****商户订单号,每次发交易测试需修改为被查询的交易的订
SEO靠我单号data.put("txnTime", txnTime); //****订单发送时间,每次发交易测试需修改为被查询的交易的订单发送时间/**请求参数设置完毕,以下对请求参数进行签名并发送http
SEO靠我post请求,接收同步应答报文------------->**/Map<String, String> reqData = AcpService.sign(data, DemoBase.encodin
SEO靠我g);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String url = SDKConfig.getConfig().getSin
SEO靠我gleQueryUrl();// 交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.singleQueryUrl//这里调用signData之后,调用su
SEO靠我bmitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过Map<String, String> rspData = AcpService.post(reqDa
SEO靠我ta, url, DemoBase.encoding);/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**///应答码规范参考open.un
SEO靠我ionpay.com帮助中心 下载 产品接口规范 《平台接入接口规范-第5部分-附录》if (!rspData.isEmpty()) {if (AcpService.validate(rspData,
SEO靠我 DemoBase.encoding)) {LogUtil.writeLog("验证签名成功");if ("00".equals(rspData.get("respCode"))) {//如果查询交易
SEO靠我成功//处理被查询交易的应答码逻辑String origRespCode = rspData.get("origRespCode");if ("00".equals(origRespCode)) {/
SEO靠我/交易成功,更新商户订单状态//TODOlog.info("交易成功,更新商户订单状态");} else if ("03".equals(origRespCode) || "04".equals(or
SEO靠我igRespCode) || "05".equals(origRespCode)) {//需再次发起交易状态查询交易//TODOlog.info("需再次发起交易状态查询交易");} else {//
SEO靠我//TODOlog.info("其他应答码为失败请排查原因");}} else {//查询交易本身失败,或者未查到原交易,检查查询交易报文要素//TODOlog.info("查询交易本身失败,或者未查
SEO靠我到原交易,检查查询交易报文要素");}} else {log.info("验证签名失败");//TODO 检查验证签名失败的原因}} else {//未返回正确的http状态log.info("未获取
SEO靠我到返回报文或返回http状态码非200");}String reqMessage = DemoBase.genHtmlResult(reqData);String rspMessage = DemoB
SEO靠我ase.genHtmlResult(rspData);try {resp.setContentType("text/html;charset=utf-8");resp.getWriter().writ
SEO靠我e("</br>请求报文:<br/>" + reqMessage + "<br/>" + "应答报文:</br>" + rspMessage + "");} catch (IOException e)
SEO靠我 {e.printStackTrace();}}
}
支付信息
public interface PaymentInfoService {/*** 生成订单支付信息,返回与此支付信息关联的
SEO靠我随机token* @param paymentInfo* @return*/public Map<String, Object> generatePaymentInfo(@RequestBody Pa
SEO靠我ymentInfo paymentInfo);/*** 通过token获取支付信息* @param token* @return*/public void getPaymentInfoToken(St
SEO靠我ring token, HttpServletRequest request, HttpServletResponse resp);/*** 使用orderId查找支付信息** @param orde
SEO靠我rId* @return*/public Map<String, Object> getByOrderIdPayInfo(@RequestParam("orderId") String orderId
SEO靠我);/*** 使用token查找支付信息** @param paymentInfo* @return*/public Map<String, Object> updatePayInfo(@Reques
SEO靠我tBody PaymentInfo paymentInfo);
}
@Slf4j
@Service
public class Paym
SEO靠我entInfoServiceImpl implements PaymentInfoService {@Autowiredprivate PaymentInfoDao paymentInfoDao;@A
SEO靠我utowiredprivate PayImplService payService;@Autowiredprivate StringRedisTemplate stringRedisTemplate;
SEO靠我@Overridepublic Map<String, Object> generatePaymentInfo(@RequestBody PaymentInfo paymentInfo) {payme
SEO靠我ntInfo.setCreateDate(new Timestamp(System.currentTimeMillis()));paymentInfo.setUpdateDate(new Timest
SEO靠我amp(System.currentTimeMillis()));int insert = paymentInfoDao.insert(paymentInfo);if (insert != 1 ||
SEO靠我paymentInfo.getId() == null) {return ResponseUtils.back(0, "系统错误");}String token = "pay-" + UUID.ran
SEO靠我domUUID();stringRedisTemplate.opsForValue().set(token, paymentInfo.getId() + "");stringRedisTemplate
SEO靠我.expire(token, 900, TimeUnit.SECONDS);return ResponseUtils.back(1, token);}@Overridepublic void getP
SEO靠我aymentInfoToken(String token, HttpServletRequest request, HttpServletResponse resp) {resp.setContent
SEO靠我Type("text/html;charset=utf-8");PrintWriter out = null;//从redis获取支付信息String payInfoId = stringRedisT
SEO靠我emplate.opsForValue().get(token);Long newPayInfoId = Long.parseLong(payInfoId);// redis信息中取出支付id查询数据
SEO靠我库PaymentInfo paymentInfo = paymentInfoDao.selectById(newPayInfoId);//开始支付String html = payService.pa
SEO靠我y(paymentInfo);try {out = resp.getWriter();} catch (IOException e) {e.printStackTrace();} finally {o
SEO靠我ut.println(html);out.close();}}@Overridepublic Map<String, Object> getByOrderIdPayInfo(@RequestParam
SEO靠我("orderId") String orderId) {QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();queryWrap
SEO靠我per.eq("order_id", orderId);PaymentInfo paymentInfo = paymentInfoDao.selectOne(queryWrapper);if (pay
SEO靠我mentInfo == null) {return ResponseUtils.back(0, "未查询到支付信息");}return ResponseUtils.back(1, paymentInf
SEO靠我o);}@Overridepublic Map<String, Object> updatePayInfo(@RequestBody PaymentInfo paymentInfo) {payment
SEO靠我InfoDao.updateById(paymentInfo);return ResponseUtils.back(1, "");}}
真正支付接口
public interface PayAdaptSe
SEO靠我rvice {/*** 通用支付接口适配器* @param paymentInfo* @param paymentType* @return*/public String pay(PaymentInf
SEO靠我o paymentInfo,PaymentType paymentType );
}
@Slf4j
@Service
public cl
SEO靠我ass YinLianPay implements PayAdaptService {@Overridepublic String pay(PaymentInfo paymentInfo, Payme
SEO靠我ntType paymentType) {return setPay(paymentType, paymentInfo);}public String setPay(PaymentType payme
SEO靠我ntType, PaymentInfo paymentInfo) {Map<String, String> requestData = new HashMap<String, String>();/*
SEO靠我**银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/requestData.put("version", DemoBase.version); //版本号,全渠道默认值re
SEO靠我questData.put("encoding", DemoBase.encoding); //字符集编码,可以使用UTF-8,GBK两种方式requestData.put("signMethod",
SEO靠我 SDKConfig.getConfig().getSignMethod()); //签名方法requestData.put("txnType", "01"); //交易类型 ,01:消费reques
SEO靠我tData.put("txnSubType", "01"); //交易子类型, 01:自助消费requestData.put("bizType", "000201"); //业务类型,B2C网关支付,
SEO靠我手机wap支付requestData.put("channelType", "07"); //渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板 08:手机/***商户接入参数**
SEO靠我*/requestData.put("merId", paymentType.getMerchantId()); //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号req
SEO靠我uestData.put("accessType", "0"); //接入类型,0:直连商户requestData.put("orderId", paymentInfo.getOrderId());
SEO靠我//商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则requestData.put("txnTime", DemoBase.getCurrentTime()); //订单发送时间
SEO靠我,取系统时间,格式为yyyyMMddHHmmss,必须取当前时间,否则会报txnTime无效requestData.put("currencyCode", "156"); //交易币种(境内商户一般是
SEO靠我156 人民币)requestData.put("txnAmt", paymentInfo.getPrice().toString()); //交易金额,单位分,不要带小数点//requestData
SEO靠我.put("reqReserved", "透传字段"); //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样
SEO靠我返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出
SEO靠我现的等号不会导致解析失败可以不用管)。requestData.put("riskRateInfo", "{commodityName=测试商品名称}");//前台通知地址 (需设置为外网能访问 htt
SEO靠我p https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址//如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限//异步通知参数详
SEO靠我见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知requestData.put("frontUrl", paymentType.getFron
SEO靠我tUrl());//后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知//后台通知参数详见o
SEO靠我pen.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知//注意:1.需设置为外网能访问,否则收不到通知 2.http https均可 3.收单后台通知后
SEO靠我需要10秒内返回http200或302状态码// 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,
SEO靠我1,2,4分钟。// 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败req
SEO靠我uestData.put("backUrl", paymentType.getBackUrl());// 订单超时时间。// 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银
SEO靠我行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。// 此时间建议取支付时的北京时间加15分钟。// 超过超时时间调查询接口应答origRespCode不是A6或者00的就
SEO靠我可以判断为失败。requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTi
SEO靠我meMillis() + 15 * 60 * 1000));////// 报文中特殊用法请查看 special_use_purchase.txt/////**请求参数设置完毕,以下对请求参数进行签名并
SEO靠我生成html表单,将表单写入浏览器跳转打开银联页面**/Map<String, String> submitFromData = AcpService.sign(requestData, DemoBa
SEO靠我se.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String requestFrontUrl = SDKCo
SEO靠我nfig.getConfig().getFrontRequestUrl(); //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUr
SEO靠我lString html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, DemoBase.encoding); //
SEO靠我生成自动跳转的Html表单log.info("-------------------------报文开始------------------------");LogUtil.writeLog("打印请
SEO靠我求HTML,此为请求报文,为联调排查问题的依据:" + html);log.info("-------------------------报文结束------------------------");
SEO靠我//将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
SEO靠我return html;}
}
回调接口
@RequestMapping("/pay/callback")
@RestController
public c
SEO靠我lass YinLianCallbackController {@Autowiredprivate YinLianCallbackService yinLianCallbackService;/***
SEO靠我 功能说明:同步回调** @return*/@RequestMapping("/syn")public String syn(HttpServletRequest request) {Map<Stri
SEO靠我ng, String> synResultMap = yinLianCallbackService.syn(request);String encoding = request.getParamete
SEO靠我r(SDKConstants.param_encoding);if (!AcpService.validate(synResultMap, encoding)) {LogUtil.writeLog("
SEO靠我验证签名结果[失败].");return "支付失败,支付金额: " + Double.parseDouble(synResultMap.get("txnAmt")) / 100 + " , 订单号:
SEO靠我 " + synResultMap.get("orderId");}LogUtil.writeLog("验证签名结果[成功].");return "支付金额: " + Double.parseDoub
SEO靠我le(synResultMap.get("txnAmt")) / 100 + " , 订单号: " + synResultMap.get("orderId");}/*** 功能说明:异步回调** @r
SEO靠我eturn*/@RequestMapping("/asyn")public String asyn(HttpServletRequest request) {return yinLianCallbac
SEO靠我kService.asyn(request);}}
public interface CallbackService {/*** 同步回调* * @return*/public Ma
SEO靠我p<String, String> syn(HttpServletRequest request);/*** 异步回调* * @return*/public String asyn(HttpServl
SEO靠我etRequest request);
}
@Slf4j
@Service
public class YinLianCallbackSe
SEO靠我rvice implements CallbackService {private static final String PAY_SUCCESS = "ok";private static fina
SEO靠我l String PAY_FAIL = "fail";@Autowiredprivate PaymentInfoDao paymentInfoDao;public static Map<String,
SEO靠我 String> valideData(final HttpServletRequest req, String encoding) {// 获取银联通知服务器发送的后台通知参数Map<String,
SEO靠我 String> reqParam = getAllRequestParam(req);LogUtil.printRequestLog(reqParam);Map<String, String> va
SEO靠我lideData = null;if (null != reqParam && !reqParam.isEmpty()) {Iterator<Entry<String, String>> it = r
SEO靠我eqParam.entrySet().iterator();valideData = new HashMap<>(reqParam.size());while (it.hasNext()) {Entr
SEO靠我y<String, String> e = it.next();String key = e.getKey();String value = e.getValue();try {value = new
SEO靠我 String(value.getBytes(encoding), encoding);} catch (Exception e2) {// TODO: handle exception}valide
SEO靠我Data.put(key, value);}}return valideData;}/*** 获取请求参数中所有的信息** @param request* @return*/public static
SEO靠我 Map<String, String> getAllRequestParam(final HttpServletRequest request) {Map<String, String> res =
SEO靠我 new HashMap<>();Enumeration<?> temp = request.getParameterNames();if (null != temp) {while (temp.ha
SEO靠我sMoreElements()) {String en = (String) temp.nextElement();String value = request.getParameter(en);re
SEO靠我s.put(en, value);// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>if (res.get(en) == null ||
SEO靠我"".equals(res.get(en))) {// System.out.println("======为空的字段名===="+en);res.remove(en);}}}return res;}
SEO靠我@Overridepublic Map<String, String> syn(HttpServletRequest req) {log.info("FrontRcvResponse前台接收报文返回开
SEO靠我始");try {String encoding = req.getParameter(SDKConstants.param_encoding);Map<String, String> valideD
SEO靠我ata = valideData(req, encoding);return valideData;} catch (Exception e) {log.info("FrontRcvResponse前
SEO靠我台接收报文返回ERROR:{}", e);return null;} finally {log.info("FrontRcvResponse前台接收报文返回结束");}}@Overridepublic
SEO靠我 String asyn(HttpServletRequest req) {log.info("BackRcvResponse接收后台通知开始");String encoding = req.getP
SEO靠我arameter(SDKConstants.param_encoding);Map<String, String> valideData = valideData(req, encoding);//
SEO靠我重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过if (!AcpService.validate(valideData, encoding)) {log.info("验证签名结
SEO靠我果[失败]");// 验签失败,需解决验签问题return PAY_FAIL;}log.info("验证签名结果[成功]");// 【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户
SEO靠我订单状态String orderId = valideData.get("orderId"); // 获取后台通知的数据,其他字段也可用类似方式获取QueryWrapper<PaymentInfo>
SEO靠我queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_id", orderId);PaymentInfo paymentInfo = p
SEO靠我aymentInfoDao.selectOne(queryWrapper);if (paymentInfo == null) {log.error("异步通知,订单id:{},无此订单", order
SEO靠我Id);return PAY_FAIL;}Integer state = paymentInfo.getState();if (state.equals(1)) {log.error("异步通知,订单
SEO靠我id:{},状态已经为已支付,不能在修改为已支付.", orderId);return PAY_SUCCESS;}paymentInfo.setPlatformorderId(valideData.g
SEO靠我et("queryId"));paymentInfo.setUpdateDate(new Timestamp(System.currentTimeMillis()));paymentInfo.setP
SEO靠我ayMessage(JSON.toJSONString(valideData));paymentInfo.setState(1);paymentInfoDao.updateById(paymentIn
SEO靠我fo);log.error("异步通知,订单id:{},状态已经为已支付,修改成功。", orderId);// 返回给银联服务器http 200 状态码return PAY_SUCCESS;}}
@Component
@Slf4j
public class AutoLoadRunner implements CommandLineRunner {@Ove
SEO靠我rridepublic void run(String... args) throws Exception {SDKConfig.getConfig().loadPropertiesFromSrc()
SEO靠我;log.info(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<");}
}
INFO 21948 --- [ rest
SEO靠我artedMain] cn.ybzy.pay.PayApplication : Started PayApplication in 4.861 seconds (JVM running for 6.5
SEO靠我15)INFO 21948 --- [ restartedMain] ACP_SDK_LOG : 从classpath: /C:/Users/admin/Desktop/pay/target/clas
SEO靠我ses/ 获取属性文件acp_sdk.propertiesINFO 21948 --- [ restartedMain] ACP_SDK_LOG : 开始从属性文件中加载配置项INFO 21948 -
SEO靠我-- [ restartedMain] ACP_SDK_LOG : 配置项:私钥签名证书路径==>acpsdk.signCert.path==>D:/certs/acp_test_sign.pfx 已
SEO靠我加载INFO 21948 --- [ restartedMain] ACP_SDK_LOG : 配置项:私钥签名证书密码==>acpsdk.signCert.pwd 已加载INFO 21948 ---
SEO靠我 [ restartedMain] ACP_SDK_LOG : 配置项:私钥签名证书类型==>acpsdk.signCert.type==>PKCS12 已加载INFO 21948 --- [ res
SEO靠我tartedMain] ACP_SDK_LOG : 配置项:敏感信息加密证书==>acpsdk.encryptCert.path==>d:/certs/acp_test_enc.cer 已加载INFO
SEO靠我 21948 --- [ restartedMain] cn.ybzy.pay.load.AutoLoadRunner : >>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<
SEO靠我<<<<<<
添加OR修改配置
修改acp_sdk.properties
将SDK示例项目配置文件复制到项目中,修改外网能访问的前台通知地址
#前台通知地址,填写银联前台通知的地址,必须外网能访问
SEO靠我 acpsdk.frontUrl=http://jackchen.imwork.net
修改application.yml
server:port: 8888spring:application:na
SEO靠我me: pay-serviceredis:host: 127.0.0.1password:port: 6379#数据库连接信息datasource:url: jdbc:mysql://127.0.0.
SEO靠我1:3306/pay?serverTimezone=UTCusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driver
CREATE TABLE `payment_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`type_id` int(11) DEFAULT NUL
SEO靠我L,`order_id` varchar(255) DEFAULT NULL,`platformorder_id` varchar(255) DEFAULT NULL,`price` decimal(
SEO靠我10,4) DEFAULT NULL,`source` varchar(255) DEFAULT NULL,`state` int(255) DEFAULT NULL,`pay_message` te
SEO靠我xt,`create_date` datetime DEFAULT NULL,`update_date` datetime DEFAULT NULL,PRIMARY KEY (`id`)
SEO靠我 ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8
CREATE TABLE `payment_type` (`id`
SEO靠我int(11) NOT NULL AUTO_INCREMENT,`type_name` varchar(255) DEFAULT NULL,`front_url` varchar(255) DEFAU
SEO靠我LT NULL,`back_url` varchar(255) DEFAULT NULL,`merchant_id` varchar(255) DEFAULT NULL,`create_date` d
SEO靠我atetime DEFAULT NULL,`update_date` datetime DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB
SEO靠我AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
添加支付类型信息
支付类型表添加数据作为支付类型的配置, 回调地址需被外网访问
INSERT INTO `pay`.`paymen
SEO靠我t_type`(`id`, `type_name`, `front_url`, `back_url`, `merchant_id`, `create_date`, `update_date`) VAL
SEO靠我UES (1, yinlianPay, http://jackchen.imwork.net/pay/callback/syn, http://jackchen.imwork.net/pay/call
SEO靠我back/asyn, 777290058110048, 2021-08-30 16:25:51, NULL);
执行测试
生成支付信息
请求http://jackchen.imwork.net/generaSEO靠我tePaymentInfo接口,生成支付信息,得到token
开始支付
以得到的token访问http://jackchen.imwork.net/payGateway?token=pay-3bb87b5SEO靠我2-35e9-4592-ae72-1516bebd8754接口跳转到支付网关
支付成功,返回商户
支付状态查询
访问https://jackchen.imwork.net/queryPayStatus?orSEO靠我derId=qwesdfsfdfds431&txnTime=20210830180004接口查询订单状态
商户测试中心查询
商户测试中心查询交易信息
可看到很重要的2个信息:回调地址