银联支付之在线网关支付

准备

中国银联.开放平台 https://open.unionpay.com/tjweb/index

选择业务产品.全渠道.在线网关支付
在这里插入图片描述

下载SDK

在线网关支付页面下载SDK
在这里插入图片描述
选择合适的SDK,导入开发工具
在这里插入图片描述

下载证书

在线网关支付页面选择网关测试进入商户入网测试中心,在测试参数项下载证书
在这里插入图片描述

SDK示例Demo

SDK示例Demo导入开发工具 ,并进行相应配置

修改acp_sdk.properties

配置项目访问路径

配置4个证书存放路径
#前台通知地址,填写银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://localhost:9999/ACPSample_B2C/frontRcvResponse

#########################入网测试环境签名证书配置 ################################
# 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。
acpsdk.signCert.path=D:/certs/acp_test_sign.pfx

##########################加密证书配置################################
# 敏感信息加密证书路径(商户号开通了商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用)
acpsdk.encryptCert.path=d:/certs/acp_test_enc.cer

##########################验签证书配置################################
# 验签中级证书路径(银联提供)
acpsdk.middleCert.path=D:/certs/acp_test_middle.cer

# 验签根证书路径(银联提供)
acpsdk.rootCert.path=D:/certs/acp_test_root.cer

启动项目

配置后启动项目,示例中包含了各种Demo,后续项目集成可参考
在这里插入图片描述

测试

测试消费样例,卡号与支付密码在测试参数处
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
返回商户
在这里插入图片描述

项目集成

参考示例Demo集成项目即可

添加依赖

	  <!-- 集成web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
           <!-- 集成redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 集成mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 集成lombok框架 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 集成mybatis-plus框架 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!-- 集成fastjson框架 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <!-- 集成commons工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
        <!-- SDK中SecureUtil类需要 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.68</version>
        </dependency>

集成SDK

将SDK,如Java Version SDK (通用版)\ACPSample_B2C\src\com\unionpay\acp\sdk目录下的类拷贝至项目

支付类型与支付信息对象

@Getter
@Setter
public class PaymentType  {
	/**
	 * 主键
	 */
	@TableId(type = IdType.AUTO)
	private Long id;
	/**
	 * 支付平台
	 */
	private String typeName;
	/**
	 * 同步通知
	 */
	private String frontUrl;

	/**
	 * 异步通知
	 */
	private String backUrl;
	/**
	 * 商户id
	 */
	private String merchantId;
	/**
	 * 创建时间
	 */
	private Timestamp createDate;
	/**
	 * 修改时间
	 */
	private Timestamp updateDate;
}
@Getter
@Setter
public class PaymentInfo {
    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 支付类型
     */
    private Long typeId;
    /**
     * 订单编号
     */
    private String orderId;
    /**
     * 第三方平台支付id
     */
    private String platformorderId;
    /**
     * 价格 以分为单位
     */
    private Long price;
    /**
     * 支付来源
     */
    private String source;
    /**
     * 支付
     */
    private Integer state;
    /**
     * 支付报文
     */
    private String payMessage;

    /**
     * 创建时间
     */
    private Timestamp createDate;
    /**
     * 修改时间
     */
    private Timestamp updateDate;
}

Mapper接口

@Mapper
public interface PaymentInfoDao extends BaseMapper<PaymentInfo> {

}
@Mapper
public interface PaymentTypeDao extends BaseMapper<PaymentType> {

}

支付接口

@Controller
public class PayController {

    @Autowired
    private PaymentInfoServiceImpl paymentInfoService;

    @Autowired
    private PayImplService payService;

    /**
     * 生成订单支付信息,返回与此支付信息关联的随机token
     *
     * @param paymentInfo
     * @return
     */
    @RequestMapping("/generatePaymentInfo")
    @ResponseBody
    public Object generatePaymentInfo(@RequestBody PaymentInfo paymentInfo) {
        return paymentInfoService.generatePaymentInfo(paymentInfo);
    }

    /**
     * 跳转支付网关
     *
     * @param request
     * @param token   订单支付信息关联的token
     * @param resp
     * @throws IOException
     */
    @RequestMapping("/payGateway")
    public void payGateway(HttpServletRequest request, String token, HttpServletResponse resp)  {
            paymentInfoService.getPaymentInfoToken(token,request,resp);
    }

    /**
     * 查询订单支付状态
     * @param orderId
     * @param txnTime
     * @param resp
     */
    @RequestMapping("/queryPayStatus")
    public void queryPayStatus(String orderId, String txnTime, HttpServletResponse resp) {
        payService.queryPayStatus(orderId, txnTime, resp);
    }

}
public interface PayService {
	/**
	 * 支付接口
	 * @param paymentInfo
	 * @return
	 */
	public String pay(PaymentInfo paymentInfo);

