首页 > 互联网 > 实用干货 | 一步一步教你在SpringBoot中集成支付宝的当面付

实用干货 | 一步一步教你在SpringBoot中集成支付宝的当面付

一:简介

当面付分为三种,不同的方式有着不同的使用场景,当面付的字面含义是面对面的付也就是买家当着卖家的面来付款。其中条码支付和扫码支付使用的最多,本文主要介绍这两种。

  • 当面付条码支付(商家使用扫码设备扫描买家支付宝的付款码):条码支付是支付宝给到线下传统行业的一种收款方式。商户使用扫码枪等条码识别设备扫描用户支付宝钱包上的条码/二维码,完成收款。一般在超市、便利店、店铺等使用。扫码支付(买家使用支付宝扫一扫扫描卖家的二维码):指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。一般在类似于无人售货机上使用,像地铁中的无人售货饮料机、医院中的自助挂号收费机等声波支付

在接入之前首先要熟悉一下当面付产品的业务文档:

  • 当面付文档
  • 沙箱当面付接入引导
  • 快速接入

当面付SDK&Demo 拿到demo我们熟悉一下demo,就可以依葫芦画瓢了,集成到自己工程中来。

通过下载Demo可以看到有两个工程,一个是TradePayDemo,一个是TradePaySDK,集成SDK必须要使用alipay-sdk-java20161213173952.jar,而TradePaySDK是对alipay-sdk-java.jar的进一步封装,在TradePayDemo中可以看到已经对TradePaySDK打成了jar包依赖进来了,当然也可以将整个TradePaySDK的src目录拖入到自己工程中, 当然也可以不使用alipay-trade-sdk.jar直接使用alipay-sdk-java.jar来开发。

集成当面付即可以参考TradePayDemo中的Main.java也可以参考WebRoot下的JSP,这里参考JSP中的代码来集成。 这里注意一点:jsp中的最上面的注释,所有的jsp文件都是一样的,作者粘贴了也不知道改一下,吐槽一下:这Demo写的真不细心

  • 条码付参考trade_pay.jsp
  • 扫码付参考trade_precreate.jsp
  • 退款参考trade_refund.jsp
  • 交易查询参考trade_query.jsp

条码支付流程图:

扫码支付流程图:

二:集成步骤

0. 创建应用、配置密钥

集成前需要先创建应用、配置密钥、回调地址等,具体操作请查看Spring Boot入门教程(三十五):支付宝集成-准备工作

1. 将Demo中的alipay-sdk-java和alipay-trade-sdk两个jar安装到本地maven仓库中

注意:将jar打到本地maven仓库时命令行最好是一行,不好有回车换行, 这里使用2017版本的,不是2016版本的,Demo中的还是2016的版本。

mvn install:install-file -DgroupId=com.alipay -DartifactId=alipay-sdk-java -Dversion= 20170725114550 -Dpackaging=jar -Dfile=alipay-sdk-java-20170725114550.jar
mvn install:install-file -DgroupId=com.alipay -DartifactId=alipay-trade-sdk -Dversion=20161215 -Dpackaging=jar -Dfile=alipay-trade-sdk-20161215.jar

2. 引入依赖



com.alipay
alipay-sdk-java
20170725114550


com.alipay
alipay-trade-sdk
20161215


com.google.code.gson
gson


commons-configuration
commons-configuration
1.10


com.google.zxing
core
3.2.1




org.projectlombok
lombok

3. 配置application.yml

注意:配置appPrivateKey和alipayPublicKey时值不要换行,returnUrl在当面付中不是必须的 注意:这里的returnUrl和notifyUrl在测试的时候需要外网能访问,可以通过natapp来实现

