spring boot支付宝沙盒扫二维码支付

Spring boot整合支付宝沙盒

1.这里没有介绍对支付宝沙盒的密钥和公钥的配置,如没配置请先配置后在看此博客, 有很多不足之处,请多多包涵


2.我使用的是扫码支付,也是就是生成二维码,当然不止有这个, 还有另外几种的支付能力,如:网站的,app的, 我使用的是当面付时创建二维码

3.SDK我使用的是alipay-easysdk

alipay-easysdk文档: https://github.com/alipay/alipay-easysdk/blob/066388d02c6f55fe0919d75b386456d80801fec2/APIDoc.md

4.开发工具IDEA, jdk1.8, 和一个内网穿透工具natapp

5.前端使用的是vue, 利用webscoket来传输支付是否完成

好了这就是基本的介绍,结果图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

让我们开始实现这个功能吧! 这里省略了spring boot的搭建,直接进入maven依赖的导入

配置

1.pom.xml依赖的导入

<!--支付宝沙盒的依赖-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-easysdk</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!-- 二维码zxing -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!--  WebSocekt-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
		<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>

一些spring boot的依赖就省略了, 这里就贴出了生成二维码的,和支付宝沙盒sdk. websoket

2.application.yml 支付宝的配置

alipay:
  #商户ID
  appId:
  #自己的支付宝私钥
  privateKey:
  #自己的公钥
  publicKey:
  #支付宝网关
  gateway: openapi.alipaydev.com
  #支付完成后需要返回的url映射地址
  returnUrl:
  #异步支付通知
  notifyUrl:

3.支付宝的初始配置类

他主要的作用是读取application.yml文件中关于alipay的属性值, 实现了ApplicationRunner接口方法

@Component
public class 你的类名 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;

    /**
     * Spring boot项目启动时初始化支付宝沙盒配置
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Factory.setOptions(getOptions());
        System.out.println("********支付宝SDK初始化完成!******");
    }

    public 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;
    }
}

到这里代表你的支付宝已经配置差不多了, 运行spring boot时会在终端出现********支付宝SDK初始化完成!******就代表没问题了

支付能力Payment

我使用的是alipay-easysdk,他的官方文档写的也很清楚:https://github.com/alipay/alipay-easysdk/blob/066388d02c6f55fe0919d75b386456d80801fec2/APIDoc.md, 可参考Payment章节

我主要介绍的是交易预创建,生成正扫二维吗,如下表格

  • API声明

precreate(subject: string, outTradeNo: string, totalAmount: string)

  • 入参:
字段名类型必填说明
subjectstring订单标题
outTradeNostring交易创建时传入的商户订单号
totalAmountstring订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
  • 出参

    我们只需要要拿到qrcode这个参数, 这是生成二维码的主要数据

    其他详细出参请参考:https://opendocs.alipay.com/apis/api_1/alipay.trade.precreate

支付能力就介绍完了, 下面就是实现了

创建一个类,用于支付宝的支付业务层的实现

@Service
public class PayService {
    @Autowired
    //订单编号生成的工具类
    private UtilsCode utilsCode;
    public String payQr(Order1 order, String userId) throws Exception {
            String serialNumaber = utilsCode.orderUUID();
            Integer number = order.getNumber();
            Integer goodsId = order.getGoodsId();
        	//重点异步:  使用的notify接口    在natapp上拿到链接, 到这上面, 支付完记得看终端 200正常
        	//  userId 因为webscoket需要获得到用户的session才能发送消息, 
        	//number 是商品数量 goodsId 是商品id, 用于修改 数据库中的数据, 可以根据自己的需求进行修改
            String notify = "http://qjaxf4.natappfree.cc/back/v1/"+notify?userId="
                    +userId+"&number="+number+"&goodsId="+goodsId;
            AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace()
                	//异步, 用于查看是否支付成功
                    .asyncNotify(notify)
                			  /*  标题           订单号,          订单金额string 类型        */
                    .preCreate("标题, 可以随便写", serialNumaber, String.valueOf(order.getMoney()));
       		//这是我们需要拿到的数据
            return response.getQrCode();
    }
    /* 
    测试
    	 public String payQr() throws Exception {
            String serialNumaber = utilsCode.orderUUID();
        	//重点异步:      在natapp上拿到链接, 到这上面, 支付完记得看终端 200正常
        	//  userId 因为webscoket需要获得到用户的session才能发送消息, 
            String notify = "http://qjaxf4.natappfree.cc/back/v1/notify?userId="
                    +userId+"&number="+number+"&goodsId="+goodsId;
            AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace()
                	//异步, 用于查看是否支付成功
                    .asyncNotify(notify)
                    .preCreate("标题, 可以随便写", serialNumaber, "123");
       		//这是我们需要拿到的数据
            return response.getQrCode();
    }
    */
    
}


