Skip to content

Commit

Permalink
#v2.2 - Payout and Billing improved (#53)
Browse files Browse the repository at this point in the history
* - Payout model restructured
* - Bill update implementation
* - unit tests updated
  • Loading branch information
Antonio Buedo authored Jul 15, 2019
1 parent eaaa4ba commit d8dc451
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 122 deletions.
105 changes: 98 additions & 7 deletions BitPay/BitPay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,77 @@ public async Task<Bill> GetBill(string billId, string facade = Facade.Merchant,
}
}

/// <summary>
/// Update a bill.
/// </summary>
/// <param name="bill">An invoice object containing the update.</param>
/// <param name="billId">The id of the bill to update.</param>
/// <returns>A new bill object returned from the server.</returns>
public async Task<Bill> UpdateBill(Bill bill, string billId)
{
try
{
var json = JsonConvert.SerializeObject(bill);
var response = await Put("bills/" + billId, json).ConfigureAwait(false);
var responseString = await ResponseToJsonString(response).ConfigureAwait(false);
var serializerSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
JsonConvert.PopulateObject(responseString, bill, serializerSettings);
}
catch (Exception ex)
{
if (!(ex.GetType().IsSubclassOf(typeof(BitPayException)) || ex.GetType() == typeof(BitPayException)))
throw new BillUpdateException(ex);

throw;
}

return bill;
}

/// <summary>
/// Retrieve a bill by id.
/// </summary>
/// <param name="status">The status to filter the bills.</param>
/// <returns>The bill object retrieved from the server.</returns>
public async Task<List<Bill>> GetBills(string status = null)
{
Dictionary<string, string> parameters = null;
try
{
// Provide the merchant token when the merchant facade is being used.
// GET/invoices expects the merchant token and not the merchant/invoice token.
try
{
parameters = new Dictionary<string, string>{};
parameters.Add("token", GetAccessToken(Facade.Merchant));
if (!String.IsNullOrEmpty(status))
{
parameters.Add("status", status);
}
}
catch (BitPayException)
{
// No token for invoice.
parameters = null;
}
var response = await Get("bills", parameters);
var responseString = await ResponseToJsonString(response);
return JsonConvert.DeserializeObject<List<Bill>>(responseString);
}
catch (Exception ex)
{
if (!(ex.GetType().IsSubclassOf(typeof(BitPayException)) || ex.GetType() == typeof(BitPayException)))
throw new BillQueryException(ex);

throw;
}
}

/// <summary>
/// Deliver a bill to the consumer.
/// </summary>
/// <param name="billId">The id of the requested bill.</param>
/// <param name="billToken">The token of the requested bill.</param>
/// <param name="facade">The facade to deliver the bill from</param>
/// <param name="signRequest">Allow unsigned request</param>
/// <returns>A response status returned from the API.</returns>
public async Task<string> DeliverBill(string billId, string billToken, bool signRequest = true)
Expand Down Expand Up @@ -423,14 +488,12 @@ public async Task<PayoutBatch> SubmitPayoutBatch(PayoutBatch batch)
batch.Guid = Guid.NewGuid().ToString();
var json = JsonConvert.SerializeObject(batch);
var response = await PostWithSignature("payouts", json);

// To avoid having to merge instructions in the response with those we sent, we just remove the instructions
// we sent and replace with those in the response.
batch.Instructions = new List<PayoutInstruction>();

var responseString = await ResponseToJsonString(response);
JsonConvert.PopulateObject(responseString, batch, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
NullValueHandling = NullValueHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace
});