# 沙箱配置
pay:
alipay:
gatewayUrl: https://openapi.alipaydev.com/gateway.do
appid: 2016091400508498
appPrivateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtXKWs+trRSuCxEUvlsEeSAuLWs3B/Dh74R8223BnfzoA29aGeoycAqfKlBMcbzU2G1KayESvZKGpwBLeejSbecRYjgZsQDyEaDimQQJtGFvZVV6u4XNnvIJ72eQzEaEIQfuiorjBTLm6DQuds4R0GxftqON6QFoIZkWB9ZrZKd02cuy16dW+UqtLVGGAHcCIAkB63zUiKSNfweMddneZ7MVs3lvu3xhMnD+5us/+n2Vp4qhfmpYLcdqIW6InU4GypeoOpyUTrfUGpgdR0l924vHy/GQJZEKFaRcK3cYK+ECyKpSIoqaJJFLHbkqsliuPpMUG+rM3jiqeIAH4psAznAgMBAAECggEBAJ5jyEbbxrJzrAh7GhHX1fwUQPYSadTbrPYAfHX2cHlnrQMJtsk+nTLhEv0r+VJwZ8WpYkfMong8kcqYtL7ajcmsHqMAFhE9EWxBxj2ymWsXLabZe93sj30IG9Rq0nxcGQgDO0RqKWLGSFgK93Al2KRInKT3InkY53K+vR61ihVLmGf7+GwPtIZteBy+tuAyvcj2RlkYvjiFi3ySyZ1wA3sJIlgrGiixd6fj20XFGNE3CnAwu0BJgXXbP/S9J4C0RRa3ZXj8fX7oONhVxz2xKxn7AT4e8OWjt7J41H2LRct8Fgl9pqgz2FJYv/WfbkG8x9fGiKYYvPXoxjjrk/tkewkCgYEA8f9Lcu5JPrE9rpw9zlwhm5cOO81xLxdwL5R5/1bRP48BZGIYuqlCbVvjJVqtO8eTnLhUwH7fG8B7cmoeO9bGr9GQrtfyCqz6FtVymTBieJlfgZDVhtzyv2qKOBMIFE8jsbSBK/NHHMvykJ+XdQ1riwCeQDdXICRuYTTFwGk2OsUCgYEAt2SoN95tVmVrvKG6ATLNEtge/ozeVywA4GjltrSw/G9vqp+DkkT2pY19uROuzMazoTzKWpPho2q/qzNlv/ANbOFM2GEmKamQ7CO88JgRxMsPTvc/HxCLU/ClMJU8LcOf9LfP2KYZpPwuheKJoF4vDGj8NsbFmccJyYSdpkNEk7sCgYBJlL2FMaz1sgC2Ue19DIhvfaunRV1P20mSPgwmNmijccETm7w3LXX0OIdFeV/JGHLqqSWj7i+6iXk/ncKZoUGCfi8G6sQ+uL/GJ5qTt6GJV+ExTS+PtSjeSO/EAw1m13Vb+C16hpstx1l23f+4aJ81gbecgPct38XsKpaiXZtOnQKBgQCMsN7QRYYxwoq9YoDUzIlAzKYyeBVWYL6najHYUZR5hG/xQIBqZRem9/4cTvpJxKInrwA6LrrqaEl0aHDFp75U6h7O3PCvA5PXZK9dD/yJsZIj7U/yX/nTQokn1UUegrYiwiTkusBvrrtuINWePsLvTVc4GpObHnPmsiNTWsWwYwKBgENaeTNOCHV2km/ysXQSEIhKbtlAMQPsgWHCt/bzHlF9m18izb1LrJyjzcSsd+Zy78R+pv4G50Q27c3e/DFPz/wYxN/yHWRbyLBA8ipJbCtMtPEdS9krpmN6cChIdLGbz4CVUqOPSRzNb9lhhgPCcCNRq6DG3HBceb1Se9VnO3zk
alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApFQKccMq+wPoWh93DcX3XYrnT7FJ3gntJA/jEwgk6Jd3iEVS2CyUCCgFVcQn8xjXT81YbZHYvoC50IBuu+A+Ey+J8VIgsBm5g9uwbOLRf66GrZjuKOlalHm5gHXjcL2gZRMBJEStOxstcO2YQriqhQzdL3EKp+HQc9u14IOVFpZdR8qq1l7CzKHn1vQo/1fUPfUrTLQqSuQTU9Ospv/QHFqOJA7DPetUQ+jnaZ082f3clr4ITw4EE8A6IWqTXcETTx5j/udCGP84g2Y4j+8i9DqYGyD5ePVgt4G0ICBX1bi1qNlylfxRg8Y3c1DFrRGyr0NpKQxSVXkYaVNvrCoudQIDAQAB
returnUrl: http://fuzsmc.natappfree.cc/alipay/return
notifyUrl: http://fuzsmc.natappfree.cc/alipay/notify

