目录
一、准备工作
二、开始对接
三、总结
一、准备工作1. 获取建行龙支付对接文档(注意:建行会给指定邮箱发送16个rar的压缩包)都下载完才能获取到完整文档,解压完可以看到名为“建行龙支付接入指南V1.32”的文件夹,里面的内容为6个文件夹1个pdf文档。
2. 获取各种资料
1).微信商户编号
2).商户柜台编号
3).建行商户编号
4).终端号
5).分行代码
6).商户公钥
3. 开通权限
注意:需要联系分管贵公司的建行工作人员,开通服务器实时反馈和退款的权限。
二、开始对接这里我使用的是SpringBoot框架进行对接
1. 配置application.yml文件
jh:
merchantId: 商户编号
posId: 柜台编号
branchId: 分行代码
subAppId: 小程序APPID(这里参考贵公司的支付渠道,我方需使用微信小程序支付)
tradeType: 支付类型(这里参考贵公司的支付渠道,我方需使用微信小程序支付)
pub: 公钥串
url: 接口地址
operatorCode: 商户操作员编号
pwd: 操作员密码
wlptServerIp: 外联平台ip地址
wlptPort: 外联平台端口号
2. 统一下单接口对接
2.1 下单接口参数实体类
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigDecimal;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class JhPlaceOrderInfo {
@JSONField(name="MERCHANTID")
private String MERCHANTID;
@JSONField(name="POSID")
private String POSID;
@JSONField(name="BRANCHID")
private String BRANCHID;
@JSONField(name="ORDERID")
private String ORDERID;
@JSONField(name="PAYMENT")
private String PAYMENT;
@JSONField(name="CURCODE")
private String CURCODE;
@JSONField(name="REMARK1")
private String REMARK1;
@JSONField(name="REMARK2")
private String REMARK2;
@JSONField(name="TXCODE")
private String TXCODE;
@JSONField(name="MAC")
private String MAC;
@JSONField(name="TYPE")
private String TYPE;
@JSONField(name="PUB")
private String PUB;
@JSONField(name="GATEWAY")
private String GATEWAY;
@JSONField(name="CLIENTIP")
private String CLIENTIP;
@JSONField(name="REGINFO")
private String REGINFO;
@JSONField(name="PROINFO")
private String PROINFO;
@JSONField(name="EFERER")
private String EFERER;
@JSONField(name="TIMEOUT")
private String TIMEOUT;
@JSONField(name="TRADE_TYPE")
private String TRADE_TYPE;
@JSONField(name="SUB_APPID")
private String SUB_APPID;
@JSONField(name="SUB_OPENID")
private String SUB_OPENID;
@JSONField(name="WX_CHANNELID")
private String WX_CHANNELID;
@JSONField(name="RETURN_FIELD")
private String RETURN_FIELD;
@JSONField(name="USERPARAM")
private String USERPARAM;
}
2.2 下单接口Service以及实现
public interface IJhPayService {
public Map<String,Object> unifiedPlaceOrder(JhPlaceOrderInfo jhPlaceOrderInfo);
}
@Service
public class JhPayServiceImpl implements IJhPayService {
@Value("${jh.merchantId}")
private String merchantId;
@Value("${jh.posId}")
private String posId;
@Value("${jh.branchId}")
private String branchId;
@Value("${jh.subAppId}")
private String subAppId;
@Value("${jh.tradeType}")
private String tradeType;
@Value("${jh.pub}")
private String pub;
@Value("${jh.url}")
private String url;
@Value("${jh.operatorCode}")
private String operatorCode;
@Value("${jh.pwd}")
private String pwd;
@Value("${jh.wlptServerIp}")
private String wlptServerIp;
@Value("${jh.wlptPort}")
private String wlptPort;
@Override
public Map<String, Object> unifiedPlaceOrder(JhPlaceOrderInfo jhPlaceOrderInfo) {
Map<String, Object> map = new HashMap<>();
OrderNoUtils idWorker = new OrderNoUtils(0, 0);
long orderId = idWorker.nextId();
jhPlaceOrderInfo.setORDERID(String.valueOf(orderId));
jhPlaceOrderInfo.setPAYMENT("支付金额");
jhPlaceOrderInfo.setSUB_OPENID("小程序/微信公众号,支付人的openId");
jhPlaceOrderInfo.setPROINFO(EscapeUtils.escape("设备租用押金") + jhPlaceOrderInfo.getPAYMENT());
String pubSub = pub.substring(pub.length() - 30);
jhPlaceOrderInfo.setMERCHANTID(merchantId);
jhPlaceOrderInfo.setPOSID(posId);
jhPlaceOrderInfo.setBRANCHID(branchId);
jhPlaceOrderInfo.setTRADE_TYPE(tradeType);
jhPlaceOrderInfo.setTIMEOUT(DateUtils.addMinute(15));
jhPlaceOrderInfo.setSUB_APPID(subAppId);
jhPlaceOrderInfo.setCURCODE("01");
jhPlaceOrderInfo.setTXCODE("530590");
jhPlaceOrderInfo.setTYPE("1");
jhPlaceOrderInfo.setGATEWAY("0");
jhPlaceOrderInfo.setPUB(pubSub);
String mac = getMac(jhPlaceOrderInfo);
jhPlaceOrderInfo.setMAC(mac);
String paramsStr = getParamsStr(JSON.parseObject(JSON.toJSONString(jhPlaceOrderInfo), Map.class));
String result = HttpUtil.post(url, paramsStr);
if (StringUtils.isEmpty(result)) {
}
JSONObject jsonObject = JSON.parseObject(result);
if (!jsonObject.getString("SUCCESS").equals("true")) {
}
String payUrl = jsonObject.getString("PAYURL");
if (StringUtils.isEmpty(payUrl)) {
}
String payUrlResult = HttpUtil.get(payUrl);
if (StringUtils.isEmpty(payUrlResult)) {
}
JSONObject payUrlJson = JSON.parseObject(payUrlResult);
if (!payUrlJson.getString("ERRCODE").equals("000000")) {
}
map.put("appId", payUrlJson.getString("appId"));
map.put("timeStamp", payUrlJson.getString("timeStamp"));
map.put("nonceStr", payUrlJson.getString("nonceStr"));
map.put("package", payUrlJson.getString("package"));
map.put("signType", payUrlJson.getString("signType"));
map.put("paySign", payUrlJson.getString("paySign"));
return map;
}
private String getMac(JhPlaceOrderInfo jhPlaceOrderInfo) {
String postParams = "MERCHANTID=" + jhPlaceOrderInfo.getMERCHANTID() + "&POSID=" + jhPlaceOrderInfo.getPOSID() + "" +
"&BRANCHID=" + jhPlaceOrderInfo.getBRANCHID() + "&ORDERID=" + jhPlaceOrderInfo.getORDERID() + "&PAYMENT=" + jhPlaceOrderInfo.getPAYMENT() + "" +
"&CURCODE=01&TXCODE=530590&REMARK1=&REMARK2=&TYPE=1&PUB=" + jhPlaceOrderInfo.getPUB() + "&GATEWAY=0&CLIENTIP=®INFO=&PROINFO=" + jhPlaceOrderInfo.getPROINFO() + "&REFERER=" +
"&TIMEOUT=" + jhPlaceOrderInfo.getTIMEOUT() + "&TRADE_TYPE=" + jhPlaceOrderInfo.getTRADE_TYPE() + "" +
"&SUB_APPID=" + jhPlaceOrderInfo.getSUB_APPID() + "&SUB_OPENID=" + jhPlaceOrderInfo.getSUB_OPENID() + "";
return MD5Utils.string2MD5(postParams);
}
private String getParamsStr(Map params) {
StringBuffer toBeMacStr = new StringBuffer();
Set<Map.Entry<String, Object>> entries = params.entrySet();
Iterator iterator = entries.iterator();
while (iterator.hasNext()) {
Object itset = iterator.next();
Map.Entry entry = (Map.Entry) itset;
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (StringUtils.isNotEmpty(value)) {
if (!key.equals("PUB")) {
toBeMacStr.append("&" + key + "=" + value);
}
}
}
return toBeMacStr.toString();
}
}
2.3 服务器通知回调实体类以及Controller
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class JhNotifyInfo {
@JSONField(name="POSID")
private String POSID;
@JSONField(name="BRANCHID")
private String BRANCHID;
@JSONField(name="ORDERID")
private String ORDERID;
@JSONField(name="PAYMENT")
private String PAYMENT;
@JSONField(name="CURCODE")
private String CURCODE;
@JSONField(name="REMARK1")
private String REMARK1;
@JSONField(name="REMARK2")
private String REMARK2;
@JSONField(name="ACC_TYPE")
private String ACC_TYPE;
@JSONField(name="SUCCESS")
private String SUCCESS;
@JSONField(name="TYPE")
private String TYPE;
@JSONField(name="REFERER")
private String REFERER;
@JSONField(name="CLIENTIP")
private String CLIENTIP;
@JSONField(name="ACCDATE")
private String ACCDATE;
@JSONField(name="INSTALLNUM")
private String INSTALLNUM;
@JSONField(name="ERRMSG")
private String ERRMSG;
@JSONField(name="USRMSG")
private String USRMSG;
@JSONField(name="USRINFO")
private String USRINFO;
@JSONField(name="DISCOUNT")
private String DISCOUNT;
@JSONField(name="ZHJF")
private String ZHJF;
@JSONField(name="OPENID")
private String OPENID;
@JSONField(name="SUB_OPENID")
private String SUB_OPENID;
@JSONField(name="PAYMENT_DETAILS")
private String PAYMENT_DETAILS;
@JSONField(name="SIGN")
private String SIGN;
}
@Slf4j
@RestController
@RequestMapping("/pay")
public class ApiPayController {
@Value("${jh.merchantId}")
private String merchantId;
@Value("${jh.posId}")
private String posId;
@Value("${jh.branchId}")
private String branchId;
@Value("${jh.subAppId}")
private String subAppId;
@Value("${jh.tradeType}")
private String tradeType;
@Value("${jh.pub}")
private String pub;
@PostMapping("/jh/notify")
public String pay(JhNotifyInfo jhNotifyInfo) {
log.info("建行回调通知参数[{}]", JSON.toJSONString(jhNotifyInfo));
RSASig rsaSig = new RSASig();
rsaSig.setPublicKey(pub);
String src = "POSID=" + jhNotifyInfo.getPOSID() + "&BRANCHID=" + jhNotifyInfo.getBRANCHID() + "&ORDERID=" + jhNotifyInfo.getORDERID() +
"&PAYMENT=" + jhNotifyInfo.getPAYMENT() + "&CURCODE=" + jhNotifyInfo.getCURCODE() + "&REMARK1=" + jhNotifyInfo.getREMARK1() + "&REMARK2=" + jhNotifyInfo.getREMARK2() + "&ACC_TYPE=" + jhNotifyInfo.getACC_TYPE() +
"&SUCCESS=" + jhNotifyInfo.getSUCCESS() + "&TYPE=" + jhNotifyInfo.getTYPE() + "&REFERER=" + jhNotifyInfo.getREFERER() + "&CLIENTIP=" + jhNotifyInfo.getCLIENTIP();
boolean verifySigature = rsaSig.verifySigature(jhNotifyInfo.getSIGN(), src);
if (verifySigature) {
log.info("验签通过");
return "SUCCESS";
} else {
log.info("验签失败");
return "FAIL";
}
}
}
<dependency>
<groupId>netpay.merchant.crypto</groupId>
<artifactId>netpay</artifactId>
<version>0.0.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/netpay.jar</systemPath>
</dependency>
2.4 部分工具类
public class DateUtils {
public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static String addMinute(int minute){
Calendar nowTime = Calendar.getInstance();
nowTime.add(Calendar.MINUTE, minute);
return parseDateToStr(YYYYMMDDHHMMSS,nowTime.getTime());
}
}
public class EscapeUtils {
private final static String[] hex = { "00", "01", "02", "03", "04", "05",
"06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B",
"1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26",
"27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31",
"32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C",
"3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47",
"48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52",
"53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D",
"5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68",
"69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73",
"74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E",
"7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
"8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94",
"95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA",
"AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5",
"B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0",
"C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB",
"CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6",
"D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1",
"E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC",
"ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
"F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" };
private final static byte[] val = { 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F };
public static String escape(String s) {
StringBuffer sbuf = new StringBuffer();
int len = s.length();
for (int i = 0; i < len; i++) {
int ch = s.charAt(i);
if ('A' <= ch && ch <= 'Z') {
sbuf.append((char) ch);
} else if ('a' <= ch && ch <= 'z') {
sbuf.append((char) ch);
} else if ('0' <= ch && ch <= '9') {
sbuf.append((char) ch);
} else if (ch == '-' || ch == '_' || ch == '.' || ch == '!'
|| ch == '~' || ch == '*' || ch == ''' || ch == '('
|| ch == ')') {
sbuf.append((char) ch);
} else if (ch <= 0x007F) {
sbuf.append('%');
sbuf.append(hex[ch]);
} else {
sbuf.append('%');
sbuf.append('u');
sbuf.append(hex[(ch >>> 8)]);
sbuf.append(hex[(0x00FF & ch)]);
}
}
return sbuf.toString();
}
public static String unescape(String s) {
StringBuffer sbuf = new StringBuffer();
int i = 0;
int len = s.length();
while (i < len) {
int ch = s.charAt(i);
if ('A' <= ch && ch <= 'Z') {
sbuf.append((char) ch);
} else if ('a' <= ch && ch <= 'z') {
sbuf.append((char) ch);
} else if ('0' <= ch && ch <= '9') {
sbuf.append((char) ch);
} else if (ch == '-' || ch == '_' || ch == '.' || ch == '!'
|| ch == '~' || ch == '*' || ch == ''' || ch == '('
|| ch == ')') {
sbuf.append((char) ch);
} else if (ch == '%') {
int cint = 0;
if ('u' != s.charAt(i + 1)) {
cint = (cint << 4) | val[s.charAt(i + 1)];
cint = (cint << 4) | val[s.charAt(i + 2)];
i += 2;
} else {
cint = (cint << 4) | val[s.charAt(i + 2)];
cint = (cint << 4) | val[s.charAt(i + 3)];
cint = (cint << 4) | val[s.charAt(i + 4)];
cint = (cint << 4) | val[s.charAt(i + 5)];
i += 5;
}
sbuf.append((char) cint);
} else {
sbuf.append((char) ch);
}
i++;
}
return sbuf.toString();
}
}
import java.util.HashSet;
import java.util.Set;
public class OrderNoUtils {
private final long twepoch = 1530607760000L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public OrderNoUtils(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
}
else {
sequence = 0L;
}
lastTimestamp = timestamp;
return (((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence);
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
OrderNoUtils idWorker = new OrderNoUtils(0, 0);
Set set = new HashSet();
long id = idWorker.nextId();
System.out.println(id);
set.add(id);
}
}
其他工具类采用Hutool,请自行百度并引入
三、总结我方暂时只需要微信小程序之后,故以上案例均为小程序支付,后续会持续更新其他几个支付类型。
列举几个坑,各位看官注意一下
1. 生成MAC签名摘要时,需要商户的柜台公钥后30位;
2. REMARK1和REMARK2可以传递两个备注,但长度不能超过30位,并且要求对中文使用js的escape函数进行编码(参考上方的后端escape编码工具类);
3. PROINFO也需要对中文使用js的escape函数进行编码(参考上方的后端escape编码工具类);
4. 在根据参数拼接MAC签名串时,要注意别把Null拼进去,就是说,要提前将Null => 空值
5. 回调验签坑1:文档中对于参数有返回值的意思是:包括空值,但不包括Null。再翻译一下:就算返回值是个空值,也算有返回值,但如果是Null就不算有返回值,就不参与验签;
6. 回调验签坑2:在验签时还需要商户柜台公钥,如果还像上面那样只截取后面的30位,就会顺利入坑。因为这次是全部;
7. 回调验签坑3:需要引入建行提供验签的jar包;
相关知识
对接支付宝、微信、第三方支付,超详细讲解+demo演示
基于Java的网上花店网站的设计与开发资源
这是我见过最全的支付系统!一套适合互联网企业使用的开源支付系统
易支付搭建与接口对接完整教程:打造专属支付系统
支付系统的监控与报警
Java SSM框架构建网上花店销售系统教程
【Java项目源码】鲜花销售系统.zip
Java通用型支付+电商平台双系统实战
鲜花订购系统购物商城(java源代码+数据库资源
【开题报告】基于SpringBoot的鲜花销售系统的设计与实现
网址: Java集成建行龙支付接口(详细) https://m.huajiangbk.com/newsview849413.html
上一篇: 支付宝在线支付接口开发教程 |
下一篇: java中支付如何实现接口 |