8
votes

For the Braintree_PaymentMethod::create() function, one of the options is:

'failOnDuplicatePaymentMethod', bool

If this option is passed and the payment method has already been added to the Vault, the request will fail. This option will not work with PayPal payment methods.

This appears to be a global compare. i.e. if the credit card information exists in the vault regardless of customer id this will fail.

Is there a way to check for duplicates on a particular customer?

4
I contacted support about this ~2 years ago and the answer was no, but it's certainly worth looking into again.Evan
so I can't have two test accounts with 41111111111111111 in them? This condition makes no sense to me. The vault should be unique to the person's account. What if two people that share a credit card have separate accounts. How does this make any sense.Simon_Weaver
Notable that it says this doesn't work with PayPal. Which is actually GREAT. If you try to add a PayPal nonce that represents an already added payment method you just get back the token corresponding to that payment method (which never changes for that customer). Unfortunately that's not how credit cards work.Simon_Weaver

4 Answers

11
votes

Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.

You and Evan are correct: this is the only pre-built way of failing on duplicate creates regardless of customer create. You could achieve what you are trying to do with your own automation, however.

To do this, simply collect the credit card unique ids that already exist from the customer object. Then when you create the new payment method, compare it with the existing cards:

function extractUniqueId($creditCard){ 
    return $creditCard->uniqueNumberIdentifier;
}

$customer = Braintree_Customer::find('your_customer');
$unique_ids = array_map(extractUniqueId,$customer->creditCards);

$result = Braintree_PaymentMethod::create(array(
    'customerId' => 'your_customer',
    'paymentMethodNonce' => 'fake-valid-discover-nonce',
));

if ($result->success) {
    if(in_array(extractUniqueId($result->paymentMethod), $unique_ids)) {
        echo "Do your duplicate logic";
    } else {
        echo "Continue with your unique logic";
    }
} 

Depending on what you want to do, you could delete the new payment method or whatever else you need.

1
votes

Checked with Braintree support--still not available out of the box:

If you use failOnDuplicatePaymentMethod any request to add duplicate payment method information into the Vault will fail.

We currently don’t have the functionality to prevent a customer from adding a duplicate card to their profile, while allowing duplicate cards to still be added under multiple profiles. If this is something you are interested in you will have to build out your own logic.

1
votes

@Raymond Berg, I made soem changes in your code, Here is the updated code:
1. Used foreach instead of in_array
2. Also delete the added card If found duplicate

$customer = Braintree_Customer::find('your_customer');
$unique_ids = array_map(extractUniqueId,$customer->creditCards);

$result = Braintree_PaymentMethod::create(array(
    'customerId' => 'your_customer',
    'paymentMethodNonce' => 'fake-valid-discover-nonce',
));

if ($result->success) {
    $cardAlreadyExist = false;
$currentPaymentMethod = $this->extractUniqueId($result->paymentMethod);
//The in_array function was not working so I used foreach to check if     card identifier exist or not
    foreach ($unique_ids as $key => $uid) {
        if( $currentPaymentMethod  == $uid->uniqueNumberIdentifier)
        {
            $cardAlreadyExist = true;
//Here you have to delete the currently added card
            $payment_token = $result->paymentMethod->token;
            Braintree_PaymentMethod::delete($payment_token);
        }
}


    if($cardAlreadyExist) {
        echo "Do your duplicate logic";
    } else {
        echo "Continue with your unique logic";
    }

}
1
votes

Here is a .NET version. Not 100% complete, but a good starter for someone with the same situation. If you find any issues or suggestions please just edit this answer.

        try
        {
            // final token value (unique across merchant account)
            string token;

            // PaymentCreate request
            var request = new PaymentMethodRequest
            {
                CustomerId = braintreeID,
                PaymentMethodNonce = nonce,
                Options = new PaymentMethodOptionsRequest()
            };

            // try to create the payment without allowing duplicates
            request.Options.FailOnDuplicatePaymentMethod = true;
            var result = await gateway.PaymentMethod.CreateAsync(request);

            // handle duplicate credit card (assume CC type in this block)
            if (result.Errors.DeepAll().Any(x => x.Code == ValidationErrorCode.CREDIT_CARD_DUPLICATE_CARD_EXISTS))
            {
                // duplicate card - so try again (could be in another vault - ffs)

                // get all customer's existing payment methods (BEFORE adding new one)
                // don't waste time doing this unless we know we have a dupe
                var vault = await gateway.Customer.FindAsync(braintreeID);


                // fortunately we can use the same nonce if it fails
                request.Options.FailOnDuplicatePaymentMethod = false;

                result = await gateway.PaymentMethod.CreateAsync(request);
                var newCard = (result.Target as CreditCard);

                // consider a card a duplicate if the expiration date is the same + unique identifier is the same
                // add on billing address fields here too if needed
                var existing = vault.CreditCards.Where(x => x.UniqueNumberIdentifier == newCard.UniqueNumberIdentifier).ToArray();
                var existingWithSameExpiration = existing.Where(x => x.ExpirationDate == newCard.ExpirationDate);

                if (existingWithSameExpiration.Count() > 1)
                {
                    throw new Exception("Something went wrong! Need to decide how to handle this!");
                }
                else
                {
                    // delete the NEW card 
                    await gateway.PaymentMethod.DeleteAsync(newCard.Token);

                    // use token from existing card
                    token = existingWithSameExpiration.Single().Token;
                }
            }
            else
            {
                // use token (could be any payment method)
                token = result.Target.Token;
            }

            // added successfully, and we know it's unique
            return token;
        }
        catch (BraintreeException ex)
        {
            throw;  
        }
        catch (Exception ex)
        {
            throw;
        }