/** * Parse transactions from payment message. * * @param params network parameters (needed for transaction deserialization) * @param paymentMessage payment message to parse * @return list of transactions */ public static List<Transaction> parseTransactionsFromPaymentMessage( NetworkParameters params, Protos.Payment paymentMessage) { final List<Transaction> transactions = new ArrayList<Transaction>(paymentMessage.getTransactionsCount()); for (final ByteString transaction : paymentMessage.getTransactionsList()) transactions.add(params.getDefaultSerializer().makeTransaction(transaction.toByteArray())); return transactions; }
@Test public void testPaymentAck() throws Exception { // Create Payment paymentMessage = Protos.Payment.newBuilder().build(); PaymentACK paymentAck = PaymentProtocol.createPaymentAck(paymentMessage, MEMO); byte[] paymentAckBytes = paymentAck.toByteArray(); // Parse PaymentACK parsedPaymentAck = PaymentACK.parseFrom(paymentAckBytes); assertEquals(paymentMessage, parsedPaymentAck.getPayment()); assertEquals(MEMO, parsedPaymentAck.getMemo()); }
@Test public void testSimplePayment() throws Exception { // Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession. MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest()); assertEquals(paymentRequestMemo, paymentSession.getMemo()); assertEquals(nanoCoins, paymentSession.getValue()); assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl()); assertTrue(new Date(time * 1000L).equals(paymentSession.getDate())); assertTrue(paymentSession.getSendRequest().tx.equals(tx)); assertFalse(paymentSession.isExpired()); // Send the payment and verify that the correct information is sent. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); Address refundAddr = new Address(params, serverKey.getPubKeyHash()); paymentSession.sendPayment(txns, refundAddr, paymentMemo); assertEquals(1, paymentSession.getPaymentLog().size()); assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString()); Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment(); assertEquals(paymentMemo, payment.getMemo()); assertEquals(merchantData, payment.getMerchantData()); assertEquals(1, payment.getRefundToCount()); assertEquals(nanoCoins.longValue(), payment.getRefundTo(0).getAmount()); TransactionOutput refundOutput = new TransactionOutput(params, null, nanoCoins, refundAddr); ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes()); assertTrue(refundScript.equals(payment.getRefundTo(0).getScript())); }
/** * Create a payment message. This wraps up transaction data along with anything else useful for * making a payment. * * @param transactions transactions to include with the payment message * @param refundOutputs list of outputs to refund coins to, or null * @param memo arbitrary, user readable memo, or null if none * @param merchantData arbitrary merchant data, or null if none * @return created payment message */ public static Protos.Payment createPaymentMessage( List<Transaction> transactions, @Nullable List<Protos.Output> refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) { Protos.Payment.Builder builder = Protos.Payment.newBuilder(); for (Transaction transaction : transactions) { transaction.verify(); builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize())); } if (refundOutputs != null) { for (Protos.Output output : refundOutputs) builder.addRefundTo(output); } if (memo != null) builder.setMemo(memo); if (merchantData != null) builder.setMerchantData(ByteString.copyFrom(merchantData)); return builder.build(); }
/** * Generates a Payment message based on the information in the PaymentRequest. Provide * transactions built by the wallet. If the PaymentRequest did not specify a payment_url, returns * null. * * @param txns list of transactions to be included with the Payment message. * @param refundAddr will be used by the merchant to send money back if there was a problem. * @param memo is a message to include in the payment message sent to the merchant. */ public @Nullable Protos.Payment getPayment( List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo) throws IOException { if (!paymentDetails.hasPaymentUrl()) return null; Protos.Payment.Builder payment = Protos.Payment.newBuilder(); if (paymentDetails.hasMerchantData()) payment.setMerchantData(paymentDetails.getMerchantData()); if (refundAddr != null) { Protos.Output.Builder refundOutput = Protos.Output.newBuilder(); refundOutput.setAmount(totalValue.longValue()); refundOutput.setScript( ByteString.copyFrom(ScriptBuilder.createOutputScript(refundAddr).getProgram())); payment.addRefundTo(refundOutput); } if (memo != null) { payment.setMemo(memo); } for (Transaction txn : txns) { txn.verify(); ByteArrayOutputStream o = new ByteArrayOutputStream(); txn.bitcoinSerialize(o); payment.addTransactions(ByteString.copyFrom(o.toByteArray())); } return payment.build(); }
@Override public Boolean call() { if (serverSocket.isClosed()) { log.warn("Server socket is closed. Aborting."); return false; } else { try { // Wait for a client connection log.debug("Await client connection to SSLSocket"); SSLSocket socket = (SSLSocket) serverSocket.accept(); socket.startHandshake(); log.debug("Sending PaymentACK"); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); // Read the inputStream - this is expected to be a header followed by a serialised Payment Reader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(reader); String line; int contentLength = -1; StringBuilder builder = new StringBuilder(); while (!"".equals((line = bufferedReader.readLine()))) { builder.append(line).append("\n"); log.debug("Read line: {}", line); if (line.startsWith("Content-Length")) { String[] tokens = line.replaceAll(" ", "").split(":"); if (tokens.length >= 2) { contentLength = Integer.parseInt(tokens[1]); } } } log.debug("Calculated contentLength: {}", contentLength); log.debug("Read the header:\n{}\n", builder.toString()); // Get the Content-Length and read those - this is expected to be the serialised Payment if (contentLength > -1) { byte buffer[] = new byte[contentLength]; for (int i = 0; i < contentLength; i++) { buffer[i] = (byte) inputStream.read(); } log.debug("Read:\n", HexUtils.toHexBytes(buffer)); try { Protos.Payment payment = Protos.Payment.parseFrom(buffer); log.debug("Successfully parsed a payment {}", payment); // Create a PaymentACK for the payment Protos.PaymentACK paymentAck = PaymentProtocol.createPaymentAck(payment, "You sent: '" + payment.getMemo() + "'"); log.debug("Sending paymentACK as a response: {}", paymentAck); // Write the HTTP header outputStream.write("HTTP/1.0 200 OK\n".getBytes(Charsets.UTF_8)); outputStream.write("Content-Type: ".getBytes(Charsets.UTF_8)); outputStream.write(contentType.getBytes(Charsets.UTF_8)); outputStream.write("\n\n".getBytes(Charsets.UTF_8)); // Write the protobuf response paymentAck.writeTo(outputStream); // ByteArrayInputStream responseInputStream = new ByteArrayInputStream(paymentAckBytes); // ByteStreams.copy(responseInputStream, outputStream); } catch (com.google.protobuf.InvalidProtocolBufferException ipbe) { log.error("Was expecting a Payment on the socket but saw something else"); ipbe.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { // Release resources log.debug("Flush then close client socket..."); socket.getOutputStream().flush(); socket.close(); } } else { log.debug("Could not find a Content-Length in the header so not reading payment"); } return true; } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } } }