How to re-verify credit card in vault before sale transaction in Braintree - braintree

How can I re-verify payment method [credit card] in vault before doing sale transaction.
Note: CVV and AVS rules are enable.
Scenario is:
Customer in braintree vault doing sale transaction with payment method [Credit card] in vault.
I want to re-verify credit card before sale transaction to make sure credit card is not expire.

I believe it depends on how you are integrated with Braintree. Are you using their Hosted Fields? Drop-In?
Basically, according to their docs, you would create a nonce that contains only the CVV you prompted the user for.
braintree.setup('YOUR_CLIENT_TOKEN', 'custom', {
id: 'my-sample-form',
hostedFields: {
cvv: {
selector: '#cvv'
}
}
});
Once you have that nonce, you can pass it to a PaymentMethod.update() call for the appropriate payment method token, and ensure verify_card is set to true.
result = braintree.PaymentMethod.update("the_payment_method_token", {
"payment_method_nonce": nonce_from_the_client,
"options": {
"verify_card": True,
}
})
Found at https://developers.braintreepayments.com/reference/request/payment-method/update/#card-verification

Related

Redirect customer if he has an incomplete payment

I would like to create a restricted page using Cashier. In a nutshell I would like the page to start a subscription to have these limitations:
Accessible to users who do not have a subscription
Not accessible to users who have an incomplete invoice/subscription and therefore an open invoice.
This is my code:
if(Auth::user()->subscribed('default')) {
return redirect()->route('index')->with('error', 'You already have an active subscription right now.');
}
if(Auth::user()->subscribed('default') || Auth::user()->subscription('default')->hasIncompletePayment()){
return redirect()->route('account.invoices')->with('warning', 'You have a payment invoice for a pending subscription. Make payment or cancel.');
}
With this code of mine the only problem is when I try to access the page with a user who has no subscription, this error appears:
Call to a member function hasIncompletePayment() on null
if(Auth::user()->subscribed('default')) {
return redirect()->route('index')->with('error', 'You already have an active subscription right now.');
}
if(!empty(Auth::user()->subscription('default'))){
if(Auth::user()->subscribed('default') || Auth::user()->subscription('default')->hasIncompletePayment()){
return redirect()->route('account.invoices')->with('warning', 'You have a payment invoice for a pending subscription. Make payment or cancel.');
}
}
Check subscription == null with empty

Support 3D Secure card Stripe payment to start subscription

