微服务对接沙箱支付宝支付,并实现回调数据修改

在前文中,我们完成了vip题目与权限的设计,于是自然而然的引出了我们怎么成为vip这个问题,传统的c端系统以及游戏,往往采用对接支付接口的方式,本文选取了对接支付宝接口的形式进行支付并获取vip权限。

扫码付款的接入流程

 

 支付宝支付的逻辑过程

 支付成功后的消息回调

 有了此处基础上,通过研究支付宝沙箱支付原内容,我们可以很好的上手

进入支付宝沙箱系统网站(沙箱应用 - 开放平台 (alipay.com)

 

 拿到这些信息

由于测试环境是本地运行,我们通过内网穿透获得一个公网地址,这里我用的内网穿透工具为natapp工具

 配置页

 

          <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.9.9</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!--方式二 https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-easysdk</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.5</version>
        </dependency>

 

/**
 * @Description:
 * @Version: V1.0
 */
@Configuration
@Data
@ConfigurationProperties(prefix = "alipay.easy")
public class AliPayProperties {
    //请求协议
    private String protocol;
    // 请求网关
    private String gatewayHost;
    // 签名类型 RSA2
    private String signType;
    // 应用ID
    private String appId;
    // 应用私钥
    private String merchantPrivateKey;
    // 支付宝公钥
    private String alipayPublicKey;
    // 异步通知接收服务地址
    private String notifyUrl;
    // 设置AES密钥
    private String encryptKey;
}
@Configuration
@Data
public class AlipayConfig {

    @Bean
    public Config config(AliPayProperties payProperties) {
        Config config = new Config();
        config.protocol = payProperties.getProtocol();
        config.gatewayHost = payProperties.getGatewayHost();
        config.signType = payProperties.getSignType();
        config.appId = payProperties.getAppId();
        config.merchantPrivateKey = payProperties.getMerchantPrivateKey();
        config.alipayPublicKey = payProperties.getAlipayPublicKey();
        //可设置异步通知接收服务地址(可选)
        config.notifyUrl = payProperties.getNotifyUrl();
        config.encryptKey = "";
        return config;
    }


}
@RestController
@Slf4j
@AllArgsConstructor
public class EasyPayController {

    private final Config config;

    @GetMapping("pay")
    public String pay() throws Exception {
        Factory.setOptions(config);
        //调用支付宝接口
        AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace().preCreate("iphone15 promax 1T", "12138881313134", "12999");
        //解析结果
        String httpBody = response.getHttpBody();
        System.out.println(httpBody);
        //转JSON对象
        JSONObject jsonObject = JSONObject.parseObject(httpBody);
       try {
           String qrUrl = jsonObject.getJSONObject("alipay_trade_precreate_response").get("qr_code").toString();
           QrCodeUtil.generate(qrUrl,300,300,new File("E://qr2.jpg"));
       }catch (Exception e){
           return "支付失败";
       }
        //生成二维码
        return httpBody;
    }

    @PostMapping("/notify")
    public String notify(HttpServletRequest request){
        log.info("收到支付成功通知");
        System.out.println("支付成功");
        String out_trade_no = request.getParameter("out_trade_no");
        log.info("流水号:{}",out_trade_no);
        System.out.println("流水号"+out_trade_no);
        //TODO 后续业务流程 发货
        //回调了多次怎么办?  考虑接口的幂等性    分布式锁 乐观锁  先判断再处理
        return "success";
    }

    @GetMapping("/query")
    public String query() throws Exception {
        Factory.setOptions(config);
        AlipayTradeQueryResponse response = Factory.Payment.Common().query("1213131313131");
        return response.getHttpBody();
    }

}

上述代码实现了一个简单的模拟支付的流程,下面引出我们刷题系统的支付流程管理。

问题:

系统是微服务,支付功能放在哪里?

系统怎么确定某个用户是否支付?

怎么提升系统复用性?

一开始的设计之初,笔者是想把支付接口与微信服务写在一起,因为二者都需要内网穿透映射在公网,同时都是对外对接的服务,但是尝试过后笔者发现设计之初时,我们的微信服务是无业务关联的,他只负责登陆,这就导致了我们将支付系统引入微信服务会导致原代码大量重构,后面笔者又想将支付接口写入auth用户微服务中,但是因为支付接口异步消息回调的原因,将用户服务暴露在公网对系统安全也是不利的,同时内网穿透本身网速就慢,这样对系统而言影响较大。

考虑到系统后期可能不止一种服务支持方式,也不止仅仅充值vip这一个服务,为了更好的服务于其他功能,我们决定将支付服务单独独立部署出来。

新建订单表

 

 @Override
    public String created(Orders order) throws Exception {
        // 创建订单预支付请求
        AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace().preCreate(order.getOrderName(), order.getOrderId(), order.getOrderPrice());
        // 解析结果
        String httpBody = response.getHttpBody();
        System.out.println(httpBody);
        // 转JSON对象
        JSONObject jsonObject = JSONObject.parseObject(httpBody);
        try {
            // 检查是否订单已支付
            if ("40004".equals(jsonObject.getJSONObject("alipay_trade_precreate_response").getString("code"))) {
                return "订单已支付,请勿重复支付";
            }
            // 获取二维码URL
            String qrUrl = jsonObject.getJSONObject("alipay_trade_precreate_response").getString("qr_code");
            // 生成二维码并保存到本地文件
            String filePath = "codeimg.png";
            qrCodeGenerator.generateQRCode(qrUrl, filePath);
            // 构建MinIO中的对象名称
            String uniqueObjectName = OBJECT_NAME + "/" + Paths.get(filePath).getFileName();
            // 将生成的二维码上传到MinIO
            Path path = Paths.get(filePath);
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(BUCKET_NAME)
                            .object(uniqueObjectName) // 使用唯一的对象名称
                            .stream(Files.newInputStream(path), Files.size(path), -1)
                            .contentType("image/png")
                            .build()
            );

            // 生成预签名URL
            String url = URL + "/" + BUCKET_NAME + "/" + uniqueObjectName;
            // 删除本地文件
            Files.delete(path);
            OrderVip orderVip = new OrderVip();
            orderVip.setOrderId(Integer.valueOf(order.getOrderId()));
            orderVip.setOrderName(order.getOrderName());
            orderVip.setOrderPrice(order.getOrderPrice());
            orderVip.setStatus(0L);
            String loginId = LoginUtil.getLoginId();
            orderVip.setUserId(loginId);
            orderVip.setIsDeleted(0);
            orderVipService.insert(orderVip);
            return url;
        } catch (Exception e) {
            // 记录异常信息
            e.printStackTrace();
            return "上传失败";
        }
    }

