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

Add support for passing level III data when creating a charge #1216

Merged
merged 2 commits into from
Jun 29, 2018
Merged
Changes from 1 commit
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
Next Next commit
Add support for passing level III data when creating a charge
remi-stripe committed Jun 24, 2018

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit f8f80de79cdfa4be89837f063034161d42cdf695
34 changes: 34 additions & 0 deletions src/Stripe.Tests.XUnit/charges/_fixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace Stripe.Tests.Xunit
{
@@ -13,6 +14,7 @@ public class charges_fixture : IDisposable
public StripeCharge UpdatedCharge { get; set; }
public StripeCharge UncapturedCharge { get; set; }
public StripeCharge CapturedCharge { get; set; }
public StripeCharge Level3Charge { get; set; }

public charges_fixture()
{
@@ -46,6 +48,38 @@ public charges_fixture()
StatementDescriptor = "CapturedCharge"
};
CapturedCharge = service.Capture(UncapturedCharge.Id, ChargeCaptureOptions);

// Create a charge with Level 3 data
var chargeLevel3Options = Cache.GetStripeChargeCreateOptions();
chargeLevel3Options.Amount = 11700;
chargeLevel3Options.Level3 = new StripeChargeLevel3Options
{
MerchantReference = "1234",
ShippingAddressZip = "94110",
ShippingAmount = 700,
LineItems = new List<StripeChargeLevel3LineItemOptions>
{
new StripeChargeLevel3LineItemOptions
{
DiscountAmount = 200,
ProductCode = "1234",
ProductDescription = "description 1",
Quantity = 2,
TaxAmount = 200,
UnitCost = 1000,
},
new StripeChargeLevel3LineItemOptions
{
DiscountAmount = 300,
ProductCode = "1235",
ProductDescription = "description 2",
Quantity = 3,
TaxAmount = 300,
UnitCost = 3000,
},
},
};
Level3Charge = service.Create(chargeLevel3Options);
}

public void Dispose() { }
Original file line number Diff line number Diff line change
@@ -106,5 +106,10 @@ public void captured_is_captured_with_right_settings()
Assert.Equal(fixture.CapturedCharge.Amount - fixture.CapturedCharge.AmountRefunded, fixture.ChargeCaptureOptions.Amount);
}

[Fact]
public void charge_level3_is_not_null()
{
fixture.Level3Charge.Should().NotBeNull();
}
}
}
7 changes: 7 additions & 0 deletions src/Stripe.net/Entities/StripeCharge.cs
Original file line number Diff line number Diff line change
@@ -237,6 +237,13 @@ internal object InternalOrder
}
#endregion

/// <summary>
/// Details about the level III data associated with the Charge.
/// This is a gated property and most integrations can not access it.
/// </summary>
[JsonProperty("level3")]
public StripeChargeLevel3 Level3 { get; set; }

/// <summary>
/// Details about whether the payment was accepted, and why.
/// </summary>
20 changes: 20 additions & 0 deletions src/Stripe.net/Entities/StripeChargeLevel3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Stripe
{
public class StripeChargeLevel3 : StripeEntity
{
[JsonProperty("line_items")]
public List<StripeChargeLevel3LineItem> LineItems { get; set; }

[JsonProperty("merchant_reference")]
public string MerchantReference { get; set; }

[JsonProperty("shipping_address_zip")]
public string ShippingAddressZip { get; set; }

[JsonProperty("shipping_amount")]
public int ShippingAmount { get; set; }
}
}
25 changes: 25 additions & 0 deletions src/Stripe.net/Entities/StripeChargeLevel3LineItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Newtonsoft.Json;