	/**
	 * 查询订单支付状态
	 * @param orderId
	 * @param txnTime
	 * @param resp
	 */
	void queryPayStatus(String orderId,String txnTime, HttpServletResponse resp);
}
@Slf4j
@Service
public class PayImplService implements PayService {

    @Autowired
    private PaymentTypeDao paymentTypeDao;

    @Autowired
    private YinLianPay yinLianPay;

    @Override
    public String pay(PaymentInfo paymentInfo) {
        Long typeId = paymentInfo.getTypeId();
        PaymentType paymentType = paymentTypeDao.selectById(typeId);
        if (paymentType == null) {
            log.error("PaymentType is null");
            return null;
        }

        String typeName = paymentType.getTypeName();
        PayAdaptService payAdaptService = null;
        //依据支付类型分发支付接口
        switch (typeName) {
            case "yinlianPay":
                payAdaptService = yinLianPay;
                break;
//			case "zhifubaoPay":
//				payAdaptService = zhifubaoPay;
//				break;
            default:
                break;
        }
        return payAdaptService.pay(paymentInfo, paymentType);
    }

    @Override
    public void queryPayStatus(String orderId, String txnTime, HttpServletResponse resp) {

        Map<String, String> data = new HashMap<String, String>();
        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        data.put("version", DemoBase.version);                 //版本号
        data.put("encoding", DemoBase.encoding);               //字符集编码 可以使用UTF-8,GBK两种方式
        data.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
        data.put("txnType", "00");                             //交易类型 00-默认
        data.put("txnSubType", "00");                          //交易子类型  默认00
        data.put("bizType", "000201");                         //业务类型 B2C网关支付,手机wap支付

        /***商户接入参数***/
        data.put("merId", "777290058110048");                  //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
        data.put("accessType", "0");                           //接入类型,商户接入固定填0,不需修改

        /***要调通交易以下字段必须修改***/
        data.put("orderId", orderId);                 //****商户订单号,每次发交易测试需修改为被查询的交易的订单号
        data.put("txnTime", txnTime);                 //****订单发送时间,每次发交易测试需修改为被查询的交易的订单发送时间

        /**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/

        Map<String, String> reqData = AcpService.sign(data, DemoBase.encoding);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。

        String url = SDKConfig.getConfig().getSingleQueryUrl();// 交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.singleQueryUrl
        //这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
        Map<String, String> rspData = AcpService.post(reqData, url, DemoBase.encoding);

        /**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if (!rspData.isEmpty()) {
            if (AcpService.validate(rspData, DemoBase.encoding)) {
                LogUtil.writeLog("验证签名成功");
                if ("00".equals(rspData.get("respCode"))) {//如果查询交易成功
                    //处理被查询交易的应答码逻辑
                    String origRespCode = rspData.get("origRespCode");
                    if ("00".equals(origRespCode)) {
                        //交易成功,更新商户订单状态
                        //TODO
                        log.info("交易成功,更新商户订单状态");
                    } else if ("03".equals(origRespCode) || "04".equals(origRespCode) || "05".equals(origRespCode)) {
                        //需再次发起交易状态查询交易
                        //TODO
                        log.info("需再次发起交易状态查询交易");
                    } else {
                        //
                        //TODO
                        log.info("其他应答码为失败请排查原因");
                    }
                } else {//查询交易本身失败,或者未查到原交易,检查查询交易报文要素
                    //TODO
                    log.info("查询交易本身失败,或者未查到原交易,检查查询交易报文要素");
                }
            } else {
                log.info("验证签名失败");
                //TODO 检查验证签名失败的原因
            }
        } else {
            //未返回正确的http状态
            log.info("未获取到返回报文或返回http状态码非200");
        }
        String reqMessage = DemoBase.genHtmlResult(reqData);
        String rspMessage = DemoBase.genHtmlResult(rspData);
        try {
            resp.setContentType("text/html;charset=utf-8");
            resp.getWriter().write("</br>请求报文:<br/>" + reqMessage + "<br/>" + "应答报文:</br>" + rspMessage + "");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

支付信息

public interface PaymentInfoService {

    /**
     * 生成订单支付信息,返回与此支付信息关联的随机token
     * @param paymentInfo
     * @return
     */
    public Map<String, Object> generatePaymentInfo(@RequestBody PaymentInfo paymentInfo);

    /**
     *  通过token获取支付信息
     * @param token
     * @return
     */
    public void getPaymentInfoToken(String token, HttpServletRequest request, HttpServletResponse resp);

    /**
     * 使用orderId查找支付信息
     *
     * @param orderId
     * @return
     */
    public Map<String, Object> getByOrderIdPayInfo(@RequestParam("orderId") String orderId);

    /**
     * 使用token查找支付信息
     *
     * @param paymentInfo
     * @return
     */
    public Map<String, Object> updatePayInfo(@RequestBody PaymentInfo paymentInfo);
}
@Slf4j
@Service
public class PaymentInfoServiceImpl implements PaymentInfoService {
    @Autowired
    private PaymentInfoDao paymentInfoDao;