My payment model idea for my application is really simple: Having a (laravel) website with a member area and some special funcionality, whereas a member account costs 19.90 / year. I wanted to integrate Stripe to my registration flow to allow a payment to happen. When the payment has succeeded, I create a subscription which will then automatically renew this payment each year.
So good so far - I managed to get it working using the Guide on how to set up a subscription by Stripe. However, cards that required 3D Secure authentication did not work yet, and this is a must-have.
So I read further and used a PaymentIntent (API Docs). However, current behavior is the following:
I create a PaymentIntent and pass the public key to the frontend
Customer enters credentials and submits
3D Secure Authentication happens correctly, returning me a payment_method_id
On the server side, I retrieve the PaymentIntent again. It has status succeeded and the payment is recieved on my Stripe Dashboard.
I then create the customer object (with the payment method I got from the PaymentIntent), and with that customer, create the subscription
The subscription has status incomplete and it seems that the subscription tries to again charge the customer but fails because of the 3D Secure validation that would be necessary the second time.
So my actual question is: How can I create a subscription which notices somehow that the customer has already paid with my PaymentIntent and the PaymentMethod that I'm passing to it?
Some Code
Create the PaymentIntent and pass that to the frontend
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
$intent = \Stripe\PaymentIntent::create([
'amount' => '1990',
'currency' => 'chf',
]);
$request->session()->put('stripePaymentIntentId',$intent->id);
return view('payment.checkout')->with('intentClientSecret',$intent->client_secret);
Frontend Checkout when clicking "Buy"
// I have stripe elements (the card input field) ready and working
// using the variable "card". The Stripe instance is saved in "stripe".
// Using "confirmCardPayment", the 3DS authentication is performed successfully.
stripe.confirmCardPayment(intentClientSecret,{
payment_method: {card: mycard},
setup_future_usage: 'off_session'
}).then(function(result) {
$('#card-errors').text(result.error ? result.error.message : '');
if (!result.error) {
submitMyFormToBackend(result.paymentIntent.payment_method);
}
else {
unlockPaymentForm();
}
});
Backend after submitting
// Get the PaymentMethod id from the frontend that was submitted
$payment_method_id = $request->get('stripePaymentMethodId');
// Get the PaymentIntent id which we created in the beginning
$payment_intent_id = $request->session()->get('stripePaymentIntentId');
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
// Get the Laravel User
$user = auth()->user();
// Firstly load Payment Intent to have this failing first if anything is not right
$intent = \Stripe\PaymentIntent::retrieve($payment_intent_id);
if ($intent instanceof \Stripe\PaymentIntent) {
// PaymentIntent loaded successfully.
if ($intent->status == 'succeeded') {
// The intent succeeded and at this point I believe the money
// has already been transferred to my account, so it's paid.
// Setting up the user with the paymentMethod given from the frontend (from
// the 3DS confirmation).
$customer = \Stripe\Customer::create([
'payment_method' => $payment_method_id,
'email' => $user->email,
'invoice_settings' => [
'default_payment_method' => $payment_method_id,
],
]);
$stripeSub = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => env('STRIPE_PLAN_ID'),
]
],
'collection_method' => 'charge_automatically',
'off_session' => false,
]);
// If the state of the subscription would be "active" or "trialing", we would be fine
// (depends on the trial settings on the plan), but both would be ok.
if (in_array($stripeSub->status,['active','trialing'])) {
return "SUCCESS";
}
// HOWEVER the state that I get here is "incomplete", thus it's an error.
else {
return "ERROR";
}
}
}
I finally got a working solution running for my site. It goes like this:
1 - Backend: Create a SetupIntent
I created a SetupIntent (SetupIntent API Docs) to cover the checkout flow entirely. The difference to a PaymentIntent (PaymentIntent API Docs) is that the PaymentIntent goes from collecting the card details, preparing the payment and effectively transferring the amount to the account, while the SetupIntent only prepares card collection, but does not yet execute the payment. You will get a PaymentMethod (PaymentMethod API Docs) from it, which you can use later.
$intent = SetupIntent::create([
'payment_method_types' => ['card'],
]);
Then I passed the $intent->client_secret key to my client side JavaScript.
2 - Frontend: Collect card details with Elements
On the frontend, I placed the Stripe card element to collect the card details.
var stripe = Stripe(your_stripe_public_key);
var elements = stripe.elements();
var style = { /* my custom style definitions */ };
var card = elements.create('card',{style:style});
card.mount('.my-cards-element-container');
// Add live error message listener
card.addEventListener('change',function(event) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
}
// Add payment button listener
$('.my-payment-submit-button').on('click',function() {
// Ensure to lock the Payment Form while performing async actions
lockMyPaymentForm();
// Confirm the setup without charging it yet thanks to the SetupIntent.
// With 3D Secure 2 cards, this will trigger the confirmation window.
// With 3D Secure cards, this will not trigger a confirmation.
stripe.confirmCardSetup(setup_intent_client_secret, {
payment_method: {card: card} // <- the latter is the card object variable
}).then(function(result) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
if (!result.error) {
submitPaymentMethodIdToBackend(result.setupIntent.payment_method);
}
else {
// There was an error so unlock the payment form again.
unlockMyPaymentForm();
}
});
}
function lockMyPaymentForm() {
$('.my-payment-submit-button').addClass('disabled'); // From Bootstrap
// Get the card element here and disable it
// This variable is not global so this is just sample code that does not work.
card.update({disabled: true});
}
function unlockMyPaymentForm() {
$('.my-payment-submit-button').removeClass('disabled'); // From Bootstrap
// Get the card element here and enable it again
// This variable is not global so this is just sample code that does not work.
card.update({disabled: false});
}
3 - Backend: Create Customer and Subscription
On the backend, I received the $payment_method_id which I submitted from the frontend.
Firstly, we need now to create a Customer (Customer API Docs) if it does not yet exist. On the customer, we will attach the payment method from the SetupIntent. Then, we create the Subscription (Subscription API Docs) which will start the charge from the SetupIntent.
$customer = \Stripe\Customer::create([
'email' => $user->email, // A field from my previously registered laravel user
]);
$paymentMethod = \Stripe\PaymentMethod::retrieve($payment_method_id);
$paymentMethod->attach([
'customer' => $customer->id,
]);
$customer = \Stripe\Customer::update($customer->id,[
'invoice_settings' => [
'default_payment_method' => $paymentMethod->id,
],
]);
$subscription = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => 'MY_STRIPE_PLAN_ID',
],
],
'off_session' => TRUE, //for use when the subscription renews
]);
Now we have a Subscription object. With regular cards, the state should be active or trialing, depending on your trial days setting on the subscription. However when dealing with 3D Secure test cards, I got the subscription still in an incomplete state. According to my Stripe support contact, this can also be a problem because of not yet fully working 3D Secure test cards. However I assume that this can also happen on production environments with some sort of cards, so we have to deal with it.
On subscriptions with status incomplete you can retrieve the latest invoice from $subscription->latest_invoice like so:
$invoice = \Stripe\Invoice::retrieve($subscription->latest_invoice);
On your invoice object, you will find a status and a hosted_invoice_url. When the status is still open, I now present the user the URL to the hosted invoice which he has to complete first. I let him open the link in a new window, which shows a nice looking invoice hosted by stripe. There, he is free to again confirm his credit card details including the 3D Secure workflow. In case he succeeds there, the $subscription->status changes to active or trialing after you re-retrieve the subscription from Stripe.
This is some sort of fool proof strategy that if anything with your implementation goes wrong, just send them to Stripe to complete it. Just be sure to hint the user that in case he has to confirm his card twice, it won't be charged twice but only once!
I was not able to create a working version of #snieguu's solution because I wanted to use Elements and not collect the credit card details separately to then create a PaymentMethod by myself.
Have You considered the opposite approach that payment intents(Also the first one) will be generated by subscription - not created manually?
So the flow will be:
Create a payment method
Create Customer(using the payment method)
Create Subscription(using Customer and payment method) - that creates also the first invoice
Retrieve payment intent from Subscription by latest_invoice.payment_intent.id. Here You can choose if this should be handled by You or Stripe. See this: How to get PaymentIntent next_action.type = redirect_to_url instead of use_stripe_sdk for Subscription
Allow finishing 3D secure flow
You have a constant price for a subscription, so it will be charged upfront:
https://stripe.com/docs/billing/subscriptions/multiplan#billing-periods-with-multiple-plans
Conventional plans that charge a fixed amount on an interval are billed at the start of each billing cycle.

