Java web项目与支付宝(当面付)对接
1. 重要文档阅读
1)沙箱登陆:https://openhome.alipay.com/platform/appDaily.htm
2)沙箱环境使用说明:https://docs.open.alipay.com/200/105311
4)当面付开发文档:https://docs.open.alipay.com/194
5)签名工具及文档:https://docs.open.alipay.com/291/
6)官方Demo:https://docs.open.alipay.com/194/105201/
2. 沙箱调试环境(买家账号和卖家账号)
蚂蚁沙箱环境(Beta)是协助开发者进行接口功能开发及主要功能联调的辅助环境。沙箱环境模拟了开放平台部分产品的主要功能和主要逻辑(当前沙箱支持产品请参考“沙箱支持产品列表”)。
在开发者应用上线审核前,开发者可以根据自身需求,先在沙箱环境中了解、组合和调试各种开放接口,进行开发调通工作,从而帮助开发者在应用上线审核完成后,能更快速、更顺利的进行线上调试和验收工作。
所以在对接支付宝的当面付功能时,可以通过蚂蚁沙箱环境来调试相关对接接口,可以在正式上线之前就调试好支付接口。使用支付宝账号登陆支付宝后,注册了开发者之后,支付宝就会自动为你创建一个沙箱应用,该应用中可以看到已经分配给你的APPID 和 UID,同时注意这里的支付宝网关是:https://openapi.alipaydev.com/gateway.do(可以看到在alipay后面有个dev,表示是开发测试用的网关,正式上线之后,这里网关就应该是https://openapi.alipay.com/gateway.do),在用沙箱环境记性调试的时候,需要用一台安卓手机安装支付宝的沙箱钱包(沙箱钱包仅提供了Android版本)来进行扫码支付。
买家和买家账号:在沙箱账号中可以看到支付宝系统分配给你的买家账号和卖家账号(用沙箱钱包登陆买家账号可以进行扫码支付,登陆卖家账号可以查看收款情况)
3. 支付宝扫码支付主业务流程和支付流程
业务流程:
支付流程:
整个流程大体如下:
1. 首先用户在商户系统中进行下单,当在系统中点击 “提交订单” 之后,那么商户系统就会向支付宝发起一个预下单请求,支付宝会同步返回一个订单二维码串
2. 商户系统获取到支付宝同步返回的二维码串(即url)后,商户系统使用二维码生成工具生成二维码图片,可以将图片进行持久化,在用户进行付款的时候,将图片地址提供给前端来显示
3.商户系统发起轮询获得支付结果:等待5秒后调用交易查询接口alipay.trade.query通过支付时传入的商户订单号(out_trade_no)查询支付结果(返回参数TRADE_STATUS),如果仍然返回等待用户付款(WAIT_BUYER_PAY),则再次等待5秒后继续查询,直到返回确切的支付结果(成功TRADE_SUCCESS 或 已撤销关闭TRADE_CLOSED),或是超出轮询时间。在最后一次查询仍然返回等待用户付款的情况下,必须立即调用交易撤销接口alipay.trade.cancel将这笔交易撤销,避免用户继续支付。
4. 除了主动轮询,支付宝会在用户付款成功,通过异步通知的方式来通知商户该订单的支付结果(异步通知的url是在预下单的时候,商户携带给支付宝的),详见扫码异步通知,注意一定要对异步通知做验签,确保通知是支付宝发出的。
4. 支付宝扫码支付重要字段
1. 预下单的入参和出参:
关键入参:
参数名称 | 参数说明 |
---|---|
out_trade_no | 商户订单号,需要保证不重复 |
total_amount | 订单金额 |
subject | 订单标题 |
store_id | 商户门店编号 |
timeout_express | 交易超时时间 |
关键出参:
参数名称 | 参数说明 |
---|---|
qr_code | 订单二维码(有效时间2小时)的内容,开发者需要自己使用工具根据内容生成二维码图片 |
查询、撤销接口参见条码支付中的示例。
2. 支付状态查询入参和出参:
公共参数
请求地址
环境 | HTTPS请求地址 |
---|---|
正式环境 | https://openapi.alipay.com/gateway.do |
公共请求参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
app_id | String | 是 | 32 | 支付宝分配给开发者的应用ID | 2014072300007148 |
method | String | 是 | 128 | 接口名称 | alipay.trade.query |
format | String | 否 | 40 | 仅支持JSON | JSON |
charset | String | 是 | 10 | 请求使用的编码格式,如utf-8,gbk,gb2312等 | utf-8 |
sign_type | String | 是 | 10 | 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 |
sign | String | 是 | 344 | 商户请求参数的签名串,详见签名 | 详见示例 |
timestamp | String | 是 | 19 | 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" | 2014-07-24 03:07:50 |
version | String | 是 | 3 | 调用的接口版本,固定为:1.0 | 1.0 |
app_auth_token | String | 否 | 40 | 详见应用授权概述 | |
biz_content | String | 是 | 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 |
请求参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
out_trade_no | String | 特殊可选 | 64 | 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no | 20150320010101001 |
trade_no | String | 特殊可选 | 64 | 支付宝交易号,和商户订单号不能同时为空 | 2014112611001004680 073956707 |
org_pid | String | 可选 | 16 | 银行间联模式下有用,其它场景请不要使用; 双联通过该参数指定需要查询的交易所属收单机构的pid; | 2088101117952222 |
公共响应参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
code | String | 是 | - | 网关返回码,详见文档 | 40004 |
msg | String | 是 | - | 网关返回码描述,详见文档 | Business Failed |
sub_code | String | 否 | - | 业务返回码,参见具体的API接口文档 | ACQ.TRADE_HAS_SUCCESS |
sub_msg | String | 否 | - | 业务返回码描述,参见具体的API接口文档 | 交易已被支付 |
sign | String | 是 | - | 签名,详见文档 |
响应参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
trade_no | String | 必填 | 64 | 支付宝交易号 | 2013112011001004330000121536 |
out_trade_no | String | 必填 | 64 | 商家订单号 | 6823789339978248 |
buyer_logon_id | String | 必填 | 100 | 买家支付宝账号 | 159****5620 |
trade_status | String | 必填 | 32 | 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款) | TRADE_CLOSED |
total_amount | Price | 必填 | 11 | 交易的订单金额,单位为元,两位小数。该参数的值为支付时传入的total_amount | 88.88 |
settle_amount | Price | 选填 | 11 | 结算币种订单金额 | 2.96 |
pay_currency | Price | 选填 | 8 | 订单支付币种 | CNY |
pay_amount | String | 选填 | 11 | 支付币种订单金额 | 8.88 |
settle_trans_rate | String | 选填 | 11 | 结算币种兑换标价币种汇率 | 30.025 |
trans_pay_rate | String | 选填 | 11 | 标价币种兑换支付币种汇率 | 0.264 |
buyer_pay_amount | Price | 选填 | 11 | 买家实付金额,单位为元,两位小数。该金额代表该笔交易买家实际支付的金额,不包含商户折扣等金额 | 8.88 |
receipt_amount | String | 选填 | 11 | 实收金额,单位为元,两位小数。该金额为本笔交易,商户账户能够实际收到的金额 | 15.25 |
buyer_user_id | String | 必填 | 16 | 买家在支付宝的用户id | 2088101117955611 |
auth_trade_pay_mode | String | 选填 | 64 | 预授权支付模式,该参数仅在信用预授权支付场景下返回。信用预授权支付:CREDIT_PREAUTH_PAY | CREDIT_PREAUTH_PAY |
buyer_user_type | String | 选填 | 18 | 买家用户类型。CORPORATE:企业用户;PRIVATE:个人用户。 | PRIVATE |
3. 支付宝异步通知重要参数:
当收银台调用预下单请求API生成二维码展示给用户后,用户通过手机扫描二维码进行支付,支付宝会将该笔订单的变更信息,沿着商户调用预下单请求时所传入的通知地址主动推送给商户。
参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 |
---|---|---|---|---|---|
notify_time | 通知时间 | Date | 是 | 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss | 2015-14-27 15:45:58 |
notify_type | 通知类型 | String(64) | 是 | 通知的类型 | trade_status_sync |
sign_type | 签名类型 | String(10) | 是 | 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 |
sign | 签名 | String(256) | 是 | 请参考异步返回结果的验签 | 601510b7970e52cc63db0f44997cf70e |
trade_no | 支付宝交易号 | String(64) | 是 | 支付宝交易凭证号 | 2013112011001004330000121536 |
app_id | 开发者的app_id | String(32) | 是 | 支付宝分配给开发者的应用Id | 2014072300007148 |
out_trade_no | 商户订单号 | String(64) | 是 | 原支付请求的商户订单号 | 6823789339978248 |
buyer_id | 买家支付宝用户号 | String(16) | 否 | 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字 | 2088102122524333 |
buyer_logon_id | 买家支付宝账号 | String(100) | 否 | 买家支付宝账号 | 15901825620 |
seller_id | 卖家支付宝用户号 | String(30) | 否 | 卖家支付宝用户号 | 2088101106499364 |
seller_email | 卖家支付宝账号 | String(100) | 否 | 卖家支付宝账号 | [email protected] |
trade_status | 交易状态 | String(32) | 否 | 交易目前所处的状态 | TRADE_CLOSED |
total_amount | 订单金额 | Number(9,2) | 否 | 本次交易支付的订单金额,单位为人民币(元) | 20 |
buyer_pay_amount | 付款金额 | Number(9,2) | 否 | 用户在交易中支付的金额 | 13.88 |
gmt_payment | 交易付款时间 | Date | 否 | 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss | 2015-04-27 15:45:57 |
交易状态说明
枚举名称 | 枚举说明 |
---|---|
WAIT_BUYER_PAY | 交易创建,等待买家付款 |
TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款 |
TRADE_SUCCESS | 交易支付成功 |
TRADE_FINISHED | 交易结束,不可退款 |
通知触发条件
触发条件名 | 触发条件描述 | 触发条件默认值 |
---|---|---|
TRADE_FINISHED | 交易完成 | false(不触发通知) |
TRADE_SUCCESS | 支付成功 | true(触发通知) |
WAIT_BUYER_PAY | 交易创建 | false(不触发通知) |
TRADE_CLOSED | 交易关闭 | false(不触发通知) |
5. 支付宝签名工具的使用
支付宝提供一键生成工具便于开发者生成一对RSA密钥,可通过下方链接下载密钥生成工具:
WINDOWS
MAC_OSX
下载该工具后,解压打开文件夹,运行“RSA签名验签工具.bat”(WINDOWS)或“RSA签名验签工具.command”(MAC_OSX)。
界面示例:
详细步骤:
1.根据开发语言选择密钥格式。
2.选择密钥长度,新建应用请务必使用2048位。
(目前已使用1024位密钥长度的应用仍然可以正常调用接口,详情请见开放平台接口签名方式升级公告。)
3.点击 “生成密钥”,会自动生成商户应用公钥和应用私钥。
4.点击“打开密钥文件路径”,即可找到生成的公私钥。如图:
生成的私钥需妥善保管,避免遗失,不要泄露。应用私钥需填写到代码中供签名时使用。应用公钥需提供给支付宝账号管理者上传到支付宝开放平台。
TIPS:除了使用支付宝提供的一键生成密钥工具外,也可以使用OpenSSL工具命令生成密钥。教程
使用沙箱环境的话,需要将签名生成工具生成的商户应用公钥设置到RSA2秘钥的位置(这里要注意,RSA2对应秘钥生成工具的秘钥长度为2048),然后就可以看到支付宝的公钥了,这个公钥在进行预下单的时候是需要的。
6. 支付宝回调的验签
必须对支付宝的回调进行验签,目的是为了防止恶意攻击,同时避免支付宝的重复通知。这里贴上支付宝回调验签的代码:
@RequestMapping("alipay_callback.do")
@ResponseBody
public Object alipayCallback(HttpServletRequest request){
Map<String, String> params = Maps.newHashMap();
Map<String, String[]> parameterMap = request.getParameterMap();
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> entry : entries){
String key = entry.getKey();
String[] values = entry.getValue();
String valueStr = "";
for(int i=0; i<values.length; i++){
valueStr = (i==values.length) ? valueStr+values[i] : valueStr+values[i]+",";
}
params.put(key, valueStr);
}
logger.info("支付宝回调,sign:{}, trad_status:{}, 参数:{}", params.get("sign"), params.get("trade_status"), params.toString());
//非常重要,需要验证回调是不是支付宝发出的,防止被恶意攻击,同时也要避免支付宝的重复通知
/**
* 在进行验签前去掉参数sign_type,通过文档知道,需要在通知返回参数列表中,除去sign、sign_type两个参数,然后再进行验签,通过查看SDK的源码,我们发现SDK提供的验签
* 方法中已经将sign参数移除了,那么这里我们只需要去掉sign_type这个参数。
* 同时,在进行验签的时候,要特别注意我们的签名类型是RSA2
*/
params.remove("sign_type");
//rsaCheckV2方法有个重载,一个可以指定签名类型,另外一个不用指定签名类型,默认使用的是RSA
//在这里需要传进去的publicKey 和 签名类型都是配置在zfbinfo.properties文件中,这个文件在预下单的时候就已经通过Configs进行了加载,所以就不需要再
//写方法来读取该文件的内容,通过查看Configs这个类型的源码发现,该类中提供了静态获取相关值得方法,所以可以直接通过Configs中的方法获取需要的参数
try {
boolean rsaCheck = AlipaySignature.rsaCheckV2(params, Configs.getPublicKey(), "utf-8", Configs.getSignType());
if(!rsaCheck){
return ServerResponse.createByErrorMessage("非法请求,验证不通过,如再恶意攻击将报警");
}
} catch (AlipayApiException e) {
logger.error("支付宝回调验证异常", e);
}
//todo 验签如果成功的话,还需要校验该订单中的订单号、总金额等信息来防止恶意攻击
//todo 处理业务逻辑,更新订单状态,更新库存等...
ServerResponse response = iOrderService.alipayCallback(params);
if(response.isSuccess()){
/**
* 支付宝文档要求:程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。
* 一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
*/
return "success";
}
return "failed";
}
7. 支付宝扫码支付官方Demo调试
从支付宝的当面付开发文档中可以获取到相关的SDK和Demo,我们这里下载了支付宝提供的Java版的Demo来进行调试。
我们将TradePayDemo这个工程导入到eclipse或Idea中(我这里使用的是Idea),然后将WebRoot\WEB-INF\lib中的jar添加的Dependencies中,保证项目没有报错。可以看到有主函数com.alipay.demo.trade.Main,该类中有详细的参数说明
package com.alipay.demo.trade;
import com.alipay.api.AlipayResponse;
import com.alipay.api.domain.TradeFundBill;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.MonitorHeartbeatSynResponse;
import com.alipay.demo.trade.config.Configs;
import com.alipay.demo.trade.model.ExtendParams;
import com.alipay.demo.trade.model.GoodsDetail;
import com.alipay.demo.trade.model.builder.*;
import com.alipay.demo.trade.model.hb.*;
import com.alipay.demo.trade.model.result.AlipayF2FPayResult;
import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
import com.alipay.demo.trade.model.result.AlipayF2FRefundResult;
import com.alipay.demo.trade.service.AlipayMonitorService;
import com.alipay.demo.trade.service.AlipayTradeService;
import com.alipay.demo.trade.service.impl.AlipayMonitorServiceImpl;
import com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl;
import com.alipay.demo.trade.service.impl.AlipayTradeWithHBServiceImpl;
import com.alipay.demo.trade.utils.Utils;
import com.alipay.demo.trade.utils.ZxingUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.*;
/**
* Created by liuyangkly on 15/8/9.
* 简单main函数,用于测试当面付api
* sdk和demo的意见和问题反馈请联系:[email protected]
*/
public class Main {
private static Log log = LogFactory.getLog(Main.class);
// 支付宝当面付2.0服务
private static AlipayTradeService tradeService;
// 支付宝当面付2.0服务(集成了交易保障接口逻辑)
private static AlipayTradeService tradeWithHBService;
// 支付宝交易保障接口服务,供测试接口api使用,请先阅读readme.txt
private static AlipayMonitorService monitorService;
static {
/** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
* Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
*/
Configs.init("zfbinfo.properties");
/** 使用Configs提供的默认参数
* AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
*/
tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
// 支付宝当面付2.0服务(集成了交易保障接口逻辑)
tradeWithHBService = new AlipayTradeWithHBServiceImpl.ClientBuilder().build();
/** 如果需要在程序中覆盖Configs提供的默认参数, 可以使用ClientBuilder类的setXXX方法修改默认参数 否则使用代码中的默认设置 */
monitorService = new AlipayMonitorServiceImpl.ClientBuilder()
.setGatewayUrl("http://mcloudmonitor.com/gateway.do").setCharset("GBK")
.setFormat("json").build();
}
// 简单打印应答
private void dumpResponse(AlipayResponse response) {
if (response != null) {
log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
if (StringUtils.isNotEmpty(response.getSubCode())) {
log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
response.getSubMsg()));
}
log.info("body:" + response.getBody());
}
}
public static void main(String[] args) {
Main main = new Main();
// 系统商商测试交易保障接口api
// main.test_monitor_sys();
// POS厂商测试交易保障接口api
// main.test_monitor_pos();
// 测试交易保障接口调度
// main.test_monitor_schedule_logic();
// 测试当面付2.0支付(使用未集成交易保障接口的当面付2.0服务)
// main.test_trade_pay(tradeService);
// 测试查询当面付2.0交易
// main.test_trade_query();
// 测试当面付2.0退货
// main.test_trade_refund();
// 测试当面付2.0生成支付二维码
main.test_trade_precreate();
}
// 测试系统商交易保障调度
public void test_monitor_schedule_logic() {
// 启动交易保障线程
DemoHbRunner demoRunner = new DemoHbRunner(monitorService);
demoRunner.setDelay(5); // 设置启动后延迟5秒开始调度,不设置则默认3秒
demoRunner.setDuration(10); // 设置间隔10秒进行调度,不设置则默认15 * 60秒
demoRunner.schedule();
// 启动当面付,此处每隔5秒调用一次支付接口,并且当随机数为0时交易保障线程退出
while (Math.random() != 0) {
test_trade_pay(tradeWithHBService);
Utils.sleep(5 * 1000);
}
// 满足退出条件后可以调用shutdown优雅安全退出
demoRunner.shutdown();
}
// 系统商的调用样例,填写了所有系统商商需要填写的字段
public void test_monitor_sys() {
// 系统商使用的交易信息格式,json字符串类型
List<SysTradeInfo> sysTradeInfoList = new ArrayList<SysTradeInfo>();
sysTradeInfoList.add(SysTradeInfo.newInstance("00000001", 5.2, HbStatus.S));
sysTradeInfoList.add(SysTradeInfo.newInstance("00000002", 4.4, HbStatus.F));
sysTradeInfoList.add(SysTradeInfo.newInstance("00000003", 11.3, HbStatus.P));
sysTradeInfoList.add(SysTradeInfo.newInstance("00000004", 3.2, HbStatus.X));
sysTradeInfoList.add(SysTradeInfo.newInstance("00000005", 4.1, HbStatus.X));
// 填写异常信息,如果有的话
List<ExceptionInfo> exceptionInfoList = new ArrayList<ExceptionInfo>();
exceptionInfoList.add(ExceptionInfo.HE_SCANER);
// exceptionInfoList.add(ExceptionInfo.HE_PRINTER);
// exceptionInfoList.add(ExceptionInfo.HE_OTHER);
// 填写扩展参数,如果有的话
Map<String, Object> extendInfo = new HashMap<String, Object>();
// extendInfo.put("SHOP_ID", "BJ_ZZ_001");
// extendInfo.put("TERMINAL_ID", "1234");
String appAuthToken = "应用授权令牌";//根据真实值填写
AlipayHeartbeatSynRequestBuilder builder = new AlipayHeartbeatSynRequestBuilder()
.setAppAuthToken(appAuthToken).setProduct(Product.FP).setType(Type.CR)
.setEquipmentId("cr1000001").setEquipmentStatus(EquipStatus.NORMAL)
.setTime(Utils.toDate(new Date())).setStoreId("store10001").setMac("0a:00:27:00:00:00")
.setNetworkType("LAN").setProviderId("2088911212323549") // 设置系统商pid
.setSysTradeInfoList(sysTradeInfoList) // 系统商同步trade_info信息
// .setExceptionInfoList(exceptionInfoList) // 填写异常信息,如果有的话
.setExtendInfo(extendInfo) // 填写扩展信息,如果有的话
;
MonitorHeartbeatSynResponse response = monitorService.heartbeatSyn(builder);
dumpResponse(response);
}
// POS厂商的调用样例,填写了所有pos厂商需要填写的字段
public void test_monitor_pos() {
// POS厂商使用的交易信息格式,字符串类型
List<PosTradeInfo> posTradeInfoList = new ArrayList<PosTradeInfo>();
posTradeInfoList.add(PosTradeInfo.newInstance(HbStatus.S, "1324", 7));
posTradeInfoList.add(PosTradeInfo.newInstance(HbStatus.X, "1326", 15));
posTradeInfoList.add(PosTradeInfo.newInstance(HbStatus.S, "1401", 8));
posTradeInfoList.add(PosTradeInfo.newInstance(HbStatus.F, "1405", 3));
// 填写异常信息,如果有的话
List<ExceptionInfo> exceptionInfoList = new ArrayList<ExceptionInfo>();
exceptionInfoList.add(ExceptionInfo.HE_PRINTER);
// 填写扩展参数,如果有的话
Map<String, Object> extendInfo = new HashMap<String, Object>();
// extendInfo.put("SHOP_ID", "BJ_ZZ_001");
// extendInfo.put("TERMINAL_ID", "1234");
AlipayHeartbeatSynRequestBuilder builder = new AlipayHeartbeatSynRequestBuilder()
.setProduct(Product.FP)
.setType(Type.SOFT_POS)
.setEquipmentId("soft100001")
.setEquipmentStatus(EquipStatus.NORMAL)
.setTime("2015-09-28 11:14:49")
.setManufacturerPid("2088000000000009")
// 填写机具商的支付宝pid
.setStoreId("store200001").setEquipmentPosition("31.2433190000,121.5090750000")
.setBbsPosition("2869719733-065|2896507033-091").setNetworkStatus("gggbbbgggnnn")
.setNetworkType("3G").setBattery("98").setWifiMac("0a:00:27:00:00:00")
.setWifiName("test_wifi_name").setIp("192.168.1.188")
.setPosTradeInfoList(posTradeInfoList) // POS厂商同步trade_info信息
// .setExceptionInfoList(exceptionInfoList) // 填写异常信息,如果有的话
.setExtendInfo(extendInfo) // 填写扩展信息,如果有的话
;
MonitorHeartbeatSynResponse response = monitorService.heartbeatSyn(builder);
dumpResponse(response);
}
// 测试当面付2.0支付
public void test_trade_pay(AlipayTradeService service) {
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = "tradepay" + System.currentTimeMillis()
+ (long) (Math.random() * 10000000L);
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店消费”
String subject = "xxx品牌xxx门店当面付消费";
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = "0.01";
// (必填) 付款条码,用户支付宝钱包手机app点击“付款”产生的付款条码
String authCode = "用户自己的支付宝付款码"; // 条码示例,286648048691290423
// (可选,根据需要决定是否使用) 订单可打折金额,可以配合商家平台配置折扣活动,如果订单部分商品参与打折,可以将部分商品总价填写至此字段,默认全部商品可打折
// 如果该值未传入,但传入了【订单总金额】,【不可打折金额】 则该值默认为【订单总金额】- 【不可打折金额】
// String discountableAmount = "1.00"; //
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0.0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品3件共20.00元"
String body = "购买商品3件共20.00元";
// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
String providerId = "2088100200300400500";
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId(providerId);
// 支付超时,线下扫码交易定义为5分钟
String timeoutExpress = "5m";
// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
// 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "xxx面包", 1000, 1);
// 创建好一个商品后添加至商品明细列表
goodsDetailList.add(goods1);
// 继续创建并添加第一条商品信息,用户购买的产品为“黑人牙刷”,单价为5.00元,购买了两件
GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "xxx牙刷", 500, 2);
goodsDetailList.add(goods2);
String appAuthToken = "应用授权令牌";//根据真实值填写
// 创建条码支付请求builder,设置请求参数
AlipayTradePayRequestBuilder builder = new AlipayTradePayRequestBuilder()
// .setAppAuthToken(appAuthToken)
.setOutTradeNo(outTradeNo).setSubject(subject).setAuthCode(authCode)
.setTotalAmount(totalAmount).setStoreId(storeId)
.setUndiscountableAmount(undiscountableAmount).setBody(body).setOperatorId(operatorId)
.setExtendParams(extendParams).setSellerId(sellerId)
.setGoodsDetailList(goodsDetailList).setTimeoutExpress(timeoutExpress);
// 调用tradePay方法获取当面付应答
AlipayF2FPayResult result = service.tradePay(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝支付成功: )");
break;
case FAILED:
log.error("支付宝支付失败!!!");
break;
case UNKNOWN:
log.error("系统异常,订单状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}
// 测试当面付2.0查询订单
public void test_trade_query() {
// (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
String outTradeNo = "tradepay14817938139942440181";
// 创建查询请求builder,设置请求参数
AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
.setOutTradeNo(outTradeNo);
AlipayF2FQueryResult result = tradeService.queryTradeResult(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("查询返回该订单支付成功: )");
AlipayTradeQueryResponse response = result.getResponse();
dumpResponse(response);
log.info(response.getTradeStatus());
if (Utils.isListNotEmpty(response.getFundBillList())) {
for (TradeFundBill bill : response.getFundBillList()) {
log.info(bill.getFundChannel() + ":" + bill.getAmount());
}
}
break;
case FAILED:
log.error("查询返回该订单支付失败或被关闭!!!");
break;
case UNKNOWN:
log.error("系统异常,订单支付状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}
// 测试当面付2.0退款
public void test_trade_refund() {
// (必填) 外部订单号,需要退款交易的商户外部订单号
String outTradeNo = "tradepay14817938139942440181";
// (必填) 退款金额,该金额必须小于等于订单的支付金额,单位为元
String refundAmount = "0.01";
// (可选,需要支持重复退货时必填) 商户退款请求号,相同支付宝交易号下的不同退款请求号对应同一笔交易的不同退款申请,
// 对于相同支付宝交易号下多笔相同商户退款请求号的退款交易,支付宝只会进行一次退款
String outRequestNo = "";
// (必填) 退款原因,可以说明用户退款原因,方便为商家后台提供统计
String refundReason = "正常退款,用户买多了";
// (必填) 商户门店编号,退款情况下可以为商家后台提供退款权限判定和统计等作用,详询支付宝技术支持
String storeId = "test_store_id";
// 创建退款请求builder,设置请求参数
AlipayTradeRefundRequestBuilder builder = new AlipayTradeRefundRequestBuilder()
.setOutTradeNo(outTradeNo).setRefundAmount(refundAmount).setRefundReason(refundReason)
.setOutRequestNo(outRequestNo).setStoreId(storeId);
AlipayF2FRefundResult result = tradeService.tradeRefund(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝退款成功: )");
break;
case FAILED:
log.error("支付宝退款失败!!!");
break;
case UNKNOWN:
log.error("系统异常,订单退款状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}
// 测试当面付2.0生成支付二维码
public void test_trade_precreate() {
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = "tradeprecreate" + System.currentTimeMillis()
+ (long) (Math.random() * 10000000L);
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
String subject = "xxx品牌xxx门店当面付扫码消费";
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = "0.01";
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = "购买商品3件共20.00元";
// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");
// 支付超时,定义为120分钟
String timeoutExpress = "120m";
// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
// 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "xxx小面包", 1000, 1);
// 创建好一个商品后添加至商品明细列表
goodsDetailList.add(goods1);
// 继续创建并添加第一条商品信息,用户购买的产品为“黑人牙刷”,单价为5.00元,购买了两件
GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "xxx牙刷", 500, 2);
goodsDetailList.add(goods2);
// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
.setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
.setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
.setTimeoutExpress(timeoutExpress)
// .setNotifyUrl("http://www.test-notify-url.com")//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
.setGoodsDetailList(goodsDetailList);
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝预下单成功: )");
AlipayTradePrecreateResponse response = result.getResponse();
dumpResponse(response);
// 需要修改为运行机器上的路径
String filePath = String.format("/Users/sudo/Desktop/qr-%s.png",
response.getOutTradeNo());
log.info("filePath:" + filePath);
// ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
break;
case FAILED:
log.error("支付宝预下单失败!!!");
break;
case UNKNOWN:
log.error("系统异常,预下单状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}
}
我们可以直接先运行一下该主函数(执行前保证代码没有编译错误),会发现执行结果有报错:
这是因为我们还没有配置支付宝的相关参数。我们发现在该项目中有zfbinfo.properties这个文件,该文件就是支付宝的相关配置文件,打开该文件,配置我们使用的沙箱环境的相关参数(比较重要的参数包括:open_api_domain, pid, appid, private_key, public_key, alipay_public_key, 这些参数必须和自己沙箱中的信息一致):
该配置文件修改完成之后,在执行main函数,正常情况下回得到如下结果,图中标注的code为1000 表示预下单成功,qr_code表示支付宝返回的二维码串,通过生成二维码工具可以生成相应的二维码,用户使用沙箱钱包扫该二维码即可进行支付。