java后端实现集成支付宝APP支付(沙箱环境)

本文介绍了Java后端如何集成支付宝应用,包括获取核心参数、集成支付宝SDK、实现用户支付、异步通知及付款校验接口的详细步骤,并分享了在沙箱环境中测试的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

如今生活中无时无刻的在使用者支付功能,最常见的支付平台"支付宝""微信"今天就来介绍一下java后端如何集成支付宝应用(使用沙箱环境 无需企业认证)

获取支付宝相关信息

支付宝开发者平台地址
支付宝平台

整体开发流程

在这里插入图片描述
在这里插入图片描述

点击进入开发服务即可

1、获取核心参数

  • APPID (商户appid是识别商户的唯一ID,是让支付宝识别,我们到底是哪一个商户,这样支付宝就能识别商户对应的账号、用户号、收款账号…等等一系列信息。)
  • 商家公钥、私钥(对商户系统与支付宝进行信息交互的数字签名用的)
  • 支付宝公钥
  • 支付宝回调地址(是用来提供给支付宝调用地址)
  • 网关地址 (是用来配置发送给支付宝的网关地址的)
  • 加密签名算法RSA2
    沙箱环境基本信息
    获取应用公钥、应用私钥
    获取应用公钥私钥
    在沙箱应用中使用之前获取的应用公钥换取支付宝公钥(用于后续开发)
    在这里插入图片描述

2、将支付宝SDK集成到项目中

支付宝将与服务端交互的接口(OpenAPI)封装在开发工具包(SDK)中,开发者无需自行实现同服务端交互的复杂逻辑,直接将 SDK 导入自己的工程后,通过 OpenAPI 的示例代码实现同支付宝服务端的交互。支付宝javaSDK下载地址
如果是采用maven 可直接将sdk 导入到pom文件中

<!-- alipay sdk 支付依赖 -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.13.0.ALL</version>
</dependency>
<!--依赖支付的空判断-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>

3、后台使用支付宝SDK与支付宝进行交互逻辑

在这里插入图片描述
下面这段话是参考其他博主描述参考文档地址
首先,我们来理一理开发的思路,按照我当前项目的需求,关于支付这一块大概操作流程是:用户在APP上选好要购买的商品,点击“立即购买”,跳转到订单详细页面。选择支付方式,点击“确定支付”跳转到支付宝APP,付款完成后,跳转回APP,完成支付。这个过程,当用户点击“确定支付”时,APP需要调用商户后台接口。
这时候就是我们所需要做的事情:先是生成商户系统一笔未支付的订单,获得商户订单ID(商户系统生成)和订单的一些其他信息,然后再调用支付宝的SDK提供的数字签名方法,将需要传给支付宝的信息进行加签,然后把加签后的字符串返回给APP。APP拉起支付宝APP,再把这个加签的字符串传给支付宝,完成支付。APP接收到同步通知后,还需要再次调用商户后台的接口(虽然同步通知也有付款情况,但需要以后台通知为准),校验订单最终的付款情况。按照支付宝API上所说,当完成支付后,支付宝会做2个操作,一个是同步返回信息给APP,一个是异步通知商户后台返回支付状态等信息,并且最终的支付结果是以异步通知为准。所以我们还需要考虑到一点,就是当用户支付成功之后,商户系统暂时没有接收到支付宝的异步通知时。我们需要拿着这个商户订单ID主动调用SDK支付宝的查询接口,去获取该订单的支付情况,并最终返回给APP。这个查询的接口应该是给APP收到同步通知后,请求商户系统后台进行校验的时候调用的。
根据我们上面思考所得,后台只需要对外提供3个接口即可
1.用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串
2.在支付完成之后,支付宝异步通知商户后台订单的付款情况
3.在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验
下面开始编写代码

1、将支付宝公共信息参数写入类中

public class AlipayConfig {

	// 1.商户appid
	public static String APPID = "2021xxxx693113";

	// 2.私钥 pkcs8格式的
	public static String RSA_PRIVATE_KEY = "MIIE...";

	// 3.支付宝公钥
	public static String ALIPAY_PUBLIC_KEY = "MIIB...";

	// 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参 数,必须外网可以正常访问
	public static String notify_url = "http://xxx:8080/Health/alipay/notify_url.do";

	// 5.页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义 参数,必须外网可以正常访问
	// 商户可以自定义同步跳转地址
	public static String return_url = "http://xxx:8080/Health/alipay/return_url.do";

