braintree / braintree_dotnet

Braintree .NET library
https://developer.paypal.com/braintree/docs/start/overview
MIT License
136 stars 73 forks source link

Transaction Status serialized as empty object #86

Closed mckennajones closed 4 years ago

mckennajones commented 5 years ago

General information

I'm noticing that if I return the Transaction object from one of my endpoints, the default .NET JSON serializer is serializing the status property it as an empty object, instead of the actual value.

Issue description

Returning the Braintree Transaction object from one of my .NET core endpoints, the Status property, as well as the Status properties in the Status History array are simply an empty objects. Here is an example:

{
    "id": "0fx5vfrv",
    "addOns": [],
    "amount": 10,
    "avsErrorResponseCode": null,
    "avsPostalCodeResponseCode": "A",
    "avsStreetAddressResponseCode": "A",
    "billingAddress": {
        "id": "5h",
        "customerId": null,
        "firstName": null,
        "lastName": null,
        "company": null,
        "streetAddress": null,
        "extendedAddress": null,
        "locality": null,
        "region": null,
        "postalCode": "94107",
        "countryCodeAlpha2": null,
        "countryCodeAlpha3": null,
        "countryCodeNumeric": null,
        "countryName": null,
        "createdAt": null,
        "updatedAt": null
    },
    "channel": null,
    "createdAt": "2019-05-22T20:22:47Z",
    "creditCard": {
        "bin": "401288",
        "cardholderName": null,
        "cardType": {},
        "createdAt": null,
        "customerId": null,
        "isDefault": null,
        "isVenmoSdk": false,
        "isExpired": null,
        "customerLocation": {},
        "lastFour": "1881",
        "uniqueNumberIdentifier": "906406403a9e494069f5b49983ca0f1a",
        "subscriptions": [],
        "token": "5xxx2q",
        "updatedAt": null,
        "billingAddress": {
            "id": null,
            "customerId": null,
            "firstName": null,
            "lastName": null,
            "company": null,
            "streetAddress": null,
            "extendedAddress": null,
            "locality": null,
            "region": null,
            "postalCode": null,
            "countryCodeAlpha2": null,
            "countryCodeAlpha3": null,
            "countryCodeNumeric": null,
            "countryName": null,
            "createdAt": null,
            "updatedAt": null
        },
        "expirationMonth": "12",
        "expirationYear": "2020",
        "prepaid": {},
        "payroll": {},
        "debit": {},
        "commercial": {},
        "healthcare": {},
        "durbinRegulated": {},
        "imageUrl": "https://assets.braintreegateway.com/payment_method_logo/visa.png?environment=sandbox",
        "verification": null,
        "accountType": null,
        "countryOfIssuance": "Unknown",
        "issuingBank": "Unknown",
        "productId": "Unknown",
        "expirationDate": "12/2020",
        "maskedNumber": "401288******1881"
    },
    "currencyIsoCode": "USD",
    "customerDetails": {
        "id": "149104609",
        "firstName": "Mark",
        "lastName": "Jones",
        "company": "Jones Co.",
        "email": "mark.jones@example.com",
        "phone": "614-555-1234",
        "fax": "419-555-1234",
        "website": "http://example.com"
    },
    "cvvResponseCode": "A",
    "descriptor": {
        "name": null,
        "phone": null,
        "url": null
    },
    "discounts": [],
    "disputes": [],
    "gatewayRejectionReason": {},
    "merchantAccountId": "skidata",
    "orderId": null,
    "planId": null,
    "processorAuthorizationCode": null,
    "processorResponseType": {},
    "processorResponseCode": "1002",
    "processorResponseText": "Processed",
    "processorSettlementResponseCode": null,
    "processorSettlementResponseText": null,
    "additionalProcessorResponse": null,
    "voiceReferralNumber": null,
    "purchaseOrderNumber": null,
    "recurring": null,
    "refundedTransactionId": "cch47ty3",
    "refundIds": [],
    "partialSettlementTransactionIds": [],
    "authorizedTransactionId": null,
    "settlementBatchId": null,
    "shippingAddress": {
        "id": null,
        "customerId": null,
        "firstName": null,
        "lastName": null,
        "company": null,
        "streetAddress": null,
        "extendedAddress": null,
        "locality": null,
        "region": null,
        "postalCode": null,
        "countryCodeAlpha2": null,
        "countryCodeAlpha3": null,
        "countryCodeNumeric": null,
        "countryName": null,
        "createdAt": null,
        "updatedAt": null
    },
    "escrowStatus": {},
    "status": {},
    "statusHistory": [
        {
            "amount": 10,
            "status": {},
            "timestamp": "2019-05-22T20:22:47Z",
            "source": {},
            "user": "mckenna.jones"
        },
        {
            "amount": 10,
            "status": {},
            "timestamp": "2019-05-22T21:00:43Z",
            "source": {},
            "user": "mckenna.jones"
        }
    ],
    "authorizationAdjustments": [],
    "subscriptionId": null,
    "subscriptionDetails": {
        "billingPeriodEndDate": null,
        "billingPeriodStartDate": null
    },
    "taxAmount": null,
    "taxExempt": false,
    "type": {},
    "updatedAt": "2019-05-22T21:00:43Z",
    "customFields": {},
    "serviceFeeAmount": null,
    "disbursementDetails": {
        "settlementAmount": null,
        "settlementCurrencyIsoCode": null,
        "settlementCurrencyExchangeRate": null,
        "fundsHeld": null,
        "success": null,
        "disbursementDate": null
    },
    "applePayDetails": null,
    "androidPayDetails": null,
    "amexExpressCheckoutDetails": null,
    "payPalDetails": null,
    "coinbaseDetails": null,
    "venmoAccountDetails": null,
    "usBankAccountDetails": null,
    "idealPaymentDetails": null,
    "visaCheckoutCardDetails": null,
    "masterpassCardDetails": null,
    "samsungPayCardDetails": null,
    "paymentInstrumentType": {},
    "riskData": null,
    "threeDSecureInfo": null,
    "facilitatedDetails": null,
    "facilitatorDetails": null,
    "discountAmount": null,
    "shippingAmount": null,
    "shipsFromPostalCode": null,
    "networkTransactionId": null,
    "authorizationExpiresAt": null
}
Epreuve commented 5 years ago

