最近在業(yè)務(wù)系統(tǒng)中集成了銀聯(lián)商務(wù)掃碼支付-被掃業(yè)務(wù)產(chǎn)品,最開始還錯(cuò)以為是銀聯(lián)谍倦,殊不知此銀聯(lián)非彼銀聯(lián)耍铜,銀聯(lián)商務(wù)跟銀聯(lián)好像沒什么太大關(guān)系邑闺,網(wǎng)上搜到的資料也寥寥無幾,在此記錄一下集成的過程棕兼。
準(zhǔn)備工作
- 平臺(tái)地址:https://open.chinaums.com/index
- 接口文檔地址:https://open.chinaums.com/resources/?code=331548310690296&url=0f3739ca-ae2a-4a2d-b83d-e5dec596bbd1
- sdk下載:https://open.chinaums.com/resources/?code=101528298407846&url=8838eff5-3809-4f8e-aae2-2c5b00ef265e
- 測(cè)試環(huán)境參數(shù):appId,appKey,mId(商戶號(hào)),tId(終端號(hào))可從《接口測(cè)試報(bào)告模板》中獲取
開放平臺(tái)仿真測(cè)試工具
從銀聯(lián)商務(wù)平臺(tái)網(wǎng)站上下載仿真測(cè)試工具陡舅,直接安裝到谷歌瀏覽器,提示不能使用伴挚,可按照以下方法使用安裝插件
安裝成功后靶衍,可在瀏覽器右上角看到如下標(biāo)志
安裝成功
仿真測(cè)試工具
sdk是否滿足需求
sdk中封裝了token的獲取,request類中封裝了請(qǐng)求地址茎芋,如果集成被掃業(yè)務(wù)颅眶,需要添加需要調(diào)用的相關(guān)接口的request,或者寫一個(gè)公用的request田弥,在請(qǐng)求前替換請(qǐng)求地址涛酗。
sdk源碼
如果使用request方式,封裝請(qǐng)求參數(shù)是包含在data中偷厦,類似如圖方式
request封裝的請(qǐng)求參數(shù)格式
而實(shí)際被掃業(yè)務(wù)中接口訪問是不需要封裝到data中的商叹,由于我沒有想到更好更簡(jiǎn)潔的方式使用sdk中方法,所以我放棄使用了sdk沪哺,未用sdk中封裝的獲取token的方法
接口對(duì)接
被掃業(yè)務(wù)的接口其實(shí)很簡(jiǎn)單沈自,我總結(jié)我認(rèn)為我集成過程中比較困擾我的點(diǎn)
- 因?yàn)榭催^銀聯(lián)接口文檔,在下單的的請(qǐng)求參數(shù)會(huì)添加回調(diào)路徑參數(shù)辜妓,以通知交易結(jié)果枯途,下單返回成功只代表下單成功,并不代表交易已成功籍滴,銀聯(lián)商務(wù)的返回碼是00酪夷,注釋為交易成功,經(jīng)過與技術(shù)支持溝通孽惰,
- 接口文檔的最后有一句“
,須在當(dāng)天進(jìn)行查詢操作勋功,僅當(dāng)查詢返回明確訂單狀態(tài)坦报,該筆訂單支持以商戶訂單號(hào)的方式進(jìn)行退款】裥”——什么情況下需要調(diào)用查詢接口片择?訂單狀態(tài)不明確又指的什么?
- 請(qǐng)求頭設(shè)置字管,訪問前需要提供一個(gè)token啰挪,直接封裝一個(gè)工具類放在里面即可,下面我把工具類代碼放在下面
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
/**
* @author zhangjz
* @date 2019/6/3
*/
public class PayUtil {
private static Logger logger = LoggerFactory.getLogger(PayUtil.class); // 日志記錄
private static RequestConfig requestConfig = null;
static
{
// 設(shè)置請(qǐng)求和傳輸超時(shí)時(shí)間
requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
}
public static String send(String url, String entity, String appId, String appKey){
CloseableHttpResponse response = null;
String resStr = null;
CloseableHttpClient httpClient=null;
if(isHttps(url)){
httpClient = HttpClients.custom().setSSLContext(generateSSLContext()).build();
}else{
httpClient = HttpClients.createDefault();
}
try {
String authorization = getOpenBodySig(appId, appKey, entity);
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Authorization", authorization);
httpPost.setHeader("Content-type", "application/json; charset=utf-8");
StringEntity se = new StringEntity(entity,"UTF-8");
se.setContentType("application/json");
httpPost.setEntity(se);
response = httpClient.execute(httpPost);
HttpEntity entity1 = response.getEntity();
resStr = null;
if(entity1 != null){
resStr = EntityUtils.toString(entity1, "UTF-8");
}
} catch (Exception e) {
logger.error("post請(qǐng)求提交失敗:url{}嘲叔,參數(shù){}" ,url,entity, e);
return null;
}finally{
if (response!=null){
try {
httpClient.close();
response.close();
} catch (IOException e) {
logger.info("鏈接釋放失敗",e);
}
}
}
return resStr;
}
public static String getOpenBodySig(String appId, String appKey, String body) throws Exception{
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String nonce = UUID.randomUUID().toString().replace("-", "");
byte[] data = body.getBytes("UTF-8");
// System.out.println("data:\n" + body);
InputStream is = new ByteArrayInputStream(data);
String bodyDigest = testSHA256(is);
String str1_C = appId+timestamp+nonce+bodyDigest;
byte[] localSignature = hmacSHA256(str1_C.getBytes(), appKey.getBytes());
String localSignatureStr = Base64.encodeBase64String(localSignature);
logger.info("Authorization:\n" + "OPEN-BODY-SIG AppId="+"\""+appId+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+", Signature="+"\""+localSignatureStr+"\"");
return ("OPEN-BODY-SIG AppId="+"\""+appId+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+", Signature="+"\""+localSignatureStr+"\"");
}
private static String testSHA256(InputStream is) {
try {
return DigestUtils.sha256Hex(is);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static byte[] hmacSHA256(byte[] data, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
}
/**
* 檢測(cè)是否https
*
* @param url
*/
private static boolean isHttps(String url) {
return url.startsWith("https");
}
/*為了訪問https接口*/
private static SSLContext generateSSLContext(){
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}, null);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return sslContext;
}
}
總結(jié)
第一次集成第三方支付產(chǎn)品亡呵,還是所獲頗豐的,如有說的不到位的地方硫戈,望小伙伴們能夠指正锰什。