V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
86322989
V2EX  ›  问与答

干货: Java 版在你的 web 应用加入比特币支付功能( blockchain 支付接口)

  •  2
     
  •   86322989 · 2017-10-16 16:09:06 +08:00 · 7116 次点击
    这是一个创建于 2620 天前的主题,其中的信息可能已经有所发展或是发生改变。

    *** 贴完整代码+掰开了揉碎了讲解代码***    #0.整体过程 网上找了很多资料,都很难找到接入比特币支付的教程,只能看英文原版+youtube 上的唯一一个视频教程。 终于调通了,无私分享给大家。    比特币支付的接入,让用户方便的打钱,并在数秒内得到反馈说他已经支付成功,以及支付了多少钱。作为网站主人的我们能够被通知收到了钱。(完全程序化,不是人工去查看);在数小时内,blockchain 会把钱打给我们的账户(根据你指定的 xpub 地址)    所需要实现的部分有两个:1 获得 blockchain 提供的比特币地址(不是我们自己的) 2 回调函数。blockchain 通知我们的时候调用(比如我的是 https://4b9xxxx3.ngrok.io/shop/order/btcCallback.html?invoiceId=500&secret=xxxxxxx3CfA5EcG6j&transaction_hash=xxxxx%20&address=1CmxxxxxxxQgkwxxxxXJgxQEv64&confirmations=5&value=0.0005

      

    1. 单纯的 java 中的 HttpClient。用于执行任意一个网址,获得返回的数据。

    public class HttpClientUtil {
    
        private final static String USER_AGENT = "Mozilla/5.0";
    
        public static void main(String[] args) throws Exception {
    
        }
    
        public static String getBlockChainAddress(Order order) throws  Exception {
            JSONObject jo = http(getReceiveUrl( order));
            return jo.getString("address");
        }
    
    static String getReceiveUrl(Order order) throws UnsupportedEncodingException {
            StringBuffer urlStringBuffer = new StringBuffer();
            final String secret = Constants.BTC_SECRET;//秘密
            String url = Constants.BTC_RECEIVE_URL;
            String apiKey = Constants.BTC_API_KEY;
            String xpub = Constants.BTC_XPUB;
    
            String callback = Constants.BTC_CALLBACKURL + "?invoiceId="+order.getId()+"&secret=" + secret;
    
            String callbackEncode = URLEncoder.encode(callback, "utf-8");
    
            urlStringBuffer.append(url).append("?key=").append(apiKey)
                    .append("&xpub=").append(xpub).append("&callback=").append(callbackEncode)
                    .append("&gap_limit=").append(Constants.BTC_GAP_NUM);
    
    
            return urlStringBuffer.toString();
        }
    
    public static JSONObject http(String url) throws Exception {
    
            HttpClient client = new DefaultHttpClient();
            HttpGet request = new HttpGet(url);
    
            //添加请求头
            request.addHeader("User-Agent", USER_AGENT);
    
    
            HttpResponse response = client.execute(request);
    
    //        System.out.println("Response Code : " +
    //                response.getStatusLine().getStatusCode());
    
            if (!"200".equals("" + response.getStatusLine().getStatusCode())) {
                //System.out.println("没有取到对方的比特币地址");
                return null;
            }
            BufferedReader rd = new BufferedReader(
                    new InputStreamReader(response.getEntity().getContent()));
    
            StringBuffer result = new StringBuffer();
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
    
            //变成 json 格式,方便取值
    
            //System.out.println(result.toString());
            JSONObject jo = new JSONObject(result.toString());
            return jo;
    
        }
    }
    

      

    2.访问 blockchain 的接口,获得一个比特币地址,用于客户往这个账户打钱。

    String blockChainAddress =	HttpClientUtil.getBlockChainAddress(order); 
    //处理支付地址
    order.setPayto(blockChainAddress);
    orderService.saveOrUpdate(order);
    

    3.让用户付钱的页面核心代码:

    <%--支付相关 js  s--%>
    <script>
        var btcs = new WebSocket('wss://ws.blockchain.info/inv');
    
    //    var payTO = '1J9ikqFuwrzPbczsDkquA9uVYeq6dEehsj';
        var payTO = '${order.payto}';
    
        btcs.onopen = function()
        {
            btcs.send( JSON.stringify( {"op":"addr_sub", "addr":payTO} ) );
        };
    
        btcs.onmessage = function(onmsg)
        {
            var response = JSON.parse(onmsg.data);
            var getOuts = response.x.out;
            var countOuts = getOuts.length;
            for(i = 0; i < countOuts; i++)
            {
                //check every output to see if it matches specified address
                var outAdd = response.x.out[i].addr;
                var specAdd = payTO;
                if (outAdd == specAdd )
                {
                    var amount = response.x.out[i].value;
                    var calAmount = amount / 100000000;
                    $('#messages').prepend("Received " + calAmount + " BTC");
    
                    var snd = new  Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
                    snd.play();
    
                }
            }
        }
    </script>
    <%--支付相关 js  e--%>
    
    <%--支付--%>
    		<div id="viewCart">
    			<span id="viewTitle">支付</span><br>
    
    			<div id="payAmt">
    				<font class="total-box-price"><c:out value="${order.total.value}" /></font> BTC
    				<br>
    				<img src="http://chart.googleapis.com/chart?chs=125x125&cht=qr&chl=${order.payto}" width="50%">
    				<br>
    				<input type="text" id="payBox" style="width:120px;" value="${order.payto}" onclick="this.select();" readonly>
    				<br><div id="messages"></div>
    			</div>
    		</div>
    

    #4.回调函数。 blockchain 收到钱之后,会执行我们的这个函数,里面处理我们自己的业务逻辑:更新订单状态为已付款等

    @RequestMapping("/btcCallback.html")
    	@ResponseBody
    	public String btcCallback4Blockchain(Model model, HttpServletRequest request) throws Exception {
    		String result = Constants.BTC_SUCCESS;
    
    		String invoiceId =(String) request.getParameter("invoiceId");
    		String secret =(String) request.getParameter("secret");
    		String transaction_hash =(String) request.getParameter("transaction_hash");//比特币交易 hash 暂无用
    		String address =(String) request.getParameter("address");//正常应该跟用户打钱的地址一样,blockchain 提供
    		String confirmations =(String) request.getParameter("confirmations");//确认的节点数量 
    		String value =(String) request.getParameter("value");
    		int confirmationInt = 0;
    		if(!Constants.BTC_SECRET.equals(secret)){
    			LOGGER.error("secret 不对");
    			return "error";
    		}
    //		if(!(Constants.BTC_XPUB.indexOf(address)>-1)){
    //			LOGGER.error("address 不是我们的地址");
    //			return "error";
    //		}
    
    		Long orderId = null;
    		try{
    
    			orderId = Long.valueOf(invoiceId);
    			confirmationInt = Integer.valueOf(confirmations).intValue();
    		}catch(Exception e){
    			LOGGER.error("转换失败:invoiceId="+invoiceId+" | confirmations= "+confirmations);
    			return "error";
    		}
    
    		if(confirmationInt<Constants.BTC_CONFIRM){
    			return "notfinish";
    		}
    
    
    		//get the order
    		Order order = orderService.getById(orderId);
    		if(order == null){
    			LOGGER.error("找不到订单 order");
    			return "error";
    		}
    		if((order.getTotal().doubleValue()*100000000)>(Double.valueOf(value))){
    			LOGGER.error("转账金额不对:"+value+"----"+order.getTotal().doubleValue());
    			return "error";
    		}
    		order.setStatus(OrderStatus.PROCESSED);
    		order.setBtcAddress(address);
    		order.setBtcTransactionHash(transaction_hash);
    		order.setBtcValue(value);//传递过来的单位是聪。 除以 10000 0000 得 BTC
    		orderService.saveOrUpdate(order);
    		return result;
    	}
    

    5.相关常量定义

    我自己的真实数据已被修改,使用的话用你自己的

    public Interface Constants {
    	public final static String BTC_RECEIVE_URL = "https://api.blockchain.info/v2/receive";
    	public final static String BTC_BALANCE_URL = "https://api.blockchain.info/v2/receive/balance_update";
    	public final static String BTC_CALLBACKURL = "https://4b9xxxxxx.ngrok.io/shop/order/btcCallback.html";
    	public final static String BTC_GAP_NUM = "50";
    	public final static String BTC_SECRET= "ZzsMxxxxxxxxEcG6j";
    	public final static String BTC_API_KEY= "b0bdf0xxxxxxx-xxxx-xxa06-939c-b8cc2880af99";
    	public final static String BTC_XPUB= "xpubxxxxxxxxxxxxxxxxxxxxxp5xuuuXcWY7VH27uwW9xxxxxFFnJpUXMbo79QA5tWobDa5Aymoo6X9gMvYRqmrpb2CZvje";
    	public final static int BTC_CONFIRM= 5;//确认数量,一般超过 5 就确定收到钱了
    	public final static String BTC_SUCCESS= "*ok*";//成功标志必须是这个,否则 bl 不知道成功了,一直 10 秒一次调用我们的回调方法,服务器会压力增大
    }
    

      

    #6.成功!~   

    #7.总结及注意及赞助 7.1 比特币不是中本聪创造的,而是上帝,中本聪只是发现者而已。它是人类历史上的一个重要节点。    7.2 测试的最小金额为 0.0005BTC,否则就会白打钱收不到。 7.3 如果你一直调用获取比特币地址却不支付,会在达到 20 次的时候不会再返回给你地址。 解决办法,打钱啊,或者把 gap_limit 参数设为大一点(我给了 50 ) 如果你测试的话,欢迎往我的账户打(***哈***),就当赞助支持这篇干货文章了(我会告诉你研究了 1 个半月?)。 我的比特币地址:1CmJdau9hmvHQgkw5ZhTq1zgTXJgxQEv64 http://chart.googleapis.com/chart?chs=125x125&cht=qr&chl=1CmJdau9hmvHQgkw5ZhTq1zgTXJgxQEv64

    5 条回复    2018-01-31 14:04:04 +08:00
    86322989
        1
    86322989  
    OP
       2017-10-19 14:23:32 +08:00
    常见问题:
    问:
    https://api.blockchain.info/v2/receive 已经可以实现收币后通知。还需要这个 https://api.blockchain.info/v2/receive/balance_update 接口,有什么作用呢。
    答:
    第一个已经足够了。 第二个是你主动去问,而不是等它来通知你

    问:
    https://api.blockchain.info/v2/receive 这个接口返回的收款地址。好像不能在 blockchain 后台的钱包地址查询到。
    答:
    是的。 这个是 blockchain 的 不是你的。 他会把钱再打给你

    问:
    有安全隐患吗。
    答:
    怕不给你钱?

    问:
    在 gap_limit 范围内。只要有一笔付款了,就会消除吗。比如 gap_limit=20。前面 15 笔未付款,第 16 笔付款了。gap_limit 会重新算不。
    答:
    是的 从零开始
    86322989
        2
    86322989  
    OP
       2017-10-19 14:25:32 +08:00
    问: 那个二维码访问不了

    答: google 被 q,可以用 https://blockchain.info/payment_request?address=1CmJdau9hmvHQgkw5ZhTq1zgTXJgxQEv64

    (bl 也被 q 了部分,访问缓慢)
    86322989
        3
    86322989  
    OP
       2017-11-08 12:59:44 +08:00
    838594661
        4
    838594661  
       2018-01-25 15:06:14 +08:00
    在吗? https://api.blockchain.info/v2/receive?xpub=$xpub&callback=$callback_url&key=$key&gap_limit=$gap_limit 访问拼好的地址,会返回 401 错误,有登录验证,你是怎么弄得,急,在线等
    hbxtyzh
        5
    hbxtyzh  
       2018-01-31 14:04:04 +08:00
    请问下 BTC_SECRET 和 BTC_API_KEY 是什么 怎么获取到呢 谢谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3831 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:22 · PVG 18:22 · LAX 02:22 · JFK 05:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.