├── NBitpayClient ├── SettlementSummary.cs ├── PushNuget.ps1 ├── RefundAmount.cs ├── SettlementReconciliationReport.cs ├── RefundAddress.cs ├── InvoiceSupportedTransactionCurrency.cs ├── RefundInfo.cs ├── Withholdings.cs ├── Rate.cs ├── Policy.cs ├── PayoutInfo.cs ├── NBitpayClient.csproj ├── InvoicePaymentUrls.cs ├── JsonConverters │ ├── MoneyJsonConverter.cs │ └── DateTimeJsonConverter.cs ├── SettlementLedgerEntry.cs ├── Rates.cs ├── InvoiceTransaction.cs ├── InvoiceData.cs ├── Ledger.cs ├── Buyer.cs ├── BitPayException.cs ├── Extensions │ └── AuthExtensions.cs ├── Settlement.cs ├── LedgerEntry.cs ├── Token.cs ├── InvoicePaymentNotification.cs ├── Invoice.cs └── BitPay.cs ├── README.md ├── NBitpayClient.Tests ├── NBitpayClient.Tests.csproj ├── Assert.cs ├── Logging │ ├── Logs.cs │ └── ConsoleLogger.cs ├── CustomServer.cs └── Program.cs ├── LICENSE ├── NBitpayClient.sln ├── .editorconfig └── .gitignore /NBitpayClient/SettlementSummary.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class SettlementSummary : Settlement 6 | { 7 | [JsonProperty(PropertyName = "token")] 8 | public string Token { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NBitpayClient/PushNuget.ps1: -------------------------------------------------------------------------------- 1 | rm "bin\release\" -Recurse -Force 2 | dotnet pack --configuration Release 3 | dotnet nuget push "bin\Release\" --source "https://api.nuget.org/v3/index.json" 4 | $ver = ((ls .\bin\release\*.nupkg)[0].Name -replace 'NBitpayClient\.(\d+(\.\d+){1,3}).*', '$1') 5 | git tag -a "v$ver" -m "$ver" 6 | git push --tags 7 | -------------------------------------------------------------------------------- /NBitpayClient/RefundAmount.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class RefundAmount 6 | { 7 | [JsonProperty(PropertyName = "btc")] 8 | public decimal Btc { get; set; } 9 | 10 | [JsonProperty(PropertyName = "usd")] 11 | public decimal Usd { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NBitpayClient/SettlementReconciliationReport.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | 4 | namespace NBitpayClient 5 | { 6 | public class SettlementReconciliationReport : Settlement 7 | { 8 | [JsonProperty(PropertyName = "ledgerEntries")] 9 | public List LedgerEntries { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NBitpayClient/RefundAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace NBitpayClient 5 | { 6 | public class RefundAddress 7 | { 8 | [JsonProperty(PropertyName = "type")] 9 | public string Type { get; set; } 10 | 11 | [JsonProperty(PropertyName = "date")] 12 | public DateTimeOffset Date { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /NBitpayClient/InvoiceSupportedTransactionCurrency.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class InvoiceSupportedTransactionCurrency 6 | { 7 | [JsonProperty(PropertyName = "enabled")] 8 | public bool Enabled { get; set; } 9 | 10 | [JsonProperty(PropertyName = "reason")] 11 | public string Reason { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /NBitpayClient/RefundInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class RefundInfo 6 | { 7 | [JsonProperty(PropertyName = "supportRequest")] 8 | public string SupportRequest { get; set; } 9 | 10 | [JsonProperty(PropertyName = "currency")] 11 | public string Currency { get; set; } 12 | 13 | [JsonProperty(PropertyName = "amounts")] 14 | public RefundAmount Amounts { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NBitpayClient/Withholdings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class Withholdings 6 | { 7 | [JsonProperty(PropertyName = "amount")] 8 | public decimal Amount { get; set; } 9 | 10 | [JsonProperty(PropertyName = "code")] 11 | public string Code { get; set; } 12 | 13 | [JsonProperty(PropertyName = "description")] 14 | public string Description { get; set; } 15 | 16 | [JsonProperty(PropertyName = "notes")] 17 | public string Notes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /NBitpayClient/Rate.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace NBitpayClient 5 | { 6 | /// 7 | /// Provides an interface to a single exchange rate. 8 | /// 9 | public class Rate 10 | { 11 | public Rate() {} 12 | 13 | [JsonProperty(PropertyName = "name")] 14 | public string Name { get; set; } 15 | 16 | [JsonProperty(PropertyName = "code")] 17 | public string Code { get; set; } 18 | 19 | [JsonProperty(PropertyName = "rate")] 20 | public decimal Value { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /NBitpayClient/Policy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | /// 8 | /// Provides BitPay token policy information. 9 | /// 10 | public class Policy 11 | { 12 | public Policy() {} 13 | 14 | [JsonProperty(PropertyName = "policy")] 15 | public string Value { get; set; } 16 | 17 | [JsonProperty(PropertyName = "method")] 18 | public string Method { get; set; } 19 | 20 | [JsonProperty(PropertyName = "params")] 21 | public List Params { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /NBitpayClient/PayoutInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NBitpayClient 4 | { 5 | public class PayoutInfo 6 | { 7 | [JsonProperty(PropertyName = "name")] 8 | public string Name { get; set; } 9 | 10 | [JsonProperty(PropertyName = "account")] 11 | public string Account { get; set; } 12 | 13 | [JsonProperty(PropertyName = "routing")] 14 | public string Routing { get; set; } 15 | 16 | [JsonProperty(PropertyName = "merchantEIN")] 17 | public string MerchantEIN { get; set; } 18 | 19 | [JsonProperty(PropertyName = "label")] 20 | public string Label { get; set; } 21 | 22 | [JsonProperty(PropertyName = "bankCountry")] 23 | public string BankCountry { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NBitpayClient/NBitpayClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3 5 | Metaco SA 6 | 1.0.0.39 7 | Copyright © Metaco SA 2017 8 | Non official .NET Bitpay API compatible client 9 | bitcoin 10 | https://github.com/MetacoSA/NBitpayClient 11 | MIT 12 | https://github.com/MetacoSA/NBitpayClient 13 | git 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NuGet](https://img.shields.io/nuget/v/NBitpayClient.svg)](https://www.nuget.org/packages/NBitpayClient) 2 | 3 | # NBitpayClient 4 | 5 | A non-offical and Bitpay API compatible client library. 6 | 7 | ## How to use 8 | 9 | You can reference the package with [Nuget](https://www.nuget.org/packages/NBitpayClient), and use the `Bitpay` class. 10 | 11 | Please, check and run the [NBitpayClient.Tests project](https://github.com/MetacoSA/NBitpayClient/blob/master/NBitpayClient.Tests/Program.cs#L97), as a working example. 12 | For the example to work, you need a fully synched Bitcoin Core Testnet node, then modify your settings [here](https://github.com/MetacoSA/NBitpayClient/blob/master/NBitpayClient.Tests/Program.cs#L20) for the test to work. 13 | 14 | ## License 15 | 16 | This project is under MIT licence. 17 | -------------------------------------------------------------------------------- /NBitpayClient/InvoicePaymentUrls.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NBitpayClient 9 | { 10 | /// 11 | /// Invoice payment URLs identified by BIP format. 12 | /// 13 | public class InvoicePaymentUrls { 14 | 15 | public InvoicePaymentUrls() {} 16 | 17 | [JsonProperty("BIP21")] 18 | public string BIP21 { get; set; } 19 | [JsonProperty("BIP72")] 20 | public string BIP72 { get; set; } 21 | [JsonProperty("BIP72b")] 22 | public string BIP72b { get; set; } 23 | [JsonProperty("BIP73")] 24 | public string BIP73 { get; set; } 25 | 26 | [JsonProperty("BOLT11")] 27 | public string BOLT11 28 | { 29 | get; set; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NBitpayClient/JsonConverters/MoneyJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Reflection; 4 | using NBitcoin; 5 | 6 | namespace NBitpayClient.JsonConverters 7 | { 8 | public class MoneyJsonConverter : JsonConverter 9 | { 10 | public override bool CanConvert(Type objectType) 11 | { 12 | return objectType.GetTypeInfo() == typeof(Money).GetTypeInfo(); 13 | } 14 | 15 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 16 | { 17 | if(reader.TokenType == JsonToken.Null) 18 | return null; 19 | return Money.Parse((string)reader.Value); 20 | } 21 | 22 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 23 | { 24 | if(value != null) 25 | { 26 | writer.WriteValue(value.ToString()); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/NBitpayClient.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/Assert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NBitpayClient.Tests 6 | { 7 | public class AssertException : Exception 8 | { 9 | public AssertException(string message) : base(message) 10 | { 11 | 12 | } 13 | } 14 | public class Assert 15 | { 16 | internal static void NotNull(T obj) where T : class 17 | { 18 | if(obj == null) 19 | throw new AssertException("Should not be null"); 20 | } 21 | 22 | internal static void Equal(T a, T b) where T : class 23 | { 24 | if(a != b) 25 | throw new AssertException("Should be equals"); 26 | } 27 | 28 | internal static void True(bool v) 29 | { 30 | if(!v) 31 | throw new AssertException("Should be true"); 32 | } 33 | 34 | internal static void False(bool v) 35 | { 36 | if(v) 37 | throw new AssertException("Should be false"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /NBitpayClient/SettlementLedgerEntry.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace NBitpayClient 5 | { 6 | public class SettlementLedgerEntry 7 | { 8 | [JsonProperty(PropertyName = "code")] 9 | public int Code { get; set; } 10 | 11 | [JsonProperty(PropertyName = "invoiceId")] 12 | public string InvoiceId { get; set; } 13 | 14 | [JsonProperty(PropertyName = "amount")] 15 | public decimal Amount { get; set; } 16 | 17 | [JsonProperty(PropertyName = "timestamp")] 18 | public DateTime Timestamp { get; set; } 19 | 20 | [JsonProperty(PropertyName = "description")] 21 | public string Description { get; set; } 22 | 23 | [JsonProperty(PropertyName = "reference")] 24 | public string Reference { get; set; } 25 | 26 | [JsonProperty(PropertyName = "invoiceData")] 27 | public InvoiceData InvoiceData { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NBitpayClient/Rates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | 5 | namespace NBitpayClient 6 | { 7 | /// 8 | /// Provides an interface to the BitPay server to obtain exchange rate information. 9 | /// 10 | public class Rates 11 | { 12 | private List _rates; 13 | 14 | public Rates(List rates) 15 | { 16 | _rates = rates; 17 | } 18 | 19 | public List AllRates 20 | { 21 | get 22 | { 23 | return _rates; 24 | } 25 | } 26 | 27 | public decimal GetRate(string currencyCode) 28 | { 29 | decimal val = 0; 30 | foreach (Rate rateObj in _rates) 31 | { 32 | if (rateObj.Code.Equals(currencyCode)) 33 | { 34 | val = rateObj.Value; 35 | break; 36 | } 37 | } 38 | return val; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NBitpayClient/InvoiceTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace NBitpayClient 5 | { 6 | /// 7 | /// Provides information about a single invoice transaction. 8 | /// 9 | public class InvoiceTransaction 10 | { 11 | public InvoiceTransaction() { } 12 | 13 | [JsonProperty(PropertyName = "amount")] 14 | public double Amount { get; set; } 15 | 16 | [JsonProperty(PropertyName = "confirmations")] 17 | public string Confirmations { get; set; } 18 | 19 | [JsonProperty(PropertyName = "receivedTime")] 20 | public DateTimeOffset ReceivedTime { get; set; } 21 | 22 | [JsonProperty(PropertyName = "time")] 23 | public string Time { get; set; } 24 | 25 | [JsonProperty(PropertyName = "type")] 26 | public string Type { get; set; } 27 | 28 | [JsonProperty(PropertyName = "txid")] 29 | public string TxId { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NBitpayClient/InvoiceData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | public class InvoiceData 8 | { 9 | [JsonProperty(PropertyName = "orderId")] 10 | public string OrderId { get; set; } 11 | 12 | [JsonProperty(PropertyName = "date")] 13 | public DateTime Date { get; set; } 14 | 15 | [JsonProperty(PropertyName = "price")] 16 | public decimal Price { get; set; } 17 | 18 | [JsonProperty(PropertyName = "currency")] 19 | public string Currency { get; set; } 20 | 21 | [JsonProperty(PropertyName = "overpaidAmount")] 22 | public decimal OverPaidAmount { get; set; } 23 | 24 | [JsonProperty(PropertyName = "payoutPercentage")] 25 | public Dictionary PayoutPercentage { get; set; } 26 | 27 | [JsonProperty(PropertyName = "btcPrice")] 28 | public decimal BtcPrice { get; set; } 29 | 30 | [JsonProperty(PropertyName = "refundInfo")] 31 | public RefundInfo RefundInfo { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Digital Garage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/Logging/Logs.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NBitpayClient.Tests 9 | { 10 | public class Logs 11 | { 12 | static Logs() 13 | { 14 | Configure(new FuncLoggerFactory(n => NullLogger.Instance)); 15 | } 16 | public static void Configure(ILoggerFactory factory) 17 | { 18 | Tests = factory.CreateLogger("Tests"); 19 | } 20 | public static ILogger Tests 21 | { 22 | get; set; 23 | } 24 | public const int ColumnLength = 16; 25 | } 26 | 27 | public class FuncLoggerFactory : ILoggerFactory 28 | { 29 | private Func createLogger; 30 | public FuncLoggerFactory(Func createLogger) 31 | { 32 | this.createLogger = createLogger; 33 | } 34 | public void AddProvider(ILoggerProvider provider) 35 | { 36 | 37 | } 38 | 39 | public ILogger CreateLogger(string categoryName) 40 | { 41 | return createLogger(categoryName); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /NBitpayClient/Ledger.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | public class Ledger 8 | { 9 | public const String LEDGER_AUD = "AUD"; 10 | public const String LEDGER_BTC = "BTC"; 11 | public const String LEDGER_CAD = "CAD"; 12 | public const String LEDGER_EUR = "EUR"; 13 | public const String LEDGER_GBP = "GBP"; 14 | public const String LEDGER_MXN = "MXN"; 15 | public const String LEDGER_NDZ = "NDZ"; 16 | public const String LEDGER_USD = "USD"; 17 | public const String LEDGER_ZAR = "ZAR"; 18 | 19 | public List Entries = null; 20 | 21 | [JsonProperty(PropertyName = "currency")] 22 | public string Currency { get; set; } 23 | 24 | [JsonProperty(PropertyName = "balance")] 25 | public decimal Balance { get; set; } 26 | 27 | public Ledger(string currency, decimal balance = 0, List entries = null) 28 | { 29 | Currency = currency; 30 | Balance = balance; 31 | Entries = entries; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NBitpayClient/Buyer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace NBitpayClient 5 | { 6 | /// 7 | /// Provides an interface to a buyer. 8 | /// 9 | public class Buyer 10 | { 11 | public Buyer() { } 12 | 13 | [JsonProperty(PropertyName = "name")] 14 | public string Name { get; set; } 15 | 16 | [JsonProperty(PropertyName = "address1")] 17 | public string Address1 { get; set; } 18 | 19 | [JsonProperty(PropertyName = "address2")] 20 | public string Address2 { get; set; } 21 | 22 | [JsonProperty(PropertyName = "city")] 23 | public string City { get; set; } 24 | 25 | [JsonProperty(PropertyName = "state")] 26 | public string State { get; set; } 27 | 28 | [JsonProperty(PropertyName = "zip")] 29 | public string zip { get; set; } 30 | 31 | [JsonProperty(PropertyName = "country")] 32 | public string country { get; set; } 33 | 34 | [JsonProperty(PropertyName = "email")] 35 | public string email { get; set; } 36 | 37 | [JsonProperty(PropertyName = "phone")] 38 | public string phone { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /NBitpayClient/JsonConverters/DateTimeJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace NBitpayClient.JsonConverters 5 | { 6 | class DateTimeJsonConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) 9 | { 10 | return objectType == typeof(DateTimeOffset); 11 | } 12 | 13 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 14 | { 15 | var v = (long)reader.Value; 16 | Check(v); 17 | return unixRef + TimeSpan.FromMilliseconds((long)v); 18 | } 19 | 20 | static DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); 21 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 22 | { 23 | var date = ((DateTimeOffset)value).ToUniversalTime(); 24 | long v = (long)(date - unixRef).TotalMilliseconds; 25 | Check(v); 26 | writer.WriteValue(v); 27 | } 28 | 29 | private static void Check(long v) 30 | { 31 | if(v < 0) 32 | throw new FormatException("Invalid datetime (less than 1/1/1970)"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /NBitpayClient/BitPayException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | 6 | namespace NBitpayClient 7 | { 8 | /// 9 | /// Provides an API specific exception handler. 10 | /// 11 | public class BitPayException : Exception 12 | { 13 | private string _message = "Exception information not provided"; 14 | private Exception _inner = null; 15 | 16 | /// 17 | /// Constructor. Creates an empty exception. 18 | /// 19 | public BitPayException() 20 | { 21 | } 22 | 23 | /// 24 | /// Constructor. Creates an exception with a message only. 25 | /// 26 | /// The message text for the exception. 27 | public BitPayException(string message) : base(message) 28 | { 29 | _message = message; 30 | } 31 | 32 | /// 33 | /// Constructor. Creates an exception with a message and root cause exception. 34 | /// 35 | /// The message text for the exception. 36 | /// The root cause exception. 37 | public BitPayException(string message, Exception inner) : base(message, inner) 38 | { 39 | _message = message; 40 | _inner = inner; 41 | } 42 | 43 | public override string Message 44 | { 45 | get 46 | { 47 | return String.Join(Environment.NewLine, new[] { base.Message }.Concat(_Errors).ToArray()); 48 | } 49 | } 50 | 51 | public ICollection Errors 52 | { 53 | get 54 | { 55 | return _Errors; 56 | } 57 | } 58 | 59 | List _Errors = new List(); 60 | internal void AddError(string errorMessage) 61 | { 62 | _Errors.Add(errorMessage); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /NBitpayClient/Extensions/AuthExtensions.cs: -------------------------------------------------------------------------------- 1 | using NBitcoin; 2 | using NBitcoin.Crypto; 3 | using NBitcoin.DataEncoders; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace NBitpayClient.Extensions 9 | { 10 | public static class BitIdExtensions 11 | { 12 | public static string GetBitIDSIN(this PubKey key) 13 | { 14 | return Encoders.Base58Check.EncodeData(Encoders.Hex.DecodeData("0f02" + key.Hash.ToString())); 15 | } 16 | 17 | public static bool ValidateSIN(this string sin) 18 | { 19 | try 20 | { 21 | var decoded = Encoders.Base58Check.DecodeData(sin); 22 | return decoded.Length == 2 + 20 && decoded[0] == 0x0f && decoded[1] == 0x02; 23 | } 24 | catch { return false; } 25 | } 26 | 27 | public static string GetBitIDSignature(this Key key, string uri, string body) 28 | { 29 | body = body ?? string.Empty; 30 | if(key == null) 31 | throw new ArgumentNullException(nameof(key)); 32 | if(uri == null) 33 | throw new ArgumentNullException(nameof(uri)); 34 | var hash = new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(uri + body))); 35 | return Encoders.Hex.EncodeData(key.Sign(hash).ToDER()); 36 | } 37 | 38 | public static bool CheckBitIDSignature(this PubKey key, string sig, string uri, string body) 39 | { 40 | body = body ?? string.Empty; 41 | if(key == null) 42 | throw new ArgumentNullException(nameof(key)); 43 | if(sig == null) 44 | throw new ArgumentNullException(nameof(sig)); 45 | if(uri == null) 46 | throw new ArgumentNullException(nameof(uri)); 47 | try 48 | { 49 | if(!Uri.IsWellFormedUriString(uri, UriKind.Absolute)) 50 | return false; 51 | var hash = new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(uri + body))); 52 | var result = key.Verify(hash, new ECDSASignature(Encoders.Hex.DecodeData(sig))); 53 | return result; 54 | } 55 | catch { return false; } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /NBitpayClient/Settlement.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | public class Settlement 8 | { 9 | [JsonProperty(PropertyName = "id")] 10 | public string Id { get; set; } 11 | 12 | [JsonProperty(PropertyName = "accountId")] 13 | public string AccountId { get; set; } 14 | 15 | [JsonProperty(PropertyName = "currency")] 16 | public string Currency { get; set; } 17 | 18 | [JsonProperty(PropertyName = "payoutInfo")] 19 | public PayoutInfo PayoutInfo { get; set; } 20 | 21 | [JsonProperty(PropertyName = "status")] 22 | public string Status { get; set; } 23 | 24 | [JsonProperty(PropertyName = "dateCreated")] 25 | public DateTime DateCreated { get; set; } 26 | 27 | [JsonProperty(PropertyName = "dateExecuted")] 28 | public DateTime DateExecuted { get; set; } 29 | 30 | [JsonProperty(PropertyName = "dateCompleted")] 31 | public DateTime DateCompleted { get; set; } 32 | 33 | [JsonProperty(PropertyName = "openingDate")] 34 | public DateTime OpeningDate { get; set; } 35 | 36 | [JsonProperty(PropertyName = "closingDate")] 37 | public DateTime ClosingDate { get; set; } 38 | 39 | [JsonProperty(PropertyName = "openingBalance")] 40 | public decimal OpeningBalance { get; set; } 41 | 42 | [JsonProperty(PropertyName = "ledgerEntriesSum")] 43 | public decimal LedgerEntriesSum { get; set; } 44 | 45 | [JsonProperty(PropertyName = "withholdings")] 46 | public List Withholdings { get; set; } 47 | 48 | [JsonProperty(PropertyName = "withholdingsSum")] 49 | public decimal WithHoldingsSum { get; set; } 50 | 51 | [JsonProperty(PropertyName = "totalAmount")] 52 | public decimal TotalAmount { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/CustomServer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Builder; 7 | using System.Threading.Tasks; 8 | using System.Threading; 9 | 10 | namespace NBitpayClient.Tests 11 | { 12 | public class CustomServer 13 | { 14 | TaskCompletionSource _Evt = null; 15 | IWebHost _Host = null; 16 | CancellationTokenSource _Closed = new CancellationTokenSource(); 17 | ulong _Cookie; 18 | public CustomServer(string bindUrl, ulong cookie) 19 | { 20 | _Cookie = cookie; 21 | _Host = new WebHostBuilder() 22 | .Configure(app => 23 | { 24 | app.Run(req => 25 | { 26 | if(req.Request.Path.Value.Contains(cookie.ToString())) 27 | { 28 | while(_Act == null) 29 | { 30 | Thread.Sleep(10); 31 | _Closed.Token.ThrowIfCancellationRequested(); 32 | } 33 | _Act(req); 34 | _Act = null; 35 | _Evt.TrySetResult(true); 36 | } 37 | req.Response.StatusCode = 200; 38 | return Task.CompletedTask; 39 | }); 40 | }) 41 | .UseKestrel() 42 | .UseUrls(bindUrl) 43 | .Build(); 44 | _Host.Start(); 45 | } 46 | 47 | 48 | 49 | Action _Act; 50 | public void ProcessNextRequest(Action act) 51 | { 52 | var source = new TaskCompletionSource(); 53 | CancellationTokenSource cancellation = new CancellationTokenSource(20000); 54 | cancellation.Token.Register(() => source.TrySetCanceled()); 55 | source = new TaskCompletionSource(); 56 | _Evt = source; 57 | _Act = act; 58 | try 59 | { 60 | _Evt.Task.GetAwaiter().GetResult(); 61 | } 62 | catch(TaskCanceledException) 63 | { 64 | throw new AssertException("Callback to the webserver was expected, check if the callback url is accessible from internet"); 65 | } 66 | } 67 | 68 | internal void Dispose() 69 | { 70 | _Closed.Cancel(); 71 | _Host.Dispose(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /NBitpayClient/LedgerEntry.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | public class LedgerEntry 8 | { 9 | [JsonProperty(PropertyName = "id")] 10 | public string Id { get; set; } 11 | 12 | [JsonProperty(PropertyName = "code")] 13 | public string Code { get; set; } 14 | 15 | [JsonProperty(PropertyName = "amount")] 16 | public string Amount { get; set; } 17 | 18 | [JsonProperty(PropertyName = "timestamp")] 19 | public string Timestamp { get; set; } 20 | 21 | [JsonProperty(PropertyName = "scale")] 22 | public string Scale { get; set; } 23 | 24 | [JsonProperty(PropertyName = "txType")] 25 | public string TxType { get; set; } 26 | 27 | [JsonProperty(PropertyName = "sale")] 28 | public string Sale { get; set; } 29 | 30 | [JsonProperty(PropertyName = "exRates")] 31 | public Dictionary ExRates { get; set; } 32 | 33 | [JsonProperty(PropertyName = "buyer")] 34 | public Buyer Buyer { get; set; } 35 | 36 | [JsonProperty(PropertyName = "invoiceId")] 37 | public string InvoiceId { get; set; } 38 | 39 | [JsonProperty(PropertyName = "sourceType")] 40 | public string SourceType { get; set; } 41 | 42 | [Newtonsoft.Json.JsonIgnore] 43 | public Dictionary CustomerData { get; set; } 44 | 45 | [JsonProperty(PropertyName = "invoiceAmount")] 46 | public double InvoiceAmount { get; set; } 47 | 48 | [JsonProperty(PropertyName = "invoiceCurrency")] 49 | public string InvoiceCurrency { get; set; } 50 | 51 | [JsonProperty(PropertyName = "description")] 52 | public string Description { get; set; } 53 | 54 | [JsonProperty(PropertyName = "type")] 55 | public string Type { get; set; } 56 | 57 | [JsonProperty(PropertyName = "transactionCurrency")] 58 | public string TransactionCurrency { get; set; } 59 | 60 | [JsonProperty(PropertyName = "buyerFields")] 61 | public Dictionary BuyerFields { get; set; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /NBitpayClient/Token.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NBitpayClient 6 | { 7 | /// 8 | /// Provides an interface to the BitPay server for the token resource. 9 | /// 10 | public class Token 11 | { 12 | public Token() {} 13 | 14 | // API fields 15 | // 16 | 17 | [JsonProperty(PropertyName = "guid")] 18 | public string Guid { get; set; } 19 | public bool ShouldSerializeGuid() { return true; } 20 | 21 | [JsonProperty(PropertyName = "nonce")] 22 | public long Nonce { get; set; } 23 | public bool ShouldSerializeNonce() { return Nonce != 0; } 24 | 25 | // Required fields 26 | // 27 | 28 | [JsonProperty(PropertyName = "id")] 29 | public string Id { get; set; } 30 | public bool ShouldSerializeId() { return !String.IsNullOrEmpty(Id); } 31 | 32 | // Optional fields 33 | 34 | [JsonProperty(PropertyName = "pairingCode")] 35 | public string PairingCode { get; set; } 36 | public bool ShouldSerializePairingCode() { return !String.IsNullOrEmpty(PairingCode); } 37 | 38 | [JsonProperty(PropertyName = "facade")] 39 | public string Facade { get; set; } 40 | public bool ShouldSerializeFacade() { return !String.IsNullOrEmpty(Facade); } 41 | 42 | [JsonProperty(PropertyName = "label")] 43 | public string Label { get; set; } 44 | public bool ShouldSerializeLabel() { return !String.IsNullOrEmpty(Label); } 45 | 46 | [JsonProperty(PropertyName = "count")] 47 | public int Count { get; set; } 48 | public bool ShouldSerializeCount() { return Count != 0; } 49 | 50 | // Response fields 51 | // 52 | 53 | [JsonProperty(PropertyName = "pairingExpiration")] 54 | public long PairingExpiration { get; set; } 55 | public bool ShouldSerializePairingExpiration() { return false; } 56 | 57 | [JsonProperty(PropertyName = "policies")] 58 | public List Policies { get; set; } 59 | public bool ShouldSerializePolicies() { return false; } 60 | 61 | [JsonProperty(PropertyName = "resource")] 62 | public string Resource { get; set; } 63 | public bool ShouldSerializeResource() { return false; } 64 | 65 | [JsonProperty(PropertyName = "token")] 66 | public string Value { get; set; } 67 | public bool ShouldSerializeValue() { return false; } 68 | 69 | [JsonProperty(PropertyName = "dateCreated")] 70 | public long DateCreated { get; set; } 71 | public bool ShouldSerializeDateCreated() { return false; } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /NBitpayClient.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.3 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBitpayClient", "NBitpayClient\NBitpayClient.csproj", "{7C0219AC-CBE1-4C37-8374-DF787F62B5E8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBitpayClient.Tests", "NBitpayClient.Tests\NBitpayClient.Tests.csproj", "{87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|x64.Build.0 = Debug|Any CPU 24 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|x86.ActiveCfg = Debug|Any CPU 25 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Debug|x86.Build.0 = Debug|Any CPU 26 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|x64.ActiveCfg = Release|Any CPU 29 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|x64.Build.0 = Release|Any CPU 30 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|x86.ActiveCfg = Release|Any CPU 31 | {7C0219AC-CBE1-4C37-8374-DF787F62B5E8}.Release|x86.Build.0 = Release|Any CPU 32 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|x64.Build.0 = Debug|Any CPU 36 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Debug|x86.Build.0 = Debug|Any CPU 38 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|x64.ActiveCfg = Release|Any CPU 41 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|x64.Build.0 = Release|Any CPU 42 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|x86.ActiveCfg = Release|Any CPU 43 | {87B2E1E0-4113-4A22-BEB4-CCCD7299A8AD}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {93D485B3-59F8-455D-A205-452F8EBF1DE6} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /NBitpayClient/InvoicePaymentNotification.cs: -------------------------------------------------------------------------------- 1 | using NBitcoin; 2 | using NBitpayClient.JsonConverters; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace NBitpayClient 9 | { 10 | 11 | //{"id":"NzzNUB5DEMLP5q95szL1VS","url":"https://test.bitpay.com/invoice?id=NzzNUB5DEMLP5q95szL1VS", 12 | //"posData":"posData","status":"paid","btcPrice":"0.001246","price":5,"currency":"USD", 13 | //"invoiceTime":1503140597709,"expirationTime":1503141497709,"currentTime":1503140607752, 14 | //"btcPaid":"0.001246","btcDue":"0.000000","rate":4012.12,"exceptionStatus":false,"buyerFields":{}} 15 | public class InvoicePaymentNotification 16 | { 17 | [JsonProperty(PropertyName = "id")] 18 | public string Id 19 | { 20 | get; set; 21 | } 22 | 23 | [JsonProperty(PropertyName = "url")] 24 | public string Url 25 | { 26 | get; set; 27 | } 28 | 29 | [JsonProperty(PropertyName = "posData")] 30 | public string PosData 31 | { 32 | get; set; 33 | } 34 | 35 | [JsonProperty(PropertyName = "status")] 36 | public string Status 37 | { 38 | get; set; 39 | } 40 | 41 | 42 | [JsonProperty(PropertyName = "btcPrice")] 43 | [JsonConverter(typeof(MoneyJsonConverter))] 44 | public Money BTCPrice 45 | { 46 | get; set; 47 | } 48 | 49 | [JsonProperty(PropertyName = "price")] 50 | public decimal Price 51 | { 52 | get; set; 53 | } 54 | 55 | [JsonProperty(PropertyName = "currency")] 56 | public string Currency 57 | { 58 | get; set; 59 | } 60 | 61 | [JsonConverter(typeof(DateTimeJsonConverter))] 62 | [JsonProperty(PropertyName = "invoiceTime")] 63 | public DateTimeOffset InvoiceTime 64 | { 65 | get; set; 66 | } 67 | 68 | [JsonConverter(typeof(DateTimeJsonConverter))] 69 | [JsonProperty(PropertyName = "expirationTime")] 70 | public DateTimeOffset ExpirationTime 71 | { 72 | get; set; 73 | } 74 | 75 | 76 | [JsonConverter(typeof(DateTimeJsonConverter))] 77 | [JsonProperty(PropertyName = "currentTime")] 78 | public DateTimeOffset CurrentTime 79 | { 80 | get; set; 81 | } 82 | 83 | [JsonProperty(PropertyName = "btcPaid")] 84 | [JsonConverter(typeof(MoneyJsonConverter))] 85 | public Money BTCPaid 86 | { 87 | get; set; 88 | } 89 | 90 | [JsonProperty(PropertyName = "btcDue")] 91 | [JsonConverter(typeof(MoneyJsonConverter))] 92 | public Money BTCDue 93 | { 94 | get; set; 95 | } 96 | 97 | [JsonProperty(PropertyName = "rate")] 98 | public decimal Rate 99 | { 100 | get; set; 101 | } 102 | 103 | [JsonProperty(PropertyName = "exceptionStatus")] 104 | public JToken ExceptionStatus 105 | { 106 | get; set; 107 | } 108 | 109 | [JsonProperty(PropertyName = "buyerFields")] 110 | public JObject BuyerFields 111 | { 112 | get; set; 113 | } 114 | 115 | [JsonProperty(PropertyName = "transactionCurrency")] 116 | public string TransactionCurrency 117 | { 118 | get; set; 119 | } 120 | [JsonProperty(PropertyName = "paymentSubtotals")] 121 | public Dictionary PaymentSubtotals 122 | { 123 | get; set; 124 | } 125 | [JsonProperty(PropertyName = "paymentTotals")] 126 | public Dictionary PaymentTotals 127 | { 128 | get; set; 129 | } 130 | 131 | [JsonProperty(PropertyName = "amountPaid")] 132 | [JsonConverter(typeof(MoneyJsonConverter))] 133 | public Money AmountPaid 134 | { 135 | get; set; 136 | } 137 | 138 | [JsonProperty(PropertyName = "exchangeRates")] 139 | public Dictionary> ExchangeRates 140 | { 141 | get; set; 142 | } 143 | 144 | [JsonProperty(PropertyName = "orderId")] 145 | public string OrderId 146 | { 147 | get; set; 148 | } 149 | 150 | [JsonProperty(PropertyName = "_warning", Order = 1)] 151 | public string Warning 152 | { 153 | get { return "This data could have easily been faked and should not be trusted. Please run any invoice checks by first fetching the invoice through the API."; } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [project.json] 15 | indent_size = 2 16 | 17 | # C# files 18 | [*.cs] 19 | # New line preferences 20 | csharp_new_line_before_open_brace = all 21 | csharp_new_line_before_else = true 22 | csharp_new_line_before_catch = true 23 | csharp_new_line_before_finally = true 24 | csharp_new_line_before_members_in_object_initializers = true 25 | csharp_new_line_before_members_in_anonymous_types = true 26 | csharp_new_line_within_query_expression_clauses = true 27 | 28 | # Indentation preferences 29 | csharp_indent_block_contents = true 30 | csharp_indent_braces = false 31 | csharp_indent_case_contents = true 32 | csharp_indent_switch_labels = true 33 | csharp_indent_labels = flush_left 34 | 35 | # avoid this. unless absolutely necessary 36 | dotnet_style_qualification_for_field = false:suggestion 37 | dotnet_style_qualification_for_property = false:suggestion 38 | dotnet_style_qualification_for_method = false:suggestion 39 | dotnet_style_qualification_for_event = false:suggestion 40 | 41 | # only use var when it's obvious what the variable type is 42 | csharp_style_var_for_built_in_types = false:none 43 | csharp_style_var_when_type_is_apparent = false:none 44 | csharp_style_var_elsewhere = false:suggestion 45 | 46 | # use language keywords instead of BCL types 47 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 48 | dotnet_style_predefined_type_for_member_access = true:suggestion 49 | 50 | # name all constant fields using PascalCase 51 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 52 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 53 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 54 | 55 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 56 | dotnet_naming_symbols.constant_fields.required_modifiers = const 57 | 58 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 59 | 60 | # internal and private fields should be _camelCase 61 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 62 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 63 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 64 | 65 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 66 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 67 | 68 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 69 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 70 | 71 | # Code style defaults 72 | dotnet_sort_system_directives_first = true 73 | csharp_preserve_single_line_blocks = true 74 | csharp_preserve_single_line_statements = false 75 | 76 | # Expression-level preferences 77 | dotnet_style_object_initializer = true:suggestion 78 | dotnet_style_collection_initializer = true:suggestion 79 | dotnet_style_explicit_tuple_names = true:suggestion 80 | dotnet_style_coalesce_expression = true:suggestion 81 | dotnet_style_null_propagation = true:suggestion 82 | 83 | # Expression-bodied members 84 | csharp_style_expression_bodied_methods = false:none 85 | csharp_style_expression_bodied_constructors = false:none 86 | csharp_style_expression_bodied_operators = false:none 87 | csharp_style_expression_bodied_properties = true:none 88 | csharp_style_expression_bodied_indexers = true:none 89 | csharp_style_expression_bodied_accessors = true:none 90 | 91 | # Pattern matching 92 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 93 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 94 | csharp_style_inlined_variable_declaration = true:suggestion 95 | 96 | # Null checking preferences 97 | csharp_style_throw_expression = true:suggestion 98 | csharp_style_conditional_delegate_call = true:suggestion 99 | 100 | # Space preferences 101 | csharp_space_after_cast = false 102 | csharp_space_after_colon_in_inheritance_clause = true 103 | csharp_space_after_comma = true 104 | csharp_space_after_dot = false 105 | csharp_space_after_keywords_in_control_flow_statements = true 106 | csharp_space_after_semicolon_in_for_statement = true 107 | csharp_space_around_binary_operators = before_and_after 108 | csharp_space_around_declaration_statements = do_not_ignore 109 | csharp_space_before_colon_in_inheritance_clause = true 110 | csharp_space_before_comma = false 111 | csharp_space_before_dot = false 112 | csharp_space_before_open_square_brackets = false 113 | csharp_space_before_semicolon_in_for_statement = false 114 | csharp_space_between_empty_square_brackets = false 115 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 116 | csharp_space_between_method_call_name_and_opening_parenthesis = false 117 | csharp_space_between_method_call_parameter_list_parentheses = false 118 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 119 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 120 | csharp_space_between_method_declaration_parameter_list_parentheses = false 121 | csharp_space_between_parentheses = false 122 | csharp_space_between_square_brackets = false 123 | 124 | # C++ Files 125 | [*.{cpp,h,in}] 126 | curly_bracket_next_line = true 127 | indent_brace_style = Allman 128 | 129 | # Xml project files 130 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 131 | indent_size = 2 132 | 133 | # Xml build files 134 | [*.builds] 135 | indent_size = 2 136 | 137 | # Xml files 138 | [*.{xml,stylecop,resx,ruleset}] 139 | indent_size = 2 140 | 141 | # Xml config files 142 | [*.{props,targets,config,nuspec}] 143 | indent_size = 2 144 | 145 | # Shell scripts 146 | [*.sh] 147 | end_of_line = lf 148 | [*.{cmd, bat}] 149 | end_of_line = crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | /NBitpayClient.Tests/TestNet/key.env 290 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using NBitcoin; 2 | using System; 3 | using Microsoft.Extensions.Logging; 4 | using System.IO; 5 | using System.Net; 6 | using System.Net.Http; 7 | using NBitcoin.RPC; 8 | using NBitcoin.Payment; 9 | using Newtonsoft.Json; 10 | using NBitpayClient.Extensions; 11 | 12 | namespace NBitpayClient.Tests 13 | { 14 | public class Program 15 | { 16 | 17 | 18 | //Bitpay parameters 19 | Uri BitPayUri = new Uri("https://test.bitpay.com/"); 20 | static Network Network = Network.TestNet; 21 | ////////////// 22 | 23 | //RPC Testnet settings 24 | static string RPCAuth = "cookiefile=G:\\Bitcoin\\testnet3\\.cookie"; // or user:pwd or null for default 25 | /// 26 | 27 | static CustomServer Server; 28 | static RPCClient RPC; 29 | static int serverPort = 38633; 30 | static string CallbackUri; 31 | 32 | Bitpay Bitpay = null; 33 | 34 | public static void Main(string[] args) 35 | { 36 | Logs.Configure(new FuncLoggerFactory(i => new CustomerConsoleLogger(i, (a, b) => true, false))); 37 | CustomServer server = null; 38 | try 39 | { 40 | new Program().TestContextFree(); 41 | RPC = new RPCClient(RPCAuth, "http://localhost:" + Network.RPCPort, Network); 42 | RPC.GetBalance(); 43 | Logs.Tests.LogInformation("Can connect to RPC"); 44 | var cookie = RandomUtils.GetUInt64(); 45 | CallbackUri = "http://" + GetExternalIp() + ":" + serverPort + "/" + cookie; 46 | Logs.Tests.LogInformation("Callback url used is " + CallbackUri); 47 | Server = new CustomServer("http://0.0.0.0:" + serverPort + "/", cookie); 48 | new Program().Run(); 49 | //Console.ReadLine(); 50 | Logs.Tests.LogInformation("Tests ran successfully"); 51 | } 52 | catch(AssertException ex) 53 | { 54 | Logs.Tests.LogError(ex.Message); 55 | } 56 | catch(Exception ex) 57 | { 58 | Logs.Tests.LogError(ex.ToString()); 59 | } 60 | finally 61 | { 62 | if(server != null) 63 | Server.Dispose(); 64 | } 65 | Console.ReadLine(); 66 | } 67 | 68 | private void TestContextFree() 69 | { 70 | CanSignAndCheckSig(); 71 | CanSerializeDeserialize(); 72 | } 73 | 74 | private void CanSignAndCheckSig() 75 | { 76 | var key = new Key(); 77 | string uri = "http://toto:9393/"; 78 | string content = "blah"; 79 | var sig = key.GetBitIDSignature(uri, content); 80 | Assert.True(key.PubKey.GetBitIDSIN().ValidateSIN()); 81 | Assert.NotNull(sig); 82 | Assert.True(key.PubKey.CheckBitIDSignature(sig, uri, content)); 83 | Assert.False(key.PubKey.CheckBitIDSignature(sig, uri + "1", content)); 84 | Assert.False(key.PubKey.CheckBitIDSignature(sig, uri, content + "1")); 85 | 86 | content = null; 87 | sig = key.GetBitIDSignature(uri, content); 88 | Assert.True(key.PubKey.CheckBitIDSignature(sig, uri, content)); 89 | Assert.True(key.PubKey.CheckBitIDSignature(sig, uri, string.Empty)); 90 | Assert.False(key.PubKey.CheckBitIDSignature(sig, uri, "1")); 91 | } 92 | 93 | private void CanSerializeDeserialize() 94 | { 95 | var str = "{\"id\":\"NzzNUB5DEMLP5q95szL1VS\",\"url\":\"https://test.bitpay.com/invoice?id=NzzNUB5DEMLP5q95szL1VS\",\"posData\":\"posData\",\"status\":\"paid\",\"btcPrice\":\"0.001246\",\"price\":5,\"currency\":\"USD\",\"invoiceTime\":1503140597709,\"expirationTime\":1503141497709,\"currentTime\":1503140607752,\"btcPaid\":\"0.001246\",\"btcDue\":\"0.000000\",\"rate\":4012.12,\"exceptionStatus\":false,\"buyerFields\":{}}"; 96 | var notif = JsonConvert.DeserializeObject(str); 97 | var serialized = JsonConvert.SerializeObject(notif); 98 | 99 | //from https://bitpay.com/docs/invoice-callbacks 100 | var example1 = "{\r\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\r\n \"url\":\"https://bitpay.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\r\n \"status\":\"paid\",\r\n \"price\":10,\r\n \"currency\":\"EUR\",\r\n \"invoiceTime\":1520373130312,\r\n \"expirationTime\":1520374030312,\r\n \"currentTime\":1520373179327,\r\n \"exceptionStatus\":false,\r\n \"buyerFields\":{\r\n \"buyerEmail\":\"test@bitpay.com\",\r\n \"buyerNotify\":false\r\n },\r\n \"paymentSubtotals\": {\r\n \"BCH\":1025900,\r\n \"BTC\":114700\r\n },\r\n \"paymentTotals\": {\r\n \"BCH\":1025900,\r\n \"BTC\":118400\r\n },\r\n \"transactionCurrency\": \"BCH\",\r\n \"amountPaid\": \"1025900\",\r\n \"exchangeRates\": {\r\n \"BTC\": {\r\n \"EUR\": 8721.690715789999,\r\n \"USD\": 10817.99,\r\n \"BCH\": 8.911763736716368\r\n },\r\n \"BCH\": {\r\n \"EUR\": 974.721189,\r\n \"USD\": 1209,\r\n \"BTC\": 0.11173752310536043\r\n }\r\n }\r\n}"; 101 | var example2 = "{\r\n \"id\":\"9E8qPC3zsvXRcA3tsbRLnC\",\r\n \"url\":\"https://bitpay.com/invoice?id=9E8qPC3zsvXRcA3tsbRLnC\",\r\n \"status\":\"confirmed\",\r\n \"btcPrice\":\"1.854061\",\r\n \"price\":\"19537.1\",\r\n \"currency\":\"USD\",\r\n \"invoiceTime\":1520417833546,\r\n \"expirationTime\":1520418733546,\r\n \"currentTime\":1520417905248,\r\n \"btcPaid\":\"1.854061\",\r\n \"btcDue\":\"0.000000\",\r\n \"rate\":10537.46,\r\n \"exceptionStatus\":false,\r\n \"buyerFields\": {\r\n \"buyerEmail\":\"test@bitpay.com\",\r\n \"buyerNotify\":false\r\n },\r\n \"paymentSubtotals\": {\r\n \"BCH\":1664857265,\r\n \"BTC\":185406100\r\n },\r\n \"paymentTotals\": {\r\n \"BCH\":1664857265,\r\n \"BTC\":185409800\r\n },\r\n \"transactionCurrency\": \"BTC\",\r\n \"amountPaid\": \"185409800\",\r\n \"exchangeRates\": {\r\n \"BTC\": {\r\n \"USD\": 10537.46373483771,\r\n \"BCH\": 8.979517456188931\r\n },\r\n \"BCH\": {\r\n \"USD\": 1173.50,\r\n \"BTC\": 0.111364558828356\r\n }\r\n }\r\n}"; 102 | 103 | var example1notif = JsonConvert.DeserializeObject(example1); 104 | var example2notif = JsonConvert.DeserializeObject(example2); 105 | var serialized1 = JsonConvert.SerializeObject(example1notif); 106 | var serialized2 = JsonConvert.SerializeObject(example2notif); 107 | 108 | 109 | //from https://bitpay.com/docs/display-invoice 110 | var invoicestr = "{\"id\":\"7MxRGVuBC1XvV138b3AqAR\",\"guid\":\"177005a3-2867-4c65-add8-7ab088e3c414\",\"itemDesc\":\"Lawncare, March\",\"invoiceTime\":1520368215297,\"expirationTime\":1520369115297,\"currentTime\":1520368235844,\"url\":\"https://test.bitpay.com/invoice?id=7MxRGVuBC1XvV138b3AqAR\",\"posData\":\"{ \\\"ref\\\" : 711454, \\\"affiliate\\\" : \\\"spring112\\\" }\",\"status\":\"new\",\"exceptionStatus\":false,\"price\":10,\"currency\":\"USD\",\"btcPrice\":\"0.000942\",\"btcDue\":\"0.000971\",\"paymentSubtotals\":{\"BTC\":94200,\"BCH\":8496000},\"paymentTotals\":{\"BTC\":97100,\"BCH\":8496000},\"btcPaid\":\"0.000000\",\"amountPaid\":0,\"rate\":10621.01,\"exRates\":{\"BTC\":1,\"BCH\":8.984103997289974,\"USD\":10608.43},\"exchangeRates\":{\"BTC\":{\"BCH\":8.997805828532702,\"USD\":10621.01},\"BCH\":{\"USD\":1177,\"BTC\":0.11077647058823528}},\"supportedTransactionCurrencies\":{\"BTC\":{\"enabled\":true},\"BCH\":{\"enabled\":true}},\"addresses\":{\"BTC\":\"mtXiukcxY2QjLSWGNaHdbvrvtakX4m5R1t\",\"BCH\":\"qz8tacx6fn0h6wwzd2k4y4ya5e2zddg0e5cm4nukfr\"},\"paymentUrls\":{\"BIP21\":\"bitcoin:mjBQNNE16a6gWKkkMxc2QiLzrZVViyruUe?amount=0.069032\",\"BIP72\":\"bitcoin:mjBQNNE16a6gWKkkMxc2QiLzrZVViyruUe?amount=0.069032&r=https://test.bitpay.com/i/7MxRGVuBC1XvV138b3AqAR\",\"BIP72b\":\"bitcoin:?r=https://test.bitpay.com/i/7MxRGVuBC1XvV138b3AqAR\",\"BIP73\":\"https://test.bitpay.com/i/7MxRGVuBC1XvV138b3AqAR\"},\"paymentCodes\":{\"BTC\":{\"BIP72b\":\"bitcoin:?r=https://test.bitpay.com/i/WoCy658tqHJrfa35F99gnp\",\"BIP73\":\"https://test.bitpay.com/i/WoCy658tqHJrfa35F99gnp\"},\"BCH\":{\"BIP72b\":\"bitcoincash:?r=https://test.bitpay.com/i/WoCy658tqHJrfa35F99gnp\",\"BIP73\":\"https://test.bitpay.com/i/WoCy658tqHJrfa35F99gnp\"}},\"token\":\"Hncf45uBVPNoiXbycHDh2cC37auMxhrxm5ijNCsTKGKfX4Y1vbjWCZvoSdciMNw5G\"}"; 111 | var invoice = JsonConvert.DeserializeObject(invoicestr); 112 | var serializedInvoice = JsonConvert.SerializeObject(invoice); 113 | 114 | 115 | } 116 | 117 | private static IPAddress GetExternalIp() 118 | { 119 | using(var http = new HttpClient()) 120 | { 121 | var ip = http.GetAsync("http://icanhazip.com").Result.Content.ReadAsStringAsync().Result; 122 | return IPAddress.Parse(ip.Replace("\n", "")); 123 | } 124 | } 125 | 126 | private void Run() 127 | { 128 | EnsureRegisteredKey(); 129 | CanMakeInvoice(); 130 | CanGetRate(); 131 | } 132 | 133 | private void CanMakeInvoice() 134 | { 135 | var invoice = Bitpay.CreateInvoice(new Invoice() 136 | { 137 | Price = 5.0m, 138 | Currency = "USD", 139 | PosData = "posData", 140 | OrderId = "orderId", 141 | //RedirectURL = redirect + "redirect", 142 | NotificationURL = CallbackUri + "/notification", 143 | ItemDesc = "Some description", 144 | FullNotifications = true 145 | }); 146 | Logs.Tests.LogInformation("Invoice created"); 147 | BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21); 148 | RPC.SendToAddress(url.Address, url.Amount); 149 | Logs.Tests.LogInformation("Invoice paid"); 150 | //Server.ProcessNextRequest((ctx) => 151 | //{ 152 | // var ipn = new StreamReader(ctx.Request.Body).ReadToEnd(); 153 | // JsonConvert.DeserializeObject(ipn); //can deserialize 154 | //}); 155 | var invoice2 = Bitpay.GetInvoice(invoice.Id); 156 | Assert.NotNull(invoice2); 157 | } 158 | 159 | private void CanGetRate() 160 | { 161 | var rates = Bitpay.GetRates(); 162 | Assert.NotNull(rates); 163 | Assert.True(rates.AllRates.Count > 0); 164 | 165 | var btcrates = Bitpay.GetRates("BTC"); 166 | Assert.NotNull(rates); 167 | Assert.True(btcrates.AllRates.Count > 0); 168 | 169 | 170 | var btcusd = Bitpay.GetRate("BTC", "USD"); 171 | Assert.NotNull(btcusd); 172 | Assert.Equal(btcusd.Code, "USD"); 173 | 174 | } 175 | 176 | private void EnsureRegisteredKey() 177 | { 178 | if(!Directory.Exists(Network.Name)) 179 | Directory.CreateDirectory(Network.Name); 180 | 181 | BitcoinSecret k = null; 182 | var keyFile = Path.Combine(Network.Name, "key.env"); 183 | try 184 | { 185 | k = new BitcoinSecret(File.ReadAllText(keyFile), Network); 186 | } 187 | catch { } 188 | 189 | if(k != null) 190 | { 191 | try 192 | { 193 | 194 | Bitpay = new Bitpay(k.PrivateKey, BitPayUri); 195 | if(Bitpay.TestAccess(Facade.Merchant)) 196 | return; 197 | } 198 | catch { } 199 | } 200 | 201 | k = k ?? new BitcoinSecret(new Key(), Network); 202 | File.WriteAllText(keyFile, k.ToString()); 203 | 204 | Bitpay = new Bitpay(k.PrivateKey, BitPayUri); 205 | var pairing = Bitpay.RequestClientAuthorization("test", Facade.Merchant); 206 | 207 | 208 | throw new AssertException("You need to approve the test key to access bitpay by going to this link " + pairing.CreateLink(Bitpay.BaseUrl).AbsoluteUri); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /NBitpayClient.Tests/Logging/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Console; 3 | using Microsoft.Extensions.Logging.Console.Internal; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace NBitpayClient.Tests 12 | { 13 | /// 14 | /// A variant of ASP.NET Core ConsoleLogger which does not make new line for the category 15 | /// 16 | public class CustomerConsoleLogger : ILogger 17 | { 18 | private static readonly string _loglevelPadding = ": "; 19 | private static readonly string _messagePadding; 20 | private static readonly string _newLineWithMessagePadding; 21 | 22 | // ConsoleColor does not have a value to specify the 'Default' color 23 | private readonly ConsoleColor? DefaultConsoleColor = null; 24 | 25 | private readonly ConsoleLoggerProcessor _queueProcessor; 26 | private Func _filter; 27 | 28 | [ThreadStatic] 29 | private static StringBuilder _logBuilder; 30 | 31 | static CustomerConsoleLogger() 32 | { 33 | var logLevelString = GetLogLevelString(LogLevel.Information); 34 | _messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length); 35 | _newLineWithMessagePadding = Environment.NewLine + _messagePadding; 36 | } 37 | 38 | public CustomerConsoleLogger(string name, Func filter, bool includeScopes) 39 | : this(name, filter, includeScopes, new ConsoleLoggerProcessor()) 40 | { 41 | } 42 | 43 | internal CustomerConsoleLogger(string name, Func filter, bool includeScopes, ConsoleLoggerProcessor loggerProcessor) 44 | { 45 | if(name == null) 46 | { 47 | throw new ArgumentNullException(nameof(name)); 48 | } 49 | 50 | Name = name; 51 | Filter = filter ?? ((category, logLevel) => true); 52 | IncludeScopes = includeScopes; 53 | 54 | _queueProcessor = loggerProcessor; 55 | 56 | if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 57 | { 58 | Console = new WindowsLogConsole(); 59 | } 60 | else 61 | { 62 | Console = new AnsiLogConsole(new AnsiSystemConsole()); 63 | } 64 | } 65 | 66 | public IConsole Console 67 | { 68 | get 69 | { 70 | return _queueProcessor.Console; 71 | } 72 | set 73 | { 74 | if(value == null) 75 | { 76 | throw new ArgumentNullException(nameof(value)); 77 | } 78 | 79 | _queueProcessor.Console = value; 80 | } 81 | } 82 | 83 | public Func Filter 84 | { 85 | get 86 | { 87 | return _filter; 88 | } 89 | set 90 | { 91 | if(value == null) 92 | { 93 | throw new ArgumentNullException(nameof(value)); 94 | } 95 | 96 | _filter = value; 97 | } 98 | } 99 | 100 | public bool IncludeScopes 101 | { 102 | get; set; 103 | } 104 | 105 | public string Name 106 | { 107 | get; 108 | } 109 | 110 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 111 | { 112 | if(!IsEnabled(logLevel)) 113 | { 114 | return; 115 | } 116 | 117 | if(formatter == null) 118 | { 119 | throw new ArgumentNullException(nameof(formatter)); 120 | } 121 | 122 | var message = formatter(state, exception); 123 | 124 | if(!string.IsNullOrEmpty(message) || exception != null) 125 | { 126 | WriteMessage(logLevel, Name, eventId.Id, message, exception); 127 | } 128 | } 129 | 130 | public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception) 131 | { 132 | var logBuilder = _logBuilder; 133 | _logBuilder = null; 134 | 135 | if(logBuilder == null) 136 | { 137 | logBuilder = new StringBuilder(); 138 | } 139 | 140 | var logLevelColors = default(ConsoleColors); 141 | var logLevelString = string.Empty; 142 | 143 | // Example: 144 | // INFO: ConsoleApp.Program[10] 145 | // Request received 146 | 147 | logLevelColors = GetLogLevelConsoleColors(logLevel); 148 | logLevelString = GetLogLevelString(logLevel); 149 | // category and event id 150 | var lenBefore = logBuilder.ToString().Length; 151 | logBuilder.Append(_loglevelPadding); 152 | logBuilder.Append(logName); 153 | logBuilder.Append(": "); 154 | var lenAfter = logBuilder.ToString().Length; 155 | while(lenAfter++ < 18) 156 | logBuilder.Append(" "); 157 | // scope information 158 | if(IncludeScopes) 159 | { 160 | GetScopeInformation(logBuilder); 161 | } 162 | 163 | if(!string.IsNullOrEmpty(message)) 164 | { 165 | // message 166 | //logBuilder.Append(_messagePadding); 167 | 168 | var len = logBuilder.Length; 169 | logBuilder.AppendLine(message); 170 | logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length); 171 | } 172 | 173 | // Example: 174 | // System.InvalidOperationException 175 | // at Namespace.Class.Function() in File:line X 176 | if(exception != null) 177 | { 178 | // exception message 179 | logBuilder.AppendLine(exception.ToString()); 180 | } 181 | 182 | if(logBuilder.Length > 0) 183 | { 184 | var hasLevel = !string.IsNullOrEmpty(logLevelString); 185 | // Queue log message 186 | _queueProcessor.EnqueueMessage(new LogMessageEntry() 187 | { 188 | Message = logBuilder.ToString(), 189 | MessageColor = DefaultConsoleColor, 190 | LevelString = hasLevel ? logLevelString : null, 191 | LevelBackground = hasLevel ? logLevelColors.Background : null, 192 | LevelForeground = hasLevel ? logLevelColors.Foreground : null 193 | }); 194 | } 195 | 196 | logBuilder.Clear(); 197 | if(logBuilder.Capacity > 1024) 198 | { 199 | logBuilder.Capacity = 1024; 200 | } 201 | _logBuilder = logBuilder; 202 | } 203 | 204 | public bool IsEnabled(LogLevel logLevel) 205 | { 206 | return Filter(Name, logLevel); 207 | } 208 | 209 | public IDisposable BeginScope(TState state) 210 | { 211 | if(state == null) 212 | { 213 | throw new ArgumentNullException(nameof(state)); 214 | } 215 | 216 | return ConsoleLogScope.Push(Name, state); 217 | } 218 | 219 | private static string GetLogLevelString(LogLevel logLevel) 220 | { 221 | switch(logLevel) 222 | { 223 | case LogLevel.Trace: 224 | return "trce"; 225 | case LogLevel.Debug: 226 | return "dbug"; 227 | case LogLevel.Information: 228 | return "info"; 229 | case LogLevel.Warning: 230 | return "warn"; 231 | case LogLevel.Error: 232 | return "fail"; 233 | case LogLevel.Critical: 234 | return "crit"; 235 | default: 236 | throw new ArgumentOutOfRangeException(nameof(logLevel)); 237 | } 238 | } 239 | 240 | private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel) 241 | { 242 | // We must explicitly set the background color if we are setting the foreground color, 243 | // since just setting one can look bad on the users console. 244 | switch(logLevel) 245 | { 246 | case LogLevel.Critical: 247 | return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red); 248 | case LogLevel.Error: 249 | return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red); 250 | case LogLevel.Warning: 251 | return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black); 252 | case LogLevel.Information: 253 | return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black); 254 | case LogLevel.Debug: 255 | return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black); 256 | case LogLevel.Trace: 257 | return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black); 258 | default: 259 | return new ConsoleColors(DefaultConsoleColor, DefaultConsoleColor); 260 | } 261 | } 262 | 263 | private void GetScopeInformation(StringBuilder builder) 264 | { 265 | var current = ConsoleLogScope.Current; 266 | string scopeLog = string.Empty; 267 | var length = builder.Length; 268 | 269 | while(current != null) 270 | { 271 | if(length == builder.Length) 272 | { 273 | scopeLog = $"=> {current}"; 274 | } 275 | else 276 | { 277 | scopeLog = $"=> {current} "; 278 | } 279 | 280 | builder.Insert(length, scopeLog); 281 | current = current.Parent; 282 | } 283 | if(builder.Length > length) 284 | { 285 | builder.Insert(length, _messagePadding); 286 | builder.AppendLine(); 287 | } 288 | } 289 | 290 | private struct ConsoleColors 291 | { 292 | public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background) 293 | { 294 | Foreground = foreground; 295 | Background = background; 296 | } 297 | 298 | public ConsoleColor? Foreground 299 | { 300 | get; 301 | } 302 | 303 | public ConsoleColor? Background 304 | { 305 | get; 306 | } 307 | } 308 | 309 | private class AnsiSystemConsole : IAnsiSystemConsole 310 | { 311 | public void Write(string message) 312 | { 313 | System.Console.Write(message); 314 | } 315 | 316 | public void WriteLine(string message) 317 | { 318 | System.Console.WriteLine(message); 319 | } 320 | } 321 | } 322 | 323 | public class ConsoleLoggerProcessor : IDisposable 324 | { 325 | private const int _maxQueuedMessages = 1024; 326 | 327 | private readonly BlockingCollection _messageQueue = new BlockingCollection(_maxQueuedMessages); 328 | private readonly Task _outputTask; 329 | 330 | public IConsole Console; 331 | 332 | public ConsoleLoggerProcessor() 333 | { 334 | // Start Console message queue processor 335 | _outputTask = Task.Factory.StartNew( 336 | ProcessLogQueue, 337 | this, 338 | TaskCreationOptions.LongRunning); 339 | } 340 | 341 | public virtual void EnqueueMessage(LogMessageEntry message) 342 | { 343 | if(!_messageQueue.IsAddingCompleted) 344 | { 345 | try 346 | { 347 | _messageQueue.Add(message); 348 | return; 349 | } 350 | catch(InvalidOperationException) { } 351 | } 352 | 353 | // Adding is completed so just log the message 354 | WriteMessage(message); 355 | } 356 | 357 | // for testing 358 | internal virtual void WriteMessage(LogMessageEntry message) 359 | { 360 | if(message.LevelString != null) 361 | { 362 | Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground); 363 | } 364 | 365 | Console.Write(message.Message, message.MessageColor, message.MessageColor); 366 | Console.Flush(); 367 | } 368 | 369 | private void ProcessLogQueue() 370 | { 371 | foreach(var message in _messageQueue.GetConsumingEnumerable()) 372 | { 373 | WriteMessage(message); 374 | } 375 | } 376 | 377 | private static void ProcessLogQueue(object state) 378 | { 379 | var consoleLogger = (ConsoleLoggerProcessor)state; 380 | 381 | consoleLogger.ProcessLogQueue(); 382 | } 383 | 384 | public void Dispose() 385 | { 386 | _messageQueue.CompleteAdding(); 387 | 388 | try 389 | { 390 | _outputTask.Wait(1500); // with timeout in-case Console is locked by user input 391 | } 392 | catch(TaskCanceledException) { } 393 | catch(AggregateException ex) when(ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { } 394 | } 395 | } 396 | 397 | public struct LogMessageEntry 398 | { 399 | public string LevelString; 400 | public ConsoleColor? LevelBackground; 401 | public ConsoleColor? LevelForeground; 402 | public ConsoleColor? MessageColor; 403 | public string Message; 404 | } 405 | 406 | } 407 | -------------------------------------------------------------------------------- /NBitpayClient/Invoice.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using NBitcoin; 6 | using NBitpayClient.JsonConverters; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace NBitpayClient 10 | { 11 | public class MinerFeeInfo 12 | { 13 | [JsonProperty("satoshisPerByte")] 14 | public decimal SatoshiPerBytes 15 | { 16 | get; set; 17 | } 18 | [JsonProperty("totalFee")] 19 | public decimal TotalFee 20 | { 21 | get; set; 22 | } 23 | } 24 | 25 | public class InvoiceCryptoInfo 26 | { 27 | [JsonProperty("cryptoCode")] 28 | public string CryptoCode 29 | { 30 | get; set; 31 | } 32 | 33 | [JsonProperty("paymentType")] 34 | public string PaymentType 35 | { 36 | get; set; 37 | } 38 | 39 | [JsonProperty("rate")] 40 | public decimal Rate 41 | { 42 | get; set; 43 | } 44 | 45 | //"exRates":{"USD":4320.02} 46 | [JsonProperty("exRates")] 47 | public Dictionary ExRates 48 | { 49 | get; set; 50 | } 51 | 52 | //"btcPaid":"0.000000" 53 | [JsonProperty("paid")] 54 | public string Paid 55 | { 56 | get; set; 57 | } 58 | 59 | //"btcPrice":"0.001157" 60 | [JsonProperty("price")] 61 | public string Price 62 | { 63 | get; set; 64 | } 65 | 66 | //"btcDue":"0.001160" 67 | /// 68 | /// Amount of crypto remaining to pay this invoice 69 | /// 70 | [JsonProperty("due")] 71 | public string Due 72 | { 73 | get; set; 74 | } 75 | 76 | [JsonProperty("paymentUrls")] 77 | public NBitpayClient.InvoicePaymentUrls PaymentUrls 78 | { 79 | get; set; 80 | } 81 | 82 | [JsonProperty("address")] 83 | public string Address 84 | { 85 | get; set; 86 | } 87 | [JsonProperty("url")] 88 | public string Url 89 | { 90 | get; set; 91 | } 92 | 93 | /// 94 | /// Total amount of this invoice 95 | /// 96 | [JsonProperty("totalDue")] 97 | public string TotalDue 98 | { 99 | get; set; 100 | } 101 | 102 | /// 103 | /// Total amount of network fee to pay to the invoice 104 | /// 105 | [JsonProperty("networkFee")] 106 | public string NetworkFee 107 | { 108 | get; set; 109 | } 110 | 111 | /// 112 | /// Number of transactions required to pay 113 | /// 114 | [JsonProperty("txCount")] 115 | public int TxCount 116 | { 117 | get; set; 118 | } 119 | 120 | /// 121 | /// Total amount of the invoice paid in this crypto 122 | /// 123 | [JsonProperty("cryptoPaid")] 124 | public string CryptoPaid 125 | { 126 | get; set; 127 | } 128 | /// 129 | /// Payments made to the invoice in this crypto 130 | /// 131 | [JsonProperty("payments")] 132 | public List Payments { get; set; } 133 | } 134 | 135 | public class InvoicePaymentInfo 136 | { 137 | public string Id { get; set; } 138 | public DateTime ReceivedDate { get; set; } 139 | public decimal Value { get; set; } 140 | public decimal Fee { get; set; } 141 | public string PaymentType { get; set; } 142 | public bool Confirmed { get; set; } 143 | public bool Completed { get; set; } 144 | public string Destination { get; set; } 145 | } 146 | 147 | public class Invoice 148 | { 149 | public const String STATUS_NEW = "new"; 150 | public const String STATUS_PAID = "paid"; 151 | public const String STATUS_CONFIRMED = "confirmed"; 152 | public const String STATUS_COMPLETE = "complete"; 153 | public const String STATUS_INVALID = "invalid"; 154 | public const String EXSTATUS_FALSE = "false"; 155 | public const String EXSTATUS_PAID_OVER = "paidOver"; 156 | public const String EXSTATUS_PAID_PARTIAL = "paidPartial"; 157 | 158 | /// 159 | /// Creates an uninitialized invoice request object. 160 | /// 161 | public Invoice() { } 162 | 163 | // Creates a minimal inovice request object. 164 | public Invoice(decimal price, String currency) 165 | { 166 | Price = price; 167 | Currency = currency; 168 | } 169 | 170 | // API fields 171 | // 172 | 173 | [JsonProperty(PropertyName = "buyer")] 174 | public Buyer Buyer { get; set; } 175 | 176 | [JsonProperty(PropertyName = "guid")] 177 | public string Guid { get; set; } 178 | 179 | [JsonProperty(PropertyName = "nonce")] 180 | public long Nonce { get; set; } 181 | public virtual bool ShouldSerializeNonce() { return Nonce != 0; } 182 | 183 | [JsonProperty(PropertyName = "token")] 184 | public string Token { get; set; } 185 | 186 | // Required fields 187 | // 188 | 189 | [JsonProperty(PropertyName = "price")] 190 | public decimal Price { get; set; } 191 | 192 | /// 193 | /// How much tax are included in the price. Only supported by BTCPay Server. 194 | /// 195 | [JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)] 196 | public decimal TaxIncluded { get; set; } 197 | 198 | String _currency = ""; 199 | [JsonProperty(PropertyName = "currency")] 200 | public string Currency 201 | { 202 | get { return _currency; } 203 | set 204 | { 205 | _currency = value; 206 | } 207 | } 208 | 209 | // Optional fields 210 | // 211 | 212 | [JsonProperty(PropertyName = "orderId")] 213 | public string OrderId { get; set; } 214 | public virtual bool ShouldSerializeOrderId() { return !String.IsNullOrEmpty(OrderId); } 215 | 216 | [JsonProperty(PropertyName = "itemDesc")] 217 | public string ItemDesc { get; set; } 218 | public virtual bool ShouldSerializeItemDesc() { return !String.IsNullOrEmpty(ItemDesc); } 219 | 220 | [JsonProperty(PropertyName = "itemCode")] 221 | public string ItemCode { get; set; } 222 | public virtual bool ShouldSerializeItemCode() { return !String.IsNullOrEmpty(ItemCode); } 223 | 224 | [JsonProperty(PropertyName = "posData")] 225 | public string PosData { get; set; } 226 | public virtual bool ShouldSerializePosData() { return !String.IsNullOrEmpty(PosData); } 227 | 228 | [JsonProperty(PropertyName = "notificationURL")] 229 | public string NotificationURL { get; set; } 230 | public virtual bool ShouldSerializeNotificationURL() { return !String.IsNullOrEmpty(NotificationURL); } 231 | 232 | [JsonProperty(PropertyName = "transactionSpeed")] 233 | public string TransactionSpeed { get; set; } 234 | public virtual bool ShouldSerializeTransactionSpeed() { return !String.IsNullOrEmpty(TransactionSpeed); } 235 | 236 | [JsonProperty(PropertyName = "fullNotifications")] 237 | public bool FullNotifications { get; set; } 238 | public virtual bool ShouldSerializeFullNotifications() { return FullNotifications; } 239 | 240 | [JsonProperty(PropertyName = "extendedNotifications")] 241 | public bool ExtendedNotifications 242 | { 243 | get; set; 244 | } 245 | public virtual bool ShouldSerializextendedNotifications() 246 | { 247 | return ExtendedNotifications; 248 | } 249 | 250 | [JsonProperty(PropertyName = "notificationEmail")] 251 | public string NotificationEmail { get; set; } 252 | public virtual bool ShouldSerializeNotificationEmail() { return !String.IsNullOrEmpty(NotificationEmail); } 253 | 254 | [JsonProperty(PropertyName = "redirectURL")] 255 | public string RedirectURL { get; set; } 256 | public virtual bool ShouldSerializeRedirectURL() { return !String.IsNullOrEmpty(RedirectURL); } 257 | 258 | [JsonProperty(PropertyName = "physical")] 259 | public bool Physical { get; set; } 260 | public virtual bool ShouldSerializePhysical() { return Physical; } 261 | 262 | [JsonProperty(PropertyName = "buyerName")] 263 | public string BuyerName { get; set; } 264 | public virtual bool ShouldSerializeBuyerName() { return !String.IsNullOrEmpty(BuyerName); } 265 | 266 | [JsonProperty(PropertyName = "buyerAddress1")] 267 | public string BuyerAddress1 { get; set; } 268 | public virtual bool ShouldSerializeBuyerAddress1() { return !String.IsNullOrEmpty(BuyerAddress1); } 269 | 270 | [JsonProperty(PropertyName = "buyerAddress2")] 271 | public string BuyerAddress2 { get; set; } 272 | public virtual bool ShouldSerializeBuyerAddress2() { return !String.IsNullOrEmpty(BuyerAddress2); } 273 | 274 | [JsonProperty(PropertyName = "buyerCity")] 275 | public string BuyerCity { get; set; } 276 | public virtual bool ShouldSerializeBuyerCity() { return !String.IsNullOrEmpty(BuyerCity); } 277 | 278 | [JsonProperty(PropertyName = "buyerState")] 279 | public string BuyerState { get; set; } 280 | public virtual bool ShouldSerializeBuyerState() { return !String.IsNullOrEmpty(BuyerState); } 281 | 282 | [JsonProperty(PropertyName = "buyerZip")] 283 | public string BuyerZip { get; set; } 284 | public virtual bool ShouldSerializeBuyerZip() { return !String.IsNullOrEmpty(BuyerZip); } 285 | 286 | [JsonProperty(PropertyName = "buyerCountry")] 287 | public string BuyerCountry { get; set; } 288 | public virtual bool ShouldSerializeBuyerCountry() { return !String.IsNullOrEmpty(BuyerCountry); } 289 | 290 | [JsonProperty(PropertyName = "buyerEmail")] 291 | public string BuyerEmail { get; set; } 292 | public virtual bool ShouldSerializeBuyerEmail() { return !String.IsNullOrEmpty(BuyerEmail); } 293 | 294 | [JsonProperty(PropertyName = "buyerPhone")] 295 | public string BuyerPhone { get; set; } 296 | public virtual bool ShouldSerializeBuyerPhone() { return !String.IsNullOrEmpty(BuyerPhone); } 297 | 298 | [JsonProperty(PropertyName = "paymentCurrencies")] 299 | public string[] PaymentCurrencies { get; set; } 300 | public virtual bool ShouldSerializePaymentCurrencies() { return PaymentCurrencies?.Length > 0 == true; } 301 | 302 | // Response fields 303 | // 304 | 305 | public string Id { get; set; } 306 | public virtual bool ShouldSerializeId() { return false; } 307 | 308 | public string Url { get; set; } 309 | public virtual bool ShouldSerializeUrl() { return false; } 310 | 311 | public string Status { get; set; } 312 | public bool IsPaid() 313 | { 314 | var paid = new[] { "paid", "confirmed", "complete" }; 315 | return Status != null && paid.Contains(Status); 316 | } 317 | public virtual bool ShouldSerializeStatus() { return false; } 318 | 319 | [JsonConverter(typeof(MoneyJsonConverter))] 320 | public Money BtcPrice { get; set; } 321 | public virtual bool ShouldSerializeBtcPrice() { return false; } 322 | 323 | [JsonConverter(typeof(DateTimeJsonConverter))] 324 | public DateTimeOffset InvoiceTime { get; set; } 325 | public virtual bool ShouldSerializeInvoiceTime() { return false; } 326 | 327 | [JsonConverter(typeof(DateTimeJsonConverter))] 328 | public DateTimeOffset ExpirationTime { get; set; } 329 | public virtual bool ShouldSerializeExpirationTime() { return ExpirationTime != default; } 330 | 331 | [JsonConverter(typeof(DateTimeJsonConverter))] 332 | public DateTimeOffset CurrentTime { get; set; } 333 | public virtual bool ShouldSerializeCurrentTime() { return false; } 334 | 335 | [JsonConverter(typeof(MoneyJsonConverter))] 336 | public Money BtcPaid { get; set; } 337 | public virtual bool ShouldSerializeBtcPaid() { return false; } 338 | 339 | [JsonConverter(typeof(MoneyJsonConverter))] 340 | public Money BtcDue { get; set; } 341 | public virtual bool ShouldSerializeBtcDue() { return false; } 342 | 343 | [JsonProperty("cryptoInfo")] 344 | public InvoiceCryptoInfo[] CryptoInfo 345 | { 346 | get; set; 347 | } 348 | public virtual bool ShouldSerializeCryptoInfo() 349 | { 350 | return CryptoInfo != null; 351 | } 352 | 353 | public List Transactions { get; set; } 354 | public virtual bool ShouldSerializeTransactions() { return false; } 355 | 356 | public decimal Rate { get; set; } 357 | public virtual bool ShouldSerializeRate() { return false; } 358 | 359 | public Dictionary ExRates { get; set; } 360 | public virtual bool ShouldSerializeExRates() { return false; } 361 | 362 | public JToken ExceptionStatus { get; set; } 363 | public virtual bool ShouldSerializeExceptionStatus() { return false; } 364 | 365 | public InvoicePaymentUrls PaymentUrls { get; set; } 366 | public virtual bool ShouldSerializePaymentUrls() { return false; } 367 | 368 | public string BitcoinAddress 369 | { 370 | get; set; 371 | } 372 | public bool ShouldBitcoinAddress() 373 | { 374 | return false; 375 | } 376 | 377 | public bool Refundable 378 | { 379 | get { return this.Flags != null && this.Flags.Refundable; } 380 | } 381 | public virtual bool ShouldSerializeRefundable() { return false; } 382 | 383 | [Newtonsoft.Json.JsonProperty] 384 | private Flags Flags { get; set; } 385 | public virtual bool ShouldSerializeFlags() { return false; } 386 | 387 | public Dictionary PaymentSubtotals { get; set; } 388 | 389 | public Dictionary PaymentTotals { get; set; } 390 | public decimal AmountPaid { get; set; } 391 | 392 | public Dictionary> ExchangeRates { get; set; } 393 | 394 | public Dictionary SupportedTransactionCurrencies { get; set; } 395 | 396 | public Dictionary MinerFees { get; set; } 397 | 398 | public Dictionary Addresses { get; set; } 399 | public virtual bool ShouldSerializeAddresses() { return false; } 400 | 401 | public Dictionary PaymentCodes 402 | { 403 | get; set; 404 | } 405 | public virtual bool ShouldSerializePaymentCodes() { return false; } 406 | 407 | public string TransactionCurrency { get; set; } 408 | public bool ShouldSerializeTransactionCurrency() { return false; } 409 | 410 | public List RefundInfo { get; set; } 411 | public bool ShouldSerializeRefundInfo() { return false; } 412 | 413 | public bool RefundAddressRequestPending { get; set; } 414 | public bool ShouldSerializeRefundAddressRequestPending() { return false; } 415 | 416 | public List> RefundAddresses { get; set; } 417 | public bool ShouldSerializeRefundAddresses() { return false; } 418 | 419 | [JsonProperty("redirectAutomatically")] 420 | public bool? RedirectAutomatically { get; set; } 421 | public bool ShouldSerializeRedirectAutomatically() { return RedirectAutomatically is bool; } 422 | 423 | } 424 | 425 | class Flags 426 | { 427 | public bool Refundable { get; set; } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /NBitpayClient/BitPay.cs: -------------------------------------------------------------------------------- 1 | using NBitcoin; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Globalization; 8 | using System.Linq; 9 | using System.Net.Http; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using NBitpayClient.Extensions; 13 | 14 | namespace NBitpayClient 15 | { 16 | 17 | public class Facade 18 | { 19 | public static Facade Merchant = new Facade("merchant"); 20 | public static Facade PointOfSale = new Facade("pos"); 21 | public static Facade User = new Facade("user"); 22 | public static Facade Payroll = new Facade("payroll"); 23 | readonly string _Value; 24 | 25 | public Facade(string value) 26 | { 27 | if(value == null) 28 | throw new ArgumentNullException(nameof(value)); 29 | _Value = value; 30 | } 31 | 32 | public override bool Equals(object obj) 33 | { 34 | Facade item = obj as Facade; 35 | if(item == null) 36 | return false; 37 | return _Value.Equals(item._Value); 38 | } 39 | 40 | public static bool operator ==(Facade a, Facade b) 41 | { 42 | if(ReferenceEquals(a, b)) 43 | return true; 44 | if((object)a == null || (object)b == null) 45 | return false; 46 | return a._Value == b._Value; 47 | } 48 | 49 | public static bool operator !=(Facade a, Facade b) 50 | { 51 | return !(a == b); 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return _Value.GetHashCode(); 57 | } 58 | 59 | public override string ToString() 60 | { 61 | return _Value; 62 | } 63 | } 64 | 65 | public class PairingCode 66 | { 67 | public PairingCode(string value) 68 | { 69 | _Value = value ?? throw new ArgumentNullException(nameof(value)); 70 | } 71 | 72 | readonly string _Value; 73 | public override string ToString() 74 | { 75 | return _Value; 76 | } 77 | 78 | public Uri CreateLink(Uri baseUrl) 79 | { 80 | var link = baseUrl.AbsoluteUri; 81 | if(!link.EndsWith("/")) 82 | link = link + "/"; 83 | link = link + "api-access-request?pairingCode=" + _Value; 84 | return new Uri(link); 85 | } 86 | } 87 | 88 | public class Bitpay 89 | { 90 | public class AccessToken 91 | { 92 | public string Key 93 | { 94 | get; set; 95 | } 96 | public string Value 97 | { 98 | get; set; 99 | } 100 | } 101 | class AuthInformation 102 | { 103 | public AuthInformation(Key key) 104 | { 105 | if(key == null) 106 | throw new ArgumentNullException(nameof(key)); 107 | SIN = key.PubKey.GetBitIDSIN(); 108 | Key = key; 109 | } 110 | 111 | public Key Key 112 | { 113 | get; 114 | private set; 115 | } 116 | 117 | public string SIN 118 | { 119 | get; 120 | private set; 121 | } 122 | 123 | internal void Sign(HttpRequestMessage message) 124 | { 125 | var uri = message.RequestUri.AbsoluteUri; 126 | var body = message.Content?.ReadAsStringAsync()?.GetAwaiter().GetResult(); //no await is ok, no network access 127 | 128 | message.Headers.Add("x-signature", Key.GetBitIDSignature(uri, body)); 129 | message.Headers.Add("x-identity", Key.PubKey.ToHex()); 130 | 131 | } 132 | 133 | ConcurrentDictionary _Tokens = new ConcurrentDictionary(); 134 | public AccessToken GetAccessToken(Facade requirement) 135 | { 136 | var token = _Tokens.TryGet(requirement.ToString()); 137 | if(token == null && (requirement == Facade.User || requirement == Facade.PointOfSale)) 138 | { 139 | token = _Tokens.TryGet(Facade.PointOfSale.ToString()) ?? _Tokens.TryGet(Facade.Merchant.ToString()); 140 | } 141 | return token; 142 | } 143 | 144 | public void SaveTokens(AccessToken[] tokens) 145 | { 146 | foreach(var token in tokens) 147 | _Tokens.TryAdd(token.Key, token); 148 | } 149 | 150 | public void SaveToken(string key, string value) 151 | { 152 | _Tokens.TryAdd(key, new AccessToken() { Key = key, Value = value }); 153 | } 154 | } 155 | 156 | 157 | 158 | 159 | private const String BITPAY_API_VERSION = "2.0.0"; 160 | 161 | private Uri _baseUrl = null; 162 | AuthInformation _Auth; 163 | static HttpClient _Client = new HttpClient(); 164 | 165 | public string SIN 166 | { 167 | get 168 | { 169 | return _Auth.SIN; 170 | } 171 | } 172 | 173 | public Uri BaseUrl 174 | { 175 | get 176 | { 177 | return _baseUrl; 178 | } 179 | } 180 | 181 | /// 182 | /// Constructor for use if the keys and SIN were derived external to this library. 183 | /// 184 | /// An elliptical curve key. 185 | /// The label for this client. 186 | /// The target server URL. 187 | public Bitpay(Key ecKey, Uri envUrl) 188 | { 189 | if(ecKey == null) 190 | throw new ArgumentNullException(nameof(ecKey)); 191 | if(envUrl == null) 192 | throw new ArgumentNullException(nameof(envUrl)); 193 | _Auth = new AuthInformation(ecKey); 194 | _baseUrl = envUrl; 195 | } 196 | 197 | /// 198 | /// Authorize (pair) this client with the server using the specified pairing code. 199 | /// 200 | /// A code obtained from the server; typically from bitpay.com/api-tokens. 201 | public virtual async Task AuthorizeClient(PairingCode pairingCode) 202 | { 203 | Token token = new Token(); 204 | token.Id = _Auth.SIN; 205 | token.Guid = Guid.NewGuid().ToString(); 206 | token.PairingCode = pairingCode.ToString(); 207 | token.Label = "DEFAULT"; 208 | String json = JsonConvert.SerializeObject(token); 209 | HttpResponseMessage response = await this.PostAsync("tokens", json).ConfigureAwait(false); 210 | var tokens = await this.ParseResponse>(response).ConfigureAwait(false); 211 | foreach(Token t in tokens) 212 | { 213 | _Auth.SaveToken(t.Facade, t.Value); 214 | } 215 | } 216 | 217 | /// 218 | /// Request authorization (a token) for this client in the specified facade. 219 | /// 220 | /// The label of this token. 221 | /// The facade for which authorization is requested. 222 | /// A pairing code for this client. This code must be used to authorize this client at BitPay.com/api-tokens. 223 | public virtual PairingCode RequestClientAuthorization(string label, Facade facade) 224 | { 225 | return RequestClientAuthorizationAsync(label, facade).GetAwaiter().GetResult(); 226 | } 227 | 228 | /// 229 | /// Request authorization (a token) for this client in the specified facade. 230 | /// 231 | /// The label of this token. 232 | /// The facade for which authorization is requested. 233 | /// A pairing code for this client. This code must be used to authorize this client at BitPay.com/api-tokens. 234 | public virtual async Task RequestClientAuthorizationAsync(string label, Facade facade) 235 | { 236 | Token token = new Token(); 237 | token.Id = _Auth.SIN; 238 | token.Guid = Guid.NewGuid().ToString(); 239 | token.Facade = facade.ToString(); 240 | token.Count = 1; 241 | token.Label = label ?? "DEFAULT"; 242 | String json = JsonConvert.SerializeObject(token); 243 | HttpResponseMessage response = await this.PostAsync("tokens", json).ConfigureAwait(false); 244 | var tokens = await this.ParseResponse>(response).ConfigureAwait(false); 245 | // Expecting a single token resource. 246 | if(tokens.Count != 1) 247 | { 248 | throw new BitPayException("Error - failed to get token resource; expected 1 token, got " + tokens.Count); 249 | } 250 | _Auth.SaveToken(tokens[0].Facade, tokens[0].Value); 251 | return new PairingCode(tokens[0].PairingCode); 252 | } 253 | 254 | /// 255 | /// Create an invoice using the specified facade. 256 | /// 257 | /// An invoice request object. 258 | /// The facade used (default POS). 259 | /// A new invoice object returned from the server. 260 | public virtual Invoice CreateInvoice(Invoice invoice, Facade facade = null) 261 | { 262 | return CreateInvoiceAsync(invoice, facade).GetAwaiter().GetResult(); 263 | } 264 | 265 | /// 266 | /// Create an invoice using the specified facade. 267 | /// 268 | /// An invoice request object. 269 | /// The facade used (default POS). 270 | /// A new invoice object returned from the server. 271 | public virtual async Task CreateInvoiceAsync(Invoice invoice, Facade facade = null) 272 | { 273 | facade = facade ?? Facade.PointOfSale; 274 | invoice.Token = (await this.GetAccessTokenAsync(facade).ConfigureAwait(false)).Value; 275 | invoice.Guid = Guid.NewGuid().ToString(); 276 | String json = JsonConvert.SerializeObject(invoice); 277 | HttpResponseMessage response = await this.PostAsync("invoices", json, true).ConfigureAwait(false); 278 | invoice = await this.ParseResponse(response).ConfigureAwait(false); 279 | // Track the token for this invoice 280 | //_Auth.SaveToken(invoice.Id, invoice.Token); 281 | return invoice; 282 | } 283 | 284 | /// 285 | /// Retrieve an invoice by id and token. 286 | /// 287 | /// The id of the requested invoice. 288 | /// The facade used (default POS). 289 | /// The invoice object retrieved from the server. 290 | public virtual Invoice GetInvoice(String invoiceId, Facade facade = null) 291 | { 292 | return GetInvoiceAsync(invoiceId, facade).GetAwaiter().GetResult(); 293 | } 294 | 295 | /// 296 | /// Retrieve an invoice by id and token. 297 | /// 298 | /// The id of the requested invoice. 299 | /// The facade used (default POS). 300 | /// The invoice object retrieved from the server. 301 | public virtual async Task GetInvoiceAsync(String invoiceId, Facade facade = null) 302 | { 303 | return await GetInvoiceAsync(invoiceId, facade); 304 | } 305 | 306 | /// 307 | /// Retrieve an invoice by id and token. 308 | /// 309 | /// The id of the requested invoice. 310 | /// The facade used (default POS). 311 | /// The invoice object retrieved from the server. 312 | public virtual async Task GetInvoiceAsync(String invoiceId, Facade facade = null) where T : Invoice 313 | { 314 | facade = facade ?? Facade.Merchant; 315 | // Provide the merchant token whenthe merchant facade is being used. 316 | // GET/invoices expects the merchant token and not the merchant/invoice token. 317 | 318 | HttpResponseMessage response = null; 319 | if (facade == Facade.Merchant) 320 | { 321 | var token = (await GetAccessTokenAsync(facade).ConfigureAwait(false)).Value; 322 | response = await this.GetAsync($"invoices/{invoiceId}?token={token}", true).ConfigureAwait(false); 323 | } 324 | else 325 | { 326 | response = await GetAsync($"invoices/" + invoiceId, true).ConfigureAwait(false); 327 | } 328 | 329 | return await ParseResponse(response).ConfigureAwait(false); 330 | } 331 | 332 | /// 333 | /// Retrieves settlement reports for the calling merchant filtered by query. The `limit` and `offset` parameters specify pages for large query sets. 334 | /// 335 | /// Date filter start (optional) 336 | /// Date filter end (optional) 337 | /// Currency filter (optional) 338 | /// Status filter (optional) 339 | /// Pagination entries ceiling (default:50) 340 | /// Pagination quantity offset (default:0) 341 | /// Array of Settlements 342 | public virtual SettlementSummary[] GetSettlements(DateTime? startDate = null, DateTime? endDate = null, string currency = null, string status = null, int limit = 50, int offset = 0) 343 | { 344 | return GetSettlementsAsync(startDate, endDate, currency, status, limit, offset).GetAwaiter().GetResult(); 345 | } 346 | 347 | /// 348 | /// Retrieves settlement reports for the calling merchant filtered by query. The `limit` and `offset` parameters specify pages for large query sets. 349 | /// 350 | /// Date filter start (optional) 351 | /// Date filter end (optional) 352 | /// Currency filter (optional) 353 | /// Status filter (optional) 354 | /// Pagination entries ceiling (default:50) 355 | /// Pagination quantity offset (default:0) 356 | /// Array of Settlements 357 | public virtual async Task GetSettlementsAsync(DateTime? startDate = null, DateTime? endDate = null, string currency = null, string status = null, int limit = 50, int offset = 0) 358 | { 359 | var token = await this.GetAccessTokenAsync(Facade.Merchant).ConfigureAwait(false); 360 | 361 | var parameters = new Dictionary(); 362 | parameters.Add("token", token.Value); 363 | if (startDate != null) 364 | parameters.Add("startDate", startDate.Value.ToString("d", CultureInfo.InvariantCulture)); 365 | if (endDate != null) 366 | parameters.Add("endDate", endDate.Value.ToString("d", CultureInfo.InvariantCulture)); 367 | if (currency != null) 368 | parameters.Add("currency", currency); 369 | if (status != null) 370 | parameters.Add("status", status); 371 | parameters.Add("limit", $"{limit}"); 372 | parameters.Add("offset", $"{offset}"); 373 | 374 | var response = await GetAsync("settlements" + BuildQuery(parameters), true).ConfigureAwait(false); 375 | return await ParseResponse(response).ConfigureAwait(false); 376 | } 377 | 378 | /// 379 | /// Retrieves a summary of the specified settlement. 380 | /// 381 | /// Id of the settlement to fetch 382 | /// Settlement object 383 | public virtual SettlementSummary GetSettlementSummary(string settlementId) 384 | { 385 | return GetSettlementSummaryAsync(settlementId).GetAwaiter().GetResult(); 386 | } 387 | 388 | /// 389 | /// Retrieves a summary of the specified settlement. 390 | /// 391 | /// Id of the settlement to fetch 392 | /// Settlement object 393 | public virtual async Task GetSettlementSummaryAsync(string settlementId) 394 | { 395 | var token = (await GetAccessTokenAsync(Facade.Merchant).ConfigureAwait(false)).Value; 396 | var response = await GetAsync($"settlements/{settlementId}?token={token}", true).ConfigureAwait(false); 397 | return await ParseResponse(response).ConfigureAwait(false); 398 | } 399 | 400 | /// 401 | /// Gets a detailed reconciliation report of the activity within the settlement period 402 | /// 403 | /// Id of the settlement to fetch 404 | /// Resource token from /settlements/{settlementId} 405 | /// SettlementReconciliationReport object 406 | public virtual SettlementReconciliationReport GetSettlementReconciliationReport(string settlementId, string settlementSummaryToken) 407 | { 408 | return GetSettlementReconciliationReportAsync(settlementId, settlementSummaryToken).GetAwaiter().GetResult(); 409 | } 410 | 411 | /// 412 | /// Gets a detailed reconciliation report of the activity within the settlement period 413 | /// 414 | /// Id of the settlement to fetch 415 | /// Resource token from /settlements/{settlementId} 416 | /// SettlementReconciliationReport object 417 | public virtual async Task GetSettlementReconciliationReportAsync(string settlementId, string settlementSummaryToken) 418 | { 419 | var response = await GetAsync($"settlements/{settlementId}/reconciliationReport?token={settlementSummaryToken}", true).ConfigureAwait(false); 420 | return await ParseResponse(response).ConfigureAwait(false); 421 | } 422 | 423 | /// 424 | /// Retrieve a list of invoices by date range using the merchant facade. 425 | /// 426 | /// The start date for the query. 427 | /// The end date for the query. 428 | /// A list of invoice objects retrieved from the server. 429 | public virtual Invoice[] GetInvoices(DateTime? dateStart = null, DateTime? dateEnd = null) 430 | { 431 | return GetInvoicesAsync(dateStart, dateEnd).GetAwaiter().GetResult(); 432 | } 433 | 434 | /// 435 | /// Retrieve a list of invoices by date range using the merchant facade. 436 | /// 437 | /// The start date for the query. 438 | /// The end date for the query. 439 | /// A list of invoice objects retrieved from the server. 440 | public virtual async Task GetInvoicesAsync(DateTime? dateStart = null, DateTime? dateEnd = null) 441 | { 442 | return await GetInvoicesAsync(dateStart, dateEnd); 443 | } 444 | 445 | /// 446 | /// Retrieve a list of invoices by date range using the merchant facade. 447 | /// 448 | /// The start date for the query. 449 | /// The end date for the query. 450 | /// A list of invoice objects retrieved from the server. 451 | public virtual async Task GetInvoicesAsync(DateTime? dateStart = null, DateTime? dateEnd = null) where T:Invoice 452 | { 453 | var token = await this.GetAccessTokenAsync(Facade.Merchant).ConfigureAwait(false); 454 | 455 | var parameters = new Dictionary(); 456 | parameters.Add("token", token.Value); 457 | if(dateStart != null) 458 | parameters.Add("dateStart", dateStart.Value.ToString("d", CultureInfo.InvariantCulture)); 459 | if(dateEnd != null) 460 | parameters.Add("dateEnd", dateEnd.Value.ToString("d", CultureInfo.InvariantCulture)); 461 | var response = await GetAsync($"invoices" + BuildQuery(parameters), true).ConfigureAwait(false); 462 | return await ParseResponse(response).ConfigureAwait(false); 463 | } 464 | 465 | private string BuildQuery(Dictionary parameters) 466 | { 467 | var result = string.Join("&", parameters 468 | .Select(p => $"{p.Key}={p.Value}") 469 | .ToArray()); 470 | if(string.IsNullOrEmpty(result)) 471 | return string.Empty; 472 | return "?" + result; 473 | } 474 | 475 | /// 476 | /// Retrieve the exchange rate table using the public facade. 477 | /// 478 | /// What currency to base the result rates on 479 | /// The rate table as an object retrieved from the server. 480 | public virtual Rates GetRates(string baseCurrencyCode = null) 481 | { 482 | return GetRatesAsync(baseCurrencyCode).GetAwaiter().GetResult(); 483 | } 484 | 485 | /// 486 | /// Retrieve the exchange rate table using the public facade. 487 | /// 488 | /// What currency to base the result rates on 489 | /// The rate table as an object retrieved from the server. 490 | public virtual async Task GetRatesAsync(string baseCurrencyCode = null) 491 | { 492 | var url = $"rates{(string.IsNullOrEmpty(baseCurrencyCode) ? $"/{baseCurrencyCode}" : String.Empty)}"; 493 | 494 | HttpResponseMessage response = await this.GetAsync(url, true).ConfigureAwait(false); 495 | var rates = await this.ParseResponse>(response).ConfigureAwait(false); 496 | return new Rates(rates); 497 | } 498 | 499 | /// 500 | /// Retrieves the exchange rate for the given currency using the public facade. 501 | /// 502 | /// The currency to base the result rate on 503 | /// The target currency to get the rate for 504 | /// The rate as an object retrieved from the server. 505 | public Rate GetRate(string baseCurrencyCode, string currencyCode) 506 | { 507 | return GetRateAsync(baseCurrencyCode, currencyCode).GetAwaiter().GetResult(); 508 | } 509 | 510 | /// 511 | /// Retrieves the exchange rate for the given currency using the public facade. 512 | /// 513 | /// The currency to base the result rate on 514 | /// The target currency to get the rate for 515 | /// The rate as an object retrieved from the server. 516 | public virtual async Task GetRateAsync(string baseCurrencyCode, string currencyCode) 517 | { 518 | var url = $"rates/{baseCurrencyCode}/{currencyCode}"; 519 | HttpResponseMessage response = await this.GetAsync(url, true).ConfigureAwait(false); 520 | var rate = await this.ParseResponse(response).ConfigureAwait(false); 521 | return rate; 522 | } 523 | 524 | /// 525 | /// Retrieves the caller's ledgers for each currency with summary. 526 | /// 527 | /// A list of ledger objects retrieved from the server. 528 | public virtual List GetLedgers() 529 | { 530 | return GetLedgersAsync().GetAwaiter().GetResult(); 531 | } 532 | 533 | /// 534 | /// Retrieves the caller's ledgers for each currency with summary. 535 | /// 536 | /// A list of ledger objects retrieved from the server. 537 | public virtual async Task> GetLedgersAsync() 538 | { 539 | var token = await this.GetAccessTokenAsync(Facade.Merchant).ConfigureAwait(false); 540 | 541 | Dictionary parameters = new Dictionary(); 542 | parameters.Add("token", token.Value); 543 | 544 | HttpResponseMessage response = await this.GetAsync($"ledgers/" + BuildQuery(parameters), true).ConfigureAwait(false); 545 | var ledgers = await this.ParseResponse>(response).ConfigureAwait(false); 546 | return ledgers; 547 | } 548 | 549 | /// 550 | /// Retrieve a list of ledger entries by date range using the merchant facade. 551 | /// 552 | /// The three digit currency string for the ledger to retrieve. 553 | /// The start date for the query. 554 | /// The end date for the query. 555 | /// A list of invoice objects retrieved from the server. 556 | public virtual Ledger GetLedger(String currency, DateTime? dateStart = null, DateTime? dateEnd = null) 557 | { 558 | return GetLedgerAsync(currency, dateStart, dateEnd).GetAwaiter().GetResult(); 559 | } 560 | 561 | /// 562 | /// Retrieve a list of ledger entries by date range using the merchant facade. 563 | /// 564 | /// The three digit currency string for the ledger to retrieve. 565 | /// The start date for the query. 566 | /// The end date for the query. 567 | /// A list of invoice objects retrieved from the server. 568 | public virtual async Task GetLedgerAsync(String currency, DateTime? dateStart = null, DateTime? dateEnd = null) 569 | { 570 | var token = await this.GetAccessTokenAsync(Facade.Merchant).ConfigureAwait(false); 571 | 572 | Dictionary parameters = new Dictionary(); 573 | parameters.Add("token", token.Value); 574 | if(dateStart != null) 575 | parameters.Add("startDate", dateStart.Value.ToString("d", CultureInfo.InvariantCulture)); 576 | if(dateEnd != null) 577 | parameters.Add("endDate", dateEnd.Value.ToString("d", CultureInfo.InvariantCulture)); 578 | 579 | HttpResponseMessage response = await this.GetAsync($"ledgers/{currency}" + BuildQuery(parameters), true).ConfigureAwait(false); 580 | var entries = await this.ParseResponse>(response).ConfigureAwait(false); 581 | return new Ledger(null, 0, entries); 582 | } 583 | 584 | private AccessToken[] ParseTokens(string response) 585 | { 586 | List tokens = new List(); 587 | try 588 | { 589 | JArray obj = (JArray)JObject.Parse(response).First.First; 590 | foreach(var jobj in obj.OfType()) 591 | { 592 | foreach(var prop in jobj.Properties()) 593 | { 594 | tokens.Add(new AccessToken() { Key = prop.Name, Value = prop.Value.Value() }); 595 | } 596 | } 597 | return tokens.ToArray(); 598 | } 599 | catch(Exception ex) 600 | { 601 | throw new BitPayException("Error: response to GET /tokens could not be parsed - " + ex.ToString()); 602 | } 603 | } 604 | 605 | public virtual async Task GetAccessTokensAsync() 606 | { 607 | HttpResponseMessage response = await this.GetAsync("tokens", true).ConfigureAwait(false); 608 | if (!response.IsSuccessStatusCode) 609 | return new AccessToken[0]; 610 | response.EnsureSuccessStatusCode(); 611 | var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 612 | return ParseTokens(result); 613 | } 614 | 615 | /// 616 | /// Test access to a facade 617 | /// 618 | /// The facade 619 | /// True authorized, else false 620 | public virtual bool TestAccess(Facade facade) 621 | { 622 | return TestAccessAsync(facade).GetAwaiter().GetResult(); 623 | } 624 | 625 | /// 626 | /// Test access to a facade 627 | /// 628 | /// The facade 629 | /// True if authorized, else false 630 | public virtual async Task TestAccessAsync(Facade facade) 631 | { 632 | if(facade == null) 633 | throw new ArgumentNullException(nameof(facade)); 634 | 635 | var auth = _Auth; 636 | var token = auth.GetAccessToken(facade); 637 | if(token != null) 638 | return true; 639 | var tokens = await GetAccessTokensAsync().ConfigureAwait(false); 640 | auth.SaveTokens(tokens); 641 | token = auth.GetAccessToken(facade); 642 | return token != null; 643 | } 644 | 645 | public virtual async Task GetAccessTokenAsync(Facade facade) 646 | { 647 | var auth = _Auth; 648 | var token = auth.GetAccessToken(facade); 649 | if(token != null) 650 | return token; 651 | 652 | var tokens = await GetAccessTokensAsync().ConfigureAwait(false); 653 | auth.SaveTokens(tokens); 654 | token = auth.GetAccessToken(facade); 655 | if(token == null) 656 | throw new BitPayException("Error: You do not have access to facade: " + facade); 657 | return token; 658 | } 659 | 660 | private async Task GetAsync(String path, bool sign) 661 | { 662 | try 663 | { 664 | var message = new HttpRequestMessage(HttpMethod.Get, GetFullUri(path)); 665 | message.Headers.Add("x-accept-version", BITPAY_API_VERSION); 666 | if(sign) 667 | { 668 | _Auth.Sign(message); 669 | } 670 | var result = await _Client.SendAsync(message).ConfigureAwait(false); 671 | return result; 672 | } 673 | catch(Exception ex) 674 | { 675 | throw new BitPayException("Error: " + ex.ToString()); 676 | } 677 | } 678 | 679 | private async Task PostAsync(String path, String json, bool signatureRequired = false) 680 | { 681 | try 682 | { 683 | var message = new HttpRequestMessage(HttpMethod.Post, GetFullUri(path)); 684 | message.Headers.Add("x-accept-version", BITPAY_API_VERSION); 685 | message.Content = new StringContent(json, Encoding.UTF8, "application/json"); 686 | if(signatureRequired) 687 | { 688 | _Auth.Sign(message); 689 | } 690 | var result = await _Client.SendAsync(message).ConfigureAwait(false); 691 | return result; 692 | } 693 | catch(Exception ex) 694 | { 695 | throw new BitPayException("Error: " + ex.ToString()); 696 | } 697 | } 698 | 699 | private string GetFullUri(string relativePath) 700 | { 701 | var uri = _baseUrl.AbsoluteUri; 702 | if(!uri.EndsWith("/", StringComparison.Ordinal)) 703 | uri += "/"; 704 | uri += relativePath; 705 | return uri; 706 | } 707 | 708 | private async Task ParseResponse(HttpResponseMessage response) 709 | { 710 | if(response == null) 711 | { 712 | throw new BitPayException("Error: HTTP response is null"); 713 | } 714 | 715 | // Get the response as a dynamic object for detecting possible error(s) or data object. 716 | // An error(s) object raises an exception. 717 | // A data object has its content extracted (throw away the data wrapper object). 718 | String responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 719 | 720 | if(responseString.Length > 0 && responseString[0] == '[') { 721 | // some endpoints return an array at the root (like /Ledgers/{currency}). 722 | // without short circuiting here, the JObject.Parse will throw 723 | return JsonConvert.DeserializeObject(responseString); 724 | } 725 | 726 | JObject obj = null; 727 | try 728 | { 729 | obj = JObject.Parse(responseString); 730 | } 731 | catch 732 | { 733 | // Probably a http error, send this instead of parsing error. 734 | response.EnsureSuccessStatusCode(); 735 | throw; 736 | } 737 | 738 | // Check for error response. 739 | if (obj.Property("error") != null) 740 | { 741 | var ex = new BitPayException(); 742 | ex.AddError(obj.Property("error").Value.ToString()); 743 | throw ex; 744 | } 745 | if(obj.Property("errors") != null) 746 | { 747 | var ex = new BitPayException(); 748 | foreach(var errorItem in ((JArray)obj.Property("errors").Value).OfType()) 749 | { 750 | ex.AddError(errorItem.Property("error").Value.ToString() + " " + errorItem.Property("param").Value.ToString()); 751 | } 752 | throw ex; 753 | } 754 | 755 | T data = default(T); 756 | // Check for and exclude a "data" object from the response. 757 | if(obj.Property("data") != null) 758 | { 759 | responseString = JObject.Parse(responseString).SelectToken("data").ToString(); 760 | data = JsonConvert.DeserializeObject(responseString); 761 | } 762 | return data; 763 | } 764 | } 765 | } 766 | --------------------------------------------------------------------------------