微信小程序对接银联支付,前端小程序无需其他调整,跟普通微信支付一致。后端代码需要对接银联的下单、关单、查单、支付回调、退款等接口,整体流程也和普通微信支付一样,只是这些接口是银联提供的,接口参数和签名方法不同而已。
目前银联支付的官方接口文档还是比较齐全的,也有官方的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;
}
}
方法写的有点长,可以根据需要把方法拆成更小的模块,比如生成签名、构建请求头部信息,在其他几个接口中也是通用的。