huangye-parking/docs/1.副本驿公里自营优惠券三方发放对接标准文档.md
2026-02-28 17:35:49 +08:00

20 KiB
Raw Permalink Blame History

驿公里自营优惠券三方发放对接标准文档

一、业务时序图

二、接口鉴权

  1. 简要描述

驿公里开放所有接口均需根据鉴权流程对参数进行指定封装以下api返回均默认忽略加解密过程具体加解密参见如下表述

  1. 邮件申请驿公里API调用权限请提供以下信息(发送gyy@haixiuyizhan.com(龚云云) 抄送li.chenyang@haixiuyizhan.com(李晨阳)czy@haixiuyizhan.com(蔡哲义)ma.xiaoda@haixiuyizhan.com(马孝达))

邮件申请请求样例

我司现需申请贵公司驿公里开放平台业务开放接口权限。
提供数据如下:
1.公司名称XXXX
2.紧急联系人姓名XXX
3.紧急联系人电话XXXXXXX
4.请求服务测试环境IP(多个用逗号分割):xxx.xxx.xxx.xxx
5.请求服务生产环境IP(多个用逗号分割):xxx.xxx.xxx.xxx
6.需要请求的接口地址(多个用逗号分割):xxx/xxx

请求方会申请到以下信息,在请求接口时请按流程进行封装

字段名 必选 类型 说明
devId string 开发者账号
desKey string des秘钥
publicKey string 三方公钥和驿公里公钥,用来验签
privateKey String 三方私钥,用来加签
  1. 接口请求示例

{
"header": {
"devId": "9999999999",
"sign": "ZYWPTWM61gNzyAy62Jcx6ZpxHDOP/S5MZeoAgxt5C1AZ85vfx7bASTQA5mXCjPjM7Mylfvd3hJkvRZ9S+u7RbQBw6u/IPVSumVgwmYveRLOKpY4+LGxih12XFuw1YZHwN+swMBRPuNMsUug0vK+j6V4ivNtx4kr47nvsRU5vX9GJAdaDm+TVEP9NfbpvHQrpKeq1nLRKZhbHghH+ShID8vaYJaUQTC4+ckmyfWaTbVBm6bFDFVoaJpEopSlGXz88SWNF51LyPrqztWr/OgUxrgIczsmbQVi7Kyy4eUCLBUi8DdckUgs7ALKbF/EClyYVTp1evbP7cX0tKs470+Qo3Q==",
"timeStamp": "1726655501583"
},
"body": "rdT9YyNphdvYdZ8HOXBip3SzvC9Xvz1TJ9nK5WdOFqhzUOW1n7u2giaDZifBpKqWkR0/6g5zs+YfQykWi4pUytzgOxysrkwY+o8RtKI8lD9RLy7kXTtdVMY1/v91suqhRNQ+2MmDgAGTI9G2NnhhLXQLK8YeNQUcl8T64kgZ6gNcEAxrKiDcNiBjfIPLV+UwfKXd4/LE+KSycy7MSB+DYy3oZwrN5FHsAwgUamIFyrc="
}

  1. 请求参数说明
参数名 类型 说明
body string 加密后的请求api请求参数
header.devId string 请求方身份
sign string 签名信息
timeStamp string 时间戳
  1. 返回示例

{
"traceId": "1660552768027423965455945",
"success": true,
"resultCode": "SUCCESS",
"errorMsg": null,
"extInfo": null,
"resultInfo": {
"header": {
"devId": "9999999999",
"sign": "fSpmk+DBtiJ2nL3tNKt460pWdbuZc9zFkSvJdAl7Gx023up8wA4qN6Ez+GQ5Wp7SEteOCHvteSaEwgBv/euLo8kE8nmwB0jGqB/I7WMESDUTjWBUOiXJrA2/OPO8m0v947EuTSOqCM4ILWX9NY+qknqB73dRTjJ1O1YjoElTsBNHlmwR0GNV3Q5aE/GS1ZZYfI38e5ACVZvDgwVgrWEaQ0dPgVpcnInLXzeryeY6+iVcK/QtyGrL/7Fi51IHQ0p7wGOpSFY6dXZYWiidz2r8tme6HJgVehFqmQb8SMMSCEswZYBa1mfmxMlwxwo6i6MUl3pZESs9u9xcpdTpEun67Q==",
"timeStamp": "1660552772062"
},
"body": "D9MxbcjyysV5WCOaV27gEH2S/TXYLoWMp6+dcfx5jy9O+uI77AN5xmrQUEpQkVvdoKtc8sE7KaO26NaUobRXrw=="
}
}

  1. 返回参数说明
