解决方案

银联支付之在线网关支付

seo靠我 2023-09-25 22:57:44

银联支付之在线网关支付

准备下载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.springframeworSEO靠我k.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><!--SEO靠我 集成redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-dSEO靠我ata-redis</artifactId></dependency><!-- 集成mysql--><dependency><groupId>mysql</groupId><artifactId>mySEO靠我sql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 集成lombok框架 --><dependency><grSEO靠我oupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependencSEO靠我y><!-- 集成mybatis-plus框架 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-SEO靠我starter</artifactId><version>3.4.2</version></dependency><!-- 集成fastjson框架 --><dependency><groupId>cSEO靠我om.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency><!-- 集成coSEO靠我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;/*** 支付平台*/prSEO靠我ivate String typeName;/*** 同步通知*/private String frontUrl;/*** 异步通知*/private String backUrl;/*** 商户idSEO靠我*/private String merchantId;/*** 创建时间*/private Timestamp createDate;/*** 修改时间*/private Timestamp updSEO靠我ateDate; } @Getter @Setter public class PaymentInfo {/*** 主键*/@TablSEO靠我eId(type = IdType.AUTO)private Long id;/*** 支付类型*/private Long typeId;/*** 订单编号*/private String ordeSEO靠我rId;/*** 第三方平台支付id*/private String platformorderId;/*** 价格 以分为单位*/private Long price;/*** 支付来源*/privSEO靠我ate String source;/*** 支付*/private Integer state;/*** 支付报文*/private String payMessage;/*** 创建时间*/priSEO靠我vate Timestamp createDate;/*** 修改时间*/private Timestamp updateDate; }

Mapper接口

@Mapper SEO靠我public interface PaymentInfoDao extends BaseMapper<PaymentInfo> {} @Mapper public iSEO靠我nterface PaymentTypeDao extends BaseMapper<PaymentType> {}

支付接口