    @Autowired
    private PayImplService payService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Map<String, Object> generatePaymentInfo(@RequestBody PaymentInfo paymentInfo) {
        paymentInfo.setCreateDate(new Timestamp(System.currentTimeMillis()));
        paymentInfo.setUpdateDate(new Timestamp(System.currentTimeMillis()));
        int insert = paymentInfoDao.insert(paymentInfo);
        if (insert != 1 || paymentInfo.getId() == null) {
            return ResponseUtils.back(0, "系统错误");
        }

        String token = "pay-" + UUID.randomUUID();
        stringRedisTemplate.opsForValue().set(token, paymentInfo.getId() + "");
        stringRedisTemplate.expire(token, 900, TimeUnit.SECONDS);
        return ResponseUtils.back(1, token);
    }

    @Override
    public void getPaymentInfoToken(String token, HttpServletRequest request, HttpServletResponse resp) {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = null;
        //从redis获取支付信息
        String payInfoId = stringRedisTemplate.opsForValue().get(token);
        Long newPayInfoId = Long.parseLong(payInfoId);
        // redis信息中取出支付id查询数据库
        PaymentInfo paymentInfo = paymentInfoDao.selectById(newPayInfoId);

        //开始支付
        String html = payService.pay(paymentInfo);

        try {
            out = resp.getWriter();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            out.println(html);
            out.close();
        }
    }

    @Override
    public Map<String, Object> getByOrderIdPayInfo(@RequestParam("orderId") String orderId) {
        QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id", orderId);
        PaymentInfo paymentInfo = paymentInfoDao.selectOne(queryWrapper);
        if (paymentInfo == null) {
            return ResponseUtils.back(0, "未查询到支付信息");
        }
        return ResponseUtils.back(1, paymentInfo);
    }

    @Override
    public Map<String, Object> updatePayInfo(@RequestBody PaymentInfo paymentInfo) {
        paymentInfoDao.updateById(paymentInfo);
        return ResponseUtils.back(1, "");
    }

}

真正支付接口

public interface PayAdaptService {
	/**
	 * 通用支付接口适配器
	 * @param paymentInfo
	 * @param paymentType
	 * @return
	 */
	public String pay(PaymentInfo paymentInfo,PaymentType paymentType );
}

@Slf4j
@Service
public class YinLianPay implements PayAdaptService {

    @Override
    public String pay(PaymentInfo paymentInfo, PaymentType paymentType) {
        return setPay(paymentType, paymentInfo);
    }

    public String setPay(PaymentType paymentType, PaymentInfo paymentInfo) {

        Map<String, String> requestData = new HashMap<String, String>();

        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        requestData.put("version", DemoBase.version);              //版本号,全渠道默认值
        requestData.put("encoding", DemoBase.encoding);              //字符集编码,可以使用UTF-8,GBK两种方式
        requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
        requestData.put("txnType", "01");                          //交易类型 ,01:消费
        requestData.put("txnSubType", "01");                          //交易子类型, 01:自助消费
        requestData.put("bizType", "000201");                      //业务类型,B2C网关支付,手机wap支付
        requestData.put("channelType", "07");                      //渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板  08:手机

        /***商户接入参数***/
        requestData.put("merId", paymentType.getMerchantId());                              //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
        requestData.put("accessType", "0");                          //接入类型,0:直连商户
        requestData.put("orderId", paymentInfo.getOrderId());             //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
        requestData.put("txnTime", DemoBase.getCurrentTime());        //订单发送时间,取系统时间,格式为yyyyMMddHHmmss,必须取当前时间,否则会报txnTime无效
        requestData.put("currencyCode", "156");                      //交易币种(境内商户一般是156 人民币)
        requestData.put("txnAmt", paymentInfo.getPrice().toString());                              //交易金额,单位分,不要带小数点
        //requestData.put("reqReserved", "透传字段");        		      //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。

        requestData.put("riskRateInfo", "{commodityName=测试商品名称}");

        //前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
        //如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
        //异步通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
        requestData.put("frontUrl", paymentType.getFrontUrl());

        //后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
        //后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
        //注意:1.需设置为外网能访问,否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码
        //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
        //    5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
        requestData.put("backUrl", paymentType.getBackUrl());

        // 订单超时时间。
        // 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。
        // 此时间建议取支付时的北京时间加15分钟。
        // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
        requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis() + 15 * 60 * 1000));