Migrating User to Cognito on Sign In

I am trying to migrate users to Cognito when they sign in the first time. For this I wrote a lambda function that does call an API to check if the users exist in db or not ? if the user exists, it will be created in cognito but I am not sure how do I tell the application that user is created and it should allow the user to login .
Here is the code in c#:
public async Task<Stream> FunctionHandlerAsync(Stream stream, ILambdaContext context)
{
RootObject rootObj = DeserializeStream(stream);
User user = new User(rootObj.userName, rootObj.request.password);
ApiResponse apiResponse = await MobileAuthenticateAsync(user.UserName, user.Password);
// Considering apiResponse returns "user authenticated", we create the user in //cognito. This is working.
// How do I send response back to Application so it knows that user is // //created and authenticated and should be allowed to login.
//Before returning stream, I am setting following 2 status.
rootObj.response.finalUserStatus = "CONFIRMED"; // is this correct ?
rootObj.response.messageAction = "SUPPRESS";
return SerializeToStream(rootObj);;
}
You're pretty close.
You can see the full documentation on the Migrate User Lambda Trigger page, however in short you need your response to look like:
{
response: {
userAttributes: {
email: 'user#example.com',
email_verified: true,
custom:myAttribute: 123,
},
finalUserStatus: 'CONFIRMED',
messageAction: 'SUPPRESS',
forceAliasCreation: false,
}
}
Where:
userAttribute: this is a dictionary/map of the user's attributes keys in cognito (note that any custom attributes need to be prefixed with custom:), to the values from the system you're migrating from. You do not need to provide all of these, although if you're using an email alias you may want to set email_verified: true to prevent the user having to re-verify their e-mail address.
finalUserStatus: if you set this to CONFIRMED then the user will not have to re-confirm their email address/phone number, which is probably a sensible default. If you are concerned that the password is given as plain-text to cognito this first-time, you can instead use RESET_REQUIRED to force them to change their password on first sign-in.
messageAction: should probably be SUPPRESS unless you want to send them a welcome email on migration.
forceAliasCreation: is important only if you're using email aliases, as it stops users who manage to sign-up into cognito being replaced on migration.
If you respond with this (keeping the rest of the original rootObj is convenient but not required then the user will migrated with attributes as specified.
If you throw (or fail to respond with the correct event shape) then the migration lambda fails and the user is told that they couldn't migrated. For example, because they do not exist in your old user database, or they haven't provided the right credentials.

Braintree, How do I delete a users credit card with the nonce from the client?

Server side there is a function to delete a payment method (result = Braintree::PaymentMethod.delete("the_token")) but it takes a payment method token. How do I get the payment methods token with the nonce from the client?
edit: I'm not using the drop in UI. I have a custom list of credit cards the user has (using the Javascript v3 SDK). I want to have a button to delete cards. The JS SDK dosnt give the credit cards token, just a nonce. What is the process for turning the data available to the client into something I can use to delete the card on the server?
edit2: The list of credit cards on the clent side uses the VaultManager from the JavaScript v3 SDK. It returns a fetchPaymentMethodsPayload.
This is the client side code:
_loadPaymentMethods() {
this.paymentService.getBraintreeToken().then( token => {
this.braintreeClient.create({
authorization: token
}, (clientErr, clientInstance) => {
if (clientErr) {
// Handle error in client creation
return;
}
var options = {
client: clientInstance,
};
this.vaultManager.create(options, (err, vaultInstance) => {
if (err) {
console.log(err);
return;
}
vaultInstance.fetchPaymentMethods({ defaultFirst: true }, (err, paymentMethods) => {
paymentMethods.forEach( paymentMethod => {
if(paymentMethod.type == 'CreditCard') {
this.cards.push(paymentMethod);
if(paymentMethod.default) {
this.card = paymentMethod;
}
}
});
});
});
});
});
}
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
If using VaultManager on the client-side to populate your cards, you will not have the functionality to allow a user to delete one of those cards. The reason for this goes back to what you said, that nonces are what's returned on the fetchPaymentMethodsPayload method. VaultManager can populate a nonce that's associated with an already created card, since it's only passing that nonce into a Transaction.sale() call. Since the nonce is populated when the form is rendered, you can not search for that nonce compared to a payment method in the vault, since it will not exist previously and nonces are meant for one time use. This is why nonces aren't passed into PaymentMethod.find() calls.
To accomplish your task you would need to build out custom logic that mimics what Vault Manager does; however, would need to returns the tokens. One way would be as mentioned in my comment: by finding the customer object and grabbing the customer's payment methods, and then pulling out the tokens associated with those payment methods.
I noticed that when you create a payment method with same creds, it won't get duplicated. So it's kinda like "find or create", you can then use that result to get the token and then do a gateway.payment_method.delete( result.payment_method.token )

Setting payment method to cart with Magento API

I'm using Magento API to create orders. My code fails when I want to add payment method to the cart :
$paymentMethod = array(
“method” => “paypal_standard”
);
$resultPaymentMethod = $proxy->call(
$sessionId,
“cart_payment.method”,
array(
$shoppingCartId,
$paymentMethod
)
);
I'm getting following error: Payment method is not allowed.
In admin section in System->Configuration->PayPal I have set Website Payments Standard but I didn't enabled any option in System->Configuration->Payment Methods cause there is none available for PayPal.
When I call:
$proxy->call($session, 'cart_payment.list')
method I get an empty array as there isn't any available payment method set. Does someone knows how and where paypal payment setting is saved in Magento ?
If I set another Payment method like "checkmo" then the order is created fine. The thing is that I only need to allow Paypal standard payment.
So my question is: How can I set payment method to PayPal to the cart so my order will be successfully created?
Thanks.
I have also facing this problem and find reason for it.
$method->canUseInternal() used in payment method api. When we use paypal or other redirect able methods in payment methos api in that case $method->canUseInternal() it getting false value.
So for this type situation we need to create own custom coding.
api function refreance:
protected function _canUsePaymentMethod($method, $quote){
if (!($method->isGateway() || $method->canUseInternal())) {
return false; }
if (!$method->canUseForCountry($quote->getBillingAddress()->getCountry())) {
return false;
}
if (!$method->canUseForCurrency(Mage::app()->getStore($quote->getStoreId())->getBaseCurrencyCode())) {
return false;
}
To pay with Paypal you need your customer to be redirected to Paypal. Because of this fact, you may not be allowed to use this payment method using API. I recommend you to have look at isAvailable() of the payment method to customize this behaviour.

Resources