29

After playing around with the Authorize.Net CIM XML API C# sample code, I started using the Authorize.Net C# SDK. I am able to add credit cards and bank accounts to customer profiles using the CIM XML API sample code. I don't see how to add bank accounts using the SDK though.

Adding bank account with CIM XML API:

...
customerPaymentProfileType new_payment_profile = new customerPaymentProfileType();
paymentType new_payment = new paymentType();

bankAccountType new_bank = new bankAccountType();
new_bank.nameOnAccount = "xyz";
new_bank.accountNumber = "4111111";
new_bank.routingNumber = "325070760";
new_payment.Item = new_bank;

new_payment_profile.payment = new_payment;

createCustomerPaymentProfileRequest request = new createCustomerPaymentProfileRequest();
XmlAPIUtilities.PopulateMerchantAuthentication((ANetApiRequest)request);

request.customerProfileId = profile_id.ToString();
request.paymentProfile = new_payment_profile;
request.validationMode = validationModeEnum.testMode;
...

Using the SDK I only see a .AddCreditCard() method, but no way to add a bank account. When I loop through all my PaymentProfiles It throws an exception when it comes across a bank account too:

CustomerGateway cg = new CustomerGateway("xxx", "yyy");

foreach (string cid in cg.GetCustomerIDs())
{
    Customer c = cg.GetCustomer(cid);
    foreach (PaymentProfile pp in c.PaymentProfiles)
    {
        Console.WriteLine(pp.ToString());
    }
}

Exception:

Unable to cast object of type 'AuthorizeNet.APICore.bankAccountMaskedType' to type 'AuthorizeNet.APICore.creditCardMaskedType'.

enter image description here

How do I add a bank account to a CIM profile using the Authorize.Net C# SDK?

Update:

Proof that CIM can store bank account information:

enter image description here

Greg
  • 8,574
  • 21
  • 67
  • 109
  • @Ramhound so you're saying that I cannot store bank account information using CIM? – Greg Jun 11 '12 at 14:24
  • @Ramhound using the CIM sample code on their site they allow you to create payment profiles for your customers so that when they login again they don't have to re-enter payment information (because Authorize.Net CIM saves it) so I don't have to save it or even have direct access to it with my application – Greg Jun 11 '12 at 14:36
  • 2
    @Ramhound Explain this then https://dl.dropbox.com/u/3115379/ProofThatCIMStoresBankAccountInformation.png – Greg Jun 11 '12 at 15:32
  • @Ramhound "The class stores the complete credit card information" I don't think you're meant to persist that class - you'll submit the information to them and they'll pass back a reference for you to bill the card in the future, exactly the same way you can optionally save your card details on sites such as Amazon. Authorize.NET appear to be owned by VISA and credit card payment processing is heavily regulated anyway - I'm sure they know what they're doing. – Rup Jun 11 '12 at 15:35
  • 1
    @Rup - They might know what they are doing but I was not impressed by their code nor their community itself. – Security Hound Jun 11 '12 at 15:53
  • 2
    @Greg - I don't know what to tell you. I took a look at their entire API. The API I downloaded makes me believe that the PaymentProfile does not support adding a checking account to a Customer's PaymentProfile. So I would suggest you contact Authorize.NET and simply ask. **I went ahead and remove the comments I made since they are not correct.** – Security Hound Jun 11 '12 at 15:56
  • @Greg I assume you've asked their support directly? – Rup Jun 13 '12 at 14:05
  • 2
    @Rup yes, no response from developer@authorize.net – Greg Jun 13 '12 at 14:20
  • Running the assembly through Reflector, it looks like you're right: the web service API does support those objects but their C# wrapper for it doesn't. Your best bet might be to decompile it all and fix the classes that need support (PaymentProfile to add extra fields and fix the cast, CustomerGateway for a new method to send the createCustomerPaymentProfileRequest) or make your own versions of those classes that make the AuthorizeNet.APICore calls directly - they've left those classes as public (they ought really be internal). – Rup Jun 13 '12 at 14:28
  • @Rup at this point I'm leaning towards just encapsulating the CIM XML API sample code in my own class and giving up on the SDK all together – Greg Jun 13 '12 at 15:35
  • 3
    As of 6/15/12 the post on Authorize.NET's developer forums says they are "looking into it"... http://community.developer.authorize.net/t5/Integration-and-Testing/Add-Bank-Account-Payment-Profile-Using-C-SDK/td-p/27060 – Peter Jun 22 '12 at 18:02
  • @Gerald Thanks for the bounty but I think this just isn't possible at the moment - the main C# SDK just assumes all payment profiles it reads are credit cards. We'd need an SDK update to fix this. – Rup Jul 10 '12 at 10:47
  • @Gerald, and everyone else - If you are willing to dig into source, I've provided an answer for you. – Peter Jul 13 '12 at 17:59

1 Answers1

10

The following is tested, but only so far as what the original question brought up (Test it more for me?), I wrote it using the provided XML example and by copying the code for the AddCreditCard code.

When you are all done updating the following code will work:

        var cg = new CustomerGateway("login", "transkey", ServiceMode.Test);
        var c = cg.CreateCustomer("peter@example.com", "test customer");
        //just to show that we didn't break CC
        cg.AddCreditCard(c.ProfileID, "cc#", 07, 2011);
        cg.AddBankAccount(c.ProfileID, "Peter", "bankaccoung#", "routing#");
        //tostring doesn't actually do much... but if you break on it you can see the details for both the CC and the bank info.
        foreach (PaymentProfile pp in cg.GetCustomer(c.ProfileID).PaymentProfiles)
        {
            Console.WriteLine(pp.ToString());
        }

