易码付接口测试

易码付平台对接调试

最近公司因公司需要,要求对接易码付聚合支付平台,这里记录一下我的对接流程,有需要代码的可直接复用

1. 页面介绍

在这里插入图片描述
右上角开发参数中就是我们所需要的接口参数了
在这里插入图片描述
其中Server 端交易密钥是我们每次请求动态生成签名用的,不要告诉别人哟

2. 接口测试前准备

2.1 签名算法编写

签名生成的通用步骤如下:

第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

特别注意以下重要规则:

◆ 参数名ASCII码从小到大排序(字典序);

◆ 如果参数的值为空不参与签名;

◆ 参数名区分大小写;

第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 注意:密钥的长度为32个字节。

◆ API Key 是商户在易码付系统中调用API的重要密钥,请安全存储,确保其不要被泄露。

按照签名生成步骤我们来实现我们的签名算法,并与平台的签名检验工具比较

public static String getSign(Map<String, Object> params, List<String> ignoreKeys, String secretKey) {
    Set<String> keySet = params.keySet();
    List<String> collect = keySet.stream().filter(key -> ignoreKeys == null || !ignoreKeys.contains(key))
        .sorted()
        .collect(Collectors.toList());

    // 拼接签名字段
    StringBuilder builder = new StringBuilder();
    for (String signKey : collect) {
        builder.append(signKey).append("=").append(params.get(signKey)).append("&");
    }

    builder.append("key=").append(secretKey);
    String signStr = builder.toString();
    // 平台生成MD5签名的编码使用的是UTF-8注意我们加密的编码。当结果存在中文字符时不同的编码最终生成的结果是不一样的
    String ret = DigestUtils.md5DigestAsHex(signStr.getBytes(StandardCharsets.UTF_8)).toUpperCase();
    return ret;
}

易码付签名测试

常见问题

1、注意参数是否区分大小写,参数大小写不正确将会导致签名错误

2、检查所有参数是否与文档完全一致

常见问题:

1、请求数据的编码是否正确,微信支付接口编码要求统一为UTF-8

2、签名原串是否存在被URLencode编码的参数,微信支付的签名原串要求使用参数的原值进行签名

3、请求参数是否存在特殊字符,或者字段长度不符的情况

易码付签名生成
在这里插入图片描述

本地代码测试
在这里插入图片描述

2.2 公用Post请求接口
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;

@Slf4j
public class HttpUtil {
    public static String sendPost(String url, Map<String, String> heads, Map<String, Object> body) {
        HttpURLConnection conn = null;
        OutputStream out = null;
        StringBuilder builder = new StringBuilder();
        BufferedReader reader = null;

        try {
            URL urlObj = new URL(url);

            //start 这一段代码必须加在open之前,即支持ip访问的关键代码
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    return true;
                }
            });

            conn = (HttpURLConnection) urlObj.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setInstanceFollowRedirects(true);
            conn.setRequestMethod("POST");

            // 设置共用请求配置信息
            conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");

            // 如果请求头参数不为空,则携带请求头
            if (heads != null && heads.size() > 0) {
                Set<Map.Entry<String, String>> entries = heads.entrySet();
                for (Map.Entry<String, String> entry : entries) {
                    conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            // 设置超时时间
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);

            // 设置 body 参数
            out = conn.getOutputStream();
            String s = JSON.toJSONString(body);
            out.write(s.getBytes(StandardCharsets.UTF_8));
            out.flush();

            // 发送请求
            int responseCode = conn.getResponseCode();
            if (HttpStatus.OK.value() != responseCode) {
                log.error("{} Http 请求失败,状态码值:{}", url, responseCode);
                return null;
            }

            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line = null;
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }
        } catch (Exception e) {
            log.error("{} 请求异常:", url, e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("HttpUtil 流关闭异常:", e);
                }
            }

            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    log.error("HttpUtil 流关闭异常:", e);
                }
            }
        }

        return builder.toString();
    }
}

3. 接口测试