// Track the token for this batch
Expand All @@ -450,12 +513,17 @@ public async Task<PayoutBatch> SubmitPayoutBatch(PayoutBatch batch)
/// <summary>
/// Retrieve a collection of BitPay payout batches.
/// </summary>
/// <param name="status">The status to filter (optional).</param>
/// <returns>A list of BitPay PayoutBatch objects.</returns>
public async Task<List<PayoutBatch>> GetPayoutBatches()
public async Task<List<PayoutBatch>> GetPayoutBatches(string status = null)
{
try
{
var parameters = InitParams();
if (!string.IsNullOrEmpty(status))
{
parameters.Add("status", status);
}
parameters.Add("token", GetAccessToken(Facade.Payroll));
var response = await Get("payouts", parameters);
var responseString = await ResponseToJsonString(response);
Expand Down Expand Up @@ -891,6 +959,29 @@ private async Task<HttpResponseMessage> Post(string uri, string json, bool signa
}
}

private async Task<HttpResponseMessage> Put(string uri, string json)
{
try
{
var bodyContent = new StringContent(UnicodeToAscii(json));
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("x-accept-version", BitpayApiVersion);
_httpClient.DefaultRequestHeaders.Add("x-bitpay-plugin-info", BitpayPluginInfo);
bodyContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

var signature = KeyUtils.Sign(_ecKey, _baseUrl + uri + json);
_httpClient.DefaultRequestHeaders.Add("x-signature", signature);
_httpClient.DefaultRequestHeaders.Add("x-identity", _ecKey?.PublicKeyHexBytes);

var result = await _httpClient.PutAsync(uri, bodyContent).ConfigureAwait(false);
return result;
}
catch (Exception ex)
{
throw new BitPayApiCommunicationException(ex);
}
}

private async Task<string> ResponseToJsonString(HttpResponseMessage response)
{
if (response == null)
Expand Down
4 changes: 3 additions & 1 deletion BitPay/BitPay.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
<Compile Include="Exceptions\BillDeliveryException.cs" />
<Compile Include="Exceptions\BillException.cs" />
<Compile Include="Exceptions\BillQueryException.cs" />
<Compile Include="Exceptions\BillUpdateException.cs" />
<Compile Include="Exceptions\BitPayApiCommunicationException.cs" />
<Compile Include="Exceptions\BitPayException.cs" />
<Compile Include="Exceptions\ConfigNotFoundException.cs" />
Expand Down Expand Up @@ -307,10 +308,10 @@
<Compile Include="Models\Invoice\InvoicePaymentUrls.cs" />
<Compile Include="Models\Invoice\InvoiceTransaction.cs" />
<Compile Include="Models\Payout\PayoutBatch.cs" />
<Compile Include="Models\Payout\PayoutInfo.cs" />
<Compile Include="Models\Payout\PayoutInstruction.cs" />
<Compile Include="Models\Payout\PayoutInstructionBtcSummary.cs" />
<Compile Include="Models\Payout\PayoutInstructionTransaction.cs" />
<Compile Include="Models\Payout\Status.cs" />
<Compile Include="Models\Policy.cs" />
<Compile Include="KeyUtils.cs" />
<Compile Include="Models\Invoice\SupportedTransactionCurrency.cs" />
Expand All @@ -319,6 +320,7 @@
<Compile Include="Models\Rate\Rate.cs" />
<Compile Include="Models\Rate\Rates.cs" />
<Compile Include="Models\Settlement\InvoiceData.cs" />
<Compile Include="Models\Settlement\PayoutInfo.cs" />
<Compile Include="Models\Settlement\RefundAmount.cs" />
<Compile Include="Models\Settlement\RefundInfo.cs" />
<Compile Include="Models\Settlement\Settlement.cs" />
Expand Down
18 changes: 18 additions & 0 deletions BitPay/Exceptions/BillUpdateException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace BitPayAPI.Exceptions
{
public class BillUpdateException : BitPayException
{
private const string BitPayCode = "BITPAY-BILL-UPDATE";
private const string BitPayMessage = "Failed to update bill";

public BillUpdateException() : base(BitPayCode, BitPayMessage)
{
}

public BillUpdateException(Exception ex) : base(BitPayCode, BitPayMessage, ex)
{
}
}
}
87 changes: 46 additions & 41 deletions BitPay/Models/Payout/PayoutBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ namespace BitPayAPI.Models.Payout
{
public class PayoutBatch
{
public const string StatusNew = "new";
public const string StatusFunded = "funded";
public const string StatusProcessing = "processing";
public const string StatusComplete = "complete";
public const string StatusFailed = "failed";
public const string StatusCancelled = "cancelled";

public const string MethodManual2 = "manual_2";
public const string MethodVwap24 = "vwap_24hr";

Expand All @@ -26,8 +19,6 @@ public PayoutBatch()
{
Amount = 0.0;
Currency = "USD";
Reference = "";
BankTransferId = "";
NotificationEmail = "";
NotificationUrl = "";
PricingMethod = MethodVwap24;
Expand All @@ -36,21 +27,17 @@ public PayoutBatch()
/// <summary>
/// Constructor, create an instruction-full request PayoutBatch object.
/// </summary>
/// <param name="currency">Currency to use on payout.</param>
/// <param name="effectiveDate">
/// Date when request is effective. Note that the time of day will automatically be set to
/// 09:00:00.000 UTC time for the given day. Only requests submitted before 09:00:00.000 UTC are guaranteed to be
/// processed on the same day.
/// </param>
/// <param name="reference">Merchant-provided data.</param>
/// <param name="bankTransferId">Merchant-provided data, to help match funding payments to payout batches.</param>
/// <param name="instructions">Payout instructions.</param>
public PayoutBatch(string currency, DateTime effectiveDate, string bankTransferId, string reference,
List<PayoutInstruction> instructions) : this()
public PayoutBatch(string currency, DateTime effectiveDate, List<PayoutInstruction> instructions) : this()
{
Currency = currency;
EffectiveDate = effectiveDate;
BankTransferId = bankTransferId;
Reference = reference;
Instructions = instructions;
_computeAndSetAmount();
}
Expand All @@ -65,22 +52,6 @@ public PayoutBatch(string currency, DateTime effectiveDate, string bankTransferI
// Required fields
//

[JsonProperty(PropertyName = "effectiveDate")]
[JsonConverter(typeof(Converters.DateStringConverter))]
public DateTime EffectiveDate { get; set; }

[JsonProperty(PropertyName = "reference")]
public string Reference { get; set; }

[JsonProperty(PropertyName = "bankTransferId")]
public string BankTransferId { get; set; }

// Optional fields
//

[JsonProperty(PropertyName = "instructions")]
public List<PayoutInstruction> Instructions { get; set; }

[JsonProperty(PropertyName = "amount")]
public double Amount { get; set; }

Expand All @@ -90,42 +61,61 @@ public string Currency
get => _currency;
set
{
if (value.Length != 3)
throw new BitPayException("Error: currency code must be exactly three characters");
if (!Models.Currency.isValid(value))
throw new BitPayException("Error: currency code must be a type of BitPayAPI.Models.Currency");

_currency = value;
}
}

[JsonProperty(PropertyName = "pricingMethod")]
public string PricingMethod { get; set; }
[JsonProperty(PropertyName = "effectiveDate")]
[JsonConverter(typeof(Converters.DateStringConverter))]
public DateTime EffectiveDate { get; set; }

[JsonProperty(PropertyName = "instructions")]
public List<PayoutInstruction> Instructions { get; set; }

// Optional fields
//

[JsonProperty(PropertyName = "reference")]
public string Reference { get; set; }

[JsonProperty(PropertyName = "notificationEmail")]
public string NotificationEmail { get; set; }

[JsonProperty(PropertyName = "notificationURL")]
public string NotificationUrl { get; set; }

[JsonProperty(PropertyName = "pricingMethod")]
public string PricingMethod { get; set; }

// Response fields
//

public string Id { get; set; }

public string Account { get; set; }

public string Status { get; set; }

public double Btc { get; set; }
public string SupportPhone { get; set; }

[JsonConverter(typeof(Converters.DateStringConverter))]
public DateTime RequestDate { get; set; }
public string Status { get; set; }

public double PercentFee { get; set; }

public double Fee { get; set; }

public double DepositTotal { get; set; }

public string SupportPhone { get; set; }
public double Rate { get; set; }

public double Btc { get; set; }

[JsonConverter(typeof(Converters.DateStringConverter))]
public DateTime RequestDate { get; set; }

[JsonConverter(typeof(Converters.DateStringConverter))]
public DateTime DateExecuted { get; set; }

// Private methods
//
Expand Down Expand Up @@ -177,6 +167,11 @@ public bool ShouldSerializeStatus()
return false;
}

public bool ShouldSerializeRate()
{
return false;
}

public bool ShouldSerializeBtc()
{
return false;
Expand Down Expand Up @@ -206,5 +201,15 @@ public bool ShouldSerializeSupportPhone()
{
return false;
}

public bool ShouldSerializeReference()
{
return !string.IsNullOrEmpty(Reference);
}

public bool ShouldSerializeDateExecuted()
{
return false;
}
}
}
Loading

0 comments on commit d8dc451

Please sign in to comment.