Can I add Card Details without charging the customer with Stripe? - laravel

I need my customers to add their credit card info, but I don't want to charge them right away. Is there any way, I can add their details to charge later when using Stripes checkout solution and just store their card token first?
I am using Laravel Cashier on the backend.

You should first create a Stripe card token. You have two options to do so:
On the client side (recommended) using Stripe.js & Elements, you first create a card Element (The Elements object) and then you create a Stripe card Token (stripe.createToken())
On the server side (Create a card token). The easiest way is to install the official Stripe PHP library with composer require stripe/stripe-php and then use the following piece of code:
\Stripe\Stripe::setApiKey("{your_stripe_api_key}");
\Stripe\Token::create([
"card" => [
"number" => "{card_number}",
"exp_month" => {card_expiration_month},
"exp_year" => {card_expiration_year},
"cvc" => "{card_security_code}"
]
]);
Once you have that token you can use Laravel Cashier to store the Stripe card token in database and update the card information in Stripe with the following methods:
$user->updateCard($token);
$user->updateCardFromStripe();
More information on those methods in the Updating Credit Cards section of the Laravel Cashier documentation.

If, like me, you're looking for getting the token :
\Stripe\Stripe::setApiKey("{your_stripe_api_key}");
$token = \Stripe\Token::create([
"card" => [
"number" => "{card_number}",
"exp_month" => {card_expiration_month},
"exp_year" => {card_expiration_year},
"cvc" => "{card_security_code}"
]
]);
$user->updateCard($token->id);
$user->updateCardFromStripe();
Thanks #louisfischer

Related

How to setup SMS delivery for Zoho Writer Merge+Sign API

I'm using Zoho writer merge and sign API to send the document for signature,
Everything works fine except that I cannot find in the documentation how to enable EMAIL + SMS delivery nor the format for providing recipient phone and country code and the document is only sent via EMAIL.
I tried formatting the signer like below, but it doesn't even add the phone and country code to the contact created inside Zoho Sign.
[
"recipient_1" => "test#email.com"
"recipient_name" => "Test Name"
"action_type" => "sign"
"recipient_phonenumber" => "123456789"
"recipient_countrycode" => "+61"
"language" => "en"
]
Appreciate if someone can advise how to achieve this.
Thanks

Error in using same card for subscription and one off payment

Scenario:
I want to create a subscription and also one time payment with the same card. I am using 3D secure card number 4000002500003155 so I got a popup to convert 3D autentication. Once I confirm, I submit payment method to server using Ajax, and I update the default payment method and subscription is creating successfully but one time charge with the same default payment method is throwing this exception:
"The payment attempt failed because additional action is required before it can be completed."
How can I use the same card to create subscription and one off payment with 3D secure card: 4000002500003155
Here is my frontend code:
stripe.confirmCardSetup(clientSecret, {
payment_method: {
card: cardElement,
billing_details:{ name: "{{$data->first_name}} {{$data->last_name}}"}
},}).then(function(result) {
if(result.error){
}else{
}}
Hoping for some help in this.
If you review the description for that test card, you'll see that it requires 3DS for one time payments unless set up and used as off-session.
If you make your one-time payment intent as off-session, after doing the card setup and attaching to a customer as you've done, then it should succeed.
\Stripe\PaymentIntent::create([
'amount' => 2000,
'currency' => 'usd',
'payment_method_types' => ['card'],
'customer' => 'cus_123',
'payment_method' => 'pm_456',
'off_session' => true,
'confirm' => true,
]);

Laravel Cashier - Unit Tests with creating subscriptions with payment methods

Looking deeper into this, I'm not sure it's even possible, which is a shame because I'm trying to learn TDD.
I would like to test my model with Billable being created and subscribed to a plan.
/** #test */
public function an_account_can_subscribe_to_a_plan() {
$account = factory("App\Account")->create();
Stripe::setApiKey('key');
$paymentMethod = PaymentMethod::create([
'type' => 'card',
'card' => [
'number' => '4242424242424242',
'exp_month' => '2',
'exp_year' => '2021',
'cvc' => '314'
]
]);
$subscription = $account->newSubscription('default', 'starter')->create($paymentMethod);
$this->assertTrue( $subscription->valid() );
}
The Laravel Cashier docs show how to send a token via Stripe.js, but that doesn't work for Unit Testing.
I've tried to include the Stripe library directly and create a PaymentMethod object, but this also requires me set an API key manually. Now the error I'm getting is that I have to verify my phone number to send raw credit card numbers to the stripe API.
I'm hoping there's a better way. How can I use Laravel Cashier in a TDD way and mock up fake subscriptions with fake payment methods?
Stripe provides not only test card numbers, but tokens and payment method's.
https://stripe.com/docs/testing#cards
Click the PaymentMethods tab. The value (for example pm_card_visa) can be used directly on the server-side in your tests without the need of front-end payment-intent implementation.
This is an example of a feature test I have:
/**
* #test
*/
public function a_user_can_subscribe_to_a_paid_plan()
{
$this->actingAs($this->user);
$this->setUpBilling();
$enterprise = Plan::where('name', 'Enterprise')->first()->stripe_plan_id;
$response = $this->post(route('paywall.payment'), [
'payment_method' => 'pm_card_visa',
'stripe_plan_id' => $enterprise
])
->assertSessionDoesntHaveErrors()
->assertRedirect();
}
Your tests may vary, but you can make a normal request to your billing controller with these test payment methods and it goes through just as it would if you do it on the front-end.
You may want to use stripe-mock for mocking during tests. If that doesn't fit your needs, mocking the objects directly may be a better option.

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.

Magento Rest API Issue When Using PayPal Payment Advanced

I'm using Magento's Rest API to sell item from my Wordpress based site. I am having an issue when I try to use PayPal Payment Advanced to complete the transaction. Here is the code below:
$paymentArray = array(
"method" => "payflow_advanced",
'cc_cid' => $_POST['cccvv'],
'cc_owner' => $_POST['firstName'] . " ". $_POST['lastName'],
'cc_number' => $_POST['ccnumber'],
//'cc_type' => "MC",
'cc_exp_year' => $_POST['ccmonth'],
'cc_exp_month' => $_POST['ccyear'],
);
$resultPaymentMethod = $client->call($session, 'cart_payment.method', array($shoppingCartIncrementId, $paymentArray));
When I run the code, it comes back as "true" but the api never hits PayPal and authorizes the transaction.
Once you var_dump($resultPaymentMethod) and see what's it displaying the output..

Resources