	// 6.请求支付宝的网关地址
	public static String URL = "https://openapi.alipaydev.com/gateway.do";

	// 7.编码
	public static String CHARSET = "UTF-8";

	// 8.返回格式
	public static String FORMAT = "json";

	// 9.加密类型
	public static String SIGNTYPE = "RSA2";

}

2、实现第一个接口:用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串。

写一个生成订单号的类 每次有交易让程序自动生成一个OutTradeNo

public class GenerateNum {
	// 全局自增数
    private static int count = 0;

    // 每毫秒秒最多生成多少订单(最好是像9999这种准备进位的值)
    private static final int total = 99;

    // 格式化的时间字符串
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");

    // 获取当前时间年月日时分秒毫秒字符串
    private static String getNowDateStr() {
        return sdf.format(new Date());
    }

    // 记录上一次的时间,用来判断是否需要递增全局数
    private static String now = null;

    /*
    生成一个订单号
    */
    public static String generateOrder() {
        String datastr = getNowDateStr();
        if (datastr.equals(now)) {
            count++;// 自增
        } else {
            count = 1;
            now = datastr;
        }
        int countInteger = String.valueOf(total).length() - String.valueOf(count).length();// 算补位
        String bu = "";// 补字符串
        for (int i = 0; i < countInteger; i++) {
            bu += "0";
        }

        bu += String.valueOf(count);
        if (count >= total) {
            count = 0;
        }
        return datastr + bu;
    }
}