Thanks for the heads up! We'll follow up here once we've taken a look at what's causing this.

Epreuve commented 5 years ago

After taking a bit of time to look over this, it seems like the serializer has an issue with values that are enums, although I'm not particularly sure why. Just to double check, you're using JSON.net as your serializer?

mckennajones commented 5 years ago

Hey @Epreuve , yes we are using the default .NET Core serializer, which I am fairly sure is JSON.net.

I did see issue #60, but it appears that nothing was merged in as a result of it.

sjh37 commented 4 years ago

We had a similar issue, not with Braintree, but another project. .NET Core serializer is amazingly fast, but not quite right in some situations. Our solution was to use Newtonsoft JSON Serilizer instead. Read how to plug it into .Net Core here: https://dotnetcoretutorials.com/2019/12/19/using-newtonsoft-json-in-net-core-3-projects/

AdamEssenmacher commented 4 years ago

This is happening because the Braintree library defined its own Enumeration type instead of using the .NET Enum type. The .NET Enum type implements IConvertible and IFormattable, while the Braintree Enumeration type does not.

If the Braintree.Enumeration type was updated to implement IConvertible and IFormattable, then most serializers would work as expected.

Alternatively, adding a public read-only string property (maybe called Value) backed by the protected Enumeration.Name field would also cause most serializers to work out of the box.

If you were starting from scratch, using the .NET Enum type or making the Braintree Enumeration type implement IConvertible and IFormattable would be ideal IMO.

However, to fix from where we are now, I would recommend adding the public read-only property to the Enumeration type. Depreciating the Enumeration type in favor of the .NET Enum would be a major breaking change for obvious reasons. Implementing IConvertible and IFormattable could be a breaking change in a more subtle way, as you could break some downstream Logstash indexes.

As a work-around, anyone using Json.NET to serialize Braintree objects could use a custom JsonConverter like this:

public class EnumerationConverter : JsonConverter<Enumeration>
{
    public override void WriteJson(JsonWriter writer, Enumeration value, JsonSerializer serializer)
    {
        writer.WriteRawValue($"\"{value}\""); // assuming all Braintree Enumeration strings are safe as a raw json value
    }

    public override Enumeration ReadJson(JsonReader reader, Type objectType, Enumeration existingValue, bool hasExistingValue,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
crookedneighbor commented 4 years ago

@AdamEssenmacher we're about to do a major version bump of the SDK, so now's the perfect time to make that change. (good timing on the comment!)

Are you interested in PR-ing it? no worries if not.

AdamEssenmacher commented 4 years ago

@crookedneighbor maybe... I suggested three possible approaches to support better serialization:

  1. Replace Braintree.Enumeration types with .NET Enum types
  2. Update Braintree.Enumeration to implement IConvertible and IFormattable
  3. Add a read-only Value property to Braintree.Enumeration

Which approach would you think is best?

sestevens commented 4 years ago

@mckennajones @AdamEssenmacher We've replaced all Braintree.Enumeration subclasses with C# enum types in version 5.0.0, which was just released. This should resolve the serialization issue.