实习期间我负责的项目需要支付功能,为了能实现这个功能

支付的逻辑几乎每个第三方的API逻辑都大差不差,第一步就是调用第三方支付的下单接口,一般在调用支付接口的时候,会有一个参数被称为 回调url,这个参数一般需要填一个自己的后台接口,这时候,第三方API就会收到一个下单的请求,返回值一般是会有一个用户支付的url,我们需要将这个url返给前端,引导用户跳转去支付,接着第三方API会从参数中找到回调url,对我们自己的接口进行一个调用,这个调用一般会告诉我们用户是否支付成功等。支付成功就需要执行相应的业务逻辑,并设法告诉用户支付成功!

支付第一步 下单!

一般的支付都是需要调用支付的第三方的API,首先需要找到需要合适的支付第三方,例如支付宝支付,微信支付等,本次用的dreamo9 API,一个国外的支付API。 https://www.showdoc.com.cn/dreamo9/10967763922846220 链接为API文档

如图就是一个第三方支付

编写自己的业务逻辑下单代码

这是service的代码,主要逻辑是先构建订单信息,并存入自己的数据库,然后调用自定义函数去发请求,调用第三方支付接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String createPaymentOrder(GoodsReqBO req) {

// 首冲用户不能再购买
if (req.getUserInfoPO().isCharged() && req.getGoodId() == 0) {
log.error("[createOrderAndGetRedirectUrl] not charge again , req is {}", JSONUtil.toJsonStr(req));
throw BizException.of(BizCode.HAS_CHARGED);
}
// 1.查找用户和商品
GoodsInfoPO goodsInfo = goodsInfoService.lambdaQuery()
.eq(GoodsInfoPO::getGoodId, req.getGoodId())
.one();
if (Objects.isNull(goodsInfo)) {
log.error("[createOrderAndGetRedirectUrl] not exist , req is {}", JSONUtil.toJsonStr(req));
throw BizException.of(BizCode.DATA_NOT_EXIST, req.getGoodId());
}
// 2.生成支付订单 并存入数据库
GoodsOrderPO paymentOrder = goodsOrderService.createGoodsOrder(goodsInfo, req.getUserInfoPO());
// 构建第三方支付参数 并发送请求
String payurl = this.createPayment(paymentOrder, req);
return payurl;
}

这是根据第三方支付的api文档中编写的请求体,根据文档中payData是支付路径,最后通过浏览器打开支付路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private String createPayment(GoodsOrderPO paymentOrder, GoodsReqBO req) {
Map<String, Object> params = new HashMap<>();
//商户号和订单号
params.put("mchNo", "M1744790674");
params.put("appId", "A1744790727");
params.put("mchOrderNo", paymentOrder.getOrderId());
params.put("amount", 2222); // 单位:分
//这个表示用美金 先写死
params.put("currency", "SAR");
//客户的ip
params.put("clientIp", req.getUserInfoPO().getRegistryIP());
//客户的uuid
params.put("uid", req.getUserInfoPO().getUuid());
params.put("customerName", req.getUserInfoPO().getName());
params.put("customerEmail", req.getUserInfoPO().getEmail());
params.put("customerPhone", "8197220658");
//回调地址 TODO 着需要一个后端接口
params.put("notifyUrl", req.getRedirectUrl());
params.put("reqTime", System.currentTimeMillis());

// 生成签名 TODO 现在没有密钥
String sign = Dreamo9Sign.getSign(params, "3V32ODHkXzPh2swrE1qpe9orXiTTwwQaymsFDFpkGVsr8jcob7K2nyZVjBEB5j7sT6nLjxgeet6KWVjyksi8N7rLJ1XqFDDNvPzOzsEPwmZIUDHK7c3fqb9eNtdxqfel");
params.put("sign", sign);

// 创建 RestTemplate 实例
RestTemplate restTemplate = new RestTemplate();

// 构造请求 URL
String url = "https://live.dreamo9.com/api/payment";

// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

// 构造请求实体
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(params, headers);

// 发送 POST 请求 并获取结果
ResponseEntity<Object> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Object.class);

ObjectMapper objectMapper = new ObjectMapper();
//转换成map
Map<String, Object> res= objectMapper.convertValue(response.getBody(), Map.class);
//获取code看是否成功
if (!res.get("code").equals(0)){
System.out.println("这个表示失败了");
throw new RuntimeException("下单失败");
}
//,下单成功获取支付url,这个应该是支付链接,需要返回前端
return (String) res.get("payData");

}

这是支付路径打开后的结果,由于这只是测试环境,正常线上用户在这里支付成功后就会进行回调接口

回调测试

当我们下单逻辑成功后,第三方支付api就会对我们的接口进行回调,但是我们首先需要将服务部署到线上才可以,因为第三方是调不通内网的,所以需要部署到服务器上,用域名进行访问

  1. 首先需要通过api文档,来看第三方支付回调携带的参数有哪些,根据参数可以判断出是否支付成功和订单号等。通过这些参数信息去执行我们的业务逻辑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //回调函数,客户支付成功后,第三方api会对这个接口进行调用
    @PostMapping("/Dreamo9/success")
    public CommonResult<String> Dreamo9SuccessPay(@RequestParam Map<String, String> params) {
    log.info("Method starts execution");
    log.info("Dreamo9SuccessPay params:{}", params);
    String state = params.get("state");
    log.info("Dreamo9SuccessPay state:{}", state);
    String mchOrderNo = params.get("mchOrderNo");
    log.info("Dreamo9SuccessPay mchOrderNo:{}", mchOrderNo);
    if (Integer.parseInt(state)==2){
    //说明支付成功
    paypalBiz.payDreamo9Success(params);
    log.info("Dreamo9SuccessPay paySuccess");
    }
    return success;
    }

项目部署

因为第三方是根本调不通内网的,要么内网穿透,要么将项目部署到线上,通过公网访问

解析域名

项目上线是需要域名,我们需要一个域名,域名指定的ip为服务器的ip即可。

项目部署

这个项目是部署到AWS服务器上的.

  1. 连接服务器
    通过ssh 连接服务器,需要管理员添加密钥,不然连接不上
    1
    ssh @服务器地址
  2. 将springboot项目的jar上传到服务器上的指定文件夹中
    1
    scp build/libs/backend-0.0.1-SNAPSHOT.jar ubuntu@40.172.61.245:/usr/myproject/
  3. 然后sudo apt install openjdk-17-jre-headless 下载环境 中途选择 y

  4. 直接 java -jar jar包名字.jar 运行jar包

这样项目正常启动就说明部署好了

这样只是部署好了,但是仍然会无法访问,需要配置nginx或者caddy 进行路由转发

配置Caddy

一、静态页面的部署

1.将静态页面上传到服务器实例机器中

2.下载Caddy(官网有命令)

3.编写Caddy,进行反代和记载静态页面

sudo setcap cap_net_bind_service=+ep $(which caddy)

1
2
3
4
5
6
7
8
9
10
11
域名 {
reverse_proxy 127.0.0.1:18080 将这个域名反代到了本地18080端口
}
域名 {
root * /myproject 去这里的文件夹中找静态页面
file_server 启动静态页面的管理
@api {
path /api/* 代理api
}
reverse_proxy @api 127.0.0.1:18080 将静态页面的请求进行反代
}

结语

从请求回调的问题 发现需要部署项目到线上,又到发现项目部署了,但是访问不到,一直报502错误,发现需要配置Caddy。到这里本地已经可以模拟回调成功了,但是仍不能第三方调通,测了好多次最后发现是 第三方API发的请求发错了,文档里写的POST 结果发来是GET!