Skip to content

Commit

Permalink
AnyOf<> generic class to handle polymorphic parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ob-stripe committed Jan 31, 2019
1 parent c67fbb4 commit aa1ce55
Show file tree
Hide file tree
Showing 16 changed files with 665 additions and 34 deletions.
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 @@ -128,6 +128,10 @@ private static List<KeyValuePair<string, string>> FlattenParamsValue(object valu
flatParams = SingleParam(keyPrefix, string.Empty);
break;

case IAnyOf anyOf:
flatParams = FlattenParamsValue(anyOf.GetValue(), 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 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.GetValue());
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 Down Expand Up @@ -34,14 +33,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("legal_entity")]
public AccountLegalEntityOptions LegalEntity { get; set; }
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; }
}
}
7 changes: 3 additions & 4 deletions src/Stripe.net/Services/Sources/SourceCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Stripe
{
using System.Collections.Generic;
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class SourceCreateOptions : BaseOptions
{
Expand Down Expand Up @@ -39,9 +40,6 @@ public class SourceCreateOptions : BaseOptions
[JsonProperty("flow")]
public string Flow { get; set; }

[JsonProperty("card")]
public string CardId { get; set; }

/// <summary>
/// Information about a mandate possiblity attached to a source object (generally for bank debits) as well as its acceptance status.
/// </summary>
Expand Down Expand Up @@ -102,7 +100,8 @@ Below we group all Source type specific paramters
public SourceBancontactCreateOptions Bancontact { get; set; }

[JsonProperty("card")]
public CreditCardOptions Card { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, CreditCardOptions> Card { get; set; }

[JsonProperty("ideal")]
public SourceIdealCreateOptions Ideal { get; set; }
Expand Down
33 changes: 25 additions & 8 deletions src/Stripe.net/Services/Tokens/TokenCreateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
namespace Stripe
{
using Newtonsoft.Json;
using Stripe.Infrastructure;

public class TokenCreateOptions : BaseOptions
{
/// <summary>
/// The customer (owned by the application's account) for which to create a token. For use
/// only with <a href="https://stripe.com/docs/connect">Stripe Connect</a>. Also, this can
/// be used only with an <a href="https://stripe.com/docs/connect/standard-accounts">OAuth
/// access token</a> or
/// <a href="https://stripe.com/docs/connect/authentication">Stripe-Account header</a>. For
/// more details, see <a href="https://stripe.com/docs/connect/shared-customers">Shared
/// Customers</a>.
/// </summary>
[JsonProperty("customer")]
public string CustomerId { get; set; }

/// <summary>
/// The card this token will represent. If you also pass in a customer, the card must be the
/// ID of a card belonging to the customer. Otherwise, if you do not pass in a customer,
/// this is a <see cref="CreditCardOptions"/> instance containing a user's credit card
/// details.
/// </summary>
[JsonProperty("card")]
public CreditCardOptions Card { get; set; }

[JsonProperty("card")]
public string CardId { get; set; }

[JsonProperty("bank_account")]
public BankAccountOptions BankAccount { get; set; }
public AnyOf<string, CreditCardOptions> Card { get; set; }

/// <summary>
/// The bank account this token will represent. If you also pass in a customer, the bank
/// account must be the ID of a bank account belonging to the customer. Otherwise, if you do
/// not pass in a customer, this is a <see cref="BankAccountOptions"/> instance containing a
/// user's bank account details.
/// </summary>
[JsonProperty("bank_account")]
public string BankAccountId { get; set; }
[JsonConverter(typeof(AnyOfConverter))]
public AnyOf<string, BankAccountOptions> BankAccount { get; set; }
}
}
Loading

0 comments on commit aa1ce55

Please sign in to comment.