## 一、配置接口信息 在`application.yml`文件中添加配置 ```yml epay: url: https://your.epay.website/ appId: 1000 appKey: *************** notifyUrl: https://开发环境可以使用内网穿透/api/order/notify returnUrl: https://开发环境可以使用内网穿透/api/order/notify ``` ## 二、添加依赖 ```xml com.google.code.gson gson 2.13.2 org.projectlombok lombok true ``` ## 三、持久层 #### 1、支付信息 创建`po/PayInfo.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayInfo { private Integer pid; private String type; @SerializedName("trade_no") private String tradeNo; @SerializedName("out_trade_no") private String outTradeNo; @SerializedName("notify_url") private String notifyUrl; @SerializedName("return_url") private String returnUrl; private String name; private String money; @SerializedName("trade_status") private String tradeStatus; private String clientip; private String device; private String param; private String sign; @SerializedName("sign_type") private String signType; } ``` #### 2、订单信息 创建`po/PayOrderInfo.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayOrderInfo { private Integer code; private String msg; @SerializedName("trade_no") private String tradeNo; @SerializedName("out_trade_no") private String outTradeNo; @SerializedName("api_trade_no") private String apiTradeNo; private String type; private Integer pid; private String addtime; private String endtime; private String name; private String money; private Integer status; private String param; private String buyer; } ``` #### 3、请求返回信息 创建`po/PayResponse.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayResponse { private Integer code; private String msg; @SerializedName("trade_no") private String tradeNo; private String payurl; private String paycode; private String payscheme; private String qrcode; private String urlscheme; } ``` #### 4、返回信息包装 创建`vo/ResponseVO.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ResponseVO { private Integer code; private String msg; private Object data; public static ResponseVO success(Object data) { ResponseVO result = new ResponseVO(); result.setCode(200); result.setMsg("success"); if (data == null) data = "{}"; result.setData(data); return result; } public static ResponseVO success(String msg, Object data) { ResponseVO result = new ResponseVO(); result.setCode(200); result.setMsg(msg); if (data == null) data = "{}"; result.setData(data); return result; } public static ResponseVO failed(String msg) { return error(203, msg); } public static ResponseVO error(Integer code, String msg) { ResponseVO result = new ResponseVO(); result.setCode(code); result.setMsg(msg); result.setData("{}"); return result; } public static ResponseVO error(String msg) { return error(400, msg); } public static ResponseVO denyAccess(String msg) { return error(403, msg); } } ``` ## 四、常量数据 #### 1、请求设备类型 创建`constants/type/EpayDeviceType.java`接口 ```java public interface EpayDeviceType { String PC = "pc"; // PC端 String MOBILE = "mobile"; // 移动端 String QQ = "qq"; // QQ内置浏览器 String WECHAT = "wechat"; // 微信内置浏览器 String ALIPAY = "alipay"; // 支付宝内置浏览器 String JUMP = "jump"; // 仅返回转跳链接 } ``` #### 2、支付方式类型 创建`constants/type/EpayPaymentType.java`接口 ```java package cn.doooog.dshop.constants.type; public interface EpayPaymentType { String WECHAT = "wxpay"; // 微信 String ALIPAY = "alipay"; // 支付宝 String QQ = "qqpay"; // QQ } ``` #### 3、签名生成类型 创建`constants/type/EpaySignType.java`接口 ```java public interface EpaySignType { String MD5 = "MD5"; String RSA = "RSA"; } ``` ## 五、服务层 #### 1、易支付服务接口 创建`service/EpayService.java`接口 ```java public interface EpayService { PayResponse createPayOrder(PayInfo info); PayOrderInfo getOrderInfo(String outTradeNo); String getParmaMD5Sign(PayInfo info); } ``` #### 2、实现服务 创建`service/impl/PeyServiceImpl.java` ```java import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.lang.reflect.Field; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @Service public class EpayServiceImpl implements EpayService { @Value("${epay.url}") private String url; @Value("${epay.appId}") private String appId; @Value("${epay.appKey}") private String appKey; @Value("${epay.notifyUrl}") private String notifyUrl; @Value("${epay.returnUrl}") private String returnUrl; private final RestTemplate restTemplate = new RestTemplate(); @PostConstruct private void init() { if (url == null || url.isEmpty()) throw new RuntimeException("请配置epay.url"); if (!url.endsWith("/")) { url += "/"; } if (appId == null || appId.isEmpty() || appKey == null || appKey.isEmpty()) throw new RuntimeException("请配置epay.appId和epay.appKey"); if (notifyUrl == null || notifyUrl.isEmpty() || returnUrl == null || returnUrl.isEmpty()) throw new RuntimeException("请配置epay.notifyUrl和epay.returnUrl"); } @Override public PayResponse createPayOrder(PayInfo info) { if (!isValid(info)) return null; MultiValueMap params = getRequestParamsMap(info); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity> requestEntity = new HttpEntity<>(params, headers); System.out.println(params); String baseUrl = url + "mapi.php"; ResponseEntity responseEntity = restTemplate.postForEntity(baseUrl, requestEntity, String.class); String responseBody = responseEntity.getBody(); System.out.println(responseBody); if (responseBody != null && responseBody.trim().startsWith("{")) { return new com.google.gson.Gson().fromJson(responseBody, PayResponse.class); } else { return null; } } @Override public PayOrderInfo getOrderInfo(String outTradeNo) { if (outTradeNo == null || outTradeNo.isEmpty()) return null; String baseUrl = url + "api.php?act=order&pid=" + appId + "&key=" + appKey + "&out_trade_no=" + outTradeNo; ResponseEntity responseEntity = restTemplate.getForEntity(baseUrl, String.class); String responseBody = responseEntity.getBody(); if (responseBody != null && responseBody.trim().startsWith("{")) { return new com.google.gson.Gson().fromJson(responseBody, PayOrderInfo.class); } else { return null; } } @Override public String getParmaMD5Sign(PayInfo info) throws RuntimeException { Map map; try { map = objectToMap(info); } catch (IllegalAccessException e) { throw new RuntimeException(e); } List keyList = map.keySet().stream() .filter(k -> !"sign".equals(k) && !"sign_type".equals(k)) .filter(k -> map.get(k) != null && !map.get(k).toString().isEmpty()) .sorted() .toList(); StringBuilder signStr = new StringBuilder(); for (int i = 0; i < keyList.size(); i++) { String key = keyList.get(i); String value = map.get(key).toString(); signStr.append(key).append("=").append(value); if (i < keyList.size() - 1) { signStr.append("&"); } } String signInput = signStr + appKey; return getMD5(signInput).toLowerCase(); } private MultiValueMap getRequestParamsMap(PayInfo info) throws RuntimeException { Map map; try { map = objectToMap(info); } catch (IllegalAccessException e) { throw new RuntimeException(e); } MultiValueMap params = new LinkedMultiValueMap<>(); for (Map.Entry entry : map.entrySet()) { if (entry.getValue() != null) { String value = entry.getValue().toString(); if (!value.isEmpty()) { params.add(entry.getKey(), value); } } } return params; } private String getMD5(String input) { if (input == null || input.isEmpty()) { return null; } try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(input.getBytes()); byte[] byteArray = md5.digest(); BigInteger bigInt = new BigInteger(1, byteArray); StringBuilder result = new StringBuilder(bigInt.toString(16)); while (result.length() < 32) { result.insert(0, "0"); } return result.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } private Map objectToMap(Object obj) throws IllegalAccessException { Map map = new HashMap<>(); for (Field field : obj.getClass().getDeclaredFields()) { field.setAccessible(true); String key = field.getName().replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(); map.put(key, field.get(obj)); } return map; } private boolean isValid(PayInfo info) { if (info == null) return false; info.setPid(Integer.parseInt(appId)); info.setTradeNo(null); info.setTradeStatus(null); if (info.getType() == null || info.getType().isEmpty()) return false; if (info.getOutTradeNo() == null || info.getOutTradeNo().isEmpty()) return false; if ((info.getNotifyUrl() == null || info.getNotifyUrl().isEmpty())) info.setNotifyUrl(notifyUrl); if ((info.getReturnUrl() == null || info.getReturnUrl().isEmpty())) info.setReturnUrl(returnUrl); if (info.getName() == null || info.getName().isEmpty()) return false; if (info.getMoney() == null || Double.parseDouble(info.getMoney()) <= 0) return false; if (info.getClientip() == null || info.getClientip().isEmpty()) return false; if (info.getDevice() == null || info.getDevice().isEmpty()) info.setDevice(EpayDeviceType.PC); if (info.getSignType() == null || info.getSignType().isEmpty() || info.getSignType().equals(EpaySignType.MD5)) { info.setSignType(EpaySignType.MD5); info.setSign(getParmaMD5Sign(info)); } return true; } } ``` ## 六、控制层 #### 1、回调接口 创建`controller/EpayController.java` ```java import com.google.gson.Gson; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/order") public class EpayController { private final EpayService epayService; public EpayController(EpayService epayService) { this.epayService = epayService; } @RequestMapping("/notify") public ResponseVO onPayResult(@RequestParam Map params) { PayInfo payInfo = new Gson().fromJson(new Gson().toJson(params), PayInfo.class); System.out.println(payInfo); System.out.println(epayService.getParmaMD5Sign(payInfo)); return epayService.getParmaMD5Sign(payInfo).equals(payInfo.getSign()) && payInfo.getTradeStatus().equals("TRADE_SUCCESS") ? ResponseVO.success("success") : ResponseVO.failed("failed"); } } ``` **注意**:该API的请求地址即为通知地址,若没有特定的回调地址,也为该地址。 #### 2、测试接口 创建`controller/TestController.java` ```java import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.Date; @RestController @RequestMapping("/api/test") public class TestController { private final EpayService epayService; public TestController(EpayService epayService) { this.epayService = epayService; } @RequestMapping("/hello") public PayResponse hello(HttpServletRequest request) { System.out.println(request.getServerName()); PayInfo info = new PayInfo(); info.setType(EpayPaymentType.WECHAT); info.setOutTradeNo(getOrderSn()); info.setName("测试"); info.setMoney(21997.00 + ""); info.setClientip("127.0.0.1"); info.setDevice(EpayDeviceType.PC); info.setSignType(EpaySignType.MD5); return epayService.createPayOrder(info); } @RequestMapping("/info/{no}") public PayOrderInfo getOrderInfo(@PathVariable String no) { return epayService.getOrderInfo(no); } private String getOrderSn() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(new Date()).substring(0, 10).replace("-", ""); String time = String.valueOf(System.currentTimeMillis()).substring(6, 13); return "DOOOOG" + date + time; } } ``` ## 附、易支付官方文档 #### 1、协议规则 请求数据格式:`application/x-www-form-urlencoded` 返回数据格式:`JSON` 签名算法:`MD5` 字符编码:`UTF-8` #### 2、页面跳转支付 此接口可用于用户前台直接发起支付,使用form表单跳转或拼接成url跳转。 URL地址:http://your.epay.website/submit.php 请求方式:POST 或 GET(推荐POST,不容易被劫持或屏蔽) 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :-------------------------------- | :------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 支付方式 | type | 否 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | | | 异步通知地址 | notify_url | 是 | String | http://www.pay.com/notify_url.php | 服务器异步通知地址 | | 跳转通知地址 | return_url | 是 | String | http://www.pay.com/return_url.php | 页面跳转通知地址 | | 商品名称 | name | 是 | String | VIP会员 | 如超过127个字节会自动截取 | | 商品金额 | money | 是 | String | 1.00 | 单位:元,最大2位小数 | | 业务扩展参数 | param | 否 | String | 没有请留空 | 支付后原样返回 | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 支付方式(type)不传会跳转到收银台支付 #### 3、API接口支付 此接口可用于服务器后端发起支付请求,会返回支付二维码链接或支付跳转url。 URL地址:http://your.epay.website/mapi.php 请求方式:POST 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :-------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 支付方式 | type | 是 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | | | 异步通知地址 | notify_url | 是 | String | http://www.pay.com/notify_url.php | 服务器异步通知地址 | | 跳转通知地址 | return_url | 否 | String | http://www.pay.com/return_url.php | 页面跳转通知地址 | | 商品名称 | name | 是 | String | VIP会员 | 如超过127个字节会自动截取 | | 商品金额 | money | 是 | String | 1.00 | 单位:元,最大2位小数 | | 用户IP地址 | clientip | 是 | String | 192.168.1.100 | 用户发起支付的IP地址 | | 设备类型 | device | 否 | String | pc | 根据用户浏览器的UA判断, 传入用户所使用的浏览器 或设备类型,默认为pc[设备类型列表](http://your.epay.website/doc_old.html#pay5) | | 业务扩展参数 | param | 否 | String | 没有请留空 | 支付后原样返回 | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 返回结果(json): | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :------------ | :-------- | :----- | :-------------------------------------------- | :------------------------------------------------------ | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | | 失败时返回原因 | | 订单号 | trade_no | String | 20160806151343349 | 支付订单号 | | 支付跳转url | payurl | String | http://your.epay.website/pay/wxpay/202010903/ | 如果返回该字段,则直接跳转到该url支付 | | 二维码链接 | qrcode | String | weixin://wxpay/bizpayurl?pr=04IPMKM | 如果返回该字段,则根据该url生成二维码 | | 小程序跳转url | urlscheme | String | weixin://dl/business/?ticket=xxx | 如果返回该字段,则使用js跳转该url,可发起微信小程序支付 | 注:payurl、qrcode、urlscheme 三个参数只会返回其中一个 #### 4、支付结果通知 通知类型:服务器异步通知(notify_url)、页面跳转通知(return_url) 请求方式:GET 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :------------------------------- | :------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 易支付订单号 | trade_no | 是 | String | 20160806151343349021 | 聚合支付平台订单号 | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | 商户系统内部的订单号 | | 支付方式 | type | 是 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商品名称 | name | 是 | String | VIP会员 | | | 商品金额 | money | 是 | String | 1.00 | | | 支付状态 | trade_status | 是 | String | TRADE_SUCCESS | 只有TRADE_SUCCESS是成功 | | 业务扩展参数 | param | 否 | String | | | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 收到异步通知后,需返回success以表示服务器接收到了订单通知 #### 5、MD5签名算法 1、将发送或接收到的所有参数按照参数名ASCII码从小到大排序(a-z),sign、sign_type、和空值不参与签名! 2、将排序后的参数拼接成URL键值对的格式,例如 `a=b&c=d&e=f`,参数值不要进行url编码。 3、再将拼接好的字符串与商户密钥KEY进行MD5加密得出sign签名参数,`sign = md5 ( a=b&c=d&e=f + KEY )` (注意:+ 为各语言的拼接符,不是字符!),md5结果为小写。 4、具体签名与发起支付的示例代码可下载SDK查看。 #### 6、支付方式列表 | 调用值 | 描述 | | :----- | :------- | | alipay | 支付宝 | | wxpay | 微信支付 | | qqpay | QQ钱包 | #### 7、设备类型列表 | 调用值 | 描述 | | :----- | :---------------- | | pc | 电脑浏览器 | | mobile | 手机浏览器 | | qq | 手机QQ内浏览器 | | wechat | 微信内浏览器 | | alipay | 支付宝客户端 | | jump | 仅返回支付跳转url | --- #### 8、[API]查询商户信息 URL地址:http://your.epay.website/api.php?act=query&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :------- | :----- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | query | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :------------ | :--------- | :------------------------------- | :---------------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 商户ID | pid | Int | 1001 | 商户ID | | 商户密钥 | key | String(32) | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | 商户密钥 | | 商户状态 | active | Int | 1 | 1为正常,0为封禁 | | 商户余额 | money | String | 0.00 | 商户所拥有的余额 | | 结算方式 | type | Int | 1 | 1:支付宝,2:微信,3:QQ,4:银行卡 | | 结算账号 | account | String | admin@pay.com | 结算的支付宝账号 | | 结算姓名 | username | String | 张三 | 结算的支付宝姓名 | | 订单总数 | orders | Int | 30 | 订单总数统计 | | 今日订单 | order_today | Int | 15 | 今日订单数量 | | 昨日订单 | order_lastday | Int | 15 | 昨日订单数量 | #### 9、[API]查询结算记录 URL地址:http://your.epay.website/api.php?act=settle&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :------- | :----- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | settle | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :----------------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询结算记录成功! | | | 结算记录 | data | Array | 结算记录列表 | | #### 11、[API]查询单个订单 URL地址:http://your.epay.website/api.php?act=order&pid={商户ID}&key={商户密钥}&out_trade_no={商户订单号} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :--------- | :----------- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | order | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 系统订单号 | trade_no | 选择 | String | 20160806151343312 | | | 商户订单号 | out_trade_no | 选择 | String | 20160806151343349 | | 提示:系统订单号 和 商户订单号 二选一传入即可,如果都传入以系统订单号为准! 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :----- | :------------------ | :--------------------------------------------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询订单号成功! | | | 易支付订单号 | trade_no | String | 2016080622555342651 | 聚合支付平台订单号 | | 商户订单号 | out_trade_no | String | 20160806151343349 | 商户系统内部的订单号 | | 第三方订单号 | api_trade_no | String | 20160806151343349 | 支付宝微信等接口方订单号 | | 支付方式 | type | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户ID | pid | Int | 1001 | 发起支付的商户ID | | 创建订单时间 | addtime | String | 2016-08-06 22:55:52 | | | 完成交易时间 | endtime | String | 2016-08-06 22:55:52 | | | 商品名称 | name | String | VIP会员 | | | 商品金额 | money | String | 1.00 | | | 支付状态 | status | Int | 0 | 1为支付成功,0为未支付 | | 业务扩展参数 | param | String | | 默认留空 | | 支付者账号 | buyer | String | | 默认留空 | #### 12、[API]批量查询订单 URL地址:http://your.epay.website/api.php?act=orders&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----- | :--- | :----- | :------------------------------- | :--------------------- | | 操作类型 | act | 是 | String | orders | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 查询订单数量 | limit | 否 | Int | 20 | 返回的订单数量,最大50 | | 页码 | page | 否 | Int | 1 | 当前查询的页码 | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :----------------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询结算记录成功! | | | 订单列表 | data | Array | | 订单列表 | #### 13、[API]提交订单退款 需要先在商户后台开启订单退款API接口开关,才能调用该接口发起订单退款 URL地址:http://your.epay.website/api.php?act=refund 请求方式:POST 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :------- | :----- | :------------------------------- | :--------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 易支付订单号 | trade_no | 特殊可选 | String | 20160806151343349021 | 易支付订单号 | | 商户订单号 | out_trade_no | 特殊可选 | String | 20160806151343349 | 订单支付时传入的商户订单号,商家自定义且保证商家系统中唯一 | | 退款金额 | money | 是 | String | 1.50 | 少数通道需要与原订单金额一致 | 注:trade_no、out_trade_no 不能同时为空,如果都传了以trade_no为准 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 退款成功 | | Loading... ## 一、配置接口信息 在`application.yml`文件中添加配置 ```yml epay: url: https://your.epay.website/ appId: 1000 appKey: *************** notifyUrl: https://开发环境可以使用内网穿透/api/order/notify returnUrl: https://开发环境可以使用内网穿透/api/order/notify ``` ## 二、添加依赖 ```xml <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.13.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> ``` ## 三、持久层 #### 1、支付信息 创建`po/PayInfo.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayInfo { private Integer pid; private String type; @SerializedName("trade_no") private String tradeNo; @SerializedName("out_trade_no") private String outTradeNo; @SerializedName("notify_url") private String notifyUrl; @SerializedName("return_url") private String returnUrl; private String name; private String money; @SerializedName("trade_status") private String tradeStatus; private String clientip; private String device; private String param; private String sign; @SerializedName("sign_type") private String signType; } ``` #### 2、订单信息 创建`po/PayOrderInfo.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayOrderInfo { private Integer code; private String msg; @SerializedName("trade_no") private String tradeNo; @SerializedName("out_trade_no") private String outTradeNo; @SerializedName("api_trade_no") private String apiTradeNo; private String type; private Integer pid; private String addtime; private String endtime; private String name; private String money; private Integer status; private String param; private String buyer; } ``` #### 3、请求返回信息 创建`po/PayResponse.java` ```java import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class PayResponse { private Integer code; private String msg; @SerializedName("trade_no") private String tradeNo; private String payurl; private String paycode; private String payscheme; private String qrcode; private String urlscheme; } ``` #### 4、返回信息包装 创建`vo/ResponseVO.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ResponseVO { private Integer code; private String msg; private Object data; public static ResponseVO success(Object data) { ResponseVO result = new ResponseVO(); result.setCode(200); result.setMsg("success"); if (data == null) data = "{}"; result.setData(data); return result; } public static ResponseVO success(String msg, Object data) { ResponseVO result = new ResponseVO(); result.setCode(200); result.setMsg(msg); if (data == null) data = "{}"; result.setData(data); return result; } public static ResponseVO failed(String msg) { return error(203, msg); } public static ResponseVO error(Integer code, String msg) { ResponseVO result = new ResponseVO(); result.setCode(code); result.setMsg(msg); result.setData("{}"); return result; } public static ResponseVO error(String msg) { return error(400, msg); } public static ResponseVO denyAccess(String msg) { return error(403, msg); } } ``` ## 四、常量数据 #### 1、请求设备类型 创建`constants/type/EpayDeviceType.java`接口 ```java public interface EpayDeviceType { String PC = "pc"; // PC端 String MOBILE = "mobile"; // 移动端 String QQ = "qq"; // QQ内置浏览器 String WECHAT = "wechat"; // 微信内置浏览器 String ALIPAY = "alipay"; // 支付宝内置浏览器 String JUMP = "jump"; // 仅返回转跳链接 } ``` #### 2、支付方式类型 创建`constants/type/EpayPaymentType.java`接口 ```java package cn.doooog.dshop.constants.type; public interface EpayPaymentType { String WECHAT = "wxpay"; // 微信 String ALIPAY = "alipay"; // 支付宝 String QQ = "qqpay"; // QQ } ``` #### 3、签名生成类型 创建`constants/type/EpaySignType.java`接口 ```java public interface EpaySignType { String MD5 = "MD5"; String RSA = "RSA"; } ``` ## 五、服务层 #### 1、易支付服务接口 创建`service/EpayService.java`接口 ```java public interface EpayService { PayResponse createPayOrder(PayInfo info); PayOrderInfo getOrderInfo(String outTradeNo); String getParmaMD5Sign(PayInfo info); } ``` #### 2、实现服务 创建`service/impl/PeyServiceImpl.java` ```java import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.lang.reflect.Field; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @Service public class EpayServiceImpl implements EpayService { @Value("${epay.url}") private String url; @Value("${epay.appId}") private String appId; @Value("${epay.appKey}") private String appKey; @Value("${epay.notifyUrl}") private String notifyUrl; @Value("${epay.returnUrl}") private String returnUrl; private final RestTemplate restTemplate = new RestTemplate(); @PostConstruct private void init() { if (url == null || url.isEmpty()) throw new RuntimeException("请配置epay.url"); if (!url.endsWith("/")) { url += "/"; } if (appId == null || appId.isEmpty() || appKey == null || appKey.isEmpty()) throw new RuntimeException("请配置epay.appId和epay.appKey"); if (notifyUrl == null || notifyUrl.isEmpty() || returnUrl == null || returnUrl.isEmpty()) throw new RuntimeException("请配置epay.notifyUrl和epay.returnUrl"); } @Override public PayResponse createPayOrder(PayInfo info) { if (!isValid(info)) return null; MultiValueMap<String, String> params = getRequestParamsMap(info); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers); System.out.println(params); String baseUrl = url + "mapi.php"; ResponseEntity<String> responseEntity = restTemplate.postForEntity(baseUrl, requestEntity, String.class); String responseBody = responseEntity.getBody(); System.out.println(responseBody); if (responseBody != null && responseBody.trim().startsWith("{")) { return new com.google.gson.Gson().fromJson(responseBody, PayResponse.class); } else { return null; } } @Override public PayOrderInfo getOrderInfo(String outTradeNo) { if (outTradeNo == null || outTradeNo.isEmpty()) return null; String baseUrl = url + "api.php?act=order&pid=" + appId + "&key=" + appKey + "&out_trade_no=" + outTradeNo; ResponseEntity<String> responseEntity = restTemplate.getForEntity(baseUrl, String.class); String responseBody = responseEntity.getBody(); if (responseBody != null && responseBody.trim().startsWith("{")) { return new com.google.gson.Gson().fromJson(responseBody, PayOrderInfo.class); } else { return null; } } @Override public String getParmaMD5Sign(PayInfo info) throws RuntimeException { Map<String, Object> map; try { map = objectToMap(info); } catch (IllegalAccessException e) { throw new RuntimeException(e); } List<String> keyList = map.keySet().stream() .filter(k -> !"sign".equals(k) && !"sign_type".equals(k)) .filter(k -> map.get(k) != null && !map.get(k).toString().isEmpty()) .sorted() .toList(); StringBuilder signStr = new StringBuilder(); for (int i = 0; i < keyList.size(); i++) { String key = keyList.get(i); String value = map.get(key).toString(); signStr.append(key).append("=").append(value); if (i < keyList.size() - 1) { signStr.append("&"); } } String signInput = signStr + appKey; return getMD5(signInput).toLowerCase(); } private MultiValueMap<String, String> getRequestParamsMap(PayInfo info) throws RuntimeException { Map<String, Object> map; try { map = objectToMap(info); } catch (IllegalAccessException e) { throw new RuntimeException(e); } MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); for (Map.Entry<String, Object> entry : map.entrySet()) { if (entry.getValue() != null) { String value = entry.getValue().toString(); if (!value.isEmpty()) { params.add(entry.getKey(), value); } } } return params; } private String getMD5(String input) { if (input == null || input.isEmpty()) { return null; } try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(input.getBytes()); byte[] byteArray = md5.digest(); BigInteger bigInt = new BigInteger(1, byteArray); StringBuilder result = new StringBuilder(bigInt.toString(16)); while (result.length() < 32) { result.insert(0, "0"); } return result.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } private Map<String, Object> objectToMap(Object obj) throws IllegalAccessException { Map<String, Object> map = new HashMap<>(); for (Field field : obj.getClass().getDeclaredFields()) { field.setAccessible(true); String key = field.getName().replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(); map.put(key, field.get(obj)); } return map; } private boolean isValid(PayInfo info) { if (info == null) return false; info.setPid(Integer.parseInt(appId)); info.setTradeNo(null); info.setTradeStatus(null); if (info.getType() == null || info.getType().isEmpty()) return false; if (info.getOutTradeNo() == null || info.getOutTradeNo().isEmpty()) return false; if ((info.getNotifyUrl() == null || info.getNotifyUrl().isEmpty())) info.setNotifyUrl(notifyUrl); if ((info.getReturnUrl() == null || info.getReturnUrl().isEmpty())) info.setReturnUrl(returnUrl); if (info.getName() == null || info.getName().isEmpty()) return false; if (info.getMoney() == null || Double.parseDouble(info.getMoney()) <= 0) return false; if (info.getClientip() == null || info.getClientip().isEmpty()) return false; if (info.getDevice() == null || info.getDevice().isEmpty()) info.setDevice(EpayDeviceType.PC); if (info.getSignType() == null || info.getSignType().isEmpty() || info.getSignType().equals(EpaySignType.MD5)) { info.setSignType(EpaySignType.MD5); info.setSign(getParmaMD5Sign(info)); } return true; } } ``` ## 六、控制层 #### 1、回调接口 创建`controller/EpayController.java` ```java import com.google.gson.Gson; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/order") public class EpayController { private final EpayService epayService; public EpayController(EpayService epayService) { this.epayService = epayService; } @RequestMapping("/notify") public ResponseVO onPayResult(@RequestParam Map<String, String> params) { PayInfo payInfo = new Gson().fromJson(new Gson().toJson(params), PayInfo.class); System.out.println(payInfo); System.out.println(epayService.getParmaMD5Sign(payInfo)); return epayService.getParmaMD5Sign(payInfo).equals(payInfo.getSign()) && payInfo.getTradeStatus().equals("TRADE_SUCCESS") ? ResponseVO.success("success") : ResponseVO.failed("failed"); } } ``` **注意**:该API的请求地址即为通知地址,若没有特定的回调地址,也为该地址。 #### 2、测试接口 创建`controller/TestController.java` ```java import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.Date; @RestController @RequestMapping("/api/test") public class TestController { private final EpayService epayService; public TestController(EpayService epayService) { this.epayService = epayService; } @RequestMapping("/hello") public PayResponse hello(HttpServletRequest request) { System.out.println(request.getServerName()); PayInfo info = new PayInfo(); info.setType(EpayPaymentType.WECHAT); info.setOutTradeNo(getOrderSn()); info.setName("测试"); info.setMoney(21997.00 + ""); info.setClientip("127.0.0.1"); info.setDevice(EpayDeviceType.PC); info.setSignType(EpaySignType.MD5); return epayService.createPayOrder(info); } @RequestMapping("/info/{no}") public PayOrderInfo getOrderInfo(@PathVariable String no) { return epayService.getOrderInfo(no); } private String getOrderSn() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(new Date()).substring(0, 10).replace("-", ""); String time = String.valueOf(System.currentTimeMillis()).substring(6, 13); return "DOOOOG" + date + time; } } ``` ## 附、易支付官方文档 #### 1、协议规则 请求数据格式:`application/x-www-form-urlencoded` 返回数据格式:`JSON` 签名算法:`MD5` 字符编码:`UTF-8` #### 2、页面跳转支付 此接口可用于用户前台直接发起支付,使用form表单跳转或拼接成url跳转。 URL地址:http://your.epay.website/submit.php 请求方式:POST 或 GET(推荐POST,不容易被劫持或屏蔽) 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :-------------------------------- | :------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 支付方式 | type | 否 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | | | 异步通知地址 | notify_url | 是 | String | http://www.pay.com/notify_url.php | 服务器异步通知地址 | | 跳转通知地址 | return_url | 是 | String | http://www.pay.com/return_url.php | 页面跳转通知地址 | | 商品名称 | name | 是 | String | VIP会员 | 如超过127个字节会自动截取 | | 商品金额 | money | 是 | String | 1.00 | 单位:元,最大2位小数 | | 业务扩展参数 | param | 否 | String | 没有请留空 | 支付后原样返回 | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 支付方式(type)不传会跳转到收银台支付 #### 3、API接口支付 此接口可用于服务器后端发起支付请求,会返回支付二维码链接或支付跳转url。 URL地址:http://your.epay.website/mapi.php 请求方式:POST 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :-------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 支付方式 | type | 是 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | | | 异步通知地址 | notify_url | 是 | String | http://www.pay.com/notify_url.php | 服务器异步通知地址 | | 跳转通知地址 | return_url | 否 | String | http://www.pay.com/return_url.php | 页面跳转通知地址 | | 商品名称 | name | 是 | String | VIP会员 | 如超过127个字节会自动截取 | | 商品金额 | money | 是 | String | 1.00 | 单位:元,最大2位小数 | | 用户IP地址 | clientip | 是 | String | 192.168.1.100 | 用户发起支付的IP地址 | | 设备类型 | device | 否 | String | pc | 根据用户浏览器的UA判断, 传入用户所使用的浏览器 或设备类型,默认为pc[设备类型列表](http://your.epay.website/doc_old.html#pay5) | | 业务扩展参数 | param | 否 | String | 没有请留空 | 支付后原样返回 | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 返回结果(json): | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :------------ | :-------- | :----- | :-------------------------------------------- | :------------------------------------------------------ | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | | 失败时返回原因 | | 订单号 | trade_no | String | 20160806151343349 | 支付订单号 | | 支付跳转url | payurl | String | http://your.epay.website/pay/wxpay/202010903/ | 如果返回该字段,则直接跳转到该url支付 | | 二维码链接 | qrcode | String | weixin://wxpay/bizpayurl?pr=04IPMKM | 如果返回该字段,则根据该url生成二维码 | | 小程序跳转url | urlscheme | String | weixin://dl/business/?ticket=xxx | 如果返回该字段,则使用js跳转该url,可发起微信小程序支付 | 注:payurl、qrcode、urlscheme 三个参数只会返回其中一个 #### 4、支付结果通知 通知类型:服务器异步通知(notify_url)、页面跳转通知(return_url) 请求方式:GET 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :--- | :----- | :------------------------------- | :------------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 易支付订单号 | trade_no | 是 | String | 20160806151343349021 | 聚合支付平台订单号 | | 商户订单号 | out_trade_no | 是 | String | 20160806151343349 | 商户系统内部的订单号 | | 支付方式 | type | 是 | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商品名称 | name | 是 | String | VIP会员 | | | 商品金额 | money | 是 | String | 1.00 | | | 支付状态 | trade_status | 是 | String | TRADE_SUCCESS | 只有TRADE_SUCCESS是成功 | | 业务扩展参数 | param | 否 | String | | | | 签名字符串 | sign | 是 | String | 202cb962ac59075b964b07152d234b70 | 签名算法[点此查看](http://your.epay.website/doc_old.html#pay3) | | 签名类型 | sign_type | 是 | String | MD5 | 默认为MD5 | 收到异步通知后,需返回success以表示服务器接收到了订单通知 #### 5、MD5签名算法 1、将发送或接收到的所有参数按照参数名ASCII码从小到大排序(a-z),sign、sign_type、和空值不参与签名! 2、将排序后的参数拼接成URL键值对的格式,例如 `a=b&c=d&e=f`,参数值不要进行url编码。 3、再将拼接好的字符串与商户密钥KEY进行MD5加密得出sign签名参数,`sign = md5 ( a=b&c=d&e=f + KEY )` (注意:+ 为各语言的拼接符,不是字符!),md5结果为小写。 4、具体签名与发起支付的示例代码可下载SDK查看。 #### 6、支付方式列表 | 调用值 | 描述 | | :----- | :------- | | alipay | 支付宝 | | wxpay | 微信支付 | | qqpay | QQ钱包 | #### 7、设备类型列表 | 调用值 | 描述 | | :----- | :---------------- | | pc | 电脑浏览器 | | mobile | 手机浏览器 | | qq | 手机QQ内浏览器 | | wechat | 微信内浏览器 | | alipay | 支付宝客户端 | | jump | 仅返回支付跳转url | --- #### 8、[API]查询商户信息 URL地址:http://your.epay.website/api.php?act=query&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :------- | :----- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | query | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :------------ | :--------- | :------------------------------- | :---------------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 商户ID | pid | Int | 1001 | 商户ID | | 商户密钥 | key | String(32) | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | 商户密钥 | | 商户状态 | active | Int | 1 | 1为正常,0为封禁 | | 商户余额 | money | String | 0.00 | 商户所拥有的余额 | | 结算方式 | type | Int | 1 | 1:支付宝,2:微信,3:QQ,4:银行卡 | | 结算账号 | account | String | admin@pay.com | 结算的支付宝账号 | | 结算姓名 | username | String | 张三 | 结算的支付宝姓名 | | 订单总数 | orders | Int | 30 | 订单总数统计 | | 今日订单 | order_today | Int | 15 | 今日订单数量 | | 昨日订单 | order_lastday | Int | 15 | 昨日订单数量 | #### 9、[API]查询结算记录 URL地址:http://your.epay.website/api.php?act=settle&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :------- | :----- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | settle | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :----------------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询结算记录成功! | | | 结算记录 | data | Array | 结算记录列表 | | #### 11、[API]查询单个订单 URL地址:http://your.epay.website/api.php?act=order&pid={商户ID}&key={商户密钥}&out_trade_no={商户订单号} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :--------- | :----------- | :--- | :----- | :------------------------------- | :---------- | | 操作类型 | act | 是 | String | order | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 系统订单号 | trade_no | 选择 | String | 20160806151343312 | | | 商户订单号 | out_trade_no | 选择 | String | 20160806151343349 | | 提示:系统订单号 和 商户订单号 二选一传入即可,如果都传入以系统订单号为准! 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :----- | :------------------ | :--------------------------------------------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询订单号成功! | | | 易支付订单号 | trade_no | String | 2016080622555342651 | 聚合支付平台订单号 | | 商户订单号 | out_trade_no | String | 20160806151343349 | 商户系统内部的订单号 | | 第三方订单号 | api_trade_no | String | 20160806151343349 | 支付宝微信等接口方订单号 | | 支付方式 | type | String | alipay | [支付方式列表](http://your.epay.website/doc_old.html#pay4) | | 商户ID | pid | Int | 1001 | 发起支付的商户ID | | 创建订单时间 | addtime | String | 2016-08-06 22:55:52 | | | 完成交易时间 | endtime | String | 2016-08-06 22:55:52 | | | 商品名称 | name | String | VIP会员 | | | 商品金额 | money | String | 1.00 | | | 支付状态 | status | Int | 0 | 1为支付成功,0为未支付 | | 业务扩展参数 | param | String | | 默认留空 | | 支付者账号 | buyer | String | | 默认留空 | #### 12、[API]批量查询订单 URL地址:http://your.epay.website/api.php?act=orders&pid={商户ID}&key={商户密钥} 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----- | :--- | :----- | :------------------------------- | :--------------------- | | 操作类型 | act | 是 | String | orders | 此API固定值 | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 查询订单数量 | limit | 否 | Int | 20 | 返回的订单数量,最大50 | | 页码 | page | 否 | Int | 1 | 当前查询的页码 | 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :----------------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 查询结算记录成功! | | | 订单列表 | data | Array | | 订单列表 | #### 13、[API]提交订单退款 需要先在商户后台开启订单退款API接口开关,才能调用该接口发起订单退款 URL地址:http://your.epay.website/api.php?act=refund 请求方式:POST 请求参数说明: | 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 | | :----------- | :----------- | :------- | :----- | :------------------------------- | :--------------------------------------------------------- | | 商户ID | pid | 是 | Int | 1001 | | | 商户密钥 | key | 是 | String | 89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i | | | 易支付订单号 | trade_no | 特殊可选 | String | 20160806151343349021 | 易支付订单号 | | 商户订单号 | out_trade_no | 特殊可选 | String | 20160806151343349 | 订单支付时传入的商户订单号,商家自定义且保证商家系统中唯一 | | 退款金额 | money | 是 | String | 1.50 | 少数通道需要与原订单金额一致 | 注:trade_no、out_trade_no 不能同时为空,如果都传了以trade_no为准 返回结果: | 字段名 | 变量名 | 类型 | 示例值 | 描述 | | :--------- | :----- | :----- | :------- | :-------------------- | | 返回状态码 | code | Int | 1 | 1为成功,其它值为失败 | | 返回信息 | msg | String | 退款成功 | | 最后修改:2025 年 12 月 31 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。