源:https://blog.ximinghui.org/42d3a28e/index.html
〇. 注册VISA开发者中心账号
VISA开发者中心:https://developer.visa.com
一. 创建一个项目
在VISA Dashboard页面创建一个项目
VISA Dashboard页面:https://developer.visa.com/portal/app/dashboard
填写表单信息(示范如下):
Project Name(项目名字): test
Select APIs(选择API):
- Visa Direct request to pay
- Visa Subscription Manager
- Visa Merchant Offers Resource Center
- Payment Account Validation
- Merchant Search
- Visa Direct
Select Authentication Method(选择认证方式):
- Mutual Authentication
- API KEY - Shared Secret (X-Pay-Token)
Submit a Certificate Signing Request(提交证书签名请求):Generate a CSR for me (default)
提交表单后会进入下一个页面,在页面中下载证书私钥(Certificate Private Key),下载到的是 命名类似key_5884a5e5-c839-4970-834c-c81a05a55320.pem
的文件。
提示:该证书私钥只能在当前页面可以被下载,之后将无法再次获取到。
点击继续后完成项目创建。
VISA有两种认证方式:
双向SSL认证 (Two-Way/Mutual SSL):
- 需要配置证书(.jks 文件)
- 使用用户ID和密码进行 Basic 认证
- 安全性更高,但配置更复杂
- 主要使用 SSL 证书进行身份验证
X-Pay-Token (API Key + Shared Secret):
- 使用 API Key 和 Shared Secret
- 通过生成 x-pay-token 进行认证
- 配置相对简单
- 使用 HMAC-SHA256 算法生成签名
二、X-Pay-Token认证方式
1. 处理X-Pay Token的Shared Secret
在项目详情页侧边栏进入 Credentials 页面,滚动到下面的X-Pay Token部分。
应该看到 API Key 已存在,但是 Shared Secret 是空白的。
这里需要自己在本地机器上生成一对儿公钥私钥,然后上传公钥到该页面的输入框中提交,Shared Secret就会出现内容(不再是空白的)。
内容为base64加密后的内容,应base64解密后再用私钥解密拿到最终的Shared Secret。步骤如下:
也可以在这里参考VISA官方资料。
a) 生成一对儿公钥私钥
:: 提示:以下命令将公私钥文件存储在C:\Users\ximinghui\Downloads目录 ::
openssl genpkey -algorithm RSA -out "C:\Users\ximinghui\Downloads\private_key.pem" -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in "C:\Users\ximinghui\Downloads\private_key.pem" -out "C:\Users\ximinghui\Downloads\public_key.pem"
b) 提交公钥内容给VISA,并获取加密的Shared Secret内容
以文本形式打开 C:\Users\ximinghui\Downloads\public_key.pem
文件,内容格式如下(示范):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzElPIpgkjzjSkvIvJERC
41jG5ZPk3l/JAd7qiK+CFax9rJCh0QiI18c5zfTpl/+7So2el51JztENnGvFMS1o
XnQPhJxJkC7Zr9hZ/XUwY8OJSRWm/1YAdaS8+WBJ5JRvjMEgobPDgZONR4HdvI2m
leFeN2rexVzat/GQkdtu5izu5EoNAOJu03MmRUThfj+p/CKTbpTqzfY0mGTmcjIM
o6xXqFPZoyDUtl2aZsR7/U6MsiyiVzwEPGXtniiB5Tm01MtGW4PMHD9DDwvA6d2e
+E1/2f9meTBWCeBNdq03G7mXHk+A0XutFCI8tLKIhrHpdgDb88t+P5yCCOImToms
HwIDAQAB
-----END PUBLIC KEY-----
取【-----BEGIN PUBLIC KEY-----】和【-----END PUBLIC KEY-----】中间部分,并移除换行(即移除回车),内容格式如下(示范):
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzElPIpgkjzjSkvIvJERC41jG5ZPk3l/JAd7qiK+CFax9rJCh0QiI18c5zfTpl/+7So2el51JztENnGvFMS1oXnQPhJxJkC7Zr9hZ/XUwY8OJSRWm/1YAdaS8+WBJ5JRvjMEgobPDgZONR4HdvI2mleFeN2rexVzat/GQkdtu5izu5EoNAOJu03MmRUThfj+p/CKTbpTqzfY0mGTmcjIMo6xXqFPZoyDUtl2aZsR7/U6MsiyiVzwEPGXtniiB5Tm01MtGW4PMHD9DDwvA6d2e+E1/2f9meTBWCeBNdq03G7mXHk+A0XutFCI8tLKIhrHpdgDb88t+P5yCCOImTomsHwIDAQAB
将内容粘贴到public key内容输入框中,并点提交按钮。
提交之后,就可以看到 Shared Secret 出现内容了。
c) 解密Shared Secret内容
:: 将加密的Shared Secret内容存储到文本文件中(根据情况调整路径和文件名) ::
echo 替换成上一步看到加密Shared Secret内容 > "C:\Users\ximinghui\Downloads\encrypted_shared_secret.txt"
:: 对上面文件进行base64解码,并将解码数据存储到二进制文件中(根据情况调整路径和文件名) ::
certutil -decode "C:\Users\ximinghui\Downloads\encrypted_shared_secret.txt" "C:\Users\ximinghui\Downloads\decoded_data.bin"
:: 使用私钥对上面的二进制文件解码,并将解码后的内容存储到文本文件中(根据情况调整路径和文件名) ::
openssl pkeyutl -decrypt -in "C:\Users\ximinghui\Downloads\decoded_data.bin" -inkey "C:\Users\ximinghui\Downloads\private_key.pem" -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 > "C:\Users\ximinghui\Downloads\decrypted_shared_secret.txt"
至此, decrypted_shared_secret.txt
文件中的内容就是解密后的Shared Secret了。
2. 测试Hello World代码
项目详情页侧边栏进入Assert页面,可以下载一些示范代码。这里贴出了VISA提供的Hello World代码的Java版本:
/*
* (c) Copyright 2018 - 2020 Visa. All Rights Reserved.**
*
* NOTICE: The software and accompanying information and documentation (together, the “Software”) remain the property of and are proprietary to Visa and its suppliers and affiliates. The Software remains protected by intellectual property rights and may be covered by U.S. and foreign patents or patent applications. The Software is licensed and not sold.*
*
* By accessing the Software you are agreeing to Visa's terms of use (developer.visa.com/terms) and privacy policy (developer.visa.com/privacy).In addition, all permissible uses of the Software must be in support of Visa products, programs and services provided through the Visa Developer Program (VDP) platform only (developer.visa.com). **THE SOFTWARE AND ANY ASSOCIATED INFORMATION OR DOCUMENTATION IS PROVIDED ON AN “AS IS,” “AS AVAILABLE,” “WITH ALL FAULTS” BASIS WITHOUT WARRANTY OR CONDITION OF ANY KIND. YOUR USE IS AT YOUR OWN RISK.** All brand names are the property of their respective owners, used for identification purposes only, and do not imply product endorsement or affiliation with Visa. Any links to third party sites are for your information only and equally do not constitute a Visa endorsement. Visa has no insight into and control over third party content and code and disclaims all liability for any such components, including continued availability and functionality. Benefits depend on implementation details and business factors and coding steps shown are exemplary only and do not reflect all necessary elements for the described capabilities. Capabilities and features are subject to Visa’s terms and conditions and may require development,implementation and resources by you based on your business and operational details. Please refer to the specific API documentation for details on the requirements, eligibility and geographic availability.*
*
* This Software includes programs, concepts and details under continuing development by Visa. Any Visa features,functionality, implementation, branding, and schedules may be amended, updated or canceled at Visa’s discretion.The timing of widespread availability of programs and functionality is also subject to a number of factors outside Visa’s control,including but not limited to deployment of necessary infrastructure by issuers, acquirers, merchants and mobile device manufacturers.*
*
*/
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
public class HelloWorld {
public static void main(String[] args) throws Exception {
String apiKey = "Credentials页面的X-Pay Token部分看到的API Key";
String sharedSecret = "上面解密后的Shared Secret内容";
String resourcePath = "helloworld";
String queryString = "apiKey=" + apiKey;
String requestBody = "";
System.out.println("START Sample Code for Api Key-Shared Secret (X-Pay-Token)");
URL url = new URL("https://sandbox.api.visa.com/vdp/helloworld?" + queryString);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json");
String xPayToken = generateXpaytoken(resourcePath, queryString, requestBody, sharedSecret);
con.setRequestProperty("x-pay-token", xPayToken);
int status = con.getResponseCode();
System.out.println("Http Status: " + status);
BufferedReader in;
if (status == 200) {
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else {
in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
System.out.println("Api Key-Shared Secret (X-Pay-Token) test failed");
}
String response;
StringBuffer content = new StringBuffer();
while ((response = in.readLine()) != null) {
content.append(response);
}
in.close();
con.disconnect();
System.out.println(content.toString());
System.out.println("END Sample Code for Api Key-Shared Secret (X-Pay-Token)");
}
public static String generateXpaytoken(String resourcePath, String queryString, String requestBody, String sharedSecret) throws SignatureException {
String timestamp = timeStamp();
String beforeHash = timestamp + resourcePath + queryString + requestBody;
String hash = hmacSha256Digest(beforeHash, sharedSecret);
String token = "xv2:" + timestamp + ":" + hash;
return token;
}
private static String timeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000L);
}
private static String hmacSha256Digest(String data, String sharedSecret)
throws SignatureException {
return getDigest("HmacSHA256", sharedSecret, data, true);
}
private static String getDigest(String algorithm, String sharedSecret, String data, boolean toLower) throws SignatureException {
try {
Mac sha256HMAC = Mac.getInstance(algorithm);
SecretKeySpec secretKey = new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), algorithm);
sha256HMAC.init(secretKey);
byte[] hashByte = sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
String hashString = toHex(hashByte);
return toLower ? hashString.toLowerCase() : hashString;
} catch (Exception e) {
throw new SignatureException(e);
}
}
private static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
}
替换 apiKey
和 sharedSecret
,运行代码,看到HTTP状态码为200,内容为 {"timestamp":"2024-12-19T05:46:02","message":"helloworld"}
就意味着成功了。
三、Two-Way/Mutual SSL认证方式
下面步骤讲述双向SSL认证 (Two-Way/Mutual SSL) 方式。
也可以参考Github上的示范代码仓库的 自述文档 。
1. 下载证书
在项目详情页侧边栏进入 Credentials 页面。
a) 在Two-Way SSL部分,点击Download Certificate按钮下载 cert.pem
证书。
b) 在Common Certificates部分,点击VDPCA按钮下载 VDPCA-SBX.pem
证书。
2. 生成jks文件
:: -inkey选项为创建项目时下载的证书私钥 ::
:: 此步需要设置一个PRIVATE_KEY_PASSWORD,这里我设置的是123456 ::
openssl pkcs12 -export -in "C:\Users\ximinghui\Downloads\cert.pem" -inkey "C:\Users\ximinghui\Downloads\key_5884a5e5-c839-4970-834c-c81a05a55320.pem" -certfile "C:\Users\ximinghui\Downloads\cert.pem" -out "C:\Users\ximinghui\Downloads\key.p12"
:: 此步需要设置一个KEYSTORE_PASSWORD,这里我设置的是000000 ::
:: 要求输入 source keystore password 时应该输入上面设置的密码123456 ::
keytool -importkeystore -srckeystore "C:\Users\ximinghui\Downloads\key.p12" -srcstoretype PKCS12 -destkeystore "C:\Users\ximinghui\Downloads\key.jks"
3. 测试Hello World代码
项目详情页侧边栏进入Assert页面,可以下载一些示范代码。这里贴出了VISA提供的Hello World代码的Java版本:
/*
* (c) Copyright 2018 - 2020 Visa. All Rights Reserved.**
*
* NOTICE: The software and accompanying information and documentation (together, the “Software”) remain the property of and are proprietary to Visa and its suppliers and affiliates. The Software remains protected by intellectual property rights and may be covered by U.S. and foreign patents or patent applications. The Software is licensed and not sold.*
*
* By accessing the Software you are agreeing to Visa's terms of use (developer.visa.com/terms) and privacy policy (developer.visa.com/privacy).In addition, all permissible uses of the Software must be in support of Visa products, programs and services provided through the Visa Developer Program (VDP) platform only (developer.visa.com). **THE SOFTWARE AND ANY ASSOCIATED INFORMATION OR DOCUMENTATION IS PROVIDED ON AN “AS IS,” “AS AVAILABLE,” “WITH ALL FAULTS” BASIS WITHOUT WARRANTY OR CONDITION OF ANY KIND. YOUR USE IS AT YOUR OWN RISK.** All brand names are the property of their respective owners, used for identification purposes only, and do not imply product endorsement or affiliation with Visa. Any links to third party sites are for your information only and equally do not constitute a Visa endorsement. Visa has no insight into and control over third party content and code and disclaims all liability for any such components, including continued availability and functionality. Benefits depend on implementation details and business factors and coding steps shown are exemplary only and do not reflect all necessary elements for the described capabilities. Capabilities and features are subject to Visa’s terms and conditions and may require development,implementation and resources by you based on your business and operational details. Please refer to the specific API documentation for details on the requirements, eligibility and geographic availability.*
*
* This Software includes programs, concepts and details under continuing development by Visa. Any Visa features,functionality, implementation, branding, and schedules may be amended, updated or canceled at Visa’s discretion.The timing of widespread availability of programs and functionality is also subject to a number of factors outside Visa’s control,including but not limited to deployment of necessary infrastructure by issuers, acquirers, merchants and mobile device manufacturers.*
*
*/
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.Base64;
public class HelloWorld {
public static void main(String[] args) throws Exception {
System.out.println("START Sample Code for Two-Way (Mutual) SSL");
URL url = new URL("https://sandbox.api.visa.com/vdp/helloworld");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
// THIS IS EXAMPLE ONLY how will cert and key look like
// keystorePath = "visa.jks"
// keystorePassword = "password"
String keystorePath = "你的jks文件路径,如 C:/Users/ximinghui/Downloads/key.jks";
String keystorePassword = "你的KEYSTORE_PASSWORD,如 000000";
// Make a KeyStore from the PKCS-12 file
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(keystorePath)) {
ks.load(fis, keystorePassword.toCharArray());
}
// Make a KeyManagerFactory from the KeyStore
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keystorePassword.toCharArray());
// Now make an SSL Context with our Key Manager and the default Trust Manager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
if (con instanceof HttpsURLConnection) {
((HttpsURLConnection) con).setSSLSocketFactory(sslContext.getSocketFactory());
}
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json");
String userId = "在项目详情页侧边栏进入Credentials页面,Two-Way SSL部分的Credentials行项,展开后看到的 User ID 和 Password";
String password = "在项目详情页侧边栏进入Credentials页面,Two-Way SSL部分的Credentials行项,展开后看到的 User ID 和 Password";
String auth = userId + ":" + password;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
String authHeaderValue = "Basic " + new String(encodedAuth);
con.setRequestProperty("Authorization", authHeaderValue);
int status = con.getResponseCode();
System.out.println("Http Status: " + status);
BufferedReader in;
if (status == 200) {
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else {
in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
System.out.println("Two-Way (Mutual) SSL test failed");
}
String response;
StringBuffer content = new StringBuffer();
while ((response = in.readLine()) != null) {
content.append(response);
}
in.close();
con.disconnect();
System.out.println(content.toString());
System.out.println("END Sample Code for Two-Way (Mutual) SSL");
}
}
替换 keystorePath
、 keystorePassword
、 userId
和 password
,运行代码,看到HTTP状态码为200,内容为 {"timestamp":"2024-12-19T05:46:02","message":"helloworld"}
就意味着成功了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。