开发主要分为三个步骤:一、生成私钥公钥。二、建立应用。三、沙箱环境。四、接口开发
1、根据开发语言选择密钥格式和密钥长度。
说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5xy7p55C-1647161550156)(练习.assets/image-20210604141400789.png)]
2、点击 生成密钥,工具会自动生成商户应用公钥(public key)和应用私钥(private key)。随后点击 打开密钥文件路径。
3、在弹出的 RSA密钥 文件夹下即可找到生成的公私钥文件。
4、登录 开发者中心控制台
这里就已经入驻了。
回到沙箱应用控制台,点击这个地方设置公钥(注意不是公钥证书哦,我们用的不是证书的方式):
https://openhome.alipay.com/platform/appDaily.htm?tab=info
在这里需要注意两个地方:
1、打开支付宝开放平台开发助手,左侧导航栏选择 签名,输入或选择以下参数。
请求参数:若使用公钥证书模式签名,在拼接待签名字符串时需添加解析出来的 应用公钥证书 SN(app_cert_sn)和 支付宝根证书 SN(alipay_root_cert_sn)。
app_id=2016101800718925&biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}&charset=utf-8&format=json&method=alipay.trade.page.pay&sign_type=RSA2×tamp=2020-10-13 09:58:50&version=1.0
关键参数说明
配置参数 | 描述 | 示例值 |
---|---|---|
appid | APPID 即创建应用后生成。 | 详情参见 [创建应用](https://opendocs.alipay.com/open/200/105310#查看 APPID)。 |
charset | 请求和签名使用的字符编码格式,支持 GBK 和 UTF-8 | 开发者根据实际工程编码配置。 |
format | 参数返回格式,只支持 json。 | json |
sign_type | 商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐商家使用 RSA2。 | RAS2 |
应用私钥:必须与应用 ID 中上传的应用公钥是匹配的,详情参见 如何检验 RSA2 密钥是否匹配、如何检验密钥证书是否匹配。
字符集:RSA 签名支持字符集 UTF-8 和 GBK。
签名方式:新建应用请务必使用支持 RSA2 即 2048 位。(目前已使用 RSA 密钥长度即 1024 位密钥长度的应用仍然可以正常调用接口,详情请参见 系统公告 >《开放平台接口签名方式升级公告》)。
2、点击按钮 开始签名,输出 待签名内容(经过格式处理后,最终要签名的内容)及 签名(sign)。同时把签名的详细过程打印到 txt 文件《签名步骤》,文件保存在工具存放目录的 RSA 密钥 文件夹下,点击按钮 查看签名生成步骤 即可打开 txt 文件查看。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alipay.sdkgroupId>
<artifactId>alipay-easysdkartifactId>
<version>2.0.2version>
dependency>
dependencies>
只有基本的web、lombok、支付宝的sdk依赖,下面介绍一下我的项目结构:
在index.html编写如下代码:
确认订单
您的订单信息如下,请确认无误后支付:
xxxxxxxxxx省略订单信息xxxxxxxxxx
在return.html编写以下代码:
支付成功
您已成功购买此商品,感谢您的支持
在application.yml配置文件,将公钥、私钥、APPID、支付宝的网关、页面回调、接口回调等信息配置进来:
alipay:
# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号,开发时使用沙箱提供的APPID,生产环境改成自己的APPID
appId: 换成你自己的APPID
# 商户私钥,您的PKCS8格式RSA2私钥
privateKey: 换成你自己的私钥,支付宝开发助手生成的私钥
# 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
publicKey: 注意:这里是你自己的支付宝的公钥
# 支付网关配置,这一项是写死的,正式环境是 openapi.alipay.com
gateway: openapi.alipaydev.com
# 支付成功之后的回调页面,只是一个友好页面。主要换成你自己映射的临时域名
returnUrl: http://内网穿透域名/return.html
# 支付成功的接口回调
notifyUrl: http://内网穿透域名/pay/fallback
新版本的SDK 把大部分操作都封装到了Factory类中,但是Factory类在使用之前需要我们配置一下参数,且全局配置一次即可,所以我们把这部分代码拿到ProjectInit类中,在项目启动时执行一次:
package com.example.springbootzfb.zfb;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 项目初始化
*
* @author wangziyang
* @date 2020-09-08
* */
@Component
public class ProjectInit implements ApplicationRunner {
//应用id
@Value("${alipay.appId}")
private String appId;
//私钥
@Value("${alipay.privateKey}")
private String privateKey;
//公钥
@Value("${alipay.publicKey}")
private String publicKey;
//支付宝网关
@Value("${alipay.gateway}")
private String gateway;
//支付成功后的接口回调地址,不是回调的友好页面,不要弄混了
@Value("${alipay.notifyUrl}")
private String notifyUrl;
/**
* 项目初始化事件
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//初始化支付宝SDK
Factory.setOptions(getOptions());
System.out.println("**********支付宝SDK初始化完成**********");
}
private Config getOptions() {
//这里省略了一些不必要的配置,可参考文档的说明
Config config = new Config();
config.protocol = "https";
config.gatewayHost = this.gateway;
config.signType = "RSA2";
config.appId = this.appId;
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
config.merchantPrivateKey = this.privateKey;
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = this.publicKey;
//可设置异步通知接收服务地址(可选)
config.notifyUrl = notifyUrl;
return config;
}
}
既然要下单,肯定少不了传参,所以我们这里简单的声明一个PaymentBO类:
package com.example.springbootzfb.zfb;
import lombok.Data;
import java.math.BigDecimal;
/**
* 发起支付时的参数
* */
@Data
public class PaymentBO {
//省略其他的业务参数,如商品id、购买数量等
//商品名称
private String subject;
//总金额
private BigDecimal total = BigDecimal.ZERO;
}
然后在OrderUtil编写如下代码:
package com.example.springbootzfb.zfb;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class OrderUtil {
/**
* 根据时间戳生成订单号 *
*/
public static String getOrderNo() {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
return df.format(localDateTime);
}
}
接下来,继续看文档:
按照文档描述,我们只需要Factory.Payment.Page.pay就可以发起一个支付请求,比之前还是简洁了不少,其他操作都封装好了。所以我们在PayService编写以下代码:
package com.example.springbootzfb.zfb;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.facetoface.models.AlipayTradePayResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* 支付宝支付,业务实现
*
* @author wangziyang
* @date 2020-09-08
* */
@Service
public class PayService {
//支付成功后要跳转的页面
@Value("${alipay.returnUrl}")
private String returnUrl;
/**
* 下单支付
* */
public Object pay (PaymentBO bo) throws Exception {
//从存储介质(如MySQL、Redis)查询商品信息、总金额等敏感信息
//…………省略相关代码,这里直接赋值…………
bo.setSubject("测试商品");
bo.setTotal(new BigDecimal(10.00));
//调用sdk,发起支付
AlipayTradePagePayResponse response = Factory.Payment
//选择网页支付平台
.Page()
//调用支付方法,设置订单名称、我们自己系统中的订单号、金额、回调页面
.pay(bo.getSubject() , OrderUtil.getOrderNo(), bo.getTotal().toString() , returnUrl);
//这里的response.body,就是一个可以直接加载的html片段,
// 这里为了简单我直接返回这个片段,前端直接
return response.body;
}
}
再往下,编写PayController:
package com.example.springbootzfb.zfb;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 支付宝支付,控制器
*
* @author wangziyang
* */
@RestController
@RequestMapping(value = "/pay")
@AllArgsConstructor
public class PayController {
private PayService payService;
/**
* 下单支付
* */
@GetMapping(value = "/confirm" , produces = {"text/html;charset=UTF-8"})
public Object pay (@RequestParam(required = false) PaymentBO bo) throws Exception {
//这个接口其实应该是post方式的,但是我这里图方便,直接以get方式访问,
//且返回格式是text/html,这样前端页面就能直接显示支付宝返回的html片段
//真实场景下由post方式请求,返回code、msg、data那种格式的标准结构,让前端拿到data里的
//html片段之后自行加载
//由于我这里并没有真正的传参数,所以象征性的new一下,避免空指针
bo = new PaymentBO();
return payService.pay(bo);
}
/**
* 支付成功的回调
* */
@PostMapping(value = "/fallback")
public Object fallback (HttpServletRequest request) {
Map map = request.getParameterMap();
System.out.println("进入了回调");
return null;
}
}
在index.html页面做如下修改,点击支付的时候访问我们的/pay/confirm接口:
确认订单
您的订单信息如下,请确认无误后支付:
xxxxxxxxxx省略订单信息xxxxxxxxxx
运行项目,点击确认支付就看到了以下页面(有时候下面的页面会提示有钓鱼风险,换一个浏览器就好,或者关了浏览器重来,这个无解):
上面是最简单的支付。大家可以在API文档看到,下单支付还有很多参数可选,如订单描述、商品类型、订单商品列表的具体信息等等……
公共参数就不用说了,Factory都为我们封装好了;但是下面的业务请求参数,也就是公共参数里的biz_content,如何传呢?pay()方法只提供了基本的4个参数,那可选的请求参数怎么传呢?
对于这个问题,新版的SDK指出需要由我们自己扩展,方式在SDK说明中有:
这个还是挺简单的,就是往Map里放参数,最后通过optional或batchOptional方法进行设置:
所以在这里我就不展示了,大家下去自己扩展就行
前面的回调只是在页面上回显信息,我们需要提供一个接口,供支付宝调用,接收支付宝传来的参数。
参数有很多,我就不一一列出了。主要的参数是商户订单号(即我们自己生成的订单号)和交易状态, 我们根据订单号是否交易成功,进行后面的业务逻辑,比如给用户加积分、将订单状态变为已支付、发送业务短信、通知商家接单……等等。
参数很多,所以我就不新建实体类了,简单的用Map接一下,在PayController新增以下接口(注意是post方式):
package com.example.springbootzfb.zfb;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 支付宝支付,控制器
*
* @author wangziyang
* */
@RestController
@RequestMapping(value = "/pay")
@AllArgsConstructor
public class PayController {
private PayService payService;
/**
* 下单支付
* */
@GetMapping(value = "/confirm" , produces = {"text/html;charset=UTF-8"})
public Object pay (@RequestParam(required = false) PaymentBO bo) throws Exception {
//这个接口其实应该是post方式的,但是我这里图方便,直接以get方式访问,
//且返回格式是text/html,这样前端页面就能直接显示支付宝返回的html片段
//真实场景下由post方式请求,返回code、msg、data那种格式的标准结构,让前端拿到data里的
//html片段之后自行加载
//由于我这里并没有真正的传参数,所以象征性的new一下,避免空指针
bo = new PaymentBO();
return payService.pay(bo);
}
/**
* 支付成功的回调
* */
@PostMapping(value = "/fallback")
public Object fallback (HttpServletRequest request) {
Map map = request.getParameterMap();
System.out.println("进入了回调");
return null;
}
}
可以看到,我们已经接收到了支付宝传来的参数,接下来就是根据自己的业务进行处理了,笔者这里就不再往下写了。
文档到这里就全部完成了。