4. AliPayProperties

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;

/**
* @author mengday zhang
*/
@Data
@Slf4j
@ConfigurationProperties(prefix = "pay.alipay")
public class AlipayProperties {

/** 支付宝gatewayUrl */
private String gatewayUrl;
/** 商户应用id */
private String appid;
/** RSA私钥,用于对商户请求报文加签 */
private String appPrivateKey;
/** 支付宝RSA公钥,用于验签支付宝应答 */
private String alipayPublicKey;
/** 签名类型 */
private String signType = "RSA2";

/** 格式 */
private String formate = "json";
/** 编码 */
private String charset = "UTF-8";

/** 同步地址 */
private String returnUrl;

/** 异步地址 */
private String notifyUrl;

/** 最大查询次数 */
private static int maxQueryRetry = 5;
/** 查询间隔(毫秒) */
private static long queryDuration = 5000;
/** 最大撤销次数 */
private static int maxCancelRetry = 3;
/** 撤销间隔(毫秒) */
private static long cancelDuration = 3000;

private AlipayProperties() {}

/**
* PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
*/
@PostConstruct
public void init() {
log.info(description());
}

public String description() {
StringBuilder sb = new StringBuilder("\nConfigs{");
sb.append("支付宝网关: ").append(gatewayUrl).append("\n");
sb.append(", appid: ").append(appid).append("\n");
sb.append(", 商户RSA私钥: ").append(getKeyDescription(appPrivateKey)).append("\n");
sb.append(", 支付宝RSA公钥: ").append(getKeyDescription(alipayPublicKey)).append("\n");
sb.append(", 签名类型: ").append(signType).append("\n");

sb.append(", 查询重试次数: ").append(maxQueryRetry).append("\n");
sb.append(", 查询间隔(毫秒): ").append(queryDuration).append("\n");
sb.append(", 撤销尝试次数: ").append(maxCancelRetry).append("\n");
sb.append(", 撤销重试间隔(毫秒): ").append(cancelDuration).append("\n");
sb.append("}");
return sb.toString();
}

private String getKeyDescription(String key) {
int showLength = 6;
if (StringUtils.isNotEmpty(key) && key.length() > showLength) {
return new StringBuilder(key.substring(0, showLength)).append("******")
.append(key.substring(key.length() - showLength)).toString();
}
return null;
}
}

5. AliPayConfiguration

@Configuration
@EnableConfigurationProperties(AlipayProperties.class)
public class AlipayConfiguration {

private AlipayProperties properties;

public AlipayConfiguration(AlipayProperties properties) {
this.properties = properties;
}

@Bean
public AlipayTradeService alipayTradeService() {
return new AlipayTradeServiceImpl.ClientBuilder()
.setGatewayUrl(properties.getGatewayUrl())
.setAppid(properties.getAppid())
.setPrivateKey(properties.getAppPrivateKey())
.setAlipayPublicKey(properties.getAlipayPublicKey())
.setSignType(properties.getSignType())
.build();
}
}

6. PayUtil

public class PayUtil {

/**
* 根据url生成二位图片对象
*
* @param codeUrl
* @return
* @throws WriterException
*/
public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
Map hints = new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
int width = 256;
BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
BufferedImage image = new BufferedImage(width, width, 1);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < width; ++y) {
image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
}
}

return image;
}
}

7. AlipayF2FController

package com.example.paydemo.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.demo.trade.model.GoodsDetail;
import com.alipay.demo.trade.model.builder.AlipayTradePayRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradeRefundRequestBuilder;
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.AlipayTradeService;
import com.alipay.demo.trade.utils.ZxingUtils;
import com.example.paydemo.configuration.AlipayProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.*;

