Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AnyOf<> generic class to handle polymorphic parameters #1495

Merged
merged 1 commit into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ private static List<KeyValuePair<string, object>> FlattenParamsValue(object valu
flatParams = SingleParam(keyPrefix, string.Empty);
break;

case IAnyOf anyOf:
flatParams = FlattenParamsValue(anyOf.Value, keyPrefix);
break;

case INestedOptions options:
flatParams = FlattenParamsOptions(options, keyPrefix);
break;
Expand Down
106 changes: 106 additions & 0 deletions src/Stripe.net/Infrastructure/JsonConverters/AnyOfConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
namespace Stripe.Infrastructure
{
using System;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

/// <summary>
/// Converts a <see cref="IAnyOf"/> to and from JSON.
/// </summary>
public class AnyOfConverter : JsonConverter
{
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite => true;

/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
switch (value)
{
case null:
serializer.Serialize(writer, null);
break;

case IAnyOf anyOf:
serializer.Serialize(writer, anyOf.Value);
break;

default:
throw new JsonSerializationException(string.Format(
"Unexpected value when converting AnyOf. Expected IAnyOf, got {0}.",
value.GetType()));
}
}

/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}

object o = null;

// Try to deserialize with each possible type
var jToken = JToken.Load(reader);
foreach (var type in objectType.GenericTypeArguments)
{
try
{
using (var subReader = jToken.CreateReader())
{
o = serializer.Deserialize(subReader, type);
}

// If deserialization succeeds, stop
break;
}
catch (JsonException)
{
// Do nothing, just try the next type
}
}

if (o == null)
{
throw new JsonSerializationException(string.Format(
"Cannot deserialize the current JSON object into any of the expected types ({0}).",
string.Join(", ", objectType.GenericTypeArguments.Select(t => t.FullName))));
}

return Activator.CreateInstance(objectType, o);
}