参数名 类型 说明
traceId string 请求唯一码
success boolean 请求成功or失败
resultCode String 请求响应码
errorMsg string 错误提示信息
extInfo object 额外信息
resultInfo object 返回数据
body string 加密后的数据信息 ,注意:后续接口返回结果均为本字段解密后的结果,不做另外说明
header.devId string 请求方身份
header.sign string 签名信息
header.timeStamp string 时间戳
  1. 加密流程
  • 加密方法(请求驿公里前请将参数加密)transmissionData
  • 解密方法(获取到的返回结果请解密后使用)parseRequest

public class SecurityUtil {
public static final String CHARSET = "UTF-8";

public SecurityUtil() {
}

private static final byte[] decryptBASE64(String key) {
return Base64.decodeBase64(key);
}

private static final String encryptBASE64(byte[] key) {
return Base64.encodeBase64String(key);
}

/**
* 通过这个方法解密响应数据中的body字段得到真正的业务数据
*
* @param jsonObject body原文
* @param cryptData      响应数据中的body字段值
* @param desKey     申请到的desKey
* @param charset     "utf-8"
* @return   业务响应数据
*/
public static final String decryptDes(String cryptData, String key, String charset) {
try {
return new String(DESCoder.decrypt(decryptBASE64(cryptData), decryptBASE64(key)), charset);
} catch (Exception var4) {
throw new RuntimeException("解密错误,错误信息:", var4);
}
}

public static final String encryptDes(String data, String key, String charset) {
try {
return encryptBASE64(DESCoder.encrypt(data.getBytes(charset), decryptBASE64(key)));
} catch (Exception var4) {
throw new RuntimeException("加密错误,错误信息:", var4);
}
}

public static final String signRSA(String data, String privateKey, String charset) {
try {
return encryptBASE64(sign(data.getBytes(charset), decryptBASE64(privateKey)));
} catch (Exception var4) {
throw new RuntimeException("签名错误,错误信息:", var4);
}
}

public static final boolean verifyRSA(String data, String publicKey, String sign, String charset) {
try {
return verify(data.getBytes(charset), decryptBASE64(publicKey), decryptBASE64(sign));
} catch (Exception var5) {
throw new RuntimeException("验签错误,错误信息:", var5);
}
}


public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(priKey);
signature.update(data);
return signature.sign();
}

public static boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(sign);
}

/**
* 加密数据
*
* @param jsonObject body原文
* @param devId      申请到的开发者ID
* @param desKey     申请到的desKey
* @param privateKey     privateKey
* @return
*/
public static final DataBean transmissionData(JSONObject jsonObject, String desKey, String privateKey, String devId) {
//body密文
String content = SecurityUtil.encryptDes(jsonObject.toJSONString(), desKey, CHARSET);
//加签
String signBack = SecurityUtil.signRSA(jsonObject.toJSONString(), privateKey, CHARSET);

DataBean dataBean = new DataBean();
dataBean.setBody(content);
DataBean.Header header = new DataBean.Header();
header.setDevId(devId);
header.setSign(signBack);
header.setTimeStamp(String.valueOf(System.currentTimeMillis()));
dataBean.setHeader(header);
return dataBean;
}