业务分析:用户调用支付接口传入订单id,订单名称,订单金额字段

 

通过阿里云面对面支付生成预创建订单

// 创建订单预支付请求
AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace().preCreate(order.getOrderName(), order.getOrderId(), order.getOrderPrice());
订单数据传入订单表

 

订单创建成功后调用zxing生成二维码传入minio然后将图片url返还给前端

 用户打开支付宝扫码完成支付

异步消息回调这里有个问题我们要主要下,由于消息是异步回调通知,所以我们回调的消息内容在threadlocal中是不包含用户数据信息的,所以我们需要回调消息的订单id去订单库中提取用户信息

 public AuthUserRoleDTO updated(String out_trade_no,String loginId) throws Exception {
        AuthUserRoleDTO authUserRoleDTO = new AuthUserRoleDTO();
      //获取用户信息 OrderVip orderVip
=orderVipService.queryByOrderId(Integer.valueOf(out_trade_no)); orderVip.setStatus(1L); orderVipService.update(orderVip); //改为vip角色 authUserRoleDTO.setRoleId(4L); // Long userId = Long.valueOf(loginId); authUserRoleDTO.setUserId(orderVip.getUserId()); AuthUserRoleDTO authUserRole=userRpc.updateRole(authUserRoleDTO); return authUserRole; }

feign传送数据至下文修改用户角色为vip角色

   public AuthUserRoleDTO updateRole(AuthUserRoleDTO authUserRole) {
        AuthUserRoleDTO result = userFeignService.updateRole(authUserRole).getData();
        return result;
    }
 @RequestMapping("updateRole")
    public Result<AuthUserRole> updateRole(@RequestBody AuthUserRoleDTO authUserRoleDTO) {
        try {
            if (log.isInfoEnabled()) {
                log.info("RoleController.update.dto:{}", JSON.toJSONString(authUserRoleDTO));
            }
           // Preconditions.checkNotNull(authUserRole.getUserId(), "用户id不能为空");
            Preconditions.checkNotNull(authUserRoleDTO.getRoleId(), "角色id不能为空");
            String loginId = authUserRoleDTO.getUserId();
            log.info("loginId:{}",loginId);
            AuthUserBO authUserBO = new AuthUserBO();
            authUserBO.setUserName(loginId);
            AuthUserBO userInfo =authUserDomainService.getUserInfo(authUserBO);
            AuthUserRole authrole = new  AuthUserRole();
            authrole.setUserId(userInfo.getId());
            authrole.setRoleId(authUserRoleDTO.getRoleId());
            AuthUserRole authUserRole1 =authUserRoleService.updateUserId(authrole);
            return Result.ok(authUserRole1);
        } catch (Exception e) {
            log.error("RoleController.update.error:{}", e.getMessage(), e);
            return Result.fail("更新角色信息失败");
        }
    }

整体效果:

 

 

 

 

 

 

posted @ 2024-10-11 14:13  橘子味芬达水  阅读(48)  评论(0编辑  收藏  举报