钢蛋
发布于 2024-09-25 / 188 阅读
0
0

微信小程序对接银联支付接口

微信小程序对接银联支付,前端小程序无需其他调整,跟普通微信支付一致。后端代码需要对接银联的下单、关单、查单、支付回调、退款等接口,整体流程也和普通微信支付一样,只是这些接口是银联提供的,接口参数和签名方法不同而已。

目前银联支付的官方接口文档还是比较齐全的,也有官方的SDK供下载使用。

这里分享一下不使用官方SDK,而是自己写代码来实现接口调用,用到了第三方工具类。

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.9</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.8</version>
</dependency>

以下单接口为例,下面是完整的下单方法代码,只需要替换实际的银联账号,然后执行即可。

package xxx.xxx.xxx;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Objects;

@Slf4j
public class UmsApiTest {
    private static final String appId = "xxxxxxxxxxxxxxxxxxxxxxx"; // 银联支付appId
    private static final String appKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"; // 银联支付appKey
    private static final String mid = "xxxxxxxxxxxxxxx"; // 商户号
    private static final String tid = "xxxxxxxx"; // 终端号
    private static final String prefix = "XXXX"; // 订单号前缀(银联接口要求的,每个商户号会有不同)

    /**
     * 测试
     */
    public static void main(String[] args) {
        String openid = "xxxxxxxxxxxxxxxxxxxxxxx"; // 微信小程序用户的openid
        String orderNo = RandomUtil.randomString(20); // 商户系统内部订单号,根据自己系统要求生成即可,这里使用20位随机字符串仅供测试
        BigDecimal payPrice = new BigDecimal("0.01");  // 这里以支付一分钱为例
        Date payTimeOut = DateUtil.offsetMinute(new Date(), 15); // 这里订单支付过期时间设为15分钟
        String expireTime = DateUtil.format(payTimeOut, "yyyy-MM-dd HH:mm:ss");

        createOrder(openid, orderNo, payPrice, expireTime);
    }

    /**
     * 银联支付 —— 微信小程序下单
     *
     * @param openid     微信用户openid
     * @param orderNo    商户系统内部订单号
     * @param payPrice   订单支付金额(元)
     * @param expireTime 订单支付过期时间
     * @return 下单结果
     */
    public static JSONObject createOrder(String openid, String orderNo, BigDecimal payPrice, String expireTime) {
        // 下单参数
        JSONObject params = new JSONObject();
        params.put("mid", mid);
        params.put("tid", tid);
        params.put("requestTimestamp", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")); // 报文请求时间 格式yyyy-MM-dd HH:mm:ss
        params.put("merOrderId", prefix + orderNo); // 商户订单号 商户自行生成
        params.put("totalAmount", payPrice.multiply(BigDecimal.valueOf(100)).intValue()); // 支付总金额 单位分
        params.put("tradeType", "MINI"); // 交易类型 微信小程序为MINI固定值
        params.put("subOpenId", openid); // 用户子标识 微信必传
        params.put("expireTime", expireTime); // 订单过期时间(默认30分钟) 格式yyyy-MM-dd HH:mm:ss
        params.put("notifyUrl", "https://www.xxx.com/xxx/xxx/xxx"); // 支付结果通知地址(退款结果通知也是这个地址)

        String paramStr = params.toString();
        log.info("下单参数:{}", paramStr);

        // 生成签名
        String signature;
        String paramShaStr = SecureUtil.sha256(paramStr).toLowerCase();
        String timestamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
        String nonce = IdUtil.fastSimpleUUID();
        String joinStr = appId + timestamp + nonce + paramShaStr;
        try {
            HMac hmac = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, appKey.getBytes(StandardCharsets.UTF_8));
            byte[] digest = hmac.digest(joinStr.getBytes(StandardCharsets.UTF_8));
            signature = Base64Utils.encodeToString(digest);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException("生成银联接口签名失败");
        }
        log.info("签名:{}", signature);

        // 构建http请求头部信息
        StringBuilder sb = new StringBuilder("OPEN-BODY-SIG").append(CharUtil.SPACE)
                .append("AppId=").append(CharUtil.DOUBLE_QUOTES).append(appId).append(CharUtil.DOUBLE_QUOTES).append(CharUtil.COMMA)
                .append("Timestamp=").append(CharUtil.DOUBLE_QUOTES).append(timestamp).append(CharUtil.DOUBLE_QUOTES).append(CharUtil.COMMA)
                .append("Nonce=").append(CharUtil.DOUBLE_QUOTES).append(nonce).append(CharUtil.DOUBLE_QUOTES).append(CharUtil.COMMA)
                .append("Signature=").append(CharUtil.DOUBLE_QUOTES).append(signature).append(CharUtil.DOUBLE_QUOTES);
        String auth = sb.toString();
        log.info("头部信息:{}", auth);

        // 准备post请求
        HttpRequest request = HttpUtil.createPost("https://api-mop.chinaums.com/v1/netpay/wx/unified-order")
                .auth(auth).body(paramStr).timeout(5000);
        int status;
        String body;
        // 发起请求
        try (HttpResponse response = request.execute()) {
            status = response.getStatus();
            body = response.body();
        }

        // 判断响应状态
        if (status == HttpStatus.HTTP_OK) {
            log.info("银联接口返回结果:{}", body);
        } else {
            log.error("请求失败:status = {}, body = {}", status, body);
            throw new RuntimeException("请求失败");
        }

        // 处理响应结果
        JSONObject json = JSONObject.parseObject(body);
        String errCode = json.getString("errCode");
        if (!Objects.equals(errCode, "SUCCESS")) {
            log.error("errCode = {}", errCode);
            throw new RuntimeException("下单失败");
        }

        return json;
    }

}

方法写的有点长,可以根据需要把方法拆成更小的模块,比如生成签名、构建请求头部信息,在其他几个接口中也是通用的。


评论