        //
        //
        //       报文中特殊用法请查看 special_use_purchase.txt
        //
        //

        /**请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面**/
        Map<String, String> submitFromData = AcpService.sign(requestData, DemoBase.encoding);  //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。

        String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
        String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, DemoBase.encoding);   //生成自动跳转的Html表单

        log.info("-------------------------报文开始------------------------");
        LogUtil.writeLog("打印请求HTML,此为请求报文,为联调排查问题的依据:" + html);
        log.info("-------------------------报文结束------------------------");
        //将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
        return html;
    }
}

回调接口

@RequestMapping("/pay/callback")
@RestController
public class YinLianCallbackController {

    @Autowired
    private YinLianCallbackService yinLianCallbackService;

    /**
     * 功能说明:同步回调
     *
     * @return
     */
    @RequestMapping("/syn")
    public String syn(HttpServletRequest request) {
        Map<String, String> synResultMap = yinLianCallbackService.syn(request);
        String encoding = request.getParameter(SDKConstants.param_encoding);
        if (!AcpService.validate(synResultMap, encoding)) {
            LogUtil.writeLog("验证签名结果[失败].");
			return "支付失败,支付金额: " + Double.parseDouble(synResultMap.get("txnAmt")) / 100 + " , 订单号: " + synResultMap.get("orderId");
        }
        LogUtil.writeLog("验证签名结果[成功].");
        return "支付金额: " + Double.parseDouble(synResultMap.get("txnAmt")) / 100 + " , 订单号: " + synResultMap.get("orderId");

    }

    /**
     * 功能说明:异步回调
     *
     * @return
     */
    @RequestMapping("/asyn")
    public String asyn(HttpServletRequest request) {
        return yinLianCallbackService.asyn(request);
    }

}
public interface CallbackService {
	/**
	 * 同步回调
	 * 
	 * @return
	 */
	public Map<String, String> syn(HttpServletRequest request);

	/**
	 * 异步回调
	 * 
	 * @return
	 */
	public String asyn(HttpServletRequest request);
}

@Slf4j
@Service
public class YinLianCallbackService implements CallbackService {

    private static final String PAY_SUCCESS = "ok";

    private static final String PAY_FAIL = "fail";

    @Autowired
    private PaymentInfoDao paymentInfoDao;

    public static Map<String, String> valideData(final HttpServletRequest req, String encoding) {
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(req);

        LogUtil.printRequestLog(reqParam);

        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<>(reqParam.size());
            while (it.hasNext()) {
                Entry<String, String> e = it.next();
                String key = e.getKey();
                String value = e.getValue();
                try {
                    value = new String(value.getBytes(encoding), encoding);
                } catch (Exception e2) {
                    // TODO: handle exception
                }
                valideData.put(key, value);
            }
        }
        return valideData;
    }

    /**
     * 获取请求参数中所有的信息
     *
     * @param request
     * @return
     */
    public static Map<String, String> getAllRequestParam(final HttpServletRequest request) {
        Map<String, String> res = new HashMap<>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
                if (res.get(en) == null || "".equals(res.get(en))) {
                    // System.out.println("======为空的字段名===="+en);
                    res.remove(en);
                }
            }
        }
        return res;
    }

    @Override
    public Map<String, String> syn(HttpServletRequest req) {
        log.info("FrontRcvResponse前台接收报文返回开始");
        try {
            String encoding = req.getParameter(SDKConstants.param_encoding);
            Map<String, String> valideData = valideData(req, encoding);
            return valideData;
        } catch (Exception e) {
            log.info("FrontRcvResponse前台接收报文返回ERROR:{}", e);
            return null;
        } finally {
            log.info("FrontRcvResponse前台接收报文返回结束");
        }

    }

    @Override
    public String asyn(HttpServletRequest req) {
        log.info("BackRcvResponse接收后台通知开始");

        String encoding = req.getParameter(SDKConstants.param_encoding);
        Map<String, String> valideData = valideData(req, encoding);
        // 重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
        if (!AcpService.validate(valideData, encoding)) {
            log.info("验证签名结果[失败]");
            // 验签失败,需解决验签问题
            return PAY_FAIL;
        }
        log.info("验证签名结果[成功]");

        // 【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态
        String orderId = valideData.get("orderId"); // 获取后台通知的数据,其他字段也可用类似方式获取
        QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id", orderId);
        PaymentInfo paymentInfo = paymentInfoDao.selectOne(queryWrapper);
        if (paymentInfo == null) {
            log.error("异步通知,订单id:{},无此订单", orderId);
            return PAY_FAIL;
        }

        Integer state = paymentInfo.getState();
        if (state.equals(1)) {
            log.error("异步通知,订单id:{},状态已经为已支付,不能在修改为已支付.", orderId);
            return PAY_SUCCESS;
        }

        paymentInfo.setPlatformorderId(valideData.get("queryId"));
        paymentInfo.setUpdateDate(new Timestamp(System.currentTimeMillis()));
        paymentInfo.setPayMessage(JSON.toJSONString(valideData));
        paymentInfo.setState(1);
        paymentInfoDao.updateById(paymentInfo);
        log.error("异步通知,订单id:{},状态已经为已支付,修改成功。", orderId);

        // 返回给银联服务器http 200 状态码
        return PAY_SUCCESS;
    }

}

加载配置信息

@Component
@Slf4j
public class AutoLoadRunner implements CommandLineRunner {

	@Override
	public void run(String... args) throws Exception {
		SDKConfig.getConfig().loadPropertiesFromSrc();
		log.info(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<");
	}
}
  INFO 21948 --- [  restartedMain] cn.ybzy.pay.PayApplication               : Started PayApplication in 4.861 seconds (JVM running for 6.515)
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 从classpath: /C:/Users/admin/Desktop/pay/target/classes/ 获取属性文件acp_sdk.properties
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 开始从属性文件中加载配置项
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 配置项:私钥签名证书路径==>acpsdk.signCert.path==>D:/certs/acp_test_sign.pfx 已加载
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 配置项:私钥签名证书密码==>acpsdk.signCert.pwd 已加载
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 配置项:私钥签名证书类型==>acpsdk.signCert.type==>PKCS12 已加载
  INFO 21948 --- [  restartedMain] ACP_SDK_LOG                              : 配置项:敏感信息加密证书==>acpsdk.encryptCert.path==>d:/certs/acp_test_enc.cer 已加载
  INFO 21948 --- [  restartedMain] cn.ybzy.pay.load.AutoLoadRunner          : >>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<

添加OR修改配置

修改acp_sdk.properties

将SDK示例项目配置文件复制到项目中,修改外网能访问的前台通知地址

#前台通知地址,填写银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://jackchen.imwork.net

修改application.yml

server:
  port: 8888

spring:
  application:
    name: pay-service
  redis:
    host: 127.0.0.1
    password:
    port: 6379

    #数据库连接信息
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/pay?serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

添加数据库表

CREATE TABLE `payment_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type_id` int(11) DEFAULT NULL,
  `order_id` varchar(255) DEFAULT NULL,
  `platformorder_id` varchar(255) DEFAULT NULL,
  `price` decimal(10,4) DEFAULT NULL,
  `source` varchar(255) DEFAULT NULL,
  `state` int(255) DEFAULT NULL,
  `pay_message` text,
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8
CREATE TABLE `payment_type` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type_name` varchar(255) DEFAULT NULL,
  `front_url` varchar(255) DEFAULT NULL,
  `back_url` varchar(255) DEFAULT NULL,
  `merchant_id` varchar(255) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

添加支付类型信息

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

INSERT INTO `pay`.`payment_type`(`id`, `type_name`, `front_url`, `back_url`, `merchant_id`, `create_date`, `update_date`) VALUES (1, 'yinlianPay', 'http://jackchen.imwork.net/pay/callback/syn', 'http://jackchen.imwork.net/pay/callback/asyn', '777290058110048', '2021-08-30 16:25:51', NULL);

执行测试

生成支付信息

请求http://jackchen.imwork.net/generatePaymentInfo接口,生成支付信息,得到token
在这里插入图片描述

开始支付

以得到的token访问http://jackchen.imwork.net/payGateway?token=pay-3bb87b52-35e9-4592-ae72-1516bebd8754接口跳转到支付网关
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
支付成功,返回商户
在这里插入图片描述

支付状态查询

访问https://jackchen.imwork.net/queryPayStatus?orderId=qwesdfsfdfds431&txnTime=20210830180004接口查询订单状态

在这里插入图片描述

商户测试中心查询

商户测试中心查询交易信息
在这里插入图片描述
可看到很重要的2个信息:回调地址
在这里插入图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeDevMaster

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值