首页 分享 Java集成建行龙支付接口(详细)

Java集成建行龙支付接口(详细)

来源:花匠小妙招 时间:2024-12-03 18:20

目录

一、准备工作

二、开始对接

三、总结

一、准备工作

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=&REGINFO=&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://www.huajiangbk.com/newsview849413.html

所属分类:花卉
上一篇: 支付宝在线支付接口开发教程
下一篇: java中支付如何实现接口

推荐分享