一次性开发,线上线下全渠道对接所有主流移动支付方式
所有请求格式均采用JSON格式,请求字符集采用UTF-8编码
1、收钱吧官方文档地址:https://doc.shouqianba.com/LitePos/
2、通用请求体样式
{
"request": {
"head": {
"version": "1.0.0",
"sign_type": "SHA1",
"appid": "28lp61847655",
"request_time": "2001-07-04T12:08:56+05:30",
"reserve": "{}"
},
"body": {
"subject": "this is request business body"
}
},
"signature":"blmSaxUF6/N2XOcz7UWRRVQ5XsVCEz1BpZl6R9Rc6TA3+IfWhJtmCsUZjtw72w1QQ8rEV6+uMh3GWbyzH02Y9dJQCW"
}
3、上干货
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
public class ShouqianbaPush {
private static Logger logger = LoggerFactory.getLogger(ShouqianbaPush.class);
/**私钥*/
private static String privateKeyStr = "收钱吧提供的私钥";
/**公钥*/
private static String publicKeyStr = "收钱吧提供的公钥";
/**轻POS接口地址*/
private static final String API_DOMAIN = "https://vapi.shouqianba.com/";
/**APPID*/
private static final String APP_ID = "你的APPID";
/**品牌编号,系统对接前由"收钱吧"分配并提供*/
private static final String BRAND_CODE = "收钱吧提供的品牌编号";
/**商户内部使用的门店编号*/
private static final String STORE_SN = "收钱吧提供的门店编号";
/**
* 收钱吧 推送支付订单信息
* @param url 接口地址
* @param orderSn 商户订单号,在商户系统中唯一
* @param createTime 商户订单创建时间
* @param price 订单价格,精确到分
* @param subject 订单简短描述,建议传8个字内
* @param operator 操作员,可以传入收款的收银员或导购员。例如"张三"
* @param customer 可以传入需要备注顾客的信息
* @param posInfo 传入商户系统的产品名称、系统编号等信息,便于帮助商户调查问题
* @return
* @author lan
* @since 2020-7-17
*/
public static boolean shouqianbaOrderPush(String url,String orderSn,Date createTime,String price,String subject,String operator,String customer,String posInfo) {
boolean result = false;
try {
String timestr = getTime(createTime);
//请求接口参数
Map<String,Object> param = new HashMap<String,Object>();
param.put("request_id", UUID.randomUUID());//请求编号,每次请求必须唯一
param.put("brand_code", BRAND_CODE);//品牌编号,系统对接前由"收钱吧"分配并提供
param.put("store_sn", STORE_SN);//商户内部使用的门店编号
param.put("workstation_sn", "0");//门店收银机编号,如果没有请传入"0"
param.put("check_sn", orderSn);
param.put("scene", "1");//场景值:1-智能终端,2-H5,4-PC
param.put("sales_time", timestr);
param.put("amount", price);
param.put("currency", "156");//币种
param.put("subject", subject);
param.put("operator", operator);
param.put("customer", customer);
param.put("industry_code", "0");//行业代码, 0=零售;1:酒店; 2:餐饮; 3:文娱; 4:教育;
param.put("pos_info", posInfo);
param.put("notify_url", "接口调用成功后的回调地址");//eg:http://127.0.0.1:8080/projectName/payNotify.json
//传入参数 生成对应签名体
//注意:完整的请求内容,如步骤1(不包含"request"字段,仅包括request的值)使用RSA私钥签名,并转换为BASE64编码。即:
/*{
"head": {
"version": "1.0.0",
"sign_type": "SHA1",
"appid": "你的APPID",
"request_time": "2001-07-04T12:08:56+05:30"
},
"body": {
"request_id": UUID.randomUUID(),
"brand_code": BRAND_CODE,
"store_sn": STORE_SN,
......
(请求接口参数全部内容)
}
}*/
String signBody = createSignatureBody(timestr,param);
//将生成的签名体使用私钥加密,生成签名
String sign = getSignature(signBody);
//将参数及签名组装成对应的通用请求体格式
String paramStr = createParamBody(param,sign,timestr);
//进行http post请求
String responseStr = sendPost(API_DOMAIN+url,paramStr);
Map map = getResponseData(responseStr);
if(null != map && null != map.get("result_code") && Integer.parseInt(map.get("result_code").toString()) == 200 ){
Map bizResponse = JSONObject.parseObject(map.get("biz_response").toString(),Map.class);
if(null != bizResponse && null != bizResponse.get("result_code") && Integer.parseInt(bizResponse.get("result_code").toString()) == 200){
result = true;
}
}
} catch (Exception e) {
logger.error("收钱吧 推送支付订单失败",e);
return false;
}
return result;
}
/**
* 获取 YYYY-MM-DDThh:mm:ssTZD 格式时间(时间格式请参考官方文档)
* @return
* @author lan
* @since 2020-6-28
*/
private static String getTime(Date date){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
if(date == null){
return sdf.format(new Date());
}
return sdf.format(date);
}
/**
* 生成签名体
* @param appId 轻POS应用编号,商户入网后由收钱吧技术支持提供
* @param storeSn 商户内部使用的门店编号
* @param brandCode 品牌编号,系统对接前由"收钱吧"分配并提供
* @param checkSn 商户订单号,在商户系统中唯一
* @param workstationSn 门店收银机编号,如果没有请传入"0"
* @return
* @author lan
* @since 2020-6-28
*/
private static String createSignatureBody(String timestr,Map<String,Object> param){
String result = null;
try {
JSONObject head = new JSONObject();
head.put("sign_type", "SHA1");
head.put("appid", APP_ID);
head.put("request_time", timestr);
head.put("version", "1.0.0");
JSONObject body = new JSONObject();
for (String key : param.keySet()) {
body.put(key, param.get(key));
}
JSONObject res = new JSONObject();
res.put("head", head);
res.put("body", body);
result = res.toJSONString();
} catch (Exception e) {
return null;
}
return result;
}
/**
* 获取签名
* @param signBody
* @return
* @author lan
* @since 2020-7-17
*/
public static String getSignature(String signBody){
try {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048);
PrivateKey privateKey = getPrivateKey(privateKeyStr);
// SHA1withRSA算法进行签名
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privateKey);
byte[] data = signBody.getBytes();
// 更新用于签名的数据
sign.update(data);
byte[] signature = sign.sign();
String res = new String(Base64.encodeBase64(signature));
return res;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取私钥
* @param privateKey 私钥字符串
* @return
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
/**
* 获取公钥
* @param publicKey 公钥字符串
* @return
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* 组建参数体
* @param appId
* @param storeSn
* @param brandCode
* @param checkSn
* @param workstationSn
* @return
*/
private static String createParamBody(Map<String,Object> param,String signature,String timestr){
String result = null;
try {
JSONObject head = new JSONObject();
head.put("sign_type", "SHA1");
head.put("appid", APP_ID);
head.put("request_time", timestr);
head.put("version", "1.0.0");
JSONObject body = new JSONObject();
for (String key : param.keySet()) {
body.put(key, param.get(key));
}
JSONObject request = new JSONObject();
request.put("head", head);
request.put("body", body);
JSONObject res = new JSONObject();
res.put("request", request);
res.put("signature", signature);
result = res.toJSONString();
} catch (Exception e) {
return null;
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "application/json");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type","application/json");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
/**
* 获取返回参数 (返回内容格式请参考官方文档-“响应通用定义”)
* @param result
* @return
* @author lan
* @since 2020-7-17
*/
private static Map<String,Object> getResponseData(String result){
if(result != null){
JSONObject res = JSONObject.parseObject(result);
if(res != null && res.get("response") != null){
String resp = res.get("response").toString();
JSONObject response = JSONObject.parseObject(resp);
if(response != null && response.get("body") != null){
Map map = JSONObject.parseObject(response.get("body").toString(), Map.class);
return map;
}else{
return null;
}
}else{
return null;
}
}else{
return null;
}
}
}
4、需要注意下的是签名体的格式问题及时间格式问题,走了些许弯路。另外,所有接口地址全部是小写地址,如果收钱吧技术人员提供的文档上写的是大写地址,请改为小写地址试一试。截止编写此篇文章之时,官方API已更新,处理了以上较为明显的问题。
仅作留存备忘,提供参考之用。