springboot 銀聯支付(掃碼支付)
應用:商戶出示二維碼,用戶掃描二維碼進行付款
官方文檔:https://open.unionpay.com/tjweb/acproduct/list?apiSvcId=468&index=2
***************************
示例
********************
配置文件
acp_sdk.properties
##############SDK配置文件(證書方式簽名)################
# 說明:
# 1. 使用時請將此文件複製到src文件夾下替換原來的acp_sdk.properties。
# 2. 具體配置項請根據註釋修改。
#
################################################
##########################入網測試環境交易發送地址(線上測試需要使用生產環境交易請求地址)#############################
##交易請求地址
acpsdk.frontTransUrl=https://gateway.test.95516.com/gateway/api/frontTransReq.do
acpsdk.backTransUrl=https://gateway.test.95516.com/gateway/api/backTransReq.do
acpsdk.singleQueryUrl=https://gateway.test.95516.com/gateway/api/queryTrans.do
acpsdk.batchTransUrl=https://gateway.test.95516.com/gateway/api/batchTrans.do
acpsdk.fileTransUrl=https://filedownload.test.95516.com/
acpsdk.appTransUrl=https://gateway.test.95516.com/gateway/api/appTransReq.do
acpsdk.cardTransUrl=https://gateway.test.95516.com/gateway/api/cardTransReq.do
#以下繳費產品使用,其餘產品用不到
acpsdk.jfFrontTransUrl=https://gateway.test.95516.com/jiaofei/api/frontTransReq.do
acpsdk.jfBackTransUrl=https://gateway.test.95516.com/jiaofei/api/backTransReq.do
acpsdk.jfSingleQueryUrl=https://gateway.test.95516.com/jiaofei/api/queryTrans.do
acpsdk.jfCardTransUrl=https://gateway.test.95516.com/jiaofei/api/cardTransReq.do
acpsdk.jfAppTransUrl=https://gateway.test.95516.com/jiaofei/api/appTransReq.do
########################################################################
# 報文版本號,固定5.1.0,請勿改動
acpsdk.version=5.1.0
# 簽名方式,證書方式固定01,請勿改動
acpsdk.signMethod=01
# 是否驗證驗簽證書的CN,測試環境請設置false,生產環境請設置true。非false的值默認都當true處理。
acpsdk.ifValidateCNName=false
# 是否驗證https證書,測試環境請設置false,生產環境建議優先嚐試true,不行再false。非true的值默認都當false處理。
acpsdk.ifValidateRemoteCert=false
#後臺通知地址,填寫接收銀聯後臺通知的地址,必須外網能訪問
acpsdk.backUrl=http://gzx7g4.natappfree.cc/notify
#前臺通知地址,填寫銀聯前臺通知的地址,必須外網能訪問
#acpsdk.frontUrl=http://gzx7g4.natappfree.cc/return
#########################入網測試環境簽名證書配置 ################################
# 多證書的情況證書路徑爲代碼指定,可不對此塊做配置。
# 簽名證書路徑,必須使用絕對路徑,如果不想使用絕對路徑,可以自行實現相對路徑獲取證書的方法;測試證書所有商戶共用開發包中的測試簽名證書,生產環境請從cfca下載得到。
# windows樣例:
acpsdk.signCert.path=D:/certs/acp_test_sign.pfx
# linux樣例(注意:在linux下讀取證書需要保證證書有被應用讀的權限)(後續其他路徑配置也同此條說明)
#acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp700000000000001.pfx
# 簽名證書密碼,測試環境固定000000,生產環境請修改爲從cfca下載的正式證書的密碼,正式環境證書密碼位數需小於等於6位,否則上傳到商戶服務網站會失敗
acpsdk.signCert.pwd=000000
# 簽名證書類型,固定不需要修改
acpsdk.signCert.type=PKCS12
##########################加密證書配置################################
# 敏感信息加密證書路徑(商戶號開通了商戶對敏感信息加密的權限,需要對 卡號accNo,pin和phoneNo,cvn2,expired加密(如果這些上送的話),對敏感信息加密使用)
acpsdk.encryptCert.path=D:/certs/acp_test_enc.cer
##########################驗簽證書配置################################
# 驗籤中級證書路徑(銀聯提供)
acpsdk.middleCert.path=D:/certs/acp_test_middle.cer
# 驗籤根證書路徑(銀聯提供)
acpsdk.rootCert.path=D:/certs/acp_test_root.cer
acpsdk.validateCert.dir=D:/certs/
********************
config 層
UnionpayConfig:讀取配置文件,初始化SDKConfig
@Configuration
public class UnionpayConfig implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
SDKConfig.getConfig().loadPropertiesFromSrc();
}
}
********************
service 層
UnionpayService
public interface UnionpayService {
String pay(String orderId,String txnAmt); //支付接口
}
********************
serviceimpl 層
UnionpayServiceImpl
@Service
public class UnionpayServiceImpl implements UnionpayService {
@Override
public String pay(String orderId, String txnAmt, String termId) {
Map<String, String> contentData = new HashMap<>();
/***銀聯全渠道系統,產品參數,除了encoding自行選擇外其他不需修改***/
contentData.put("version", DemoBase.version); //版本號 全渠道默認值
contentData.put("encoding", DemoBase.encoding); //字符集編碼 可以使用UTF-8,GBK兩種方式
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //簽名方法
contentData.put("txnType", "01"); //交易類型 01:消費
contentData.put("txnSubType", "07"); //交易子類 07:申請消費二維碼
contentData.put("bizType", "000000"); //填寫000000
contentData.put("channelType", "08"); //渠道類型 08手機
/***商戶接入參數***/
contentData.put("merId", "777***"); //商戶號碼,請改成自己申請的商戶號或者open上註冊得來的777商戶號測試
contentData.put("accessType", "0"); //接入類型,商戶接入填0 ,不需修改(0:直連商戶, 1: 收單機構 2:平臺商戶)
contentData.put("orderId", orderId); //商戶訂單號,8-40位數字字母,不能含“-”或“_”,可以自行定製規則
contentData.put("txnTime", DemoBase.getCurrentTime()); //訂單發送時間,取系統時間,格式爲YYYYMMDDhhmmss,必須取當前時間,否則會報txnTime無效
contentData.put("txnAmt", txnAmt); //交易金額 單位爲分,不能帶小數點
contentData.put("currencyCode", "156"); //境內商戶固定 156 人民幣
contentData.put("termId", termId); //選填,原則是可以通過交易上送的終端編號準確定位商戶每一個門店內每一臺收銀設備,建議按“門店編號+收銀機編號”或“設備編號”組成8位終端編號在交易中上送。商戶需將終端編號與門店對應關係反饋給銀聯。
// 請求方保留域,透傳字段,查詢、通知、對賬文件中均會原樣出現,如有需要請啓用並修改自己希望透傳的數據。
// 出現部分特殊字符時可能影響解析,請按下面建議的方式填寫:
// 1. 如果能確定內容不會出現&={}[]"'等符號時,可以直接填寫數據,建議的方法如下。
// contentData.put("reqReserved", "透傳信息1|透傳信息2|透傳信息3");
// 2. 內容可能出現&={}[]"'符號時:
// 1) 如果需要對賬文件裏能顯示,可將字符替換成全角&={}【】“‘字符(自己寫代碼,此處不演示);
// 2) 如果對賬文件沒有顯示要求,可做一下base64(如下)。
// 注意控制數據長度,實際傳輸的數據長度不能超過1024位。
// 查詢、通知等接口解析時使用new String(Base64.decodeBase64(reqReserved), DemoBase.encoding);解base64後再對數據做後續解析。
//contentData.put("reqReserved", Base64.encodeBase64String("任意格式的信息都可以".toString().getBytes(DemoBase.encoding)));
//後臺通知地址(需設置爲外網能訪問 http https均可),支付成功後銀聯會自動將異步通知報文post到商戶上送的該地址,【支付失敗的交易銀聯不會發送後臺通知】
//後臺通知參數詳見open.unionpay.com幫助中心 下載 產品接口規範 網關支付產品接口規範 消費交易 商戶通知
//注意:1.需設置爲外網能訪問,否則收不到通知 2.http https均可 3.收單後臺通知後需要10秒內返回http200或302狀態碼
// 4.如果銀聯通知服務器發送通知後10秒內未收到返回狀態碼或者應答碼非http200或302,那麼銀聯會間隔一段時間再次發送。總共發送5次,銀聯後續間隔1、2、4、5 分鐘後會再次通知。
// 5.後臺通知地址如果上送了帶有?的參數,例如:http://abc/web?a=b&c=d 在後臺通知處理程序驗證簽名之前需要編寫邏輯將這些字段去掉再驗籤,否則將會驗籤失敗
contentData.put("backUrl", DemoBase.backUrl);
/**對請求參數進行簽名併發送http post請求,接收同步應答報文**/
Map<String, String> reqData = AcpService.sign(contentData, DemoBase.encoding); //報文中certId,signature的值是在signData方法中獲取並自動賦值的,只要證書配置正確即可。
String requestAppUrl = SDKConfig.getConfig().getBackRequestUrl(); //交易請求url從配置文件讀取對應屬性文件acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData, requestAppUrl, DemoBase.encoding); //發送請求報文並接受同步應答(默認連接超時時間30秒,讀取返回結果超時時間30秒);這裏調用signData之後,調用submitUrl之前不能對submitFromData中的鍵值對做任何修改,如果修改會導致驗籤不通過
String qrCode = null;
if (!rspData.isEmpty()) {
if (AcpService.validate(rspData, DemoBase.encoding)) {
LogUtil.writeLog("驗證簽名成功");
String respCode = rspData.get("respCode");
if (("00").equals(respCode)) {
System.out.println("受理成功");
//TODO
qrCode=rspData.get("qrCode");
} else {
//其他應答碼爲失敗請排查原因或做失敗處理
//TODO
System.out.println("受理失敗");
}
}else {
LogUtil.writeLog("驗籤失敗");
}
}else {
LogUtil.writeErrorLog("未獲取到返回報文或返回http狀態碼非200");
}
return qrCode;
}
}
********************
controller 層
UnionpayController
@RestController
public class UnionpayController {
@Resource
private UnionpayService unionpayService;
@RequestMapping("/pay")
public void pay(HttpServletResponse response) throws Exception{
String qrCode=unionpayService.pay7(DemoBase.getOrderId(),"2000","12387658");
makeQRCode(qrCode,response.getOutputStream());
}
public void makeQRCode(String content, OutputStream outputStream) throws Exception{ //生成二維碼
Map<EncodeHintType,Object> map=new HashMap<>();
map.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);
map.put(EncodeHintType.MARGIN,2);
QRCodeWriter qrCodeWriter=new QRCodeWriter();
BitMatrix bitMatrix=qrCodeWriter.encode(content, BarcodeFormat.QR_CODE,200,200,map);
MatrixToImageWriter.writeToStream(bitMatrix,"jpeg",outputStream);
}
@RequestMapping("/notify")
public void hello2(HttpServletRequest request,HttpServletResponse response) throws Exception{
Map<String,String> result=new HashMap<>();
System.out.println("======= 後臺通知 ========");
Enumeration<String> names=request.getParameterNames();
if (names!=null){
while (names.hasMoreElements()){
String name=names.nextElement();
String value=request.getParameter(name);
result.put(name,value);
System.out.println(name+" ==> "+value);
if (result.get(name)==null||"".equals(result.get(name))){
result.remove(name);
}
}
}
if (AcpService.validate(result, SDKConstants.UTF_8_ENCODING)){
System.out.println("後臺驗籤成功");
}else {
System.out.println("後臺驗籤失敗");
}
response.getWriter().print("ok");
}
}
***************************
使用測試
localhost:8080/pay