From d74c175eb9472ef932da52b21631200418fa0efd Mon Sep 17 00:00:00 2001 From: Ru Uuy Date: Sat, 11 Jan 2014 19:58:19 +0100 Subject: [PATCH] Send rewards directly to Btc, Coinbase fallback Users can specify a 'Send-Bitcoin-reward: '-line in the commit message, containing the address they want to get the payment delivered to. This will be used as default. In the case the line is not supplied, money is sent to coinbase e-mail. --- README.md | 16 ++++ .../bithub/client/CoinbaseClient.java | 9 ++- .../bithub/controllers/GithubController.java | 20 ++++- .../controllers/GithubControllerTest.java | 26 +++++- .../payloads/commit_send_to_line.json | 79 +++++++++++++++++++ 5 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/payloads/commit_send_to_line.json diff --git a/README.md b/README.md index 30330fd..f34b4f3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,22 @@ Opting Out If you'd like to opt out of receiving a payment, simply include the string "FREEBIE" somewhere in your commit message, and you will not receive BTC for that commit. +Payment processing +------------------ + +By default, you need a coinbase account for recieving payment. However, if you add a `Send-bitcoin-reward: `-line to your commit, you recieve payment to that address instead. +You may consider adding a git hook that appends this line for you: + + $ git config --global user.reward-btc "your-btc-address" + $ cat > .git/hooks/commit-msg << "EOF" + #!/bin/sh + VAR=$(git config -z user.reward-btc) + if [ -n "$VAR" ]; then + awk "/Signed-off-by:.*/ && c == 0 {c = 1; print \"Send-bitcoin-reward: $VAR\"}; {print} END{if(c == 0) {print \"Send-bitcoin-reward: $VAR\"}}" $1 > "$1".tmp + mv "$1".tmp $1 + fi + EOF + $ chmod +x .git/hooks/commit-msg Building ------------- diff --git a/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java b/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java index aeedbbc..b7ed0f0 100644 --- a/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java +++ b/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java @@ -85,7 +85,7 @@ public BigDecimal getExchangeRate() throws IOException { } } - public void sendPayment(Author author, BigDecimal amount, String url) + public void sendPayment(Author author, String overwritingBtcAddress, BigDecimal amount, String url) throws TransferFailedException { try { @@ -95,7 +95,12 @@ public void sendPayment(Author author, BigDecimal amount, String url) String note = "Commit payment:\n__" + author.getUsername() + "__ " + url; - BitcoinTransaction transaction = new BitcoinTransaction(author.getEmail(), + String recipient = author.getEmail(); + if (overwritingBtcAddress != null && overwritingBtcAddress != "") { + recipient = overwritingBtcAddress; + } + + BitcoinTransaction transaction = new BitcoinTransaction(recipient, amount.toPlainString(), note); diff --git a/src/main/java/org/whispersystems/bithub/controllers/GithubController.java b/src/main/java/org/whispersystems/bithub/controllers/GithubController.java index 085a593..dca536e 100644 --- a/src/main/java/org/whispersystems/bithub/controllers/GithubController.java +++ b/src/main/java/org/whispersystems/bithub/controllers/GithubController.java @@ -51,6 +51,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Handles incoming API calls from GitHub. These are currently only @@ -125,12 +127,19 @@ public void handleCommits(@Auth Authentication auth, private void sendPaymentsFor(Repository repository, List commits, BigDecimal balance, BigDecimal exchangeRate) { + Pattern addressPattern = Pattern.compile("^Send-Bitcoin-reward: (.*)$", Pattern.MULTILINE); + for (Commit commit : commits) { try { BigDecimal payout = balance.multiply(payoutRate); if (isViablePaymentAmount(payout)) { - coinbaseClient.sendPayment(commit.getAuthor(), payout, commit.getUrl()); + String authorBtcAddress = getHeaderBtcAddress(commit.getMessage(), addressPattern); + if (authorBtcAddress != null) { + logger.info("Retrieved btc address from commit message: " + authorBtcAddress + "."); + } + coinbaseClient.sendPayment(commit.getAuthor(), authorBtcAddress, payout, + commit.getUrl()); } balance = balance.subtract(payout); @@ -181,6 +190,15 @@ private boolean isViableMessage(String message, String defaultMode) { (message.contains("MONEYMONEY") && defaultMode.equals("FREEBIE")); } + private String getHeaderBtcAddress(String message, Pattern pattern) { + Matcher addressMatcher = pattern.matcher(message); + String btcAddress = null; + while (addressMatcher.find()) { + btcAddress = addressMatcher.group(1); + } + return btcAddress; + } + private boolean isViablePaymentAmount(BigDecimal payment) { return payment.compareTo(new BigDecimal(0)) == 1; } diff --git a/src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java b/src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java index 8de96cc..30f7c75 100644 --- a/src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java +++ b/src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java @@ -162,6 +162,24 @@ public void testOptOutCommit() throws Exception, TransferFailedException { .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), + anyString(), + any(BigDecimal.class), + anyString()); + } + + @Test + public void testCommitSendToLine() throws Exception, TransferFailedException { + String payloadValue = payload("/payloads/commit_send_to_line.json"); + MultivaluedMapImpl post = new MultivaluedMapImpl(); + post.add("payload", payloadValue); + ClientResponse response = client().resource("/v1/github/commits/") + .header("X-Forwarded-For", "192.30.252.1") + .header("Authorization", authString) + .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) + .post(ClientResponse.class, post); + + verify(coinbaseClient).sendPayment(any(Author.class), + eq("1PRmBDjTcgjR13FPMQ3m4fLhTxo3s4tCkg"), any(BigDecimal.class), anyString()); } @@ -178,6 +196,7 @@ public void testValidCommit() throws Exception, TransferFailedException { .post(ClientResponse.class, post); verify(coinbaseClient).sendPayment(any(Author.class), + anyString(), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @@ -194,6 +213,7 @@ public void testNonMaster() throws Exception, TransferFailedException { .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), + anyString(), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @@ -209,9 +229,9 @@ public void testValidMultipleCommitsMultipleAuthors() throws Exception, Transfer .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); - verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))), + verify(coinbaseClient, times(1)).sendPayment(any(Author.class), anyString(), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); - verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.subtract(BALANCE.multiply(new BigDecimal(0.02))) + verify(coinbaseClient, times(1)).sendPayment(any(Author.class), anyString(), eq(BALANCE.subtract(BALANCE.multiply(new BigDecimal(0.02))) .multiply(new BigDecimal(0.02))), anyString()); } @@ -227,6 +247,7 @@ public void testOptInCommit() throws Exception, TransferFailedException { .post(ClientResponse.class, post); verify(coinbaseClient).sendPayment(any(Author.class), + anyString(), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @@ -243,6 +264,7 @@ public void testNoOptInCommit() throws Exception, TransferFailedException { .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), + anyString(), any(BigDecimal.class), anyString()); } diff --git a/src/test/resources/payloads/commit_send_to_line.json b/src/test/resources/payloads/commit_send_to_line.json new file mode 100644 index 0000000..5db33f2 --- /dev/null +++ b/src/test/resources/payloads/commit_send_to_line.json @@ -0,0 +1,79 @@ +{ + "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f", + "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1", + "commits": [ + { + "added": [], + "author": { + "email": "moxie@thoughtcrime.org", + "name": "Moxie Marlinspike", + "username": "moxie0" + }, + "committer": { + "email": "moxie@thoughtcrime.org", + "name": "Moxie Marlinspike", + "username": "moxie0" + }, + "distinct": true, + "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5", + "message": "Update path\n\nsome-description\nSend-Bitcoin-reward: 1PRmBDjTcgjR13FPMQ3m4fLhTxo3s4tCkg", + "modified": [ + "README.md" + ], + "removed": [], + "timestamp": "2013-12-14T11:42:28-08:00", + "url": "https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5" + }], + "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32", + "created": false, + "deleted": false, + "forced": false, + "head_commit": { + "added": [], + "author": { + "email": "moxie@thoughtcrime.org", + "name": "Moxie Marlinspike", + "username": "moxie0" + }, + "committer": { + "email": "moxie@thoughtcrime.org", + "name": "Moxie Marlinspike", + "username": "moxie0" + }, + "distinct": true, + "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f", + "message": "Merge branch 'master' of github.com:moxie0/tempt", + "modified": [], + "removed": [], + "timestamp": "2013-12-14T11:42:44-08:00", + "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f" + }, + "pusher": { + "email": "moxie@thoughtcrime.org", + "name": "moxie0" + }, + "ref": "refs/heads/master", + "repository": { + "created_at": 1386866024, + "description": "test", + "fork": false, + "forks": 1, + "has_downloads": true, + "has_issues": true, + "has_wiki": true, + "id": 15141344, + "master_branch": "master", + "name": "tempt", + "open_issues": 0, + "owner": { + "email": "moxie@thoughtcrime.org", + "name": "moxie0" + }, + "private": false, + "pushed_at": 1387050173, + "size": 216, + "stargazers": 1, + "url": "https://github.com/moxie0/test", + "watchers": 1 + } +}