3.1 创建订单接口

1. 渠道配置

在主页,点击自己的应用,可以查看对应的交易信息,包括支付渠道配置等。创建订单的对应渠道需要在渠道配置中配置,否则订单创建失败。
在这里插入图片描述

2. 创建订单代码

public OrderMode<CreateOrderModel> createOrder(CreateOrderDto orderDto) {
    // 参数校验:将支付渠道定义为枚举值
    if (!PayTypeEnum.checkPayType(orderDto.getPayType())) {
        throw new OrderException("支付渠道非法,请检查支付类型参数");
    }

    // 获取参数
    Map<String, Object> params = getCreateOrderParams(orderDto);
    log.info("开始请求:{}", params);

    // 发起请求
    String ret = HttpUtil.sendPost(baseUrl + "/order.html", null, params);
    if (StringUtils.isEmpty(ret)) {
        throw new OrderException("网络忙,请稍候再试");
    }
    OrderMode<CreateOrderModel> mode = JSONObject.parseObject(ret, new TypeReference<OrderMode<CreateOrderModel>>() {});
    return mode;
}

private Map<String, Object> getCreateOrderParams(CreateOrderDto orderDto) {
    Map<String, Object> map = new HashMap<>();

    // 应用ID 易码付平台的Appid
    map.put("app_id", applId);
    // 商户订单号,使用UUID测试
    map.put("out_trade_no", UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
    // 支付渠道
    map.put("pay_type", orderDto.getPayType());
    // 商品描述
    map.put("description", orderDto.getDescription());
    // 接口传递元,使用字符串接收 BigDecimal 转换为分
    map.put("amount", new BigDecimal(orderDto.getAmount()).multiply(new BigDecimal(100)).intValue());
    map.put("client_ip", "127.0.0.1");
    // 通知URL
    map.put("notify_url", "https://822873ew63.goho.co/yimapay/notify");
    map.put("time_expire", timeExpire);

    // 附加参数:建议在此处转换为 JSON 串,方便后续生成签名
    map.put("attach", "{\"userId\":888,\"level\":3}");

    // 生成签名(sign 不参与签名生成)
    String sign = SignUtil.getSign(map, null, secretKey);
    map.put("sign", sign);
    return map;
}

3. 请求测试
在这里插入图片描述

3.2 查询订单

1. 查询订单代码

public OrderMode<QueryOrderModel> queryOrder(String queryOrder) {
    JSONObject param = JSON.parseObject(queryOrder);
    String outTradeNo = param.getString("out_trade_no");
    String tradeNo = param.getString("trade_no");

    Map<String, Object> params = new HashMap<>();
    params.put("app_id", applId);
    params.put("out_trade_no", outTradeNo);
    params.put("trade_no", tradeNo);

    // 生成签名
    String sign = SignUtil.getSign(params, null, secretKey);
    params.put("sign", sign);

    // 发起请求
    String ret = HttpUtil.sendPost(baseUrl + "/orderid.html", null, params);
    if (StringUtils.isEmpty(ret)) {
        throw new OrderException("网络忙,请稍候再试");
    }
    
    // 接口响应中文使用,URLEncoder加密,解密后再转换为实体类
    try {
        ret = URLDecoder.decode(ret, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
    OrderMode<QueryOrderModel> mode = JSONObject.parseObject(ret, new TypeReference<OrderMode<QueryOrderModel>>() {});
    return mode;
}

2. 请求测试
在这里插入图片描述
在平台页面也可以查看到刚刚生成的一条订单记录
在这里插入图片描述

3.3 关闭订单

关闭订单代码

public OrderMode closeOrder(String tradeNo) {
    Map<String, Object> params = new HashMap<>();
    params.put("app_id", applId);
    params.put("out_trade_no", tradeNo);

    // 生成签名
    String sign = SignUtil.getSign(params, null, secretKey);
    params.put("sign", sign);

    // 发送请求
    String ret = HttpUtil.sendPost(baseUrl + "/close.html", null, params);
    if (StringUtils.isEmpty(ret)) {
        throw new OrderException("网络忙,请稍候再试");
    }

    OrderMode orderMode = JSON.parseObject(ret, OrderMode.class);
    return orderMode;
}

请求测试
在这里插入图片描述
再次查询订单
在这里插入图片描述

3.4 申请退款

创建订单
在这里插入图片描述
查看订单状态
在这里插入图片描述
使用草料二维码将创建订单生成的链接转换为二维码,微信扫码支付

付款成功回调接口

public Map<String, String> payNotify(HttpServletRequest request) throws UnsupportedEncodingException {
    Map<String, Object> notifyDto = new HashMap<>();
    Enumeration<String> paramNames = request.getParameterNames();
    while (paramNames.hasMoreElements()) {
        String paramName = paramNames.nextElement();
        String[] paramValues = request.getParameterValues(paramName);
        if (paramValues != null && paramValues.length > 0) {
            String paramValue = URLDecoder.decode(paramValues[0], "utf-8");
            notifyDto.put(paramName, paramValue);
        }
    }

    // 验证签名
    log.info("回调:{}", notifyDto);
    String sign = SignUtil.getSign(notifyDto, Arrays.asList("sign"), secretKey);
    log.info("生成签名:{}", sign);
    if (!sign.equals(notifyDto.get("sign"))) {
        Map<String, String> ret = new HashMap<>();
        ret.put("code", "FAIL");
        ret.put("message", "签名错误");
        return ret;
    }

    Map<String, String> ret = new HashMap<>();
    ret.put("code", "SUCCESS");

    return ret;
}

在这里插入图片描述
再次查看订单状态
在这里插入图片描述

申请退款代码

public OrderMode<RefundOrderModel> refundOrder(RefundOrderDto orderDto) {
    Map<String, Object> params = new HashMap<>();
    params.put("app_id", applId);
    params.put("out_trade_no", orderDto.getOutTradeNo());
    params.put("out_refund_no", UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());

    int i = new BigDecimal(orderDto.getRefundAmount()).multiply(new BigDecimal(100)).intValue();
    params.put("refund_amount", i);
    params.put("refund_reason", orderDto.getRefundReason());

    // 生成签名
    String sign = SignUtil.getSign(params, null, secretKey);
    params.put("sign", sign);
    log.info("关闭订单开始请求:{}", params);

    String ret = HttpUtil.sendPost(baseUrl + "/refund.html", null, params);
    if (StringUtils.isEmpty(ret)) {
        throw new OrderException("网络忙,请稍候再试");
    }

    OrderMode<RefundOrderModel> mode = JSONObject.parseObject(ret, new TypeReference<OrderMode<RefundOrderModel>>() {});
    return mode;
}

申请退款测试

微信收到退款
在这里插入图片描述

3.5 退款查询

退款查询代码

public OrderMode<QueryRefundOrderModel> queryRefund(String param) {
    JSONObject jsonObject = JSON.parseObject(param);
    String outRefundNo = jsonObject.getString("out_refund_no");
    String refundNo = jsonObject.getString("refund_no");

    Map<String, Object> params = new HashMap<>();
    params.put("app_id", applId);
    params.put("out_refund_no", outRefundNo);
    params.put("refund_no", refundNo);

    // 获取签名
    String sign = SignUtil.getSign(params, null, secretKey);
    params.put("sign", sign);

    // 开始请求
    String ret = HttpUtil.sendPost(baseUrl + "/refundid.html", null, params);
    if (StringUtils.isEmpty(ret)) {
        throw new OrderException("网络忙,请稍候再试");
    }

    // 退款信息中包含 URLEncoder 加密的中文,使用 URLDecoder 解密
    try {
        ret = URLDecoder.decode(ret, "utf-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
    OrderMode<QueryRefundOrderModel> mode = JSONObject.parseObject(ret, new TypeReference<OrderMode<QueryRefundOrderModel>>() {});
    return mode;
}

退款查询测试
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值