diff --git a/CHANGELOG.md b/CHANGELOG.md index c1143f98..1ce92fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.6.0 +- Add `PayPalMessages` to `Dispute` +- Add `TaxIdentifiers` to `CustomerRequest` +- Add webhook sample for `GrantedPaymentMethodRevoked` + ## 5.5.0 - Add `StoreId` and `StoreIds` to `TransactionSearchRequest` - Add support for `LocalPaymentReversed` webhook notifications diff --git a/src/Braintree/Braintree.csproj b/src/Braintree/Braintree.csproj index cca75688..ca630da9 100644 --- a/src/Braintree/Braintree.csproj +++ b/src/Braintree/Braintree.csproj @@ -4,7 +4,7 @@ Braintree Client Library Copyright © Braintree, a division of PayPal, Inc. 2021 - 5.5.0 + 5.6.0 Braintree net452;netstandard2.0 @@ -12,11 +12,9 @@ Braintree braintree;paypal;venmo;intenational;payments;gateway;currencies;money;visa;mastercard;bitcoin;maestro;apple pay;android pay;amex;jcb;diners club;discover;american express - - Add `StoreId` and `StoreIds` to `TransactionSearchRequest` - - Add support for `LocalPaymentReversed` webhook notifications - - Add `Transaction.AdjustAuthorization` method to support for multiple authorizations for a single transaction - - Add `MerchantAccountId` to `TransactionRefundRequest` - - Add `PhoneNumber` to `Address` + - Add `PayPalMessages` to `Dispute` + - Add `TaxIdentifiers` to `CustomerRequest` + - Add webhook sample for `GrantedPaymentMethodRevoked` https://github.com/braintree/braintree_dotnet false diff --git a/src/Braintree/CustomerRequest.cs b/src/Braintree/CustomerRequest.cs index c6c43a7a..0311d46a 100644 --- a/src/Braintree/CustomerRequest.cs +++ b/src/Braintree/CustomerRequest.cs @@ -61,6 +61,7 @@ public class CustomerRequest : Request public UsBankAccountRequest UsBankAccount { get; set; } public RiskDataRequest RiskData { get; set; } public CustomerOptionsRequest Options { get; set; } + public TaxIdentifierRequest[] TaxIdentifiers { get; set; } public override string ToXml() { @@ -86,7 +87,7 @@ public override string ToQueryString(string root) protected virtual RequestBuilder BuildRequest(string root) { - return new RequestBuilder(root). + var request = new RequestBuilder(root). AddElement("id", Id). AddElement("first-name", FirstName). AddElement("last-name", LastName). @@ -102,7 +103,13 @@ protected virtual RequestBuilder BuildRequest(string root) AddElement("risk-data", RiskData). AddElement("device-data", DeviceData). AddElement("options", Options); + + if (TaxIdentifiers != null && TaxIdentifiers.Length > 0) + { + request.AddElement("tax-identifiers", TaxIdentifiers); + } + + return request; } } } - diff --git a/src/Braintree/Dispute.cs b/src/Braintree/Dispute.cs index cfe37e0a..3c11de92 100644 --- a/src/Braintree/Dispute.cs +++ b/src/Braintree/Dispute.cs @@ -66,6 +66,7 @@ public class Dispute public virtual DisputeTransaction Transaction { get; protected set; } public List StatusHistory; public List Evidence; + public List PayPalMessages; public Dispute(NodeWrapper node) { @@ -103,6 +104,12 @@ public Dispute(NodeWrapper node) Evidence.Add(new DisputeEvidence(evidenceResponse)); } + PayPalMessages = new List(); + foreach (var paypalMessageResponse in node.GetList("paypal-messages/paypal-messages")) + { + PayPalMessages.Add(new DisputePayPalMessage(paypalMessageResponse)); + } + StatusHistory = new List(); foreach (var historyStatusResponse in node.GetList("status-history/status-history")) { diff --git a/src/Braintree/DisputePayPalMessage.cs b/src/Braintree/DisputePayPalMessage.cs new file mode 100644 index 00000000..32fafef7 --- /dev/null +++ b/src/Braintree/DisputePayPalMessage.cs @@ -0,0 +1,21 @@ +using System; + +namespace Braintree +{ + public class DisputePayPalMessage + { + public virtual string Message { get; protected set; } + public virtual string Sender { get; protected set; } + public virtual DateTime? SentAt { get; protected set; } + + public DisputePayPalMessage(NodeWrapper node) + { + Message = node.GetString("message"); + Sender = node.GetString("sender"); + SentAt = node.GetDateTime("sent-at"); + } + + [Obsolete("Mock Use Only")] + protected internal DisputePayPalMessage() { } + } +} diff --git a/src/Braintree/Properties/AssemblyInfo.cs b/src/Braintree/Properties/AssemblyInfo.cs index 549a4654..c298cb10 100644 --- a/src/Braintree/Properties/AssemblyInfo.cs +++ b/src/Braintree/Properties/AssemblyInfo.cs @@ -15,7 +15,7 @@ [assembly: AssemblyCulture("")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Braintree.Tests")] [assembly: CLSCompliant(true)] -//[assembly: AssemblyKeyFileAttribute("../../../braintreeSgKey.snk")] +//[assembly: AssemblyKeyFile("../../braintreeSgKey.snk")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -32,5 +32,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("5.5.0.0")] -[assembly: AssemblyFileVersion("5.5.0.0")] +[assembly: AssemblyVersion("5.6.0.0")] +[assembly: AssemblyFileVersion("5.6.0.0")] diff --git a/src/Braintree/TaxIdentifierRequest.cs b/src/Braintree/TaxIdentifierRequest.cs new file mode 100644 index 00000000..2ea94656 --- /dev/null +++ b/src/Braintree/TaxIdentifierRequest.cs @@ -0,0 +1,25 @@ +namespace Braintree +{ + public class TaxIdentifierRequest: Request + { + public string CountryCode { get; set; } + public string Identifier { get; set; } + + public override string ToXml(string root) + { + return BuildRequest(root).ToXml(); + } + + public override string ToQueryString(string root) + { + return BuildRequest(root).ToQueryString(); + } + + protected virtual RequestBuilder BuildRequest(string root) + { + return new RequestBuilder(root) + .AddElement("countryCode", CountryCode) + .AddElement("identifier", Identifier); + } + } +} diff --git a/src/Braintree/WebhookTestingGateway.cs b/src/Braintree/WebhookTestingGateway.cs index 3ec09cc5..d9acf92a 100644 --- a/src/Braintree/WebhookTestingGateway.cs +++ b/src/Braintree/WebhookTestingGateway.cs @@ -96,7 +96,9 @@ private string SubjectSampleXml(WebhookKind kind, string id) return GrantedPaymentInstrumentUpdateSampleXml(); } else if (kind == WebhookKind.PAYMENT_METHOD_REVOKED_BY_CUSTOMER) { return PaymentMethodRevokedByCustomerSampleXml(id); - } else if (kind == WebhookKind.LOCAL_PAYMENT_COMPLETED) { + } else if (kind == WebhookKind.GRANTED_PAYMENT_METHOD_REVOKED) { + return GrantedPaymentMethodRevokedSampleXml(id); + } else if (kind == WebhookKind.LOCAL_PAYMENT_COMPLETED) { return LocalPaymentCompletedSampleXml(); } else if (kind == WebhookKind.LOCAL_PAYMENT_REVERSED) { return LocalPaymentReversedSampleXml(); @@ -533,6 +535,22 @@ private static string PaymentMethodRevokedByCustomerSampleXml(string id) { Node("limited-use-order-id", NIL_TRUE, ""), NodeAttr("revoked-at", TYPE_DATE_TIME, "2019-01-02T12:00:00Z") ); + } + + private static string GrantedPaymentMethodRevokedSampleXml(string id) { + return Node("venmo-account", + NodeAttr("created-at", TYPE_DATE_TIME, "2021-05-05T21:28:37Z"), + NodeAttr("updated-at", TYPE_DATE_TIME, "2021-05-05T21:28:37Z"), + NodeAttr("default", TYPE_BOOLEAN, "true"), + Node("image-url", "https://assets.braintreegateway.com/payment_method_logo/venmo.png?environment=test"), + Node("token", id), + Node("source-description", "Venmo Account: venmojoe"), + Node("username", "venmojoe"), + Node("venmo-user-id", "456"), + NodeAttr("subscriptions", TYPE_ARRAY), + Node("customer-id", "venmo_customer_id"), + Node("global-id", "cGF5bWVudG1ldGhvZF92ZW5tb2FjY291bnQ") + ); } private static string LocalPaymentCompletedSampleXml() { diff --git a/test/Braintree.Tests.Integration/CustomerIntegrationTest.cs b/test/Braintree.Tests.Integration/CustomerIntegrationTest.cs index 4affef4a..31a25194 100644 --- a/test/Braintree.Tests.Integration/CustomerIntegrationTest.cs +++ b/test/Braintree.Tests.Integration/CustomerIntegrationTest.cs @@ -987,7 +987,7 @@ public void Create_WithThreeDSecureNonce() [Test] public void Create_WithPayPalPaymentMethodNonce() - { + { string nonce = TestHelper.GenerateFuturePaymentPayPalNonce(gateway); Result result = gateway.Customer.Create(new CustomerRequest{ PaymentMethodNonce = nonce @@ -1000,7 +1000,7 @@ public void Create_WithPayPalPaymentMethodNonce() [Test] public void Create_WithPayPalOrderPaymentMethodNonce() - { + { string nonce = TestHelper.GenerateOrderPaymentPayPalNonce(gateway); Result result = gateway.Customer.Create(new CustomerRequest{ PaymentMethodNonce = nonce @@ -1009,7 +1009,7 @@ public void Create_WithPayPalOrderPaymentMethodNonce() var customer = result.Target; Assert.AreEqual(1, customer.PayPalAccounts.Length); Assert.AreEqual(customer.PayPalAccounts[0].Token, customer.DefaultPaymentMethod.Token); - + } [Test] @@ -1500,7 +1500,7 @@ public void Update_UpdateCustomerWithThreeDSecurePassThruParams() Assert.IsTrue(result.IsSuccess()); } - + [Test] public void Update_AcceptsNestedBillingAddressId() { @@ -2249,5 +2249,70 @@ public void Update_CustomerWithNonceAndInvalidMerchantCurrencyOption() updatedCustomer.Errors.DeepAll()[0].Code ); } + + [Test] + public void Create_CustomerWithTaxIdentifiers() + { + var createRequest = new CustomerRequest() + { + TaxIdentifiers = new TaxIdentifierRequest[] + { + new TaxIdentifierRequest + { + CountryCode = "US", + Identifier = "123" + }, + new TaxIdentifierRequest + { + CountryCode = "CL", + Identifier = "456" + }, + } + }; + + Result result = gateway.Customer.Create(createRequest); + Assert.IsTrue(result.IsSuccess()); + } + + [Test] + public void Update_CustomerWithTaxIdentifiers() + { + var createRequest = new CustomerRequest() + { + TaxIdentifiers = new TaxIdentifierRequest[] + { + new TaxIdentifierRequest + { + CountryCode = "US", + Identifier = "123" + }, + new TaxIdentifierRequest + { + CountryCode = "CL", + Identifier = "456" + }, + } + }; + + Result createResult = gateway.Customer.Create(createRequest); + Assert.IsTrue(createResult.IsSuccess()); + + var id = createResult.Target.Id; + + var updateRequest = new CustomerRequest() + { + TaxIdentifiers = new TaxIdentifierRequest[] + { + new TaxIdentifierRequest + { + CountryCode = "CL", + Identifier = "789" + }, + } + }; + + Result updateResult = gateway.Customer.Update(id, updateRequest); + Assert.IsTrue(updateResult.IsSuccess()); + } } } diff --git a/test/Braintree.Tests/CustomerRequestTest.cs b/test/Braintree.Tests/CustomerRequestTest.cs index 03108ba4..7549e937 100644 --- a/test/Braintree.Tests/CustomerRequestTest.cs +++ b/test/Braintree.Tests/CustomerRequestTest.cs @@ -52,5 +52,32 @@ public void ToXml_Includes_DeviceData() Assert.IsTrue(request.ToXml().Contains("fraud_merchant_id")); Assert.IsTrue(request.ToXml().Contains("my_fmid")); } + + [Test] + public void ToXml_Includes_TaxIdentifiers() + { + var request = new CustomerRequest() + { + TaxIdentifiers = new TaxIdentifierRequest[] + { + new TaxIdentifierRequest + { + CountryCode = "US", + Identifier = "123" + }, + new TaxIdentifierRequest + { + CountryCode = "CL", + Identifier = "456" + } + } + }; + + Assert.IsTrue(request.ToXml().Contains("US")); + Assert.IsTrue(request.ToXml().Contains("123")); + + Assert.IsTrue(request.ToXml().Contains("CL")); + Assert.IsTrue(request.ToXml().Contains("456")); + } } } diff --git a/test/Braintree.Tests/DisputeTest.cs b/test/Braintree.Tests/DisputeTest.cs index ebfaba0d..4c849129 100644 --- a/test/Braintree.Tests/DisputeTest.cs +++ b/test/Braintree.Tests/DisputeTest.cs @@ -82,6 +82,13 @@ public string Payload_attributes() NodeAttr("effective-date", TYPE_DATE, "2013-04-10") ) ), + NodeAttr("paypal-messages", TYPE_ARRAY, + Node("paypal-messages", + Node("message", "message"), + Node("sender", "seller"), + NodeAttr("sent-at", TYPE_DATE, "2013-04-10T10:50:39Z") + ) + ), NodeAttr("evidence", TYPE_ARRAY, Node("evidence", NodeAttr("created-at", TYPE_DATE, "2013-04-10T10:50:39Z"), @@ -207,6 +214,9 @@ public void Constructor_populatesNewFields() Assert.AreEqual(DisputeStatus.OPEN, result.StatusHistory[0].Status); Assert.AreEqual(DateTime.Parse("2013-04-10T10:50:39Z"), result.StatusHistory[0].Timestamp); Assert.AreEqual(DateTime.Parse("2013-04-10"), result.StatusHistory[0].EffectiveDate); + Assert.AreEqual("message", result.PayPalMessages[0].Message); + Assert.AreEqual("seller", result.PayPalMessages[0].Sender); + Assert.AreEqual(DateTime.Parse("2013-04-10T10:50:39Z"), result.PayPalMessages[0].SentAt); Assert.AreEqual("evidence1", result.Evidence[0].Id); Assert.AreEqual("url_of_file_evidence", result.Evidence[0].Url); Assert.AreEqual(DateTime.Parse("2013-04-10T10:50:39Z"), result.Evidence[0].CreatedAt); @@ -232,6 +242,7 @@ public void Constructor_handlesEmptyFields() Assert.IsNull(result.DateOpened); Assert.IsNull(result.DateWon); Assert.IsEmpty(result.Evidence); + Assert.IsEmpty(result.PayPalMessages); Assert.IsNull(result.ReplyByDate); Assert.IsEmpty(result.StatusHistory); } diff --git a/test/Braintree.Tests/WebhookNotificationTest.cs b/test/Braintree.Tests/WebhookNotificationTest.cs index c47c57db..323d8a65 100644 --- a/test/Braintree.Tests/WebhookNotificationTest.cs +++ b/test/Braintree.Tests/WebhookNotificationTest.cs @@ -614,6 +614,21 @@ public void SampleNotification_ReturnsANotificationForGrantedVenmoAccountRevoked Assert.IsTrue(metadata.RevokedPaymentMethod is VenmoAccount); } + [Test] + public void WebhookTesting_SampleNotification_ReturnsANotificationForGrantedVenmoAccountRevoked() + { + Dictionary sampleNotification = gateway.WebhookTesting.SampleNotification(WebhookKind.GRANTED_PAYMENT_METHOD_REVOKED, "granted_payment_method_revoked_id"); + + WebhookNotification notification = gateway.WebhookNotification.Parse(sampleNotification["bt_signature"], sampleNotification["bt_payload"]); + + Assert.AreEqual(WebhookKind.GRANTED_PAYMENT_METHOD_REVOKED, notification.Kind); + RevokedPaymentMethodMetadata metadata = notification.RevokedPaymentMethodMetadata; + + Assert.AreEqual("venmo_customer_id", metadata.CustomerId); + Assert.AreEqual("granted_payment_method_revoked_id", metadata.Token); + Assert.IsTrue(metadata.RevokedPaymentMethod is VenmoAccount); + } + [Test] public void SampleNotification_ReturnsANotificationForPaymentMethodRevokedByCustomer() {