namespace Stripe
{
public class StripeChargeLevel3LineItem : StripeEntity
{
[JsonProperty("discount_amount")]
public int? DiscountAmount { get; set; }

[JsonProperty("product_code")]
public string ProductCode { get; set; }

[JsonProperty("product_description")]
public string ProductDescription { get; set; }

[JsonProperty("quantity")]
public int? Quantity { get; set; }

[JsonProperty("tax_amount")]
public int? TaxAmount { get; set; }

[JsonProperty("unit_cost")]
public int? UnitCost { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;

namespace Stripe.Infrastructure.Middleware
{
internal class ChargeLevel3Plugin : IParserPlugin
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should revisit how plugins work. It's all copy/pasta for the sake of adding one list of items. There has to be a better way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I took a stab at this in #1218.

{
public bool Parse(ref string requestString, JsonPropertyAttribute attribute, PropertyInfo property, object propertyValue, object propertyParent)
{
if (attribute.PropertyName != "charge_level3_line_items_array") return false;

var items = ((List<StripeChargeLevel3LineItemOptions>) property.GetValue(propertyParent, null));

var itemIndex = 0;
foreach (var item in items)
{
var properties = item.GetType().GetRuntimeProperties();

foreach (var prop in properties)
{
var value = prop.GetValue(item, null);
if (value == null) continue;

// it must have a json attribute matching stripe's key, and only one
var attr = prop.GetCustomAttributes<JsonPropertyAttribute>().SingleOrDefault();
if (attr == null) continue;

RequestStringBuilder.ApplyParameterToRequestString(ref requestString,
$"level3[line_items][{itemIndex}][{attr.PropertyName}]", value.ToString());
}

itemIndex++;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -26,7 +26,8 @@ static RequestStringBuilder()
new PlanTiersPlugin(),
new SubscriptionItemPlugin(),
new SubscriptionItemUpdatedPlugin(),
new InvoiceSubscriptionItemPlugin()
new InvoiceSubscriptionItemPlugin(),
new ChargeLevel3Plugin()
};
}

Original file line number Diff line number Diff line change
@@ -51,6 +51,12 @@ public class StripeChargeCreateOptions : StripeBaseOptions, ISupportMetadata
[JsonProperty("exchange_rate")]
public decimal? ExchangeRate { get; set; }

/// <summary>
/// Extra information about the charge passing level III data to card networks
/// </summary>
[JsonProperty("level3")]
public StripeChargeLevel3Options Level3 { get; set; }

/// <summary>
/// A string that identifies this transaction as part of a group. See the Connect documentation for details.
/// </summary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Newtonsoft.Json;

namespace Stripe
{
public class StripeChargeLevel3LineItemOptions : INestedOptions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand when something should be called Options versus Option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should always be Options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would work. I thought that we started using Option for nested ones to differentiate them from the top-level ones.

{
[JsonProperty("discount_amount")]
public int? DiscountAmount { get; set; }

[JsonProperty("product_code")]
public string ProductCode { get; set; }

[JsonProperty("product_description")]
public string ProductDescription { get; set; }

[JsonProperty("quantity")]
public int? Quantity { get; set; }

[JsonProperty("tax_amount")]
public int? TaxAmount { get; set; }

[JsonProperty("unit_cost")]
public int? UnitCost { get; set; }
}
}
22 changes: 22 additions & 0 deletions src/Stripe.net/Services/Charges/StripeChargeLevel3Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Stripe
{
public class StripeChargeLevel3Options : INestedOptions
{
// this will actually send `line_items`. this is to flag it for the middleware
// to process it as a plugin
[JsonProperty("charge_level3_line_items_array")]
public List<StripeChargeLevel3LineItemOptions> LineItems { get; set; }

[JsonProperty("level3[merchant_reference]")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works but since the parent has JsonProperty(level3) why isn't it passed to the lower level and you have to repeat it? The same happened to shipping.

public string MerchantReference { get; set; }

[JsonProperty("level3[shipping_address_zip]")]
public string ShippingAddressZip { get; set; }

[JsonProperty("level3[shipping_amount]")]
public int ShippingAmount { get; set; }
}
}