First, download the C# source code for the API from http://developer.authorize.net/downloads/.

In reviewing the code I can see 4 files that use "creditCardType", these are SubscriptionRequest.cs, CustomerGateway.cs, PaymentProfile.cs and AnetApiSchema.cs (this last one we don't have to touch). We also need to watch out for 'creditCardMaskedType', which is used in PaymentProfile.cs, Transaction.cs and AnetApiSchema.cs. Any place these files show up we need to make sure we support the bankAccount equivelants as well.

Open the AuthorizeNET solution. We'll be jumping around a bit through the files listed above.

In CustomerGateway.cs add the following block of code:

    /// <summary>
    /// Adds a bank account profile to the user and returns the profile ID
    /// </summary>
    /// <returns></returns>
    public string AddBankAccount(string profileID, string nameOnAccount, string accountNumber, string routingNumber)
    {
        var req = new createCustomerPaymentProfileRequest();
        req.customerProfileId = profileID;
        req.paymentProfile = new customerPaymentProfileType();
        req.paymentProfile.payment = new paymentType();

        bankAccountType new_bank = new bankAccountType();
        new_bank.nameOnAccount = nameOnAccount;
        new_bank.accountNumber = accountNumber;
        new_bank.routingNumber = routingNumber;

        req.paymentProfile.payment.Item = new_bank;

        var response = (createCustomerPaymentProfileResponse)_gateway.Send(req);

        return response.customerPaymentProfileId;
    }

In PaymentProfile.cs add some public properties

    public string BankNameOnAccount {get; set; }
    public string BankAccountNumber { get; set; }
    public string BankRoutingNumber { get; set; }

Modify the the following block of the PaymentProfile(customerPaymentProfileMaskedType apiType) constructor:

        if (apiType.payment != null) {
            if(apiType.payment.Item is bankAccountMaskedType) {
                var bankAccount = (bankAccountMaskedType)apiType.payment.Item;
                this.BankNameOnAccount = bankAccount.nameOnAccount;
                this.BankAccountNumber = bankAccount.accountNumber;
                this.BankRoutingNumber = bankAccount.routingNumber;
            }
            else if (apiType.payment.Item is creditCardMaskedType)
            {
                var card = (creditCardMaskedType)apiType.payment.Item;
                this.CardType = card.cardType;
                this.CardNumber = card.cardNumber;
                this.CardExpiration = card.expirationDate;
            }
        }

Add this block to the PaymentProfile.ToAPI() method:

        if (!string.IsNullOrEmpty(this.BankAccountNumber))
        {
            bankAccountType new_bank = new bankAccountType();
            new_bank.nameOnAccount = BankNameOnAccount;
            new_bank.accountNumber = BankAccountNumber;
            new_bank.routingNumber = BankRoutingNumber;

            result.payment.Item = new_bank;
        }

Add the following public properties to SubscriptionRequest.cs > SubscriptionRequest class (around line 187)

    public string BankNameOnAccount {get; set; }
    public string BankAccountNumber { get; set; }
    public string BankRoutingNumber { get; set; }

Add the following else if block TWICE to SubscriptionRequest. The first time is in the ToAPI method, the second is in the ToUpdateableAPI method, in both cases it goes after the CC number null check.

        else if (!String.IsNullOrEmpty(this.BankAccountNumber))
        {
            bankAccountType new_bank = new bankAccountType();
            new_bank.nameOnAccount = BankNameOnAccount;
            new_bank.accountNumber = BankAccountNumber;
            new_bank.routingNumber = BankRoutingNumber;

            sub.payment = new paymentType();
            sub.payment.Item = new_bank;
        }

Add the following public properties to Transaction.cs

    public string BankNameOnAccount { get; set; }
    public string BankAccountNumber { get; set; }
    public string BankRoutingNumber { get; set; }

In Transaction.cs in the static NewFromResponse(transactionDetailsType trans) method, find the block that checks for trans.payment != null and tweak as shown:

        if (trans.payment != null) {
            if (trans.payment.Item.GetType() == typeof(creditCardMaskedType))
            {
                var cc = (creditCardMaskedType)trans.payment.Item;
                result.CardNumber = cc.cardNumber;
                result.CardExpiration = cc.expirationDate;
                result.CardType = cc.cardType;
            } 
            else if (trans.payment.Item.GetType() == typeof(bankAccountMaskedType))
            {
                var bankAccount = (bankAccountMaskedType)trans.payment.Item;
                result.BankNameOnAccount = bankAccount.nameOnAccount;
                result.BankAccountNumber = bankAccount.accountNumber;
                result.BankRoutingNumber = bankAccount.routingNumber;
            }
        }
Peter
  • 9,643
  • 6
  • 61
  • 108
  • Nice! I'd completely missed that there was a source download :-/ – Rup Jul 16 '12 at 09:28
  • Thank you very much for this sample code. I had support at authorize.net telling me that the C# SDK could not use CIM unless it were done through SOAP/XML, even though I was pointing out to them that there were clearly methods within the SDK to implement CIM. – Ricky Mar 18 '14 at 18:01
  • @Ricky It's sad that almost two years after I wrote this code they still haven't added this functionality to their .Net SDK. Glad to hear it helped you out. – Peter Mar 18 '14 at 18:48
  • @Peter For credit cards it works perfectly, but it's not in the SDK or CIM documentation. I used your first block of code and was able to successfully set up a CIM profile and verify it, without using any of your other code. Am already working on implementing it so that we can get away from storing credit card details in our system, for PCI compliance. Thanks again. – Ricky Mar 18 '14 at 20:37
  • Thanks. It did take a little bit to understand where to add items to the SubscriptionRequest class. – FrankO Apr 07 '14 at 15:51