Controller层 大家可以更具自己的具体业务来编写

	/**
	 * 获取支付宝加签后台的订单信息字符串
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping(value = "getOrderInfo.do", produces = "application/json,charset=UTF-8", method = RequestMethod.POST)
	@ResponseBody
	public String getOrderInfo(HttpServletRequest request, HttpServletResponse response) {
		int userid = Integer.valueOf(request.getParameter("userid"));
		String totalAmount = request.getParameter("totalAmount");
		int effectiveDay = Integer.valueOf(request.getParameter("effectiveDay"));
		PayDto payDto = new PayDto();
		payDto.setEffectiveDay(effectiveDay);
		payDto.setOutTradeNo(GenerateNum.generateOrder());
		payDto.setUserid(userid);
		payDto.setTotalAmount(totalAmount);
		return alipayService.alipaytest(payDto);
	}

Service层

	@Override
	public String alipaytest(PayDto payDto) {
		String orderInfo = "";
		String orderNumber = GenerateNum.generateOrder();
		// 创建支付宝订单
		AlipaymentOrder alipaymentOrder = new AlipaymentOrder();
		alipaymentOrder.setUserid(payDto.getUserid());// 订单用户id
		alipaymentOrder.setEffectiveDay(payDto.getEffectiveDay());// 获取有效天数
		alipaymentOrder.setOutTradeNo(payDto.getOutTradeNo());// 后台生成的订单号
		alipaymentOrder.setTradeStatus(0);// 交易状态
		alipaymentOrder.setTotalAmount(Double.parseDouble(payDto.getTotalAmount()));// 订单总金额
		alipaymentOrder.setReceiptAmount(0.00);// 实收金额
		alipaymentOrder.setInvoiceAmount(0.00);// 开票金额
		alipaymentOrder.setBuyerPayAmount(0.00);// 付款金额
		alipaymentOrder.setRefundFee(0.00); // 总退款金额

		try {
			// 实例化客户端
			AlipayClient alipayClient = getAlipayClient();
//					new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID,
//					AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET,
//					AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE);
			// 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
			AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
			// SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
			AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
			model.setBody("我是测试数据");
			model.setSubject("App支付测试Java");// 商品名称
			model.setOutTradeNo(payDto.getOutTradeNo());// 交易订单号
			model.setTimeoutExpress("30m");// 交易超时时间
			model.setTotalAmount(payDto.getTotalAmount());// 支付金额
			model.setProductCode("QUICK_MSECURITY_PAY");// 销售产品码(固定值)
			request.setBizModel(model);
			request.setNotifyUrl(AlipayConfig.notify_url);// 异步回调地址
			request.setReturnUrl(AlipayConfig.return_url);// 同步回调地址
			// 这里和普通的接口调用不同,使用的是sdkExecute
			AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
			orderInfo = response.getBody();
			System.out.println(response.getBody());// 就是orderString 可以直接给客户端请求,无需再做处理。
			// 创建新的商户支付宝订单
			this.alipayDao.createAlipayMentOrder(alipaymentOrder);
		} catch (AlipayApiException e) {
			e.printStackTrace();
		}
		return orderInfo;
	}

到此 我们的第一接口 后台返回加签后的字符串已经完成

3、实现第二个接口在支付完成之后,支付宝异步通知商户后台订单的付款情况

	/**
	 * 支付宝支付成功后,异步请求该参数
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping(value = "notify_url.do", produces = "application/json,charset=UTF-8", method = RequestMethod.POST)
	@ResponseBody
	public String notify_url(HttpServletRequest request, HttpServletResponse response) {
		// 1.从支付宝回调的request域中取值
		// 获取支付宝返回的参数集合
		Map<String, String[]> aliParams = request.getParameterMap();
		// 用以存放转化后的参数集合
		Map<String, String> conversionParams = new HashMap<String, String>();
		for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext();) {
			String key = iter.next();
			String[] values = aliParams.get(key);
			String valueStr = "";
			for (int i = 0; i < values.length; i++) {
				valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
			}
			// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
			// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8");
			conversionParams.put(key, valueStr);
		}
//		logger.info("==================返回参数集合:" + conversionParams);
		String status = alipayService.notify(conversionParams);

		return status;
	}

Service层

	/**
	 * 支付宝异步请求逻辑
	 */
	@Override
	public String notify(Map<String, String> conversionParams) {
		// 签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
		boolean signVerified = false;

		try {
			// 调用SDK验证签名
			signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY,
					AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);

		} catch (AlipayApiException e) {
			e.printStackTrace();
		}

		if (signVerified) {
			// 验签通过
			// 获取需要保存的数据
			String appId = conversionParams.get("app_id");// 支付宝分配给开发者的应用Id
			String notifyTime = conversionParams.get("notify_time");// 通知时间:yyyy-MM-dd HH:mm:ss
			String gmtCreate = conversionParams.get("gmt_create");// 交易创建时间:yyyy-MM-dd HH:mm:ss
			String gmtPayment = conversionParams.get("gmt_payment");// 交易付款时间
			String gmtRefund = conversionParams.get("gmt_refund");// 交易退款时间
			String gmtClose = conversionParams.get("gmt_close");// 交易结束时间
			String tradeNo = conversionParams.get("trade_no");// 支付宝的交易号
			String outTradeNo = conversionParams.get("out_trade_no");// 获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
			String outBizNo = conversionParams.get("out_biz_no");// 商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
			String buyerLogonId = conversionParams.get("buyer_logon_id");// 买家支付宝账号
			String sellerId = conversionParams.get("seller_id");// 卖家支付宝用户号
			String sellerEmail = conversionParams.get("seller_email");// 卖家支付宝账号
			Double totalAmount = Double.parseDouble(conversionParams.get("total_amount"));// 订单金额:本次交易支付的订单金额,单位为人民币(元)
			String receiptAmount = conversionParams.get("receipt_amount");// 实收金额:商家在交易中实际收到的款项,单位为元
			String invoiceAmount = conversionParams.get("invoice_amount");// 开票金额:用户在交易中支付的可开发票的金额
			String buyerPayAmount = conversionParams.get("buyer_pay_amount");// 付款金额:用户在交易中支付的金额
			String tradeStatus = conversionParams.get("trade_status");// 获取交易状态
			// 支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
			AlipaymentOrder alipaymentOrder = alipayDao.selectByOutTradeNo(outTradeNo);
			if (alipaymentOrder != null && totalAmount.equals(alipaymentOrder.getTotalAmount())
					&& AlipayConfig.APPID.equals(appId)) {
				// 修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里,以便以后查证)
				try {
					System.out.println("yyyyyyyy" + notifyTime);
					alipaymentOrder.setNotifyTime(dateformat(notifyTime));
					alipaymentOrder.setGmtCreate(dateformat(gmtCreate));
					alipaymentOrder.setGmtPayment(dateformat(gmtPayment));
					alipaymentOrder.setGmtRefund(dateformat(gmtRefund));
					alipaymentOrder.setGmtClose(dateformat(gmtClose));
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				alipaymentOrder.setTradeNo(tradeNo);
				alipaymentOrder.setOutBizNo(outBizNo);
				alipaymentOrder.setBuyerLogonId(buyerLogonId);
				alipaymentOrder.setSellerId(sellerId);
				alipaymentOrder.setSellerEmail(sellerEmail);
				alipaymentOrder.setTotalAmount(totalAmount);
				alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount));
				alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount));
				alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount));
				switch (tradeStatus) // 判断交易结果
				{
				case "TRADE_FINISHED": // 交易结束并不可退款
					alipaymentOrder.setTradeStatus((byte) 3);
					break;
				case "TRADE_SUCCESS": // 交易支付成功
					alipaymentOrder.setTradeStatus((byte) 2);
					break;
				case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
					alipaymentOrder.setTradeStatus((byte) 1);
					break;
				case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
					alipaymentOrder.setTradeStatus((byte) 0);
					break;
				default:
					break;
				}
				int returnResult = this.alipayDao.updateAlipayMentOrder(alipaymentOrder);
				if (tradeStatus.equals("TRADE_SUCCESS")) { // 只处理支付成功的订单: 修改交易表状态,支付成功

					if (returnResult > 0) {
						return "success";
					} else {
						return "fail";
					}
				} else {
					return "fail";
				}

			} else {
				return "fail";
			}

		} else { // 验签不通过
			return "fail";
		}

	}