@Controller public class PayCoSEO靠我ntroller {@Autowiredprivate PaymentInfoServiceImpl paymentInfoService;@Autowiredprivate PayImplServiSEO靠我ce payService;/*** 生成订单支付信息,返回与此支付信息关联的随机token** @param paymentInfo* @return*/@RequestMapping("/geneSEO靠我ratePaymentInfo")@ResponseBodypublic Object generatePaymentInfo(@RequestBody PaymentInfo paymentInfoSEO靠我) {return paymentInfoService.generatePaymentInfo(paymentInfo);}/*** 跳转支付网关** @param request* @param SEO靠我token 订单支付信息关联的token* @param resp* @throws IOException*/@RequestMapping("/payGateway")public void paSEO靠我yGateway(HttpServletRequest request, String token, HttpServletResponse resp) {paymentInfoService.getSEO靠我PaymentInfoToken(token,request,resp);}/*** 查询订单支付状态* @param orderId* @param txnTime* @param resp*/@RSEO靠我equestMapping("/queryPayStatus")public void queryPayStatus(String orderId, String txnTime, HttpServlSEO靠我etResponse resp) {payService.queryPayStatus(orderId, txnTime, resp);}} public interface PaySEO靠我Service {/*** 支付接口* @param paymentInfo* @return*/public String pay(PaymentInfo paymentInfo);/*** 查询订SEO靠我单支付状态* @param orderId* @param txnTime* @param resp*/void queryPayStatus(String orderId,String txnTimSEO靠我e, HttpServletResponse resp); } @Slf4j @Service public class PayImpSEO靠我lService implements PayService {@Autowiredprivate PaymentTypeDao paymentTypeDao;@Autowiredprivate YiSEO靠我nLianPay yinLianPay;@Overridepublic String pay(PaymentInfo paymentInfo) {Long typeId = paymentInfo.gSEO靠我etTypeId();PaymentType paymentType = paymentTypeDao.selectById(typeId);if (paymentType == null) {logSEO靠我.error("PaymentType is null");return null;}String typeName = paymentType.getTypeName();PayAdaptServiSEO靠我ce payAdaptService = null;//依据支付类型分发支付接口switch (typeName) {case "yinlianPay":payAdaptService = yinLiSEO靠我anPay;break; // case "zhifubaoPay": // payAdaptService = zhifubaoPay; // breSEO靠我ak;default:break;}return payAdaptService.pay(paymentInfo, paymentType);}@Overridepublic void queryPaSEO靠我yStatus(String orderId, String txnTime, HttpServletResponse resp) {Map<String, String> data = new HaSEO靠我shMap<String, String>();/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/data.put("version", DemoBase.versiSEO靠我on); //版本号data.put("encoding", DemoBase.encoding); //字符集编码 可以使用UTF-8,GBK两种方式data.put("signMethod", SSEO靠我DKConfig.getConfig().getSignMethod()); //签名方法data.put("txnType", "00"); //交易类型 00-默认data.put("txnSubSEO靠我Type", "00"); //交易子类型 默认00data.put("bizType", "000201"); //业务类型 B2C网关支付,手机wap支付/***商户接入参数***/data.puSEO靠我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.encodinSEO靠我g);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String url = SDKConfig.getConfig().getSinSEO靠我gleQueryUrl();// 交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.singleQueryUrl//这里调用signData之后,调用suSEO靠我bmitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过Map<String, String> rspData = AcpService.post(reqDaSEO靠我ta, url, DemoBase.encoding);/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**///应答码规范参考open.unSEO靠我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(orSEO靠我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 = DemoBSEO靠我ase.genHtmlResult(rspData);try {resp.setContentType("text/html;charset=utf-8");resp.getWriter().writSEO靠我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 PaSEO靠我ymentInfo paymentInfo);/*** 通过token获取支付信息* @param token* @return*/public void getPaymentInfoToken(StSEO靠我ring token, HttpServletRequest request, HttpServletResponse resp);/*** 使用orderId查找支付信息** @param ordeSEO靠我rId* @return*/public Map<String, Object> getByOrderIdPayInfo(@RequestParam("orderId") String orderIdSEO靠我);/*** 使用token查找支付信息** @param paymentInfo* @return*/public Map<String, Object> updatePayInfo(@RequesSEO靠我tBody PaymentInfo paymentInfo); } @Slf4j @Service public class PaymSEO靠我entInfoServiceImpl implements PaymentInfoService {@Autowiredprivate PaymentInfoDao paymentInfoDao;@ASEO靠我utowiredprivate PayImplService payService;@Autowiredprivate StringRedisTemplate stringRedisTemplate;SEO靠我@Overridepublic Map<String, Object> generatePaymentInfo(@RequestBody PaymentInfo paymentInfo) {paymeSEO靠我ntInfo.setCreateDate(new Timestamp(System.currentTimeMillis()));paymentInfo.setUpdateDate(new TimestSEO靠我amp(System.currentTimeMillis()));int insert = paymentInfoDao.insert(paymentInfo);if (insert != 1 || SEO靠我paymentInfo.getId() == null) {return ResponseUtils.back(0, "系统错误");}String token = "pay-" + UUID.ranSEO靠我domUUID();stringRedisTemplate.opsForValue().set(token, paymentInfo.getId() + "");stringRedisTemplateSEO靠我.expire(token, 900, TimeUnit.SECONDS);return ResponseUtils.back(1, token);}@Overridepublic void getPSEO靠我aymentInfoToken(String token, HttpServletRequest request, HttpServletResponse resp) {resp.setContentSEO靠我Type("text/html;charset=utf-8");PrintWriter out = null;//从redis获取支付信息String payInfoId = stringRedisTSEO靠我emplate.opsForValue().get(token);Long newPayInfoId = Long.parseLong(payInfoId);// redis信息中取出支付id查询数据SEO靠我库PaymentInfo paymentInfo = paymentInfoDao.selectById(newPayInfoId);//开始支付String html = payService.paSEO靠我y(paymentInfo);try {out = resp.getWriter();} catch (IOException e) {e.printStackTrace();} finally {oSEO靠我ut.println(html);out.close();}}@Overridepublic Map<String, Object> getByOrderIdPayInfo(@RequestParamSEO靠我("orderId") String orderId) {QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();queryWrapSEO靠我per.eq("order_id", orderId);PaymentInfo paymentInfo = paymentInfoDao.selectOne(queryWrapper);if (paySEO靠我mentInfo == null) {return ResponseUtils.back(0, "未查询到支付信息");}return ResponseUtils.back(1, paymentInfSEO靠我o);}@Overridepublic Map<String, Object> updatePayInfo(@RequestBody PaymentInfo paymentInfo) {paymentSEO靠我InfoDao.updateById(paymentInfo);return ResponseUtils.back(1, "");}}

真正支付接口

public interface PayAdaptSeSEO靠我rvice {/*** 通用支付接口适配器* @param paymentInfo* @param paymentType* @return*/public String pay(PaymentInfSEO靠我o paymentInfo,PaymentType paymentType ); } @Slf4j @Service public clSEO靠我ass YinLianPay implements PayAdaptService {@Overridepublic String pay(PaymentInfo paymentInfo, PaymeSEO靠我ntType paymentType) {return setPay(paymentType, paymentInfo);}public String setPay(PaymentType paymeSEO靠我ntType, PaymentInfo paymentInfo) {Map<String, String> requestData = new HashMap<String, String>();/*SEO靠我**银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/requestData.put("version", DemoBase.version); //版本号,全渠道默认值reSEO靠我questData.put("encoding", DemoBase.encoding); //字符集编码,可以使用UTF-8,GBK两种方式requestData.put("signMethod",SEO靠我 SDKConfig.getConfig().getSignMethod()); //签名方法requestData.put("txnType", "01"); //交易类型 ,01:消费requesSEO靠我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测试商户号reqSEO靠我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()); //交易金额,单位分,不要带小数点//requestDataSEO靠我.put("reqReserved", "透传字段"); //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样SEO靠我返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出SEO靠我现的等号不会导致解析失败可以不用管)。requestData.put("riskRateInfo", "{commodityName=测试商品名称}");//前台通知地址 (需设置为外网能访问 httSEO靠我p https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址//如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限//异步通知参数详SEO靠我见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知requestData.put("frontUrl", paymentType.getFronSEO靠我tUrl());//后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知//后台通知参数详见oSEO靠我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 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败reqSEO靠我uestData.put("backUrl", paymentType.getBackUrl());// 订单超时时间。// 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银SEO靠我行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。// 此时间建议取支付时的北京时间加15分钟。// 超过超时时间调查询接口应答origRespCode不是A6或者00的就SEO靠我可以判断为失败。requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTiSEO靠我meMillis() + 15 * 60 * 1000));////// 报文中特殊用法请查看 special_use_purchase.txt/////**请求参数设置完毕,以下对请求参数进行签名并SEO靠我生成html表单,将表单写入浏览器跳转打开银联页面**/Map<String, String> submitFromData = AcpService.sign(requestData, DemoBaSEO靠我se.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String requestFrontUrl = SDKCoSEO靠我nfig.getConfig().getFrontRequestUrl(); //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrSEO靠我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 cSEO靠我lass YinLianCallbackController {@Autowiredprivate YinLianCallbackService yinLianCallbackService;/***SEO靠我 功能说明:同步回调** @return*/@RequestMapping("/syn")public String syn(HttpServletRequest request) {Map<StriSEO靠我ng, String> synResultMap = yinLianCallbackService.syn(request);String encoding = request.getParameteSEO靠我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.parseDoubSEO靠我le(synResultMap.get("txnAmt")) / 100 + " , 订单号: " + synResultMap.get("orderId");}/*** 功能说明:异步回调** @rSEO靠我eturn*/@RequestMapping("/asyn")public String asyn(HttpServletRequest request) {return yinLianCallbacSEO靠我kService.asyn(request);}} public interface CallbackService {/*** 同步回调* * @return*/public MaSEO靠我p<String, String> syn(HttpServletRequest request);/*** 异步回调* * @return*/public String asyn(HttpServlSEO靠我etRequest request); } @Slf4j @Service public class YinLianCallbackSeSEO靠我rvice implements CallbackService {private static final String PAY_SUCCESS = "ok";private static finaSEO靠我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> vaSEO靠我lideData = null;if (null != reqParam && !reqParam.isEmpty()) {Iterator<Entry<String, String>> it = rSEO靠我eqParam.entrySet().iterator();valideData = new HashMap<>(reqParam.size());while (it.hasNext()) {EntrSEO靠我y<String, String> e = it.next();String key = e.getKey();String value = e.getValue();try {value = newSEO靠我 String(value.getBytes(encoding), encoding);} catch (Exception e2) {// TODO: handle exception}valideSEO靠我Data.put(key, value);}}return valideData;}/*** 获取请求参数中所有的信息** @param request* @return*/public staticSEO靠我 Map<String, String> getAllRequestParam(final HttpServletRequest request) {Map<String, String> res =SEO靠我 new HashMap<>();Enumeration<?> temp = request.getParameterNames();if (null != temp) {while (temp.haSEO靠我sMoreElements()) {String en = (String) temp.nextElement();String value = request.getParameter(en);reSEO靠我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> valideDSEO靠我ata = valideData(req, encoding);return valideData;} catch (Exception e) {log.info("FrontRcvResponse前SEO靠我台接收报文返回ERROR:{}", e);return null;} finally {log.info("FrontRcvResponse前台接收报文返回结束");}}@OverridepublicSEO靠我 String asyn(HttpServletRequest req) {log.info("BackRcvResponse接收后台通知开始");String encoding = req.getPSEO靠我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 = pSEO靠我aymentInfoDao.selectOne(queryWrapper);if (paymentInfo == null) {log.error("异步通知,订单id:{},无此订单", orderSEO靠我Id);return PAY_FAIL;}Integer state = paymentInfo.getState();if (state.equals(1)) {log.error("异步通知,订单SEO靠我id:{},状态已经为已支付,不能在修改为已支付.", orderId);return PAY_SUCCESS;}paymentInfo.setPlatformorderId(valideData.gSEO靠我et("queryId"));paymentInfo.setUpdateDate(new Timestamp(System.currentTimeMillis()));paymentInfo.setPSEO靠我ayMessage(JSON.toJSONString(valideData));paymentInfo.setState(1);paymentInfoDao.updateById(paymentInSEO靠我fo);log.error("异步通知,订单id:{},状态已经为已支付,修改成功。", orderId);// 返回给银联服务器http 200 状态码return PAY_SUCCESS;}}

加载SEO靠我配置信息

@Component @Slf4j public class AutoLoadRunner implements CommandLineRunner {@OveSEO靠我rridepublic void run(String... args) throws Exception {SDKConfig.getConfig().loadPropertiesFromSrc()SEO靠我;log.info(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<");} } INFO 21948 --- [ restSEO靠我artedMain] cn.ybzy.pay.PayApplication : Started PayApplication in 4.861 seconds (JVM running for 6.5SEO靠我15)INFO 21948 --- [ restartedMain] ACP_SDK_LOG : 从classpath: /C:/Users/admin/Desktop/pay/target/clasSEO靠我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 --- [ resSEO靠我tartedMain] ACP_SDK_LOG : 配置项:敏感信息加密证书==>acpsdk.encryptCert.path==>d:/certs/acp_test_enc.cer 已加载INFOSEO靠我 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:naSEO靠我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

SEO靠我加数据库表

CREATE TABLE `payment_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`type_id` int(11) DEFAULT NULSEO靠我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` teSEO靠我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) DEFAUSEO靠我LT NULL,`back_url` varchar(255) DEFAULT NULL,`merchant_id` varchar(255) DEFAULT NULL,`create_date` dSEO靠我atetime DEFAULT NULL,`update_date` datetime DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB SEO靠我AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

添加支付类型信息

支付类型表添加数据作为支付类型的配置, 回调地址需被外网访问

INSERT INTO `pay`.`paymenSEO靠我t_type`(`id`, `type_name`, `front_url`, `back_url`, `merchant_id`, `create_date`, `update_date`) VALSEO靠我UES (1, yinlianPay, http://jackchen.imwork.net/pay/callback/syn, http://jackchen.imwork.net/pay/callSEO靠我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个信息:回调地址

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2