/**
* 支付宝-当面付 控制器.
*


* https://openclub.alipay.com/read.php?tid=1720&fid=40
*
* https://docs.open.alipay.com/203/105910
*
* @author Mengday Zhang
* @version 1.0
* @since 2018/6/4
*/
@Slf4j
@RestController
@RequestMapping("/alipay")
public class AlipayF2FController {

@Autowired
private AlipayTradeService alipayTradeService;

@Autowired
private AlipayProperties aliPayProperties;

/**
* 当面付-条码付
*
* 商家使用扫码工具(扫码枪等)扫描用户支付宝的付款码
*
* @param authCode
*/
@PostMapping("/f2fpay/barCodePay")
public String barCodePay(String authCode){
// 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试

// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
String outTradeNo = UUID.randomUUID().toString();

// (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
String subject = "测试订单";

// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = "购买商品2件共20.05元";

// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = "0.01";

// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "";

// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";

// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";


// 商品明细列表,需填写购买商品详细信息,
List goodsDetailList = new ArrayList<>();
GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
goodsDetailList.add(goods1);
GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
goodsDetailList.add(goods2);

// 支付超时,线下扫码交易定义为5分钟
String timeoutExpress = "5m";


AlipayTradePayRequestBuilder builder = new AlipayTradePayRequestBuilder()
.setOutTradeNo(outTradeNo)
.setSubject(subject)
.setBody(body)
.setTotalAmount(totalAmount)
.setAuthCode(authCode)
.setTotalAmount(totalAmount)
.setStoreId(storeId)
.setOperatorId(operatorId)
.setGoodsDetailList(goodsDetailList)
.setTimeoutExpress(timeoutExpress);

// 当面付,面对面付,face to face pay -> face 2 face pay -> f2f pay
// 同步返回支付结果
AlipayF2FPayResult f2FPayResult = alipayTradeService.tradePay(builder);
// 注意:一定要处理支付的结果,因为不是每次支付都一定会成功,可能会失败
switch (f2FPayResult.getTradeStatus()) {
case SUCCESS:
log.info("支付宝支付成功: )");
break;

case FAILED:
log.error("支付宝支付失败!!!");
break;

case UNKNOWN:
log.error("系统异常,订单状态未知!!!");
break;

default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}

/**
* {
* "alipay_trade_pay_response": {
* "code": "10000",
* "msg": "Success",
* "buyer_logon_id": "ekf***@sandbox.com",
* "buyer_pay_amount": "0.01",
* "buyer_user_id": "2088102176027680",
* "buyer_user_type": "PRIVATE",
* "fund_bill_list": [
* {
* "amount": "0.01",
* "fund_channel": "ALIPAYACCOUNT"
* }
* ],
* "gmt_payment": "2018-06-10 14:54:16",
* "invoice_amount": "0.01",
* "out_trade_no": "91fbd3fa-8558-443a-82c2-bd8e941d7e71",
* "point_amount": "0.00",
* "receipt_amount": "0.01",
* "total_amount": "0.01",
* "trade_no": "2018061021001004680200326495"
* },
* "sign": "BNgMmA2t8fwFZNSa39kyEKgL6hV45DVOKOsdyyzTzsQnX8HEkKOzVevQEDyK083dNYewip1KK/K92BTDY2KMAsrOEqcCNxsk9NLAvK9ZQVxQzFbAFKqs5EBAEzncSWnChJcb7VMhDakUxHZFmclHg38dLJiHE2bEcF8ar9R1zj0p4V0Jr+BXO10kLtaSTc9NeaCwJZ89sPHKitNnUWRroU7t0xPHc1hWpstObwixKmAWnsFyb9eyGwPQnqNBsUVNSNWCJ7Pg3rb03Tx6J3zNsqH5f0YhWilMi09npPe33URFc6zG1HJSfhEm4Gq1zwQrPoA/anW8BbdmEUUmNo1dEw=="
* }
*/
String result = f2FPayResult.getResponse().getBody();

return result;
}

/**
* 当面付-扫码付
*
* 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
*
* 发起预下单请求,同步返回订单二维码
*
* 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
* @return
* @throws AlipayApiException
*/
@PostMapping("/precreate")
public void precreate(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试

// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
String outTradeNo = UUID.randomUUID().toString();

// (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
String subject = "测试订单";

// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = "购买商品2件共20.05元";

// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = "0.01";

// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "";

// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";

// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";

// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";


// 商品明细列表,需填写购买商品详细信息,
List goodsDetailList = new ArrayList<>();
GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
goodsDetailList.add(goods1);
GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
goodsDetailList.add(goods2);

// 支付超时,线下扫码交易定义为5分钟
String timeoutExpress = "5m";

AlipayTradePrecreateRequestBuilder builder =new AlipayTradePrecreateRequestBuilder()
.setSubject(subject)
.setTotalAmount(totalAmount)
.setOutTradeNo(outTradeNo)
.setUndiscountableAmount(undiscountableAmount)
.setSellerId(sellerId)
.setBody(body)
.setOperatorId(operatorId)
.setStoreId(storeId)
.setTimeoutExpress(timeoutExpress)
//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
.setNotifyUrl(aliPayProperties.getNotifyUrl())
.setGoodsDetailList(goodsDetailList);

AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
String qrCodeUrl = null;
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝预下单成功: )");

AlipayTradePrecreateResponse res = result.getResponse();
BufferedImage image = PayUtil.getQRCodeImge(res.getQrCode());

response.setContentType("image/jpeg");
response.setHeader("Pragma","no-cache");
response.setHeader("Cache-Control","no-cache");
response.setIntHeader("Expires",-1);
ImageIO.write(image, "JPEG", response.getOutputStream());
break;

case FAILED:
log.error("支付宝预下单失败!!!");
break;

case UNKNOWN:
log.error("系统异常,预下单状态未知!!!");
break;

default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}