4、第三个接口在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验。

	/**
	 * 向支付宝发起订单查询
	 */
	@Override
	public int checkAlipay(String outTradeno) {
		String responseString = "";
		AlipayClient alipayClient = getAlipayClient();
		Map<String,String> paraMap = new HashMap<String,String>();
		paraMap.put("out_trade_no",outTradeno);//商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
		paraMap.put("trade_no",null);// trade_no  即便是空你也得写上   不能省略 !!!!!!!!!!!
		AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();// 创建API对应的request类
		request.setBizContent(JSON.toJSONString(paraMap));
		AlipayTradeQueryResponse alipayTradeQueryResponse;
		
		try {
			alipayTradeQueryResponse = alipayClient.execute(request);
			responseString = alipayTradeQueryResponse.getBody();
			System.out.println(responseString);
			if (alipayTradeQueryResponse.isSuccess()) {
				AlipaymentOrder alipaymentOrder = alipayDao.selectByOutTradeNo(outTradeno);
				// 修改数据库支付宝订单表
				alipaymentOrder.setTradeNo(alipayTradeQueryResponse.getTradeNo());
				alipaymentOrder.setBuyerLogonId(alipayTradeQueryResponse.getBuyerLogonId());
				alipaymentOrder.setTotalAmount(Double.parseDouble(alipayTradeQueryResponse.getTotalAmount()));
				alipaymentOrder.setReceiptAmount(Double.parseDouble(alipayTradeQueryResponse.getReceiptAmount()));
				alipaymentOrder.setInvoiceAmount(Double.parseDouble(alipayTradeQueryResponse.getInvoiceAmount()));
				alipaymentOrder.setBuyerPayAmount(Double.parseDouble(alipayTradeQueryResponse.getBuyerPayAmount()));
				switch (alipayTradeQueryResponse.getTradeStatus()) // 判断交易结果
				{
				case "TRADE_FINISHED": // 交易结束并不可退款
					alipaymentOrder.setTradeStatus((byte) 3);
					break;
				case "TRADE_SUCCESS": // 交易支付成功
					alipaymentOrder.setTradeStatus((byte) 2);
					
					break;
				case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
					alipaymentOrder.setTradeStatus((byte) 1);
					break;
				case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
					alipaymentOrder.setTradeStatus((byte) 0);
					break;
				default:
					break;
				}
				this.alipayDao.updateAlipayMentOrder(alipaymentOrder); // 更新表记录
				return alipaymentOrder.getTradeStatus();
			} else {

			}
		} catch (AlipayApiException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} // 通过alipayClient调用API,获得对应的response类

		return 0;
	}

到此 我们的三个接口已近全部完成 是不是没有想象中的那么复杂呢

后台集成支付宝支付遇到的坑

在通过订单号查询交易记录的时候我们穿了两个值 一个是outTradeNo(后台生成) 还有一个为TradeNo(支付宝生成)
在这里插入图片描述
本以为两个二选一即可 但是测试多边后 即使是trade_no传入null也是要传入 不然会报40004(业务处理失败)

Map<String,String> paraMap = new HashMap<String,String>();
		paraMap.put("out_trade_no",outTradeno);//商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
		paraMap.put("trade_no",null);// trade_no  即便是空你也得写上   不能省略 !!!!!!!!!!!

提醒使用沙箱环境进行测试的时候 必须使用沙箱环境给我们的账户进行测试

在这里插入图片描述
好啦 今天的分享就到这里 本人小白一个 如有问题请多多指教
QQ:2289091393 (请注明来意)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值