9

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?

Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
Revent
  • 2,091
  • 2
  • 18
  • 33
  • I contacted support about this ~2 years ago and the answer was no, but it's certainly worth looking into again. – Evan Dec 23 '15 at 03:10
  • 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 Dec 14 '18 at 07:24
  • 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 Jun 03 '19 at 20:48

5 Answers5

14

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.

Raymond Berg
  • 860
  • 5
  • 17
  • Thanks for the full answer! Any insight into why per customer doesn't exist? – Evan Dec 23 '15 at 16:53
  • No, except to say there are lots of ways to solve problems like these. In releasing new features we always look to feedback from Braintree support. Please keep giving it! :) – Raymond Berg Dec 23 '15 at 17:45
  • Thanks for the answer Raymond! So is this `uniqueNumberIdentifier` some sort of hash on just the credit card number? Or all the data including expiration? Is it merchant-specific? – Revent Dec 23 '15 at 18:41
  • More info on the uniqueNumberIdentifier: http://security.stackexchange.com/questions/63248/hashing-a-credit-card-number-for-use-as-a-fingerprint – Revent Dec 23 '15 at 18:42
  • 1
    @Revent Definitely merchant specific! But I can confirm the hash does not consider the expiration date. You'd have to do that yourself. I added a bunch of test cards (to the braintree vault) and retrieved them with 'find customer'. The results coming back show different expiration dates (but obviously never different card types). `Visa 03/2021 80988f0eaaff19cd36ea5ab99a4fb90d` `Visa 12/2018 80988f0eaaff19cd36ea5ab99a4fb90d` `Visa DEFAULT 03/2021 80988f0eaaff19cd36ea5ab99a4fb90d` `Discover 01/2019 b8389212b5076ddbd08f2736eca7b6e3` – Simon_Weaver Dec 14 '18 at 05:12
1

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.

Community
  • 1
  • 1
Evan
  • 457
  • 3
  • 22
1

@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";
    }

}
Mahak Choudhary
  • 1,286
  • 1
  • 16
  • 13
1

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;
        }
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
0

Available for cards now as stated here . Not applicable for Paypal , Gpay and other payment methods. But this requires us to send the braintree customerID along.

nav3916872
  • 978
  • 13
  • 20