@Component
public class UtilsCode {
    //生成唯一订单号
    public String orderUUID() {
        //生成8位数的随机数   import org.apache.commons.lang3.RandomStringUtils;
        String randomString = RandomStringUtils.randomNumeric(8);
        String orderIdStr = "wch-" + datestr() + "-" + randomString;
        //返回完整的字符串
        return orderIdStr;
    }
}

编写前端控制器

  /**
     * 支付宝沙盒接口的调用,生成二维码
     */
    @Autowired
    private PayService payService;
	//produces = MediaType.IMAGE_JPEG_VALUE必须加上去
    @PostMapping(value="pay",  produces = MediaType.IMAGE_JPEG_VALUE)
	/*如果你想测试,可以把方法里的参数给去掉, 对应的你调用的方法也可以去掉如
		public BufferedImage  pay() throws Exception {
        String result = this.payService.payQr();
        BufferedImage bufferedImage = QrCodeUtils.createQr(result);
        return bufferedImage;
    }
	*/
    public BufferedImage  pay(@RequestBody Order1 order, String userId) throws Exception {
        String result = this.payService.payQr(order, userId);
        BufferedImage bufferedImage = QrCodeUtils.createQr(result);
        return bufferedImage;
    }

    /**
     * 生成BuffereImage, 加载配置类
     * @return
     */
    @Bean
    public BufferedImageHttpMessageConverter addConverter(){
        return new BufferedImageHttpMessageConverter();
    }


	//异步,支付是否成功
	@Autowired
    private WebScoketAlipay webScoketAlipay;
    /**
     * 异步通知处理
     */
    @PostMapping("/notify")
    public void asyncNotify(@RequestBody String notifyData, String userId, /*可根据自己的需求删掉*/String goodsId
            ,String number) throws IOException {
        if (notifyData != null) {
            webScoketAlipay.sendMessageToFinduserId("支付成功", userId);
            //如测试下面可以不用, 可根据自己的需求
            Slideshow slideshow = new Slideshow();
            slideshow.setShowDay(Integer.parseInt(number));
            slideshow.setImgId(Integer.parseInt(goodsId));
            this.slideshowService.topUpSildeShow(slideshow);
        }
    }


二维码的生成

需要使用到zxing这个sdk, 直接上关键代码, 如需要图片请自行百度

public class QrCodeUtils {
    private static final String CHARSET = "utf-8";
    public static final String FORMAT = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 350;
    // LOGO宽度
    private static final int LOGO_WIDTH = 60;
    // LOGO高度
    private static final int LOGO_HEIGHT = 60;
    
    public static BufferedImage createQr(String contenxt) {
        BufferedImage img = null;
        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(contenxt, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE);
           img=   MatrixToImageWriter.toBufferedImage(bitMatrix);

        } catch (WriterException e) {
            e.printStackTrace();
        }
        return img;
    } 
}

好了这样就完成一半了, 可以用postman测试下,你就会发现生成的二维码图片了, 下面就是我们的前端了

webscoket的配置与使用

配置

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

使用

