1.范围

业务范围

    “银联钱包”增值服务主要包括三种类型:优惠券、积分和电子票。“优惠券”是指在结账金额上进行打折或扣减的优惠权益;“积分”可分为“银联积分”和“商户专用积分”两种。“银联积分”也称“通用积分”,是指可在银联所有的线上和线下积分合作商户使用的积分,1个银联积分等值于1元人民币;“商户专用积分”是指仅可在获赠该积分的商户及其连锁、加盟店使用的积分;“电子票”是传统纸质票据的电子化表示,如机场贵宾厅服务、电影票、登机牌、景点门票、体验券和兑换券等,可以直接在合作商户刷卡使用。

2.快速入门

2.1 概述

银联钱包系统REST服务通过HTTPS方式开放。若无特殊说明,服务默认都以POST的方式被调用。

2.2 请求报文格式

请求报文统以POST方式提交,报文为json形式,由四部分组成: 
1. appId:由银联钱包系统分配的给每个接入系统的唯一key,每次请求报文都必须包含; 
2. data:上送的数据报文主体,因服务接口而有所差别,详见API文档(说明:API文档中所列接口请求的参数均为data中的参数,不包含公共参数appId/version/signToken ;data数据域后续升级版本时可能会增加字段,key-value键值对不保证顺序,客户端需保证兼容性。)。 
3. signToken:请求报文data内容的签名信息,详见生成请求签名 
4. version:服务版本(默认填1.0);

注意:
1.请求JSON报文中元素的key(含嵌套的结构如data中的元素)需要按照ASCII的升序排序。
2.请使用UTF-8编码。 

完整的请求报文示例如下: 
{"appId":"c65d818e3eee4f3","data":{"userId":"c00000000001","username":"Jesse"},"signToken":"safasfsafasdfe","version":"1.0"}

2.3 应答报文格式

报文为json形式,由三部分组成: 
1. respCd:应答码,不同的应代码代表不同的业务含义,详见应答码说明; 
2. msg:应答提示信息; 
3. data:应答数据,因服务接口而有所差别,详见API文档。(说明:API文档中所列接口应答的参数均为data中的参数,不包含公共参数respCd/msg ;data数据域后续升级版本时可能会增加字段,key-value键值对不保证顺序,客户端需保证兼容性。) 

完整的应答报文示例如下: 
{"respCd":"000000","msg":"成功","data":"{"userId":"c00000000001"}"}

2.4 调用示例

Step 1: 获取AppId、AppSecret和AppPrivateKeyFile 
说明:AppId用来唯一标识开发者的调用、AppSecret用来加密敏感要素、AppSignToken用来对调用请求做签名。您可以登录管理平台查询这些信息,请勿对外泄露。 
您可以登录管理平台查询这些信息,请勿对外泄露。

1
2
3
String appId = "123456789012345";  
String appSecret = "sdlkfjljaelkjwljk";  
File appSignToken = new File("/localPath/privateKeyFile.pk8");

Step 2: 调用示例: 
以下为关联银行卡的示例代码。引用了jersey相关的组件:jersey-core-1.16.jar、jersey-client-1.16.jar、commons-codec-1.7.jar、gson-2.2.2.jar。 
其中,加密工具EncryptionAES请参考加密敏感信息,签名工具SignUtil请参考生成请求签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();  
TreeMap reqData = new TreeMap();  
   
reqData.put("userId""c00000000000001");  
// 对卡号加密  
reqData.put("cardNo", EncryptionAES.entrypt("6288888888888888""secret"));   
reqData.put("openService""215");  
reqData.put("needAuth""0");  
// 构造签名  
String data = gson.toJson(reqData);  
String signToken = SignUtil.sign(data, appSignToken);  
   
Client client = Client.create();  
WebResource webResource = client.resource(REQ_URL);  
TreeMap mReq = new TreeMap();  
mReq.put("data", reqData);  
mReq.put("appId", appId);  
mReq.put("version""1.0");  
mReq.put("signToken", signToken);  
   
String resp = webResource.type(MediaType.APPLICATION_JSON).post(  
String.class, gson.toJson(mReq));  
Map mResp = gson.fromJson(resp, Map.class);  
String respCd = (String) mResp.get("respCd");  
String msg = (String) mResp.get("msg");  
if (!"000000".equals(respCd)) {  
    System.out.println("失败:" + msg);  
}else{  
    Map respData = (Map) mResp.get("data");  
    String userId = (String) respData.get("userId");  
    System.out.println("成功,userId=" + userId);  
}
3.生成请求签名

3.1 Step 1: 获取签名私钥文件AppPrivateKeyFile 

您可以登录管理平台查询这些信息,请勿对外泄露。

1
2
File privateKeyFile = new File("/localPath/privateKeyFile.pk8");  
// privateKeyFile为后续生成签名方法的输入参数。

3.2 Step 2: 构造待签名的参数

完整的请求参数形如:

1
2
3
4
5
6
7
8
9
10
{  
    "appId":"123456789012345",         // 发起方ID  
    "data":  
        {  
            "para1":"xxxxx1",          // 参数1  
            "para2":"xxxxx2",          // 参数2  
            "para3":"xxxxx1"           // 参数3  
        }  
    "signToken":"saff3rsdfwa3raefsds"  // 签名信息  
}

需要签名的参数为data部分,构造为如下形式:

1
2
String dataToSign = "{"para1":"xxxxx1", "para2":"xxxxx2", "para3":"xxxxx1"}";  
// dataToSign为后续生成签名方法的输入参数,注意应使用UTF-8编码。

