我们在做开发时很多时候会涉及到支付功能,常见会对接支付宝和微信支付,本文将对JAVA对接支付宝进行详细的讲解。
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站点击跳转浏览。
在进行开发编码之前我们首选需要去申请开发所需要的支付宝一些配置,即支付宝沙盒环境的申请、
- 支付宝沙盒环境申请
1.1 注册
登录支付宝开发者网站 https://developers.alipay.com/ ,使用个人的支付宝扫码登录后,填写个人信息并提交
1.2 沙盒环境设置
按如下图所示进入沙盒环境设置页面
打开页面 https://opendocs.alipay.com/open/291/105971 下载公私钥生成工具,安装后生成应用的公钥和私钥
生成公私钥如下图
将公私钥保存好,后面程序中要用到,同时将公钥复制到沙盒环境的设置中,如下图
保存会生成一个支付宝的公钥,这个公钥用于后续的支付宝支付后的回调验签使用
至此。沙盒环境设置完毕。
- 开发部分
我们模拟一个简单的订单支付功能,只有一个简单的金额字段
2.1 数据库表结构
CREATE TABLE `user_order_t` ( `order_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单Id', `user_id` varchar(64) DEFAULT NULL COMMENT '用户Id', `order_no` varchar(64) NOT NULL COMMENT '订单号', `order_amount` decimal(16,4) NOT NULL COMMENT '订单金额', `order_status` int(11) NOT NULL COMMENT '0 待付款 1 已付款 -1 已取消', `create_time` varchar(32) NOT NULL COMMENT '创建时间', `last_update_time` varchar(32) NOT NULL COMMENT '最后修改时间', PRIMARY KEY (`order_id`) ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4;
2.2 项目结构
新建一个springboot项目,结构如图
2.3 pom依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.devin</groupId> <artifactId>alipay_demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>alipay_demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.0.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>2.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.17</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.22</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>2.1.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>2.2.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>3.4.27.ALL</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>maven-ali</id> <url>http://maven.aliyun.com/nexus/content/groups/public//</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> </repository> </repositories> </project>
2.4 模型的定义
2.4.1 订单model
package com.devin.alipay_demo.entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.math.BigDecimal; /** * @author Devin Zhang * @className UserOrder * @description TODO * @date 2020/5/12 10:49 */ @Table(name = "user_order_t") public class UserOrder { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Integer orderId; private String orderNo; private String userId; private BigDecimal orderAmount; private Integer orderStatus; private String createTime; private String lastUpdateTime; public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public String getOrderNo() { return orderNo; } public void setOrderNo(String orderNo) { this.orderNo = orderNo; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public BigDecimal getOrderAmount() { return orderAmount; } public void setOrderAmount(BigDecimal orderAmount) { this.orderAmount = orderAmount; } public Integer getOrderStatus() { return orderStatus; } public void setOrderStatus(Integer orderStatus) { this.orderStatus = orderStatus; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getLastUpdateTime() { return lastUpdateTime; } public void setLastUpdateTime(String lastUpdateTime) { this.lastUpdateTime = lastUpdateTime; } }
2.4.2 支付宝调用参数封装model
package com.devin.alipay_demo.dto; /** * @author Devin Zhang * @className AlipayBean * @description TODO * @date 2020/5/12 11:33 */ public class AlipayBean { /** * 商户订单号,必填 */ private String out_trade_no; /** * 订单名称,必填 */ private String subject; /** * 付款金额,必填 * 根据支付宝接口协议,必须使用下划线 */ private String total_amount; /** * 商品描述,可空 */ private String body; /** * 超时时间参数 */ private String timeout_express = "10m"; /** * 产品编号 */ private String product_code = "FAST_INSTANT_TRADE_PAY"; public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getTotal_amount() { return total_amount; } public void setTotal_amount(String total_amount) { this.total_amount = total_amount; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getTimeout_express() { return timeout_express; } public void setTimeout_express(String timeout_express) { this.timeout_express = timeout_express; } public String getProduct_code() { return product_code; } public void setProduct_code(String product_code) { this.product_code = product_code; } }
2.5 工具类
2.5.1 支付宝参数读取
package com.devin.alipay_demo.util; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * @author Devin Zhang * @className AlipayProperties * @description 读取支付宝配置 * @date 2020/5/12 11:16 */ @Component public class AlipayProperties { private static final String APP_ID = "appId"; private static final String PRIVATE_KEY = "privateKey"; private static final String PUBLIC_KEY = "publicKey"; private static final String NOTIFY_URL = "notifyUrl"; private static final String RETURN_URL = "returnUrl"; private static final String SIGN_TYPE = "signType"; private static final String CHARSET = "charset"; private static final String GATEWAY_URL = "gatewayUrl"; private static final String LOG_PATH = "logPath"; /** * 保存加载配置参数 */ private static Map<String, String> propertiesMap = new HashMap<>(); /** * 加载属性 */ @PostConstruct public void loadProperties() throws Exception { // 获得PathMatchingResourcePatternResolver对象 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { // 加载resource文件(也可以加载resources) Resource resources = resolver.getResource("classpath:config/alipay.properties"); PropertiesFactoryBean config = new PropertiesFactoryBean(); config.setLocation(resources); config.afterPropertiesSet(); Properties prop = config.getObject(); // 循环遍历所有得键值对并且存入集合 for (String key : prop.stringPropertyNames()) { propertiesMap.put(key, (String) prop.get(key)); } } catch (Exception e) { throw new Exception("配置文件加载失败"); } } public String getAppId() { return propertiesMap.get(APP_ID); } public String getPrivateKey() { return propertiesMap.get(PRIVATE_KEY); } public String getPublicKey() { return propertiesMap.get(PUBLIC_KEY); } public String getNotifyUrl() { return propertiesMap.get(NOTIFY_URL); } public String getReturnUrl() { return propertiesMap.get(RETURN_URL); } public String getSignType() { return propertiesMap.get(SIGN_TYPE); } public String getCharset() { return propertiesMap.get(CHARSET); } public String getGatewayUrl() { return propertiesMap.get(GATEWAY_URL); } public String getLogPath() { return propertiesMap.get(LOG_PATH); } }
2.5.2 支付宝工具类
package com.devin.alipay_demo.util; import com.alibaba.fastjson.JSON; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.request.AlipayTradePagePayRequest; import com.devin.alipay_demo.dto.AlipayBean; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author Devin Zhang * @className AliPayUtil * @description TODO * @date 2020/5/12 10:47 */ @Component public class AliPayUtil { @Resource private AlipayProperties alipayProperties; /** * 支付接口 * * @param alipayBean 封装的支付宝入参 * @return 返回支付结果 * @throws AlipayApiException 抛出异常 */ public String pay(AlipayBean alipayBean) throws AlipayApiException { // 1、获得初始化的AlipayClient String serverUrl = alipayProperties.getGatewayUrl(); String appId = alipayProperties.getAppId(); String privateKey = alipayProperties.getPrivateKey(); String format = "json"; String charset = alipayProperties.getCharset(); String alipayPublicKey = alipayProperties.getPublicKey(); String signType = alipayProperties.getSignType(); String returnUrl = alipayProperties.getReturnUrl(); String notifyUrl = alipayProperties.getNotifyUrl(); AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType); // 2、设置请求参数 AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); // 页面跳转同步通知页面路径 alipayRequest.setReturnUrl(returnUrl); // 服务器异步通知页面路径 alipayRequest.setNotifyUrl(notifyUrl); // 封装参数 alipayRequest.setBizContent(JSON.toJSONString(alipayBean)); // 3、请求支付宝进行付款,并获取支付结果 return alipayClient.pageExecute(alipayRequest).getBody(); } }
2.5.3 订单状态枚举
package com.devin.alipay_demo.util; /** * @author Devin Zhang * @className OrderEnum * @description TODO * @date 2020/5/12 11:43 */ public enum OrderEnum { ORDER_STATUS_PAID(1, "已支付"), ORDER_STATUS_NOT_PAY(0, "待支付"), ORDER_STATUS_CANCEL(2, "已取消"); private int status; private String statusName; OrderEnum(int status, String statusName) { this.status = status; this.statusName = statusName; } public int getStatus(){ return this.status; } }
2.6 dao
2.6.1 订单操作dao
因为使用了通用mapper tkmybatis,所以dao只是实现了相应的父类
package com.devin.alipay_demo.dao; import com.devin.alipay_demo.entity.UserOrder; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author Devin Zhang * @className UserOrderMapper * @description TODO * @date 2020/5/12 10:54 */ public interface UserOrderMapper extends Mapper<UserOrder>, MySqlMapper<UserOrder> { }
2.6.2 对应的mapper xml文件
同样,xml文件中内容也为空
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 3 <mapper namespace="com.devin.alipay_demo.dao.UserOrderMapper" > 4 5 </mapper>
2.7 service
service中定义的下单等方法,其中下单时回去调用支付宝
package com.devin.alipay_demo.service; import com.alipay.api.AlipayApiException; import com.devin.alipay_demo.dao.UserOrderMapper; import com.devin.alipay_demo.dto.AlipayBean; import com.devin.alipay_demo.entity.UserOrder; import com.devin.alipay_demo.util.AliPayUtil; import com.devin.alipay_demo.util.OrderEnum; import org.apache.commons.lang3.time.DateFormatUtils; import org.springframework.stereotype.Service; import tk.mybatis.mapper.entity.Example; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.Date; import java.util.UUID; /** * @author Devin Zhang * @className OrderService * @description TODO * @date 2020/5/12 10:53 */ @Service public class OrderService { @Resource private UserOrderMapper userOrderMapper; @Resource private AliPayUtil aliPayUtil; /** * 下单 * * @param orderAmount 订单金额 * @return 返回支付结果页面内容 * @throws AlipayApiException */ public String orderPay(BigDecimal orderAmount) throws AlipayApiException { //1. 产生订单 UserOrder order = new UserOrder(); order.setOrderNo(System.currentTimeMillis() + ""); order.setUserId(UUID.randomUUID().toString()); order.setOrderAmount(orderAmount); order.setOrderStatus(OrderEnum.ORDER_STATUS_NOT_PAY.getStatus()); String format = "yyyy-MM-dd HH:mm:ss"; order.setCreateTime(DateFormatUtils.format(new Date(), format)); order.setLastUpdateTime(DateFormatUtils.format(new Date(), format)); userOrderMapper.insert(order); //2. 调用支付宝 AlipayBean alipayBean = new AlipayBean(); alipayBean.setOut_trade_no(order.getOrderNo()); alipayBean.setSubject("充值:" + order.getOrderAmount()); alipayBean.setTotal_amount(orderAmount.toString()); String pay = aliPayUtil.pay(alipayBean); System.out.println("pay:" + pay); return pay; } /** * 根据订单号查询订单 * * @param orderNo * @return 返回订单信息 */ public UserOrder getOrderByOrderNo(String orderNo) { Example example = new Example(UserOrder.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("orderNo", orderNo); return userOrderMapper.selectOneByExample(example); } /** * 更新订单 * * @param userOrder 订单对象 * @return 返回更新结果 */ public int updateOrder(UserOrder userOrder) { return userOrderMapper.updateByPrimaryKeySelective(userOrder); } }