@Component
@ServerEndpoint("/notfiyPay/{id}")
public class WebScoketAlipay {
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebScoketAlipay> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收userId
     */
    private String id = "";
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("id") String id) {
        this.session = session;
        this.id = id;
        //从map数据中取出key,判断是否存在
        if (webSocketMap.containsKey(id)) {
            //不能够二次登录
            webSocketMap.remove(id);
            webSocketMap.put(id, this);
        } else {
            webSocketMap.put(id, this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }
        System.out.println("用户连接:" + id + ",当前在线人数为:" + getOnlineCount());
        //sendMessage("连接成功");
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(id)) {
            webSocketMap.remove(id);
            //从set中删除
            subOnlineCount();
        }
        System.out.println("用户退出:" + id + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("用户消息:" + this.id + ",报文:" + message);
        //可以群发消息
        //消息保存到数据库、redis
        if (StringUtils.isNotBlank(message)) {
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUserId", this.id);
                String toUserId = jsonObject.getString("toUserId");
                String text = jsonObject.getString("contentText");
                //判断发送方是否在线
                if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
                    //发送消息
                    webSocketMap.get(toUserId).sendMessage(text);
                } else {
                    System.out.println("请求的userId:" + toUserId + "不在该服务器上");
                    //否则不在这个服务器上,发送到mysql或者redis
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("用户错误:" + this.id + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessageToFinduserId(String message, String userId) {
        try {
            //websocket session发送文本消息方法:getAsyncRemote()
//            for(WebScoketAlipay map: webSocketMap.values()){
//                map.session.getAsyncRemote().sendText(message);
//            }
            webSocketMap.get(userId).session.getAsyncRemote().sendText(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void sendMessage(String message){
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        ++WebScoketAlipay.onlineCount;
    }

    public static synchronized void subOnlineCount() {
        WebScoketAlipay.onlineCount--;
    }
}

好了,后端基本就已经完成了

前端vue界面

<template>
    <el-dialog title="续费" :visible.sync="open" width="500px" @close="closeDialog()"  :close-on-click-modal="false">
        <el-form ref="form" :model="order" label-width="80px">
            
            
            <div  style="display: flex">
                <div style="border-right: 1px dashed #D5D7D9">
                    <el-form-item label="商家名称:" style="width: 220px; font-size: 16px;">
                        <span>{{edit.shopName}}</span>
                    </el-form-item>
                    <el-form-item label="剩余时间:">
                        <span>{{edit.remain}}</span>
                    </el-form-item>
                
                    <el-form-item label="使用情况">
                        <span>{{edit.imgCase}}</span>
                    </el-form-item>
                </div>
                <div>
                    <el-form-item label="审批进度:">
                        <span>{{edit.useImg}}</span>
                    </el-form-item>

                    <el-form-item label="结束时间">
                        <span style="">{{edit.imgData}}</span>
                    </el-form-item>

                    <el-form-item label="广告图片:">
                        <img v-if="edit.homeImg" :src="edit.homeImg" style="width:130px; height: 70px;">
                    </el-form-item>
                    
                </div>
            </div>
            
            
            
            
            
            
            <!-- 重点看这些    -->
            <el-form-item label="天数充值">
                <el-input-number v-model="order.number"
                 controls-position="right" 
                 :min="0" :max="1860" style="width: 370px"></el-input-number>
                <el-radio-group v-model="order.number">
                    <el-radio-button :label="31">1个月</el-radio-button>
                    <el-radio-button :label="62">2个月</el-radio-button>
                    <el-radio-button :label="93">3个月</el-radio-button>
                    <el-radio-button :label="124">4个月</el-radio-button>
                    <el-radio-button :label="372">1年</el-radio-button>
                </el-radio-group>
            </el-form-item>

            <el-form-item label="支付方式">
                <el-radio-group v-model="radio">
                    <el-radio :label="1" @change="wallet('form')">余额</el-radio>
                    <el-radio :label="2" @change="alipay()">支付宝支付</el-radio>
                    <el-radio :label="3" @change="wechat()">微信支付(功能待开发)</el-radio>
                </el-radio-group>
            </el-form-item>

            <el-form-item v-if="payUrl != ''" label="扫码支付">
                 <img  :src="payUrl" style="width:120px; height: 120px;">
                 <span style="color: red">总计需支付{{order.money}}(元)</span>
            </el-form-item>
        </el-form>
    </el-dialog>
</template>

<script>
export default {
    name: "topUp",
    data(){
        return{ 
            edit: {
                imgId: "",
                homeImg: "",
                imgUrl: "",
                shopId: "",
                useImg: "",
                ImgCase: "",
                showDay: "",
                remain: ""    
            },

            // 表单参数 edit可以删掉
            order:{
                title: "",
                goodsId:"",
                number: 1,   //数量       
                money: "",   //总计多少元
                price: 0.5
            },
            payUrl: "",
            radio: 0,
            // 是否显示弹出层
            open: false,
        }
    },
    methods:{
        // 窗口初始化方法,nextTick方法可以添加逻辑,如打开窗口时查询数据填充
        init(id){
             this.$nextTick(() => {
                    let vm = this
                    this.$post(vm.API.API_URL_FINDBYIDSLIDESHOW+id).then(res =>{
                        if(res.errorCode == 0){
                            vm.edit = res.data
                            vm.edit.showDay = 0
                            vm.edit.homeImg =  vm.API.BASE_SERVER_URL + res.data.homeImg
                        }
                    })
                 this.open = true
             })
        },

        
        
        
        
        //系统余额钱包支付
        wallet(form){
            this.payUrl = ''
        },
        //支付宝支付 重点
        alipay(){
            let vm = this
            this.order.goodsId = this.edit.imgId
            this.order.title = this.edit.shopName
            this.order.money = this.order.number * this.order.price 
            //id根据自己系统啊!!!!  测试的话记得随便写个值
            let id =  JSON.parse(window.sessionStorage.getItem('accese-usename')).userId
            console.log(id)
            
            //请求后端   返回图片   {responseType: 'arraybuffer'}参数必须要
            axios.post("http://localhost:4099/back/v1/pay?userId="+id, vm.order,{
                responseType: 'arraybuffer'
            }).then(res =>{
                	//将图片以base64显示
                    let bytes = new Uint8Array(res.data);
                    let data = "";
                    let len = bytes.byteLength;
                    for (let i = 0; i < len; i++) {
                      data += String.fromCharCode(bytes[i]);
                    }
                   vm.payUrl = "data:image/jpg;base64," + window.btoa(data);
               
            })
            this.initWebSocket(id) 
        },
        //关闭对话框清除数
        closeDialog(){
            this.edit = this.$options.data().edit
            this.order = this.$options.data().order
            this.payUrl = ''
            this.radio = 0
        },
        //websocket和axiose的轮询 来查看支付信息是否成功
        initWebSocket(id){
          if(typeof WebSocket == "undefined"){
            alert("浏览器不支持WebSocket")
            //如浏览器不支持
          }else{
              //填自己的webscoket地址
            let socketUrl = "http://localhost:4099/notfiyPay/"+id;
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            this.websocket = new WebSocket(socketUrl);
            this.websocket.onopen = this.websocketonopen;
            this.websocket.onerror = this.websocketonerror;
            this.websocket.onmessage = this.websocketonmessage;
            this.websocket.onclose = this.websocketclose();
            }
        },
        websocketonopen(){
            console.log("连接成功")
        },

        websocketonerror(e){
            console.log("连接发生错误")
        },

        websocketonmessage(e){
            this.$notify({
                title: "消息",
                message: e.data
            })
            if(e.data == "支付成功"){
                this.open = false
                this.payUrl = ''
                //支付成功关闭websocket,
                this.websocket.close()
            }
        },

        websocketclose(){
            console.log("连接关闭")
        }

    }

}
</script>
<style  scoped>
.el-form-item {
    margin: 1px;
}
</style>
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值