/**
* 解密数据
*/
private static void parseRequest(String bodyStr,String desKey,String publicKey) {
// 转JSONObject
JSONObject requestBody = JSONObject.parseObject(bodyStr);
// 1. 获取消息体中key为"body"的value值
String body = requestBody.getString("body");
if (StringUtils.isEmpty(body)) {
throw new IllegalArgumentException("消息体中key为body的value值为空");
}
// 2.获取消息体中key为"header"的value值
String header = requestBody.getString("header");
if (StringUtils.isEmpty(header)) {
throw new IllegalArgumentException("消息体中key为header的value值为空");
}
DataBean.Header headerBody = JSONObject.parseObject(header, DataBean.Header.class);

// 根据解析出来的headerBody对象获取指定的属性值
String devId = headerBody.getDevId();
if (StringUtils.isEmpty(devId)) {
throw new IllegalArgumentException("devId为空");
}

String sign = headerBody.getSign();
if (StringUtils.isEmpty(sign)) {
throw new IllegalArgumentException("sign为空");
}
// 解密消息体中的body值
String data = SecurityUtil.decryptDes(body, desKey, SecurityUtil.CHARSET);
// 验签
if (!SecurityUtil.verifyRSA(data, publicKey, sign, SecurityUtil.CHARSET)) {
throw new IllegalArgumentException("验签不通过");
}

HashMap<String, String> map = new HashMap(4);
map.put("devId", devId);
map.put("data", data);
System.out.println("解密后的数据为: "+JSONObject.toJSONString(map));
}



public static void main(String[] args) throws Exception {

JSONObject jsonObject = new JSONObject();
jsonObject.put("name","sdfsfsf");
//加密数据
DataBean dataBean = transmissionData(jsonObject, "${desKey}", "${privateKey}", "${devId}");
System.out.println("加密后的数据为: "+JSONObject.toJSONString(dataBean));
//解密数据
parseRequest(JSONObject.toJSONString(dataBean),"${desKey}","${publicKey}");
}

}
@Data
class DataBean {
private Header header;
private String body;

@Data
static class Header {
private String devId;
private String sign;
      private String timeStamp;
}
}
class DESCoder {

public DESCoder() {
}

private static SecretKey toKey(byte[] key) {
SecretKey k = new SecretKeySpec(key, "DES");
return k;
}

public static byte[] decrypt(byte[] data, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
SecretKey k = toKey(key);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5PADDING", "BC");
cipher.init(2, k);
return cipher.doFinal(data);
}

public static byte[] encrypt(byte[] data, byte[] key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException, NoSuchProviderException {
Key k = toKey(key);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5PADDING", "BC");
cipher.init(1, k);
return cipher.doFinal(data);
}
}

三、API调用说明

3.1 baseUrl的值根据环境区分

3.2 返回参数说明

{
"traceId": "1722324871690553293106291",
"success": true,
"resultCode": "SUCCESS",
"errorMsg": null,
"extInfo": {
"requestTime": "1722324880384",
"message": "操作成功"
},
"resultInfo": {
"header": {
"devId": "9999999999",
"sign": "AOkgoadCmRRvT+IoA0A1DpMf7rR0D84lmbEeAh4aN/XWJ1iIfn35a7wrsFWFoevIeD0RpKLhwxVuxnnvUrjFECWvYs5W6M71UT8AXnZBEfXTqnZRwtywjKVmoctfYRinEphj/attADj2vXZsyGVZYzIiak782Cz0TC5fJsB5eLT4owwd6mVyOnmp4pfsfiXwCCA6XqyOZzP6AFc1hPU09fnLbu9Dwrdw1LUg8+M2C8dwO4zK9FQxd2WW5e7miRMqtQIj3eT9wD7Xca/3yD+NcZ+Yb4JYQLnqjw4GWw9iVYnCrSS/CnsqoRi0jeM9vcYuQR+a5kKn06Dh5SuTwThA/w==",
"timeStamp": "1722324925168"
},
"body": "uGHNuwp5DTmcr/iNDgTK1L+2byrJiQ+Uy8uXCgmPqn2pe3FJrvgnnvorBiAdDWDuiY9e+rK1+Qbpf1eDLSdBsxgOFv+ScrkjNfD4BHsHOClMB6E/8qr2VekH82puteUdmdXa0I+w8f0="
}
}

参数名 类型 说明
traceId String 请求唯一码
success boolean 请求成功or失败
resultCode String 请求响应码
erroMsg String 错误提示信息
extInfo Map 额外信息
resultInfo object 返回数据
resultInfo.header.devId String 请求方身份
resultInfo.header.sign String 签名信息
resultInfo.body String 加密后的数据信息
  • success等于true 或者 resultCode等于"SUCCESS"表示请求成功
  • 请求成功了如果有数据返回则在resultInfo里面
  • 注意此文档以下的API返回结果中的resultInfo均为resultInfo.body解密后的结果不做另外说明

四、开放平台接口设计

4.1 优惠券基本信息查询接口

