I am using ASP.NET MVC 4, the .NET Braintree Payments API, and Braintree.js.
Here is a simple wrapper I have built for Braintree:
public class PaymentBL
{
private static BraintreeGateway _braintreeGateway = new BraintreeGateway
{
Environment = Braintree.Environment.SANDBOX,
MerchantId = "xxxxxxx",
PublicKey = "xxxxxxxxxxxx",
PrivateKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
public Result<Transaction> ChargeCardOnce(decimal amount, string cardholderName, string cardNumber, string expiration,
string cvv)
{
TransactionCreditCardRequest creditCardRequest = new TransactionCreditCardRequest();
creditCardRequest.CardholderName = cardholderName;
creditCardRequest.Number = cardNumber;
creditCardRequest.ExpirationDate = expiration;
creditCardRequest.CVV = cvv;
TransactionOptionsRequest optionsRequest = new TransactionOptionsRequest();
optionsRequest.SubmitForSettlement = true;
TransactionRequest transactionRequest = new TransactionRequest();
transactionRequest.Amount = amount;
transactionRequest.CreditCard = creditCardRequest;
transactionRequest.Options = optionsRequest;
return _braintreeGateway.Transaction.Sale(transactionRequest);
}
/// <summary>
/// Stores a credit card in the Braintree vault. In some cases, will put a $1 temporary charge
/// on the credit card that will come off a few days later.
///
/// From BrainTree: Regardless of card type, any instance where a $1 authorization returns a successful result,
/// we immediately follow-up with an automatic void request to ensure that the transaction will fall off
/// of the cardholder's statement as soon as possible.
/// </summary>
public Result<CreditCard> StoreCustomer(int customerId, string cardholderName, string cardNumber, string expiration, string cvv)
{
//CreditCardAddressRequest addressRequest = new CreditCardAddressRequest();
//addressRequest.PostalCode = postalCode;
CreditCardOptionsRequest optionsRequest = new CreditCardOptionsRequest();
optionsRequest.VerifyCard = true;
optionsRequest.VerificationMerchantAccountId = _braintreeGateway.MerchantId;
CreditCardRequest creditCard = new CreditCardRequest();
creditCard.CustomerId = customerId.ToString();
creditCard.Token = customerId.ToString(); // Use same token to ensure overwrite
creditCard.CardholderName = cardholderName;
creditCard.Number = cardNumber;
creditCard.ExpirationDate = expiration;
creditCard.CVV = cvv;
creditCard.Options = optionsRequest;
return _braintreeGateway.CreditCard.Create(creditCard);
}
/// <summary>
/// Search BrainTree vault to retrieve credit card
/// </summary>
/// <param name="customerId"></param>
public CreditCard GetCreditCardOnFile(int customerId)
{
Customer customer = null;
try
{
customer = _braintreeGateway.Customer.Find(customerId.ToString());
}
catch (Braintree.Exceptions.NotFoundException)
{
return null;
}
if (customer.CreditCards == null || customer.CreditCards.Length == 0)
{
return null;
}
if (customer.CreditCards.Length >= 2)
{
throw new Exception(string.Format("Customer {0} has {1} credit cards",
customerId, customer.CreditCards.Length));
}
return customer.CreditCards[0];
}
}
When I call this method, it works:
Result<Transaction> result = paymentBL.ChargeCardOnce(
2.34m
, formCollection["name"]
, formCollection["number"]
, formCollection["exp"]
, formCollection["cvv"]
);
Subsequently, I can view the completed test transactions in the Braintree dashboard. Therefore, I know that the encrypted form values from Braintree.js are arriving at my controller action correctly, and that my keys and merchant account IDs are all set up correctly.
When I replace the above call to ChargeCardOnce with the below call to StoreCustomer, I receive an Braintree.Exceptions.AuthorizationException at the line return _braintreeGateway.CreditCard.Create(creditCard);
Result<CreditCard> result = paymentBL.StoreCustomer(
systemHost.Customer.CustomerId
, formCollection["name"]
, formCollection["number"]
, formCollection["exp"]
, formCollection["cvv"]
);
From Braintree support: "You are able to create a customer as well as a credit card in the sandbox, as it is built to exactly mirror what the production environment would look like."
Has anyone experience this also? I'm referring Braintree support to this question, but if anyone on SO has seen this and knows a solution or workaround, I would be much relieved.
I work at Braintree. It looks like we already got back to you with the answer to your question, but I'll answer here as well for anyone who has the same issue in the future.
In this case, the problem is:
optionsRequest.VerificationMerchantAccountId = _braintreeGateway.MerchantId;
Your merchant ID identifies your payment gateway account, while your merchant account ID identifies the bank account you want to use for the verification. An article in our support center explains the difference:
MerchantAccountId
With Braintree, you can have multiple merchant accounts all processing via the same gateway account. This means that you can have multiple locations, multiple businesses, multiple currencies, etc. all setup and processing under a single account. This makes it easy to keep track of all of your processing via unified reporting and access and can even save you money.
You can find the IDs for all merchant accounts in your gateway account by following these steps:
login to your account
hover over your account name and click "Processing"
scroll to the bottom of the page to the section labeled "Merchant Accounts".
An AuthorizationException
is HTTP Status Code 403 Forbidden. It means that the server is declining your request, because you don't have permission to access a resource (even though you may be authenticated).
Since there is no merchant account available to your user with the ID you specify (since it's not a merchant account ID at all), you get the AuthorizationException
.
If, as is often the case, your merchant has only one merchant account, or you want to use the default account, it's not necessary to specify a VerificationMerchantAccountId
.