/**
* 订单查询(最主要用于查询订单的支付状态)
* @param orderNo 商户订单号
* @return
*/
@GetMapping("/query")
public String query(String orderNo){

AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
.setOutTradeNo(orderNo);
AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("查询返回该订单支付成功: )");

AlipayTradeQueryResponse resp = result.getResponse();
log.info(resp.getTradeStatus());
// log.info(resp.getFundBillList());
break;

case FAILED:
log.error("查询返回该订单支付失败!!!");
break;

case UNKNOWN:
log.error("系统异常,订单支付状态未知!!!");
break;

default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
return result.getResponse().getBody();
}

/**
* 退款
* @param orderNo 商户订单号
* @return
*/
@PostMapping("/refund")
public String refund(String orderNo){
AlipayTradeRefundRequestBuilder builder = new AlipayTradeRefundRequestBuilder()
.setOutTradeNo(orderNo)
.setRefundAmount("0.01")
.setRefundReason("当面付退款测试")
.setOutRequestNo(String.valueOf(System.nanoTime()))
.setStoreId("A1");
AlipayF2FRefundResult result = alipayTradeService.tradeRefund(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝退款成功: )");
break;

case FAILED:
log.error("支付宝退款失败!!!");
break;

case UNKNOWN:
log.error("系统异常,订单退款状态未知!!!");
break;

default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}

return result.getResponse().getBody();
}


/**
* 扫码付异步结果通知
* https://docs.open.alipay.com/194/103296
* @param request
*/
@RequestMapping("/notify")
public String notify(HttpServletRequest request) throws AlipayApiException {
// 一定要验签,防止黑客篡改参数
Map parameterMap = request.getParameterMap();
parameterMap.forEach((key, value) -> System.out.println(key+"="+value[0]));

// https://docs.open.alipay.com/54/106370
// 获取支付宝POST过来反馈信息
Map 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] + ",";
}
params.put(name, valueStr);
}

boolean flag = AlipaySignature.rsaCheckV1(params,
aliPayProperties.getAlipayPublicKey(),
aliPayProperties.getCharset(),
aliPayProperties.getSignType());

if (flag) {
/**
* TODO 需要严格按照如下描述校验通知数据的正确性
*
* 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 同时需要校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
*
* 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
*/

return "success";
}

return null;
}
}

总结

以上代码都实际测试过,保证所有代码都是可用的,Spring Boot集成支付宝只需把代码复制过去即可,关注并私信“当面付”获取支付宝集成源码!

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/hlw/378296.html