商户代扣产品介绍 - 支付宝文档中心 (alipay.com)
签约接口
由于前端使用的是钉钉小程序,无法直接跳转支付宝,所以使用pageExecute执行,将支付宝返回的链接封装成二维码返给前端,前端点击二维码扫码前往支付宝签约
/**
* 调用支付宝签约接口
*/
@ResponseBody
@PostMapping("/alipaySignup")
public AjaxResult alipaySignup(@RequestBody JnDingdingUser dingdingUser) throws AlipayApiException, IOException, WriterException {
AlipayClient alipayClient = new DefaultAlipayClient(AliPayConfig.GATEWAYURL,
AliPayConfig.APP_ID,
AliPayConfig.MERCHANT_PRIVATE_KEY,
"json",
AliPayConfig.CHARSET,
AliPayConfig.ALIPAY_PUBLIC_KEY,
AliPayConfig.SIGN_TYPE);
AlipayUserAgreementPageSignModel bizModel = new AlipayUserAgreementPageSignModel();
/**
* 个人签约产品码,商户和支付宝签约时确定。商户代扣场景固定 GENERAL_WITHHOLDING_P
*/
bizModel.setPersonalProductCode("GENERAL_WITHHOLDING_P");
/**
* 销售产品码,商户签约的支付宝合同所对应的产品码。商户代扣场景固定为 GENERAL_WITHHOLDING
*/
bizModel.setProductCode("GENERAL_WITHHOLDING");
/**
* 协议签约场景,商户和支付宝签约时确定,商户可咨询技术支持。本参数为空时默认为 DEFAULT|DEFAULT。更多支持场景参见 代扣场景码介绍。
*
* 注意: 当传入商户签约号 external_agreement_no 时,场景不能为默认值 DEFAULT|DEFAULT。
*/
bizModel.setSignScene("INDUSTRY|CATERING");
/**
* 商户签约号,代扣协议中标示用户的唯一签约号(确保在商户系统中唯一)。
格式规则:支持大写小写字母和数字,最长32位。
商户系统按需传入,如果同一用户在同一产品码、同一签约场景下,签订了多份代扣协议,那么需要指定并传入该值。
*/
bizModel.setExternalAgreementNo (dingdingUser.getUserid());
/**
* 用户在商户网站的登录账号,用于在签约页面展示,如果为空,则不展示
*/
bizModel.setExternalLogonId(dingdingUser.getUserid());
AccessParams accessParams = new AccessParams();
accessParams.setChannel("ALIPAYAPP");
bizModel.setAccessParams(accessParams);
AlipayUserAgreementPageSignRequest request = new AlipayUserAgreementPageSignRequest();
request.setBizModel(bizModel);
// 异步通知服务器地址写在application.yml中
String notifyUrl = severUrl+"/signupNotify";
request.setNotifyUrl(notifyUrl);
AlipayUserAgreementPageSignResponse response = alipayClient.pageExecute(request,"get");
String url = response.getBody();
// System.out.println(url);
QRCodeWriter qrCodeWriter = new QRCodeWriter();
// 第一个参数为二维码的内容 第二个参数不变 第三 四 个参数依次为 宽高
BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, 800, 800);
File folder = new File(RuoYiConfig.getQRCodePath());
if (!folder.exists() && !folder.isDirectory()) {
folder.mkdirs();
}
String fileName = UUID.randomUUID().toString()+ ".png";
String path = RuoYiConfig.getQRCodePath() +"/"+ fileName;
MatrixToImageWriter.writeToPath(bitMatrix, "png",Paths.get(path));
// 处理图片路径返回给前端
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(RuoYiConfig.getQRCodePath(), dirLastIndex);
String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
if (response.isSuccess()) {
return AjaxResult.success(pathFileName);
} else {
return AjaxResult.error("签约失败");
}
}
签约异步回调接口
主要返回参数:
external_logon_id 用户在商户系统中的id,签约时给的是什么返回的就是什么
alipay_logon_id 用户在支付宝中的登录账号,返回时带星号
alipay_user_id 支付宝用户号,纯数字
agreement_no 签约号,商户系统通过签约号发起免密扣款
返回值“success” 如果返回的不是success 支付宝会在24小时内不断发异步通知
/**
* 签约异步通知接口
*/
@ResponseBody
@PostMapping("/signupNotify")
public String signupNotify(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {
//获取支付宝POST过来反馈信息
Map< String , String > params = new HashMap<>();
Map requestParams = request.getParameterMap();
System.out.println(requestParams);
System.out.println("————————————————————");
for(Iterator iter = requestParams.keySet().iterator();iter.hasNext();){
String name = (String)iter.next();
String[] values = (String [])requestParams.get(name);
String valueStr = "";
for(int i = 0;i < values.length;i ++ ){
valueStr = (i==values.length-1)?valueStr + values [i]:valueStr + values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put (name,valueStr);
}
System.out.println(params);
//切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean flag = AlipaySignature.rsaCheckV1 (params,AliPayConfig.ALIPAY_PUBLIC_KEY, AliPayConfig.CHARSET,"RSA2");
if (flag){
System.out.println("验签成功,处理业务");
String userid = params.get("external_logon_id");
String alipayLogonId = params.get("alipay_logon_id");
String agreementNo = params.get("agreement_no");
String alipayUserId = params.get("alipay_user_id");
return "success";
}else {
System.out.println("验签失败");
return "fail";
}
}
同步代扣接口
同步代扣的response中会返回code,前端调用免密代扣时根据code返回结果给前端
code 结果码和处理方式
-
根据公共返回参数中的 code,这笔交易可能有四种状态:支付成功(10000),支付失败(40004),等待用户付款(10003)和未知异常(20000)。
-
对于 扣款失败 或 未知异常, 存在如下两种场景解决方案:
-
针对耗时敏感的业务场景(如:免密支付,用户需要同步等待结果):建议商家优先使用 alipay.trade.query 接口查询交易状态(查询次数和间隔可依据商家场景),如果交易状态是成功,则无需特殊处理;如果交易状态是等待买家付款,则可以使用 alipay.trade.cancel 接口发起交易撤销。
-
针对耗时不敏感的业务场景(如:周期续费代扣,无需用户同步等待结果):建议商家等待5分钟后,使用 alipay.trade.query 接口查询交易状态,若查询 4 次后依然返回等待用户付款,商户可以选择幂等重试代扣或通过 alipay.trade.cancel 接口发起交易撤销。
-
public Map<String, String> dinguserPay(String userid, String totalAmount, String orderSn) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(AliPayConfig.GATEWAYURL,
AliPayConfig.APP_ID,
AliPayConfig.MERCHANT_PRIVATE_KEY,
"json",
AliPayConfig.CHARSET,
AliPayConfig.ALIPAY_PUBLIC_KEY,
AliPayConfig.SIGN_TYPE);
Map<String, String> map = new HashMap<>();
//根据userid找到user并获取agreementNo
User user = userService.getUserById(userid);
String agreementNo= user.getagreementNo();//获取签约号
if (StringUtils.isEmpty(agreementNo)){
map.put("status","fail");
map.put("msg","未查询到支付宝信息,请先绑定支付宝再操作!");
return map;
}
AlipayTradePayRequest request = new AlipayTradePayRequest();
//获取异步通知服务器地址,再拼接具体业务通知地址
String notifyUrl = url+"/payNotify";
request.setNotifyUrl(notifyUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderSn);
bizContent.put("total_amount", totalAmount);//支付金额
bizContent.put("subject", "支付宝中显示的消费信息");
bizContent.put("product_code", "GENERAL_WITHHOLDING");//商户代扣固定GENERAL_WITHHOLDING
//协议信息
JSONObject agreementParams = new JSONObject();
agreementParams.put("agreement_no", agreementNo);
bizContent.put("agreement_params", agreementParams);
request.setBizContent(bizContent.toString());
AlipayTradePayResponse response = alipayClient.execute(request);
System.out.println(response);
if(response.isSuccess()){
String code = response.getCode();
map.put("tradeNo",response.getTradeNo());
//结果码10000,付款成功
if (code.equals("10000")){
map.put("msg",response.getMsg());
map.put("status","success");
return map;
}
//针对耗时敏感的业务场景
//结果码10003等待用户付款,20000未知异常,等待一秒调用查询接口
else if (code.equals("10003")||code.equals("20000")){
try {
Thread.sleep(1000); //1000 毫秒,也就是1秒.
//调用查询接口
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
JSONObject content = new JSONObject();
content.put("out_trade_no", orderSn);
request.setBizContent(content.toString());
AlipayTradeQueryResponse queryResponse = alipayClient.execute(queryRequest);
if(queryResponse.isSuccess()){
//查询到支付成功
if (queryResponse.getCode().equals("10000")){
map.put("msg",response.getMsg());
map.put("status","success");
return map;
}else if(queryResponse.getCode().equals("10003")){//查询为等待支付
//发起交易取消
AlipayTradeCancelRequest cancelRequest = new AlipayTradeCancelRequest();
JSONObject cancelContent = new JSONObject();
cancelContent.put("out_trade_no", orderSn);
request.setBizContent(cancelContent.toString());
AlipayTradeCancelResponse cancelResponse = alipayClient.execute(cancelRequest);
if(cancelResponse.isSuccess()){
map.put("status","fail");
map.put("msg","交易取消");
return map;
} else {
map.put("status","fail");
map.put("msg",cancelResponse.getMsg());
return map;
}
}
} else {
map.put("status","fail");
map.put("msg",queryResponse.getMsg());
return map;
}
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}else {//结果码40000,交易失败
map.put("status","fail");
map.put("msg",response.getSubMsg());
return map;
}
} else {//response.isSuccess为false
map.put("status","fail");
map.put("msg",response.getSubMsg());
return map;
}
map.put("status","fail");
map.put("msg",response.getSubMsg());
return map;
}
免密代扣异步通知接口(以异步通知为准)
/**
* 扣款异步通知接口
*/
@ResponseBody
@PostMapping("/payNotify")
public String payNotify(HttpServletRequest request) throws AlipayApiException, ParseException {
//获取支付宝POST过来反馈信息
Map< String , String > params = new HashMap<>();
Map requestParams = request.getParameterMap();
for(Iterator iter = requestParams.keySet().iterator();iter.hasNext();){
String name = (String)iter.next();
String[] values = (String [])requestParams.get(name);
String valueStr = "";
for(int i = 0;i < values.length;i ++ ){
valueStr = (i==values.length-1)?valueStr + values [i]:valueStr + values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put (name,valueStr);
}
//切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean flag = AlipaySignature.rsaCheckV1 (params,AliPayConfig.ALIPAY_PUBLIC_KEY, AliPayConfig.CHARSET,"RSA2");
if (flag){
//处理业务
JnAlipayRecord alipayRecord = new JnAlipayRecord();
alipayRecord.setTradeNo(params.get("trade_no"));
alipayRecord.setOutTradeNo(params.get("out_trade_no"));
alipayRecord.setSellerEmail(params.get("seller_email"));
alipayRecord.setSubject(params.get("subject"));
alipayRecord.setBuyerId(params.get("buyer_id"));
alipayRecord.setInvoiceAmount(params.get("invoice_amount"));
alipayRecord.setNotifyId(params.get("notify_id"));
alipayRecord.setFundBillList(params.get("fund_bill_list"));
alipayRecord.setNotifyType(params.get("notify_type"));
alipayRecord.setTradeStatus(params.get("trade_status"));
alipayRecord.setReceiptAmount(params.get("receipt_amount"));
alipayRecord.setAppId(params.get("app_id"));
alipayRecord.setBuyerPayAmount(params.get("buyer_pay_amount"));
alipayRecord.setSellerId(params.get("seller_id"));
alipayRecord.setTotalAmount(params.get("total_amount"));
alipayRecord.setBuyerLogonId(params.get("buyer_logon_id"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String gmtCreate = params.get("gmt_create");
String gmtPayment = params.get("gmt_payment");
String notifyTime = params.get("notify_time");
alipayRecord.setGmtCreate(sdf.parse(gmtCreate));
alipayRecord.setGmtPayment(sdf.parse(gmtPayment));
alipayRecord.setNotifyTime(sdf.parse(notifyTime));
jnAlipayRecordService.insertJnAlipayRecord(alipayRecord);
return "success";
}else {
return "fail";
}
}