  • 请求路径:{{baseUrl}}/open/yglOpenPlatform/getTicketPackageInfo
  • 请求方法POST application/json
  • 请求入参:

{
"ticketPackageCode": "32a982ad16984ab1bd3f7aa7d860e0c6",
"mobile":"17800000000",
"extParams":{}
}

参数 示例 是否必填 类型 备注
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户需要购买的券包码
mobile 17800000000 String 用户手机号
extParams JSONObject 拓展字段,非必传
  • 返回参数

{
"traceId": null,
"success": true,
"resultCode": "SUCCESS",
"errorMsg": null,
"extInfo": null,
"resultInfo": {
"devId": "9999999999",
"data": {
"couponType": 1,
"endTime": "2024-09-30 23:59:59",
"startTime": "2024-09-18 00:00:00",
"ticketNum": 1,
"ticketPackageName": "三方通用单券券包测试"
}
}
}

参数 示例 类型 备注
couponType 1 Integer 券包类型1 单券包单券/2 单券包多券)
startTime 2024-09-18 00:00:00 String 购买有效期开始时间
endTime 2024-09-30 23:59:59 String 购买有效期结束时间
ticketNum 1 Integer 优惠券数量(一个券包中有几张优惠券就返回几)
ticketPackageName 三方通用单券券包测试 String 券包名称

建议:由三方系统根据返回信息自行判断优惠券是否可以购买,避免发生用户购买后优惠券无法发放的问题

4.2 优惠券发放接口

  • 请求路径:{{baseUrl}}/open/yglOpenPlatform/generateTicket
  • 请求方法POST application/json
  • 请求入参:

{
"ticketPackageCode": "32a982ad16984ab1bd3f7aa7d860e0c6",
"mobile": "17800000000",
"orderId": "123321123324",
"couponType": 1,
"amount": {
"totalPrice": "299",
"settlementPrice": "299"
}
}

参数 示例 是否必填 类型 备注
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户需要购买的券包码(向驿公里申请获得)
mobile 17800000000 String 用户手机号
orderId 123321123324 String 三方系统订单号
couponType 1 Integer 券包类型1 单券包单券/2 单券包多券)
amount.totalPrice 299 BigDecimal 订单原价,单位分
amount.settlementPrice 299 BigDecimal 订单结算价,单位分
  • 返回参数

{
"traceId": null,
"success": true,
"resultCode": "SUCCESS",
"errorMsg": null,
"extInfo": null,
"resultInfo": {
"devId": "9999999999",
"data": {
"orderId": "123321123324",
"ticketList": [
{
"isUsed": 0,
"status": 0,
"ticketEndTime": "2024-12-18 23:59:59",
"ticketId": "1836651122727530498",
"ticketName": "三方通用发券单券测试",
"ticketPackageCode": "32a982ad16984ab1bd3f7aa7d860e0c6",
"ticketStartTime": "2024-09-19 14:18:46"
}
],
"ticketPackageCode": "32a982ad16984ab1bd3f7aa7d860e0c6"
}
}
}

参数 示例 类型 备注
orderId 123321123324 String 三方系统订单id
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户需要购买的券包码(向驿公里申请获得)
ticketList.isUsed 0 Integer 0未使用、1已使用
ticketList.status 0 Integer 0有效、1无效
ticketList.ticketStartTime 2024-09-19 14:18:46 String 优惠券有效开始时间
ticketList.ticketEndTime 2024-12-18 23:59:59 String 优惠券有效结束时间
ticketList.ticketId 1836651122727530498 String 优惠券id
ticketList.ticketName 三方通用发券单券测试 String 优惠券名称

4.3 优惠券取消接口

  • 请求路径:{{baseUrl}}/open/yglOpenPlatform/cancelTicket
  • 请求方法POST application/json
  • 请求入参:

{
"ticketPackageCode": "32a982ad16984ab1bd3f7aa7d860e0c6",
"orderId": "123321123324",
"refundReason": "退款测试",
"ticketIds": [
"1836368497303564289"
]
}

参数 示例 是否必填 类型 备注
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户需要购买的券包码
orderId 123321123324 String 三方系统订单id
refundReason 退款测试 String 退款理由
ticketIds ["1836368497303564289"] ArrayList 退券的优惠券id列表
  • 返回参数

{
"traceId": null,
"success": true,
"resultCode": "SUCCESS",
"errorMsg": null,
"extInfo": null,
"resultInfo": {
"devId": "9999999999",
"data": "优惠券回收成功"
}
}

参数 示例 类型 备注
data 优惠券回收成功 String 回收结果

五、合作方需提供的接口标准

合作方如果接口返回异常驿公里侧会在一小时内进行3次重试3次结果皆失败后不再重试回调

5.1 优惠券核销回调接口

  • 请求路径:由合作方提供
  • 请求方法POST application/json
  • 请求入参:

{
"orderId": "1836608722336997377",
"ticketPackageCode":"32a982ad16984ab1bd3f7aa7d860e0c6",
"ticketId":"1836344565135241217",
"useTime":"2024-09-18 18:00:37",
"factory":{
"factoryId":"4345",
"factoryName":"滨江区区政府38",
"factoryAddress":"xxxxxx"
},
"orderPrice":{
"price":"1500",
"discountPrice":"1500",
"orderPrice":"0"
}


}

参数 示例 是否必填 类型 备注
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户所使用的优惠券券包码
orderId 1836608722336997377 String 驿公里的订单id
ticketId 1836344565135241217 String 用户所使用的优惠券id
useTime 2024-09-18 18:00:37 String 核销时间
factory.factoryId 4345 String 核销门店id
factory.factoryName 滨江区区政府38 String 核销门店名称
factory.factoryAddress xxxxxx String 核销门店地址
orderPrice.price 1500 BigDecimal 订单原价,单位分
orderPrice.discountPrice 1500 BigDecimal 订单折扣价,单位分
orderPrice.payPrice 0 BigDecimal 订单实付金额,单位分
  • 返回参数

{
"success": true,
"errorMsg": null
}

参数 示例 类型 备注
success true boolean 接口请求结果
errorMsg 错误信息 String 如果接口请求失败,请写明失败原因

5.2 优惠券退回回调接口

  • 请求路径:由合作方提供
  • 请求方法POST application/json
  • 请求入参:

{
"orderId": "1836608722336997377",
"ticketPackageCode":"32a982ad16984ab1bd3f7aa7d860e0c6",
"ticketId":"1836344565135241217",
"refundTime":"2024-09-18 18:00:37"
}

参数 示例 是否必填 类型 备注
ticketPackageCode 32a982ad16984ab1bd3f7aa7d860e0c6 String 用户所使用的优惠券券包码
orderId 1836608722336997377 String 驿公里的订单id
ticketId 1836344565135241217 String 用户所使用的优惠券id
refundTime 2024-09-18 18:00:37 String 核销时间
  • 返回参数

{
"success": true,
"errorMsg": null
}

参数 示例 类型 备注
success true boolean 接口请求结果
errorMsg 错误信息 String 如果接口请求失败,请写明失败原因