/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return typeof(IAnyOf).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
}
}
23 changes: 15 additions & 8 deletions src/Stripe.net/Services/Account/AccountSharedOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
namespace Stripe
{
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;
Expand All @@ -25,14 +24,22 @@ public abstract class AccountSharedOptions : BaseOptions
[JsonProperty("email")]
public string Email { get; set; }

/// <summary>
/// <para>
/// A card or bank account to attach to the account. You can provide either a token, like
/// the ones returned by <a href="https://stripe.com/docs/stripe.js">Stripe.js</a>, or a
/// <see cref="AccountBankAccountOptions"/> or <see cref="AccountCardOptions"/> instance.
/// </para>
/// <para>
/// By default, providing an external account sets it as the new default external account
/// for its currency, and deletes the old default if one exists. To add additional external
/// accounts without replacing the existing default for the currency, use the bank account
/// or card creation API.
/// </para>
/// </summary>
[JsonProperty("external_account")]
public string ExternalAccountId { get; set; }

[JsonProperty("external_account")]
public AccountCardOptions ExternalCardAccount { get; set; }

[JsonProperty("external_account")]
public AccountBankAccountOptions ExternalBankAccount { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, AccountBankAccountOptions, AccountCardOptions> ExternalAccount { get; set; }

[JsonProperty("metadata")]
public Dictionary<string, string> Metadata { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ namespace Stripe
{
using System;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class BalanceTransactionListOptions : ListOptionsWithCreated
{
/// <summary>
/// A filter on the list based on the object available_on field. The value can be a
/// <see cref="DateTime"/> or a <see cref="DateRangeOptions"/>.
/// </summary>
[JsonProperty("available_on")]
public DateTime? AvailableOn { get; set; }

[JsonProperty("available_on")]
public DateRangeOptions AvailableOnRange { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<DateTime?, DateRangeOptions> AvailableOn { get; set; }

[JsonProperty("currency")]
public string Currency { get; set; }
Expand Down
13 changes: 9 additions & 4 deletions src/Stripe.net/Services/BankAccounts/BankAccountCreateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
namespace Stripe
{
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class BankAccountCreateOptions : BaseOptions
{
/// <summary>
/// REQUIRED. Either a token, like the ones returned by
/// <a href="https://stripe.com/docs/stripe.js">Stripe.js</a>, or a
/// <see cref="SourceBankAccount"/> instance containing a user’s bank account
/// details.
/// </summary>
[JsonProperty("source")]
public string SourceToken { get; set; }

[JsonProperty("source")]
public SourceBankAccount SourceBankAccount { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, SourceBankAccount> Source { get; set; }
}
}
14 changes: 9 additions & 5 deletions src/Stripe.net/Services/Cards/CardCreateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace Stripe
{
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class CardCreateOptions : BaseOptions
{
Expand All @@ -12,11 +12,15 @@ public class CardCreateOptions : BaseOptions
[JsonProperty("metadata")]
public Dictionary<string, string> Metadata { get; set; }

/// <summary>
/// REQUIRED. Either a token, like the ones returned by
/// <a href="https://stripe.com/docs/stripe.js">Stripe.js</a>, or a
/// <see cref="CardCreateNestedOptions"/> instance containing a user’s card
/// details.
/// </summary>
[JsonProperty("source")]
public string SourceToken { get; set; }

[JsonProperty("source")]
public CardCreateNestedOptions SourceCard { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, CardCreateNestedOptions> Source { get; set; }

[JsonProperty("validate")]
public bool? Validate { get; set; }
Expand Down
13 changes: 9 additions & 4 deletions src/Stripe.net/Services/Charges/ChargeCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Stripe
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class ChargeCreateOptions : BaseOptions
{
Expand Down Expand Up @@ -92,11 +93,15 @@ public class ChargeCreateOptions : BaseOptions
[JsonProperty("customer")]
public string CustomerId { get; set; }

/// <summary>
/// A payment source to be charged. This can be the ID of a card (i.e., credit or debit
/// card), a bank account, a source, a token, or a connected account. For certain
/// sources—namely, cards, bank accounts, and attached sources—you must also pass the ID of
/// the associated customer.
/// </summary>
[JsonProperty("source")]
public string SourceId { get; set; }

[JsonProperty("source")]
public CardCreateNestedOptions SourceCard { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, CardCreateNestedOptions> Source { get; set; }

/// <summary>
/// An arbitrary string to be displayed on your customer's credit card statement. This may
Expand Down
49 changes: 17 additions & 32 deletions src/Stripe.net/Services/Customers/CustomerCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,23 @@ public class CustomerCreateOptions : BaseOptions
[JsonProperty("shipping")]
public ShippingOptions Shipping { get; set; }

/// <summary>
/// The source can either be a Token or a Source, as returned by
/// <a href="https://stripe.com/docs/elements">Elements</a>, or a
/// <see cref="CardCreateNestedOptions"/> containing a user’s credit card details. You must
/// provide a source if the customer does not already have a valid source attached, and you
/// are subscribing the customer to be charged automatically for a plan that is not free.
/// Passing <c>source</c> will create a new source object, make it the customer default
/// source, and delete the old customer default if one exists. If you want to add an
/// additional source, instead use
/// <see cref="CardService.Create(string, CardCreateOptions, RequestOptions)"/> to add the
/// card and then <see cref="CustomerService.Update(string, CustomerUpdateOptions, RequestOptions)"/>
/// to set it as the default. Whenever you attach a card to a customer, Stripe will
/// automatically validate the card.
/// </summary>
[JsonProperty("source")]
public string SourceToken { get; set; }

[JsonProperty("source")]
public CardCreateNestedOptions SourceCard { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, CardCreateNestedOptions> Source { get; set; }

/// <summary>
/// Describes the customer’s tax exemption status. One of <c>none</c>, <c>exempt</c>, or
Expand All @@ -90,35 +102,8 @@ public class CustomerCreateOptions : BaseOptions
[JsonProperty("tax_percent")]
public decimal? TaxPercent { get; set; }

#region Trial End

[JsonIgnore]
public DateTime? TrialEnd { get; set; }

[JsonIgnore]
public bool EndTrialNow { get; set; }

[JsonProperty("trial_end")]
internal string TrialEndInternal
{
get
{
if (this.EndTrialNow)
{
return "now";
}
else if (this.TrialEnd.HasValue)
{
return EpochTime.ConvertDateTimeToEpoch(this.TrialEnd.Value).ToString();
}
else
{
return null;
}
}
}

#endregion
public AnyOf<DateTime?, SubscriptionTrialEnd?> TrialEnd { get; set; }

[JsonProperty("validate")]
public bool? Validate { get; set; }
Expand Down
16 changes: 12 additions & 4 deletions src/Stripe.net/Services/Customers/CustomerUpdateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Stripe
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class CustomerUpdateOptions : BaseOptions
{
Expand Down Expand Up @@ -57,11 +58,18 @@ public class CustomerUpdateOptions : BaseOptions
[JsonProperty("shipping")]
public ShippingOptions Shipping { get; set; }

/// <summary>
/// A Token’s or a Source’s ID, as returned by
/// <a href="https://stripe.com/docs/elements">Elements</a>. Passing <c>source</c> will
/// create a new source object, make it the new customer default source, and delete the old\
/// customer default if one exists. If you want to add additional sources instead of
/// replacing the existing default, use
/// <see cref="CardService.Create(string, CardCreateOptions, RequestOptions)"/>. Whenever
/// you attach a card to a customer, Stripe will automatically validate the card.
/// </summary>
[JsonProperty("source")]
public string SourceToken { get; set; }

[JsonProperty("source")]
public CardCreateNestedOptions SourceCard { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, CardCreateNestedOptions> Source { get; set; }

/// <summary>
/// Describes the customer’s tax exemption status. One of <c>none</c>, <c>exempt</c>, or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,33 @@ namespace Stripe
{
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class ExternalAccountCreateOptions : BaseOptions
{
/// <summary>
/// REQUIRED. Either a token, like the ones returned by
/// <a href="https://stripe.com/docs/stripe.js">Stripe.js</a>, or a
/// <see cref="AccountBankAccountOptions"/> instance containing a user’s bank account
/// details.
/// </summary>
[JsonProperty("external_account")]
public string ExternalAccountTokenId { get; set; }

[JsonProperty("external_account")]
public AccountBankAccountOptions ExternalAccountBankAccount { get; set; }

[JsonProperty("metadata")]
public Dictionary<string, string> Metadata { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, AccountBankAccountOptions> ExternalAccount { get; set; }

/// <summary>
/// When set to <c>true</c>, or if this is the first external account added in this
/// currency, this account becomes the default external account for its currency.
/// </summary>
[JsonProperty("default_for_currency")]
public bool? DefaultForCurrency { get; set; }
}

/// <summary>
/// A set of key-value pairs that you can attach to an external account object. It can be
/// useful for storing additional information about the external account in a structured
/// format.
/// </summary>
[JsonProperty("metadata")]
public Dictionary<string, string> Metadata { get; set; }
}
}
Loading