diff --git a/bin/controller/BitPay.class b/bin/controller/BitPay.class new file mode 100644 index 00000000..f6495378 Binary files /dev/null and b/bin/controller/BitPay.class differ diff --git a/bin/controller/BitPayException.class b/bin/controller/BitPayException.class new file mode 100644 index 00000000..52306aa7 Binary files /dev/null and b/bin/controller/BitPayException.class differ diff --git a/bin/controller/KeyUtils.class b/bin/controller/KeyUtils.class new file mode 100644 index 00000000..d3385598 Binary files /dev/null and b/bin/controller/KeyUtils.class differ diff --git a/bin/model/Invoice.class b/bin/model/Invoice.class new file mode 100644 index 00000000..e7b26c9d Binary files /dev/null and b/bin/model/Invoice.class differ diff --git a/bin/model/InvoicePaymentUrls.class b/bin/model/InvoicePaymentUrls.class new file mode 100644 index 00000000..ab2e4452 Binary files /dev/null and b/bin/model/InvoicePaymentUrls.class differ diff --git a/bin/model/InvoiceTransaction.class b/bin/model/InvoiceTransaction.class new file mode 100644 index 00000000..39c629a0 Binary files /dev/null and b/bin/model/InvoiceTransaction.class differ diff --git a/bin/model/Policy.class b/bin/model/Policy.class new file mode 100644 index 00000000..002a42c8 Binary files /dev/null and b/bin/model/Policy.class differ diff --git a/bin/model/Rate.class b/bin/model/Rate.class new file mode 100644 index 00000000..36156cc5 Binary files /dev/null and b/bin/model/Rate.class differ diff --git a/bin/model/Rates.class b/bin/model/Rates.class new file mode 100644 index 00000000..661aa5f9 Binary files /dev/null and b/bin/model/Rates.class differ diff --git a/bin/model/Token.class b/bin/model/Token.class new file mode 100644 index 00000000..c04bc6d2 Binary files /dev/null and b/bin/model/Token.class differ diff --git a/bin/test/BitPayTest.class b/bin/test/BitPayTest.class new file mode 100644 index 00000000..f1f6bdf4 Binary files /dev/null and b/bin/test/BitPayTest.class differ diff --git a/bin/test/BitPayTest2.class b/bin/test/BitPayTest2.class new file mode 100644 index 00000000..cdd692fb Binary files /dev/null and b/bin/test/BitPayTest2.class differ diff --git a/lib/bitcoinj-0.12-SNAPSHOT.jar b/lib/bitcoinj-0.12-SNAPSHOT.jar new file mode 100644 index 00000000..2aaa3e6d Binary files /dev/null and b/lib/bitcoinj-0.12-SNAPSHOT.jar differ diff --git a/lib/com.google.common_1.0.0.201004262004.jar b/lib/com.google.common_1.0.0.201004262004.jar new file mode 100755 index 00000000..1d249463 Binary files /dev/null and b/lib/com.google.common_1.0.0.201004262004.jar differ diff --git a/lib/commons-codec-1.6.jar b/lib/commons-codec-1.6.jar new file mode 100644 index 00000000..ee1bc49a Binary files /dev/null and b/lib/commons-codec-1.6.jar differ diff --git a/lib/commons-logging-1.1.3.jar b/lib/commons-logging-1.1.3.jar new file mode 100644 index 00000000..ab512540 Binary files /dev/null and b/lib/commons-logging-1.1.3.jar differ diff --git a/lib/core-1.51.0.0.jar b/lib/core-1.51.0.0.jar new file mode 100644 index 00000000..60b24ddd Binary files /dev/null and b/lib/core-1.51.0.0.jar differ diff --git a/lib/fluent-hc-4.3.1.jar b/lib/fluent-hc-4.3.1.jar new file mode 100644 index 00000000..d0e9e868 Binary files /dev/null and b/lib/fluent-hc-4.3.1.jar differ diff --git a/lib/httpclient-4.3.1.jar b/lib/httpclient-4.3.1.jar new file mode 100644 index 00000000..9b47364a Binary files /dev/null and b/lib/httpclient-4.3.1.jar differ diff --git a/lib/httpclient-cache-4.3.1.jar b/lib/httpclient-cache-4.3.1.jar new file mode 100644 index 00000000..52789a24 Binary files /dev/null and b/lib/httpclient-cache-4.3.1.jar differ diff --git a/lib/httpcore-4.3.jar b/lib/httpcore-4.3.jar new file mode 100644 index 00000000..e5da4578 Binary files /dev/null and b/lib/httpcore-4.3.jar differ diff --git a/lib/httpmime-4.3.1.jar b/lib/httpmime-4.3.1.jar new file mode 100644 index 00000000..f6ee60e2 Binary files /dev/null and b/lib/httpmime-4.3.1.jar differ diff --git a/lib/jackson-annotations-2.4.0.jar b/lib/jackson-annotations-2.4.0.jar new file mode 100644 index 00000000..0b555590 Binary files /dev/null and b/lib/jackson-annotations-2.4.0.jar differ diff --git a/lib/jackson-core-2.4.2.jar b/lib/jackson-core-2.4.2.jar new file mode 100644 index 00000000..fad6f9b2 Binary files /dev/null and b/lib/jackson-core-2.4.2.jar differ diff --git a/lib/jackson-databind-2.4.2.jar b/lib/jackson-databind-2.4.2.jar new file mode 100644 index 00000000..ea95c53a Binary files /dev/null and b/lib/jackson-databind-2.4.2.jar differ diff --git a/lib/slf4j-api-1.7.7.jar b/lib/slf4j-api-1.7.7.jar new file mode 100644 index 00000000..b28e220b Binary files /dev/null and b/lib/slf4j-api-1.7.7.jar differ diff --git a/lib/slf4j-simple-1.7.7.jar b/lib/slf4j-simple-1.7.7.jar new file mode 100644 index 00000000..2387c438 Binary files /dev/null and b/lib/slf4j-simple-1.7.7.jar differ diff --git a/src/controller/BitPay.java b/src/controller/BitPay.java new file mode 100644 index 00000000..1a981e51 --- /dev/null +++ b/src/controller/BitPay.java @@ -0,0 +1,520 @@ +package controller; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Date; +import java.util.Hashtable; +import java.util.List; + +import model.Invoice; +import model.Rate; +import model.Rates; +import model.Token; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.ParseException; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.bitcoin.core.ECKey; + +public class BitPay { + + private static final String BITPAY_API_VERSION = "2.0.0"; + private static final String BITPAY_PLUGIN_INFO = "BitPay Java Client " + BITPAY_API_VERSION; + private static final String BITPAY_URL = "https://bitpay.com/"; + + public static final String FACADE_PAYROLL = "payroll"; + public static final String FACADE_POS = "pos"; + public static final String FACADE_MERCHANT = "merchant"; + public static final String FACADE_USER = "user"; + + private HttpClient _httpClient = null; + private String _baseUrl = BITPAY_URL; + private ECKey _ecKey = null; + private String _identity = ""; + private long _nonce = new Date().getTime(); + private boolean _disableNonce = false; + private String _clientName = ""; + private Hashtable _tokenCache; // {facade, token} + + /** + * Constructor for use if the keys and SIN are managed by this library. + * @param clientName - The label for this client. + * @param envUrl - The target server URL. + * @throws BitPayException + */ + public BitPay(String clientName, String envUrl) throws BitPayException + { + if (clientName.equals(BITPAY_PLUGIN_INFO)) + { + try { + clientName += " on " + java.net.InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + clientName += " on unknown host"; + } + } + // Eliminate special characters from the client name (used as a token label). Trim to 60 chars. + _clientName = clientName.replaceAll("[^a-zA-Z0-9_ ]", "_"); + if (_clientName.length() > 60) + { + _clientName = _clientName.substring(0, 60); + } + + _baseUrl = envUrl; + _httpClient = HttpClientBuilder.create().build(); + + try { + this.initKeys(); + } catch (IOException e) { + throw new BitPayException("Error: failed to intialize public/private key pair\n" + e.getMessage()); + } + this.deriveIdentity(); + this.tryGetAccessTokens(); + } + + public BitPay(String clientName) throws BitPayException + { + this(clientName, BITPAY_URL); + } + + public BitPay() throws BitPayException + { + this(BITPAY_PLUGIN_INFO, BITPAY_URL); + } + + /** + * Constructor for use if the keys and SIN were derived external to this library. + * @param ecKey - An elliptical curve key. + * @param clientName - The label for this client. + * @param envUrl - The target server URL. + * @throws BitPayException + */ + public BitPay(ECKey ecKey, String clientName, String envUrl) throws BitPayException + { + _ecKey = ecKey; + this.deriveIdentity(); + _baseUrl = envUrl; + _httpClient = HttpClientBuilder.create().build(); + this.tryGetAccessTokens(); + } + + public String getIdentity() + { + return _identity; + } + + public boolean getDisableNonce() + { + return _disableNonce; + } + + public void setDisableNonce(boolean value) + { + _disableNonce = value; + } + + public void authorizeClient(String pairingCode) throws BitPayException + { + Token token = new Token(); + token.setId(_identity); + token.setGuid(this.getGuid()); + token.setNonce(getNextNonce()); + token.setPairingCode(pairingCode); + token.setLabel(_clientName); + + ObjectMapper mapper = new ObjectMapper(); + String json; + try { + json = mapper.writeValueAsString(token); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to serialize Token object : " + e.getMessage()); + } + HttpResponse response = this.post("tokens", json); + + List tokens; + try { + tokens = Arrays.asList(mapper.readValue(this.responseToJsonString(response), Token[].class)); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Tokens) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Tokens) : " + e.getMessage()); + } + for (Token t : tokens) + { + _tokenCache.put(t.getFacade(), t.getValue()); + } + } + + public String requestClientAuthorization(String facade) throws BitPayException + { + Token token = new Token(); + token.setId(_identity); + token.setGuid(this.getGuid()); + token.setNonce(getNextNonce()); + token.setFacade(facade); + token.setCount(1); + token.setLabel(_clientName); + + ObjectMapper mapper = new ObjectMapper(); + String json; + try { + json = mapper.writeValueAsString(token); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to serialize Token object : " + e.getMessage()); + } + HttpResponse response = this.post("tokens", json); + + List tokens; + try { + tokens = Arrays.asList(mapper.readValue(this.responseToJsonString(response), Token[].class)); + // Expecting a single token resource. + if (tokens.size() != 1) + { + throw new BitPayException("Error - failed to get token resource; expected 1 token, got " + tokens.size()); + } + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Tokens) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Tokens) : " + e.getMessage()); + } + _tokenCache.put(tokens.get(0).getFacade(), tokens.get(0).getValue()); + return tokens.get(0).getPairingCode(); + } + + public boolean clientIsAuthorized(String facade) + { + return _tokenCache.containsKey(facade); + } + + public Invoice createInvoice(Invoice invoice, String facade) throws BitPayException + { + invoice.setToken(this.getAccessToken(facade)); + invoice.setGuid(this.getGuid()); + invoice.setNonce(this.getNextNonce()); + + ObjectMapper mapper = new ObjectMapper(); + String json; + try { + json = mapper.writeValueAsString(invoice); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to serialize Invoice object : " + e.getMessage()); + } + + HttpResponse response = this.postWithSignature("invoices", json); + + try { + invoice = mapper.readerForUpdating(invoice).readValue(this.responseToJsonString(response)); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoice) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoice) : " + e.getMessage()); + } + return invoice; + } + + public Invoice createInvoice(Invoice invoice) throws BitPayException + { + return this.createInvoice(invoice, FACADE_POS); + } + + public Invoice getInvoice(String invoiceId) throws BitPayException + { + HttpResponse response = this.get("invoices/" + invoiceId); + Invoice i; + try { + i = new ObjectMapper().readValue(this.responseToJsonString(response), Invoice.class); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoice) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoice) : " + e.getMessage()); + } + return i; + } + + public List getInvoices(String dateStart, String dateEnd) throws BitPayException + { + Hashtable parameters = this.getParams(); + parameters.put("token", this.getAccessToken(FACADE_MERCHANT)); + parameters.put("dateStart", dateStart); + parameters.put("dateEnd", dateEnd); + HttpResponse response = this.get("invoices", parameters); + + List invoices; + try { + invoices = Arrays.asList(new ObjectMapper().readValue(this.responseToJsonString(response), Invoice[].class)); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoices) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Invoices) : " + e.getMessage()); + } + return invoices; + } + + public Rates getRates() throws BitPayException + { + HttpResponse response = this.get("rates"); + + List rates; + try { + rates = Arrays.asList(new ObjectMapper().readValue(this.responseToJsonString(response), Rate[].class)); + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Rates) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Rates) : " + e.getMessage()); + } + return new Rates(rates, this); + } + + private void initKeys() throws IOException + { + if (KeyUtils.privateKeyExists()) + { + _ecKey = KeyUtils.loadEcKey(); + + // Alternatively, load your private key from a location you specify. + // _ecKey = KeyUtils.createEcKeyFromHexStringFile("C:\\Users\\key.priv"); + } + else + { + _ecKey = KeyUtils.createEcKey(); + KeyUtils.saveEcKey(_ecKey); + } + } + + private void deriveIdentity() throws IllegalArgumentException + { + // Identity in this implementation is defined to be the SIN. + _identity = KeyUtils.deriveSIN(_ecKey); + } + + private long getNextNonce() + { + if (!getDisableNonce()) + { + _nonce++; + } + else + { + _nonce = 0; // Nonce must be 0 when it has been disabled (0 value prevents serialization) + } + return _nonce; + } + + private Hashtable responseToTokenCache(HttpResponse response) throws BitPayException + { + // The response is expected to be an array of key/value pairs (facade name = token). + String json = this.responseToJsonString(response); + + try { + json = json.replaceAll("\\[", ""); // Remove the array context so we can map to a hashtable. + json = json.replaceAll("\\]", ""); + if (json.length() > 0) + { + _tokenCache = new ObjectMapper().readValue(json, new TypeReference>(){}); + } + } catch (JsonProcessingException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Token array) : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to deserialize BitPay server response (Token array) : " + e.getMessage()); + } + return _tokenCache; + } + + private void clearAccessTokenCache() + { + _tokenCache = new Hashtable(); + } + + private boolean tryGetAccessTokens() throws BitPayException + { + // Attempt to get access tokens for this client identity. + try + { + // Success if at least one access token was returned. + return this.getAccessTokens() > 0; + } + catch (BitPayException ex) + { + // If the error states that the identity is invalid then this client has not been + // registered with the BitPay account. + if (ex.getMessage().contains("Unauthorized sin")) + { + this.clearAccessTokenCache(); + return false; + } + else + { + // Propagate all other errors. + throw ex; + } + } + } + + private int getAccessTokens() throws BitPayException + { + this.clearAccessTokenCache(); + Hashtable parameters = this.getParams(); + HttpResponse response = this.get("tokens", parameters); + _tokenCache = responseToTokenCache(response); + return _tokenCache.size(); + } + + private String getAccessToken(String facade) throws BitPayException + { + if (!_tokenCache.containsKey(facade)) + { + throw new BitPayException("Error: You do not have access to facade: " + facade); + } + return _tokenCache.get(facade); + } + + private Hashtable getParams() + { + Hashtable params = new Hashtable(); + params.put("nonce", getNextNonce() + ""); + return params; + } + + private HttpResponse get(String uri, Hashtable parameters) throws BitPayException + { + try { + + String fullURL = _baseUrl + uri; + HttpGet get = new HttpGet(fullURL); + if (parameters != null) + { + fullURL += "?"; + for (String key : parameters.keySet()) { + fullURL += key + "=" + parameters.get(key) + "&"; + } + fullURL = fullURL.substring(0,fullURL.length() - 1); + get.setURI(new URI(fullURL)); + String signature = KeyUtils.sign(_ecKey, fullURL); + get.addHeader("x-bitpay-plugin-info", BITPAY_PLUGIN_INFO); + get.addHeader("x-accept-version", BITPAY_API_VERSION); + get.addHeader("x-signature", signature); + get.addHeader("x-identity", KeyUtils.bytesToHex(_ecKey.getPubKey())); + } + return _httpClient.execute(get); + + } catch (URISyntaxException e) { + throw new BitPayException("Error: GET failed\n" + e.getMessage()); + } catch (ClientProtocolException e) { + throw new BitPayException("Error: GET failed\n" + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error: GET failed\n" + e.getMessage()); + } + } + + private HttpResponse get(String uri) throws BitPayException + { + return this.get(uri, null); + } + + private HttpResponse post(String uri, String json, boolean signatureRequired) throws BitPayException + { + try { + HttpPost post = new HttpPost(_baseUrl + uri); + post.setEntity(new ByteArrayEntity(json.toString().getBytes("UTF8"))); + if (signatureRequired) { + String signature = KeyUtils.sign(_ecKey, _baseUrl + uri + json); + post.addHeader("x-signature", signature); + post.addHeader("x-identity", KeyUtils.bytesToHex(_ecKey.getPubKey())); + } + post.addHeader("x-accept-version", BITPAY_API_VERSION); + post.addHeader("x-bitpay-plugin-info", BITPAY_PLUGIN_INFO); + post.addHeader("Content-Type","application/json"); + return _httpClient.execute(post); + + } catch (UnsupportedEncodingException e) { + throw new BitPayException("Error: POST failed\n" + e.getMessage()); + } catch (ClientProtocolException e) { + throw new BitPayException("Error: POST failed\n" + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error: POST failed\n" + e.getMessage()); + } + } + + private HttpResponse post(String uri, String json) throws BitPayException + { + return this.post(uri, json, false); + } + + private HttpResponse postWithSignature(String uri, String json) throws BitPayException + { + return this.post(uri, json, true); + } + + private String responseToJsonString(HttpResponse response) throws BitPayException + { + if (response == null) + { + throw new BitPayException("Error: HTTP response is null"); + } + try { + // Get the JSON string from the response. + HttpEntity entity = response.getEntity(); + String jsonString; + jsonString = EntityUtils.toString(entity, "UTF-8"); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(jsonString); + + JsonNode node = rootNode.get("error"); + if (node != null) + { + throw new BitPayException("Error: " + node.asText()); + } + + node = rootNode.get("errors"); + if (node != null) + { + String message = "Multiple errors:"; + if (node.isArray()) { + for (final JsonNode errorNode : node) { + message += "\n" + errorNode.asText(); + } + throw new BitPayException(message); + } + } + + node = rootNode.get("data"); + if (node != null) + { + jsonString = node.toString(); + } + + return jsonString; + + } catch (ParseException e) { + throw new BitPayException("Error - failed to retrieve HTTP response body : " + e.getMessage()); + } catch (JsonMappingException e) { + throw new BitPayException("Error - failed to parse json response to map : " + e.getMessage()); + } catch (IOException e) { + throw new BitPayException("Error - failed to retrieve HTTP response body : " + e.getMessage()); + } + } + + private String getGuid() + { + int Min = 0; + int Max = 99999999; + return Min + (int)(Math.random() * ((Max - Min) + 1)) + ""; + } +} diff --git a/src/controller/BitPayException.java b/src/controller/BitPayException.java new file mode 100644 index 00000000..6b315e5f --- /dev/null +++ b/src/controller/BitPayException.java @@ -0,0 +1,10 @@ +package controller; + +public class BitPayException extends Exception { + + public BitPayException(String message) { + super(message); + } + + private static final long serialVersionUID = 1L; +} diff --git a/src/controller/KeyUtils.java b/src/controller/KeyUtils.java new file mode 100644 index 00000000..77afc095 --- /dev/null +++ b/src/controller/KeyUtils.java @@ -0,0 +1,149 @@ +package controller; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigInteger; + +import com.google.bitcoin.core.Base58; +import com.google.bitcoin.core.ECKey; +import com.google.bitcoin.core.ECKey.ECDSASignature; +import com.google.bitcoin.core.Sha256Hash; +import com.google.bitcoin.core.Utils; + +public class KeyUtils { + + final private static char[] hexArray = "0123456789abcdef".toCharArray(); + final private static String PRIV_KEY_FILENAME = "bitpay_private.key"; + + public KeyUtils() {} + + public static boolean privateKeyExists() + { + return new File(PRIV_KEY_FILENAME).exists(); + } + + public static ECKey createEcKey() + { + //Default constructor uses SecureRandom numbers. + return new ECKey(); + } + + public static ECKey createEcKeyFromHexString(String privateKey) + { + BigInteger privKey = new BigInteger(privateKey, 16); + ECKey key = new ECKey(privKey, null, true); + return key; + } + + /** + * Convenience method. + */ + public static ECKey createEcKeyFromHexStringFile(String privKeyFile) throws IOException + { + String privateKey = getKeyStringFromFile(privKeyFile); + return createEcKeyFromHexString(privateKey); + } + + public static ECKey loadEcKey() throws IOException + { + FileInputStream fileInputStream = null; + File file = new File(PRIV_KEY_FILENAME); + byte[] bytes = new byte[(int) file.length()]; + fileInputStream = new FileInputStream(file); + fileInputStream.read(bytes); + fileInputStream.close(); + ECKey key = ECKey.fromASN1(bytes); + return key; + } + + public static String getKeyStringFromFile(String filename) throws IOException + { + BufferedReader br; + br = new BufferedReader(new FileReader(filename)); + String line = br.readLine(); + br.close(); + return line; + } + + public static void saveEcKey(ECKey ecKey) throws IOException + { + byte[] bytes = ecKey.toASN1(); + FileOutputStream output = new FileOutputStream(new File(PRIV_KEY_FILENAME)); + output.write(bytes); + output.close(); + } + + public static String deriveSIN(ECKey ecKey) throws IllegalArgumentException + { + // Get sha256 hash and then the RIPEMD-160 hash of the public key (this call gets the result in one step). + byte[] pubKeyHash = ecKey.getPubKeyHash(); + + // Convert binary pubKeyHash, SINtype and version to Hex + String version = "0F"; + String SINtype = "02"; + String pubKeyHashHex = bytesToHex(pubKeyHash); + + // Concatenate all three elements + String preSIN = version + SINtype + pubKeyHashHex; + + // Convert the hex string back to binary and double sha256 hash it leaving in binary both times + byte[] preSINbyte = hexToBytes(preSIN); + byte[] hash2Bytes = Utils.doubleDigest(preSINbyte); + + // Convert back to hex and take first four bytes + String hashString = bytesToHex(hash2Bytes); + String first4Bytes = hashString.substring(0, 8); + + // Append first four bytes to fully appended SIN string + String unencoded = preSIN + first4Bytes; + byte[] unencodedBytes = new BigInteger(unencoded, 16).toByteArray(); + String encoded = Base58.encode(unencodedBytes); + + return encoded; + } + + public static String sign(ECKey key, String input) { + byte[] data = input.getBytes(); + Sha256Hash hash = Sha256Hash.create(data); + ECDSASignature sig = key.sign(hash, null); + byte[] bytes = sig.encodeToDER(); + return bytesToHex(bytes); + } + + private static int getHexVal(char hex) + { + int val = (int)hex; + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } + + public static byte[] hexToBytes(String hex) throws IllegalArgumentException + { + char[] hexArray = hex.toCharArray(); + + if (hex.length() % 2 == 1) + { + throw new IllegalArgumentException("Error: The binary key cannot have an odd number of digits"); + } + byte[] arr = new byte[hex.length() >> 1]; + + for (int i = 0; i < hex.length() >> 1; ++i) + { + arr[i] = (byte)((getHexVal(hexArray[i << 1]) << 4) + (getHexVal(hexArray[(i << 1) + 1]))); + } + return arr; + } + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/src/model/Invoice.java b/src/model/Invoice.java new file mode 100644 index 00000000..16be16a4 --- /dev/null +++ b/src/model/Invoice.java @@ -0,0 +1,488 @@ +package model; + +import java.util.Hashtable; +import java.util.List; + +import com.fasterxml.jackson.annotation.*; + +import controller.BitPayException; + +public class Invoice { + + public static final String STATUS_NEW = "new"; + public static final String STATUS_PAID = "paid"; + public static final String STATUS_CONFIRMED = "confirmed"; + public static final String STATUS_COMPLETE = "complete"; + public static final String STATUS_INVALID = "invalid"; + public static final String EXSTATUS_FALSE = "false"; + public static final String EXSTATUS_PAID_OVER = "paidOver"; + public static final String EXSTATUS_PAID_PARTIAL = "paidPartial"; + + private Long _nonce = 0L; + private String _guid = ""; + private String _token = ""; + + private Double _price; + private String _currency; + private String _posData = ""; + private String _notificationURL = ""; + private String _transactionSpeed = ""; + private boolean _fullNotifications = false; + private String _notificationEmail = ""; + private String _redirectURL = ""; + private String _orderId = ""; + private String _itemDesc = ""; + private String _itemCode = ""; + private boolean _physical = false; + + private String _buyerName = ""; + private String _buyerAddress1 = ""; + private String _buyerAddress2 = ""; + private String _buyerCity = ""; + private String _buyerState = ""; + private String _buyerZip = ""; + private String _buyerCountry = ""; + private String _buyerEmail = ""; + private String _buyerPhone = ""; + + private String _id; + private String _url; + private String _status; + private String _btcPrice; + private String _invoiceTime; + private long _expirationTime; + private long _currentTime; + private String _btcPaid; + private String _btcDue; + private List _transactions; + private String _rate; + private Hashtable _exRates; + private String _exceptionStatus; + private InvoicePaymentUrls _paymentUrls; + + public Invoice() {} + + public Invoice(Double _price, String _currency) + { + this._price = _price; + this._currency = _currency; + } + + // API fields + // + + @JsonProperty("guid") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getGuid() { + return _guid; + } + + @JsonProperty("guid") + public void setGuid(String _guid) { + this._guid = _guid; + } + + @JsonProperty("nonce") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public Long getNonce() { + return _nonce; + } + + @JsonProperty("nonce") + public void setNonce(Long _nonce) { + this._nonce = _nonce; + } + + @JsonProperty("token") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getToken() { + return _token; + } + + @JsonProperty("token") + public void setToken(String _token) { + this._token = _token; + } + + // Required fields + // + + @JsonProperty("price") + public Double getPrice() { + return _price; + } + + @JsonProperty("price") + public void setPrice(Double _price) { + this._price = _price; + } + + @JsonProperty("currency") + public String getCurrency() { + return _currency; + } + + @JsonProperty("currency") + public void setCurrency(String _currency) throws BitPayException { + if (_currency.length() != 3) + { + throw new BitPayException("Error: currency code must be exactly three characters"); + } + this._currency = _currency; + } + + // Optional fields + // + + @JsonProperty("orderId") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getOrderId() { + return _orderId; + } + + @JsonProperty("orderId") + public void setOrderId(String _orderId) { + this._orderId = _orderId; + } + + @JsonProperty("itemDesc") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getItemDesc() { + return _itemDesc; + } + + @JsonProperty("itemDesc") + public void setItemDesc(String _itemDesc) { + this._itemDesc = _itemDesc; + } + + @JsonProperty("itemCode") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getItemCode() { + return _itemCode; + } + + @JsonProperty("itemCode") + public void setItemCode(String _itemCode) { + this._itemCode = _itemCode; + } + + @JsonProperty("posData") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getPosData() { + return _posData; + } + + @JsonProperty("posData") + public void setPosData(String _posData) { + this._posData = _posData; + } + + @JsonProperty("notificationURL") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getNotificationURL() { + return _notificationURL; + } + + @JsonProperty("notificationURL") + public void setNotificationURL(String _notificationURL) { + this._notificationURL = _notificationURL; + } + + @JsonProperty("transactionSpeed") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getTransactionSpeed() { + return _transactionSpeed; + } + + @JsonProperty("transactionSpeed") + public void setTransactionSpeed(String _transactionSpeed) { + this._transactionSpeed = _transactionSpeed; + } + + @JsonProperty("fullNotifications") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public boolean getFullNotifications() { + return _fullNotifications; + } + + @JsonProperty("fullNotifications") + public void setFullNotifications(boolean _fullNotifications) { + this._fullNotifications = _fullNotifications; + } + + @JsonProperty("notificationEmail") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getNotificationEmail() { + return _notificationEmail; + } + + @JsonProperty("notificationEmail") + public void setNotificationEmail(String _notificationEmail) { + this._notificationEmail = _notificationEmail; + } + + @JsonProperty("redirectURL") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getRedirectURL() { + return _redirectURL; + } + + @JsonProperty("redirectURL") + public void setRedirectURL(String _redirectURL) { + this._redirectURL = _redirectURL; + } + + @JsonProperty("physical") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public boolean getPhysical() { + return _physical; + } + + @JsonProperty("physical") + public void setPhysical(boolean _physical) { + this._physical = _physical; + } + + @JsonProperty("buyerName") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerName() { + return _buyerName; + } + + @JsonProperty("buyerName") + public void setBuyerName(String _buyerName) { + this._buyerName = _buyerName; + } + + @JsonProperty("buyerAddress1") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerAddress1() { + return _buyerAddress1; + } + + @JsonProperty("buyerAddress1") + public void setBuyerAddress1(String _buyerAddress1) { + this._buyerAddress1 = _buyerAddress1; + } + + @JsonProperty("buyerAddress2") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerAddress2() { + return _buyerAddress2; + } + + @JsonProperty("buyerAddress2") + public void setBuyerAddress2(String _buyerAddress2) { + this._buyerAddress2 = _buyerAddress2; + } + + @JsonProperty("buyerCity") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerCity() { + return _buyerCity; + } + + @JsonProperty("buyerCity") + public void setBuyerCity(String _buyerCity) { + this._buyerCity = _buyerCity; + } + + @JsonProperty("buyerState") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerState() { + return _buyerState; + } + + @JsonProperty("buyerState") + public void setBuyerState(String _buyerState) { + this._buyerState = _buyerState; + } + + @JsonProperty("buyerZip") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerZip() { + return _buyerZip; + } + + @JsonProperty("buyerZip") + public void setBuyerZip(String _buyerZip) { + this._buyerZip = _buyerZip; + } + + @JsonProperty("buyerCountry") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerCountry() { + return _buyerCountry; + } + + @JsonProperty("buyerCountry") + public void setBuyerCountry(String _buyerCountry) { + this._buyerCountry = _buyerCountry; + } + + @JsonProperty("buyerEmail") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerEmail() { + return _buyerEmail; + } + + @JsonProperty("buyerEmail") + public void setBuyerEmail(String _buyerEmail) { + this._buyerEmail = _buyerEmail; + } + + @JsonProperty("buyerPhone") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getBuyerPhone() { + return _buyerPhone; + } + + @JsonProperty("buyerPhone") + public void setBuyerPhone(String _buyerPhone) { + this._buyerPhone = _buyerPhone; + } + + // Response fields + // + + @JsonIgnore + public String getId() { + return _id; + } + + @JsonProperty("id") + public void setId(String _id) { + this._id = _id; + } + + @JsonIgnore + public String getUrl() { + return _url; + } + + @JsonProperty("url") + public void setUrl(String _url) { + this._url = _url; + } + + @JsonIgnore + public String getStatus() { + return _status; + } + + @JsonProperty("status") + public void setStatus(String _status) { + this._status = _status; + } + + @JsonIgnore + public String getBtcPrice() { + return _btcPrice; + } + + @JsonProperty("btcPrice") + public void setBtcPrice(String _btcPrice) { + this._btcPrice = _btcPrice; + } + + @JsonIgnore + public String getInvoiceTime() { + return _invoiceTime; + } + + @JsonProperty("invoiceTime") + public void setInvoiceTime(String _invoiceTime) { + this._invoiceTime = _invoiceTime; + } + + @JsonIgnore + public long getExpirationTime() { + return _expirationTime; + } + + @JsonProperty("expirationTime") + public void setExpirationTime(long _expirationTime) { + this._expirationTime = _expirationTime; + } + + @JsonIgnore + public long getCurrentTime() { + return _currentTime; + } + + @JsonProperty("currentTime") + public void setCurrentTime(long _currentTime) { + this._currentTime = _currentTime; + } + + @JsonIgnore + public String getBtcPaid() { + return _btcPaid; + } + + @JsonProperty("btcPaid") + public void setBtcPaid(String _btcPaid) { + this._btcPaid = _btcPaid; + } + + @JsonIgnore + public String getBtcDue() { + return _btcDue; + } + + @JsonProperty("btcDue") + public void setBtcDue(String _btcDue) { + this._btcDue = _btcDue; + } + + @JsonIgnore + public List getTransactions() { + return _transactions; + } + + @JsonProperty("transactions") + public void setTransactions(List _transactions) { + this._transactions = _transactions; + } + + @JsonIgnore + public String getRate() { + return _rate; + } + + @JsonProperty("rate") + public void setRate(String _rate) { + this._rate = _rate; + } + + @JsonIgnore + public Hashtable getExRates() { + return _exRates; + } + + @JsonProperty("exRates") + public void setExRates(Hashtable _exRates) { + this._exRates = _exRates; + } + + @JsonIgnore + public String getExceptionStatus() { + return _exceptionStatus; + } + + @JsonProperty("exceptionStatus") + public void setExceptionStatus(String _exceptionStatus) { + this._exceptionStatus = _exceptionStatus; + } + + @JsonIgnore + public InvoicePaymentUrls getPaymentUrls() { + return _paymentUrls; + } + + @JsonProperty("paymentUrls") + public void setPaymentUrls(InvoicePaymentUrls _paymentUrls) { + this._paymentUrls = _paymentUrls; + } +} diff --git a/src/model/InvoicePaymentUrls.java b/src/model/InvoicePaymentUrls.java new file mode 100644 index 00000000..0aeeee86 --- /dev/null +++ b/src/model/InvoicePaymentUrls.java @@ -0,0 +1,54 @@ +package model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class InvoicePaymentUrls { + + private String _BIP21 = ""; + private String _BIP72 = ""; + private String _BIP72b = ""; + private String _BIP73 = ""; + + public InvoicePaymentUrls() {} + + @JsonIgnore + public String getBIP21() { + return _BIP21; + } + + @JsonProperty("BIP21") + public void setBIP21(String _BIP21) { + this._BIP21 = _BIP21; + } + + @JsonIgnore + public String getBIP72() { + return _BIP72; + } + + @JsonProperty("BIP72") + public void setBIP72(String _BIP72) { + this._BIP72 = _BIP72; + } + + @JsonIgnore + public String getBIP72b() { + return _BIP72b; + } + + @JsonProperty("BIP72b") + public void setBIP72b(String _BIP72b) { + this._BIP72b = _BIP72b; + } + + @JsonIgnore + public String getBIP73() { + return _BIP73; + } + + @JsonProperty("BIP73") + public void setBIP73(String _BIP73) { + this._BIP73 = _BIP73; + } +} diff --git a/src/model/InvoiceTransaction.java b/src/model/InvoiceTransaction.java new file mode 100644 index 00000000..4e49d0f3 --- /dev/null +++ b/src/model/InvoiceTransaction.java @@ -0,0 +1,43 @@ +package model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class InvoiceTransaction { + + private String _txid; + private String _type; + private double _amount; + + public InvoiceTransaction() {} + + @JsonIgnore + public String getTxid() { + return _txid; + } + + @JsonProperty("txid") + public void setTxid(String _txid) { + this._txid = _txid; + } + + @JsonIgnore + public String getType() { + return _type; + } + + @JsonProperty("type") + public void setType(String _type) { + this._type = _type; + } + + @JsonIgnore + public double getAmount() { + return _amount; + } + + @JsonProperty("amount") + public void setAmount(double _amount) { + this._amount = _amount; + } +} diff --git a/src/model/Policy.java b/src/model/Policy.java new file mode 100644 index 00000000..260c23fe --- /dev/null +++ b/src/model/Policy.java @@ -0,0 +1,51 @@ +package model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Policy { + + private String _policy; + private String _method; + private List _params; + + public Policy() {} + + @JsonIgnore + public String getPolicy() + { + return _policy; + } + + @JsonProperty("policy") + public void setPolicy(String _policy) + { + this._policy = _policy; + } + + @JsonIgnore + public String getMethod() + { + return _method; + } + + @JsonProperty("method") + public void setMethod(String _method) + { + this._method = _method; + } + + @JsonIgnore + public List getParams() + { + return _params; + } + + @JsonProperty("params") + public void setParams(List _params) + { + this._params = _params; + } +} diff --git a/src/model/Rate.java b/src/model/Rate.java new file mode 100644 index 00000000..856aa955 --- /dev/null +++ b/src/model/Rate.java @@ -0,0 +1,44 @@ +package model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Rate { + + private String _name; + private String _code; + private double _value; + + public Rate() {} + + @JsonIgnore + public String getName() { + return _name; + } + + @JsonProperty("name") + public void setName(String _name) { + this._name = _name; + } + + @JsonIgnore + public String getCode() { + return _code; + } + + @JsonProperty("code") + public void setCode(String _code) { + this._code = _code; + } + + @JsonIgnore + public double getValue() { + return _value; + } + + @JsonProperty("rate") + public void setValue(double _value) { + this._value = _value; + } + +} diff --git a/src/model/Rates.java b/src/model/Rates.java new file mode 100644 index 00000000..9a416c51 --- /dev/null +++ b/src/model/Rates.java @@ -0,0 +1,42 @@ +package model; + +import java.util.List; + +import controller.BitPay; +import controller.BitPayException; + +public class Rates { + + private BitPay _bp; + private List _rates; + + public Rates(List rates, BitPay bp) + { + _bp = bp; + _rates = rates; + } + + public List getRates() + { + return _rates; + } + + public void update() throws BitPayException + { + _rates = _bp.getRates().getRates(); + } + + public double getRate(String currencyCode) + { + double val = 0; + for (Rate rateObj : _rates) + { + if (rateObj.getCode().equals(currencyCode)) + { + val = rateObj.getValue(); + break; + } + } + return val; + } +} diff --git a/src/model/Token.java b/src/model/Token.java new file mode 100644 index 00000000..31f95163 --- /dev/null +++ b/src/model/Token.java @@ -0,0 +1,161 @@ +package model; + +import java.util.List; +import com.fasterxml.jackson.annotation.*; + +public class Token { + + private String _guid; + private long _nonce = 0; + private String _id = ""; + private String _pairingCode = ""; + private long _pairingExpiration; + private String _facade = ""; + private String _label = ""; + private int _count = 0; + private List _policies; + private String _resource; + private String _value; + private long _dateCreated; + + public Token() {} + + // API fields + // + + @JsonProperty("guid") + public String getGuid() { + return _guid; + } + + @JsonProperty("guid") + public void setGuid(String _guid) { + this._guid = _guid; + } + + @JsonProperty("nonce") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public long getNonce() { + return _nonce; + } + + @JsonProperty("nonce") + public void setNonce(long _nonce) { + this._nonce = _nonce; + } + + // Required fields + // + + @JsonProperty("id") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getId() { + return _id; + } + + @JsonProperty("id") + public void setId(String _id) { + this._id = _id; + } + + // Optional fields + // + + @JsonProperty("pairingCode") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getPairingCode() { + return _pairingCode; + } + + @JsonProperty("pairingCode") + public void setPairingCode(String _pairingCode) { + this._pairingCode = _pairingCode; + } + + @JsonProperty("facade") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getFacade() { + return _facade; + } + + @JsonProperty("facade") + public void setFacade(String _facade) { + this._facade = _facade; + } + + @JsonProperty("label") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public String getLabel() { + return _label; + } + + @JsonProperty("label") + public void setLabel(String _label) { + this._label = _label; + } + + @JsonProperty("count") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public int getCount() { + return _count; + } + + @JsonProperty("count") + public void setCount(int _count) { + this._count = _count; + } + + // Response fields + // + + @JsonIgnore + public long getPairingExpiration() { + return _pairingExpiration; + } + + @JsonProperty("pairingExpiration") + public void setPairingExpiration(long _pairingExpiration) { + this._pairingExpiration = _pairingExpiration; + } + + @JsonIgnore + public List getPolicies() { + return _policies; + } + + @JsonProperty("policies") + public void setPolicies(List _policies) { + this._policies = _policies; + } + + @JsonIgnore + public String getResource() { + return _resource; + } + + @JsonProperty("resource") + public void setResource(String _resource) { + this._resource = _resource; + } + + @JsonIgnore + public String getValue() { + return _value; + } + + @JsonProperty("token") + public void setValue(String _value) { + this._value = _value; + } + + @JsonIgnore + public long getDateCreated() { + return _dateCreated; + } + + @JsonProperty("dateCreated") + public void setDateCreated(long _dateCreated) { + this._dateCreated = _dateCreated; + } + +} diff --git a/src/test/BitPayTest.java b/src/test/BitPayTest.java new file mode 100644 index 00000000..df9a3058 --- /dev/null +++ b/src/test/BitPayTest.java @@ -0,0 +1,241 @@ +package test; + +import static org.junit.Assert.*; + +import java.util.List; + +import model.Invoice; +import model.Rate; +import model.Rates; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import controller.BitPay; +import controller.BitPayException; + +public class BitPayTest { + + private BitPay bitpay; + private Invoice basicInvoice; + private static double BTC_EPSILON = .000000001; + private static double EPSILON = .001; + + private static String pairingCode = "0u4pyWN"; + private static String clientName = "BitPay Java Library Tester"; + + @Before + public void setUp() throws Exception + { + // This scenario qualifies that this (test) client does not have merchant facade access. + clientName += " on " + java.net.InetAddress.getLocalHost(); + bitpay = new BitPay(clientName); + + if (!bitpay.clientIsAuthorized(BitPay.FACADE_POS)) + { + // Get POS facade authorization. + // Obtain a pairingCode from your BitPay account administrator. When the pairingCode + // is created by your administrator it is assigned a facade. To generate invoices a + // POS facade is required. + bitpay.authorizeClient(pairingCode); + } + } + + @After + public void tearDown() throws Exception + { + } + + @Test + public void testShouldGetInvoiceId() + { + Invoice invoice = new Invoice(50.0, "USD"); + try { + basicInvoice = bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(basicInvoice.getId()); + } + + @Test + public void testShouldGetInvoiceURL() + { + Invoice invoice = new Invoice(50.0, "USD"); + try { + basicInvoice = bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(basicInvoice.getUrl()); + } + + @Test + public void testShouldGetInvoiceStatus() + { + Invoice invoice = new Invoice(50.0, "USD"); + try { + basicInvoice = bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(Invoice.STATUS_NEW, basicInvoice.getStatus()); + } + + @Test + public void testShouldGetInvoiceBTCPrice() + { + Invoice invoice = new Invoice(50.0, "USD"); + try { + basicInvoice = bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(basicInvoice.getBtcPrice()); + } + + @Test + public void testShouldCreateInvoiceOneTenthBTC() + { + Invoice invoice = new Invoice(0.1, "BTC"); + try { + invoice = this.bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(0.1, invoice.getPrice(), BTC_EPSILON); + } + + @Test + public void testShouldCreateInvoice100USD() + { + Invoice invoice = new Invoice(100.0, "USD"); + try { + invoice = this.bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(100.0, invoice.getPrice(), EPSILON); + + } + + @Test + public void testShouldCreateInvoice100EUR() + { + Invoice invoice = new Invoice(100.0, "EUR"); + try { + invoice = this.bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(100.0, invoice.getPrice(), EPSILON); + } + + @Test + public void testShouldGetInvoice() + { + Invoice invoice = new Invoice(100.0, "EUR"); + Invoice retreivedInvoice = null; + try { + invoice = this.bitpay.createInvoice(invoice); + retreivedInvoice = this.bitpay.getInvoice(invoice.getId()); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(invoice.getId(), retreivedInvoice.getId()); + } + + @Test + public void testShouldCreateInvoiceWithAdditionalParams() + { + Invoice invoice = new Invoice(100.0, "USD"); + invoice.setBuyerName("Satoshi"); + invoice.setBuyerEmail("satoshi@bitpay.com"); + invoice.setFullNotifications(true); + invoice.setNotificationEmail("satoshi@bitpay.com"); + invoice.setPosData("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + try { + invoice = this.bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertEquals(Invoice.STATUS_NEW, invoice.getStatus()); + assertEquals(100.0, invoice.getPrice(), EPSILON); + assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", invoice.getPosData()); + assertEquals("Satoshi", invoice.getBuyerName()); + assertEquals("satoshi@bitpay.com", invoice.getBuyerEmail()); + assertEquals(true, invoice.getFullNotifications()); + assertEquals("satoshi@bitpay.com", invoice.getNotificationEmail()); + } + + @Test + public void testShouldGetExchangeRates() + { + Rates rates = null; + List rateList = null; + try { + rates = this.bitpay.getRates(); + rateList = rates.getRates(); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(rateList); + } + + @Test + public void testShouldGetEURExchangeRate() + { + Rates rates = null; + double rate = 0.0; + try { + rates = this.bitpay.getRates(); + rate = rates.getRate("EUR"); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertTrue(rate != 0); + } + + @Test + public void testShouldGetCNYExchangeRate() + { + Rates rates = null; + double rate = 0.0; + try { + rates = this.bitpay.getRates(); + rate = rates.getRate("CNY"); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertTrue(rate != 0); + } + + @Test + public void testShouldUpdateExchangeRates() + { + Rates rates = null; + List rateList = null; + try { + rates = this.bitpay.getRates(); + rates.update(); + rateList = rates.getRates(); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(rateList); + } + + /* + @Test + public void testShouldGetInvoices() { + List invoices = null; + try { + invoices = this.bitpay.getInvoices("2014-01-01", "2014-09-01"); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertTrue(invoices.size() > 0); + } + */ +} diff --git a/src/test/BitPayTest2.java b/src/test/BitPayTest2.java new file mode 100644 index 00000000..7eafa262 --- /dev/null +++ b/src/test/BitPayTest2.java @@ -0,0 +1,59 @@ +package test; + +import static org.junit.Assert.*; + +import model.Invoice; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import controller.BitPay; +import controller.BitPayException; + +public class BitPayTest2 { + + private BitPay bitpay; + private static String clientName = "BitPay Java Library Tester2"; + + @Before + public void setUp() throws Exception + { + // If this test has never been run before then this test must be run twice in order to pass. + // The first time this test runs it will create an identity and emit a client pairing code. + // The pairing code must then be authorized in a BitPay account. Running the test a second + // time should result in the authorized client (this test) running to completion. + clientName += " on " + java.net.InetAddress.getLocalHost(); + bitpay = new BitPay(clientName); + + if (!bitpay.clientIsAuthorized(BitPay.FACADE_POS)) + { + // Get POS facade authorization code. + // Obtain a pairingCode from the BitPay server. The pairingCode must be emitted from + // this device and input into and approved by the desired merchant account. To + // generate invoices a POS facade is required. + String pairingCode = bitpay.requestClientAuthorization(BitPay.FACADE_POS); + + // Signal the device operator that this client needs to be paired with a merchant account. + System.out.print("Info: Pair this client with your merchant account using the pairing code: " + pairingCode); + throw new BitPayException("Error: client is not authorized."); + } + } + + @After + public void tearDown() throws Exception + { + } + + @Test + public void testShouldGetInvoiceId() + { + Invoice invoice = new Invoice(50.0, "USD"); + try { + invoice = bitpay.createInvoice(invoice); + } catch (BitPayException e) { + e.printStackTrace(); + } + assertNotNull(invoice.getId()); + } +}