可以使用gson来构造签名参数,如:

1
2
3
4
5
6
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().disableHtmlEscaping().create();  
TreeMap reqData = new TreeMap();  
reqData.put("userId""c00000000000001");  
reqData.put("openService""215");  
reqData.put("needAuth""0");  
String dataToSign = gson.toJson(reqData);

3.3 Step 3: 生成请求签名

注意这里使用SHA1withRSA算法生成签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.security.Signature;  
import java.security.KeyFactory;  
import java.security.interfaces.RSAPrivateKey;  
import java.security.spec.EncodedKeySpec;  
import java.security.spec.PKCS8EncodedKeySpec;  
import java.security.spec.X509EncodedKeySpec;  
import org.apache.commons.codec.binary.Base64;  
   
class SignUtil {  
    public static String sign(String dataToSign,File privateKeyFile) {  
        byte[] sign=null;  
        try {  
            byte[] data = dataToSign.getBytes("UTF-8");  
            Signature signature = Signature.getInstance("SHA1withRSA" );  
            PrivateKey privateKey = getPrivateKey(privateKeyFile);  
            if(privateKey == null ){  
                privateKey = getPrivateKey(null);  
            }  
            if(privateKey ==  null) {  
                throw new Exception();  
            }  
            // 初始化签名,由私钥构建  
            signature.initSign(privateKey);  
            signature.update(data);  
            sign =  signature.sign();  
       catch (Exception e) {  
            System.out.println("签名出错");  
            e.printStackTrace();  
       }  
       try {  
            return byteArr2HexStr(sign);  
       catch (Exception e) {  
            e.printStackTrace();  
       }  
       return null;  
    }  
   
    public static String byteArr2HexStr(byte[] arrB) throws Exception {  
        int iLen = arrB.length;  
        StringBuffer sb = new StringBuffer(iLen * 2);  
        for (int i = 0; i < iLen; i++) {  
            int intTmp = arrB[i];  
            while (intTmp < 0) {  
                intTmp = intTmp + 256;  
            }  
            if (intTmp < 16) {  
                sb.append("0");  
            }  
            sb.append(Integer.toString(intTmp, 16));  
        }  
        return sb.toString();  
    }  
   
    private static PrivateKey getPrivateKey(File priKeyFile){  
        File file = priKeyFile;      
           
        try {  
            BufferedReader br = new BufferedReader(new FileReader(file));  
            String s = br.readLine();  
            String str = "";  
            s = br.readLine();  
            while (s.charAt(0) != '-'){  
                str += s + "\r";  
                s = br.readLine();  
            }  
                       
            byte[] b = Base64.decodeBase64(str);  
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
            EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(b);  
            RSAPrivateKey priKey = (RSAPrivateKey)keyFactory.generatePrivate(privateKeySpec);  
            return priKey;  
        catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
}
4.加密敏感信息

接口中的卡号等敏感信息要素需要加密传输,下面以java示例加密API的用法:

4.1 Step1: 获取AppSecret

您可以登录管理平台查询这些信息,请勿对外泄露。

1
2
String key = "ksjfjajwojelwjewljlksjld";  
// key为后续生成签名方法的输入参数。

4.2 Step2: 加密敏感要素

加密方法如下,使用entrypt(sensitiveInformation, key)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.io.UnsupportedEncodingException;  
import java.security.InvalidKeyException;  
import java.security.NoSuchAlgorithmException;  
   
import javax.crypto.BadPaddingException;  
import javax.crypto.Cipher;  
import javax.crypto.IllegalBlockSizeException;  
import javax.crypto.NoSuchPaddingException;  
import javax.crypto.spec.SecretKeySpec;  
import org.apache.commons.codec.binary.Base64;  
   
public class EncryptionAES {  
   
    /** 
     * 加密 
     * @param sensitiveInformation  加密的内容 
     * @param key  加密的钥匙 
     * @return 
     */  
    @SuppressWarnings("restriction")  
    public static String entrypt(String sensitiveInformation, String key)  
            throws NoSuchAlgorithmException, UnsupportedEncodingException,  
            NoSuchPaddingException, InvalidKeyException,  
            IllegalBlockSizeException, BadPaddingException {  
        String strPassword = key;  
        if (null == sensitiveInformation || sensitiveInformation.trim().length() < 1) {  
            return null;  
        }  
        strPassword = generateKey(strPassword);  
        byte[] raw = strPassword.getBytes("UTF8");  
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  
        Cipher cipher = Cipher.getInstance("AES");  
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);  
        byte[] encrypted = cipher.doFinal(sensitiveInformation.getBytes());  
        return new Base64.encodeBase64String(encrypted);  
    }  
   
    /** 
     * BASE64编码 
     *  
     * @param str 
     * @return 
     */  
    @SuppressWarnings("restriction")  
    private static String generateKey(String str)  
            throws NoSuchAlgorithmException, UnsupportedEncodingException {  
        if (null == str) {  
            str = "defaultpassword";  
        else if (str.length() < 1) {  
            str = "emptypassword";  
        }  
        java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");  
        md.update(str.getBytes("UTF8"));  
        String strret = Base64.encodeBase64String(md.digest());  
        while (strret.length() < 16) {  
            strret += "%";  
        }  
        if (strret.length() > 16) {  
            int nbegin = (strret.length() - 16) / 2;  
            strret = strret.substring(nbegin, nbegin + 16);  
        }  
        return strret;  
    }  
}

中国银联版权所有©2002-2019沪 ICP备07032180号