Stripe doubles anything - php

I am using a function I created that I have tried creating customers from, and creating charges from. For whatever reason it seems to be double charging in test mode (Not bringing into live mode under these conditions) and I'm trying to understand why. I had it going through a few functions so I made it all happen in one function to make sure that it had nothing to do with what I had made. I'm lost on why this is happening. I try to make charges from token, doubles in less than a second. I try to create a customer from token, doubles in less than a second. I am using Stripes latest stripe-php library.
public function invoice($invoice = null) {
//Provides billing info for invoice.ctp
$this->loadModel('Invoice');
$billingi = $this->Invoice->get($invoice, [
'contain' => ['Items'],
]);
$dollars = 0;
foreach ($billingi->items as $item) {
$dollars += $item->price;
}
$cents = bcmul($dollars, 100);
$price = floatval($cents);
if ($this->request->is('post')) {
$stripeToken = $this->request->data('stripeToken');
//Sets stripe API
\Stripe\Stripe::setApiKey("sk_test_QVYouMViTf1k3zfVu2VAyZge");
//Retrieves stripe token from stripe API
//$response = \Stripe\Token::retrieve($stripeToken);
\Stripe\Customer::create(array(
"description" => "Test customer",
"source" => $stripeToken // obtained with Stripe.js
));
$this->Flash->success(__('Thank you for your payment!'));
return $this->redirect(['action' => 'approved', $invoice]);
}
/*
if ($response && $this->checkExists($response->card->cvc_check, $response->card->address_zip_check) == true) {
$this->insertCharge($invoice, $response, $price);
} else {
//Throw error because cvc_check or zip came back null (doesn't exist)
}
}
*/
$this->set('billingi', $billingi);
$this->set('_serialize', ['billing']);
}
The reason why there are things commented out is because I wanted to test the function without it, but adding it back later when I understand what the issue is.

In your code, the only API request sent to Stripe is a customer creation request (\Stripe\Customer::create(...)).
This doesn't charge the user -- it merely validates the card from the token in the source parameter, and creates a persistent customer object that you can in turn use to create actual charges. This tutorial explains this flow.
There's nothing in your code that would cause the API request to be sent twice. It's very unlikely the issue is on Stripe's end. More likely, your code is being called twice for some reason that's not related to Stripe. You'd need to add traces to your code to figure out what exactly is being called in what order.

Related

Stripe PHP API : You cannot confirm this PaymentIntent because it's missing a payment method

I am trying to implement the Stripe PHP API, but I am stuck since last week.
At first I thought I did not mention the payment method correctly in the way of a correct notation.
However this seems not to be the case.
I have created a wrapper class that loads all stripe API files and following handles all calls required for me.
Hence that's why the code layout.
When I execute my script I receive the following error :
Fatal error: Uncaught (Status 400) (Request req_AQbkjKFB4mo31Z) You
cannot confirm this PaymentIntent because it's missing a payment
method. You can either update the PaymentIntent with a payment method
and then confirm it again, or confirm it again directly with a payment
method. thrown in
httpdocs/stripe/stripe/lib/Exception/ApiErrorException.php
on line 38
I have contacted the Stripe helpdesk already however they keep referring me to their manual but that doesn't get me any further either.
The iDeal payment method has been enabled in their dashboard. So that's not the reason for failure.
Where it COULD be is the manner I setup my payment. But for as far as I am concerned I do not have set the payment method correctly. But for as far as I can see that has been done accordingly their manual.
Also, I cannot find any confirmatory documentation about using the retrieve call. Should I do this? Or is this simply double and unneeded.
public function create_payment($amount, $order_id, $method = 'ideal', $return_url = NULL, $currency = 'eur'){
///######## CHECK IF CURRENCY IS ALLOWED
if(!$this->currency_supported($currency)) exit('<strong>error</strong>, stripe currency not supported : '.$currency);
///######## SETUP PAYMENT
$result = $this->obj->paymentIntents->create(
array(
'amount' => $amount,
'currency' => $currency,
'payment_method_types' => array($method)
)
);
///######## IF ERROR
if(!is_object($result) || !isset($result->id)) exit('<strong>error</strong>, something went wrong during stripe payment intend creation');
///######## SETUP PAYMENT ID
$payment_id = $result->id;
///######## RETRIEVE PAYMENT INTEND DETAILS
$result = $this->obj->paymentIntents->retrieve($payment_id, []);
///######## SET AN ORDER ID
$result = $this->obj->paymentIntents->update($payment_id, array(
'metadata' => array(
'order_id' => $order_id,
),
'payment_method_types' => array($method),
));
///######## SETUP PARAMETRES
$params = array('payment_method_types' => array($method));
///######## IF THE RETURN URL HAS BEEN SET
if($return_url) $params['return_url'] = $return_url;
///######## CONFIRM A PAYMENT INTEND
$result = $this->obj->paymentIntents->confirm($payment_id, $params);
exit(print_r($result));
}
I hope that one could point me at my error. Since I am absolutely stuck here.

Some bookings have problem when customer paid using Payone wallet method (paypal)

I have implemented the Payone payment gateway (Creditcard, Sofort, Paydirect and Paypal) successfully. After successful payment I am getting txaction response (appointed and paid) and everything is fine. But sometimes I am not getting response from Payone after customer paid using Paypal(I checked around 60 successful transactions. But in that 2 or 3 transactions are not got response and the customer's amount has been deducted from their account).
After successful transaction payone is posting data in to this route
/* Response from payone */
Route::post('/payment/response', 'PaymentController#response')->name('payment.response');
I think laravel request is not capturing data from url. or
There is something wrong to using this method Schema::hasColumn.
Any help would be appreciated thanks.
PaymentController.php
public function response(Request $request)
{
// Here I created to store all request in to table but data is not storing.
/* Testing purpose begin */
$payment = new Payment;
foreach($_POST as $key => $value) {
if(Schema::hasColumn($payment->getTable(), $key)){
if(is_array($value)) {
$payment->{$key} = $value[1];
} else {
$payment->{$key} = $value;
}
}
}
$payment->save();
/* Testing purpose end */
if ($_POST["key"] == hash("md5", env('KEY'))) {
echo "TSOK"; // If key is valid, TSOK notification is for PAYONE
$user = Userlist::where('is_delete', 0)
->where('usrActive', '1')
->where('userid', $_POST["userid"])
->first();
if($user && $_POST["clearingtype"] && $_POST["txaction"]) {
$bookings = Booking::select('_id', 'old_booking_id', 'status', 'payment_status')
->where('user', new \MongoDB\BSON\ObjectID($user->_id))
->whereIn('status', ['5', '8', '10', '11']) //5=>Waiting for payment, 8=>Cart, 10=> Temporary (This status is using in edit booking section), 11=> On processing
->where('is_delete', 0)
->where('txid', $_POST["txid"])
->where('userid', $_POST["userid"])
->get();
if($bookings) {
if ($_POST["txaction"] == "appointed") {
update booking status and sent email
}
else if ($_POST["txaction"] == "paid") {
update paid status
}
else {
update failed status
}
}
}
}
}
laravel log
[2018-09-11 09:04:14] production.ERROR: Method [error] does not exist on [App\Http\Controllers\PaymentController]. {"userId":"5afa790212236cc4660ed509","exception":"[object] (BadMethodCallException(code: 0): Method [error] does not exist on [App\\Http\\Controllers\\PaymentController]. at /var/www/vhosts/cabin-holiday.frontend/vendor/laravel/framework/src/Illuminate/Routing/Controller.php:68)
Method [error] does not exist on [App\Http\Controllers\PaymentController].
Write error method on the PaymentController. Try to find documentation on error method mentioned somewhere in your payment gateway documentation. And read when it is called and why. So that you will get idea of what to handle in error method in PaymentController. You might have to declare route or may be it is already there in your routes since it is not complaining about route but method. Hope this helps you.
The solution for this issue is to change the method it should be get method. Because we are getting response from the paypal in url. So you need to set the GET method.
/* Response from payone */
Route::get('/payment/response', 'PaymentController#response')->name('payment.response');
Finally found the issue. Reason for this issue is sometimes response data like address, city etc are not getting in correct format (Users are from Germany). So I strictly converted the response data in to UTF-8 format and stored important data in to database.

Strange behaviour in WordPress MySQL insert of JSON encoded data

I'm looking at both my code and my result and I don't see any glaring error, so I thought it could be useful to have a few extra sets of eyes look it over.
I have a custom PayPal IPN listener that updates a transaction table in the database. I deployed it Friday before leaving work, and after returning today it seems to be working mostly correctly; but I would like to figure out why one insert behaved strangely.
Here's a capture of the inserts which happened over the weekend:
As you can see, the intended JSON value for the log column of the 4th transaction is empty. I find it strange because the value of the transaction_id column is being parsed from the same array.
Here is the relevant db insert code:
// Generate valid IPN log
private function generateIpnLog () {
global $wpdb;
// prepare log
$array_log = [];
$array_log['verified'] = true;
$array_log['ipn_response'] = (isset($this->PayPal_Response)) ? : 'Error reading from POST array';
// Parse transaction ID
$transaction_id = (isset($this->PayPal_Response['txn_id'])) ? $this->PayPal_Response['txn_id'] : null;
// Generate log
$log = json_encode($array_log);
// Update DB
$wpdb->insert(
'log_paypal',
[
'transaction_id' => ($transaction_id) ? $transaction_id : 'Error getting transaction ID',
'log' => ($log) ? $log : 'Error generating transaction log'
],
[
'%s',
'%s'
]
);
// json log response
$this->json_return = $log;
}
Seeing as the transaction id is parsed fine from the PayPal response, and because we know $array_log['verified'] has an explictly declared value my guess is there must be a problem with json_encode($array_log).
I also checked the data from PayPal IPN history of the PayPal account in question, and can verify there isn't anything different about the way the data is being formed in the null log column vs the others.
Anyone have an idea about what could be happening in this instance?
As suggested by #ishegg it was an encoding issue since PayPal IPN uses windows-1252 encoding and the DB field was encoded in UTF-8.
It was easy to fix in this case since the PayPal return data is not nested / multidimensional (see below).
Called in an earlier function after a PayPal IPN entry is cryptographically verified by the cert chain:
// Process IPN response
$this->PayPal_Response = $this->processIpn();
Then, the function itself:
// Manipulate IPN response and prepare
// for database update and log
private function processIpn () {
// Response ref.
$response = $this->post_array;
if ( isset($response['charset']) ) {
if ($response['charset'] == "windows-1252") {
foreach ($response as $key => $ipn_value) {
$response[$key] = mb_convert_encoding($ipn_value, 'UTF-8', 'Windows-1252');
}
}
}
return $response;
}

In Braintree is it possible to verify duplicate payment method for just one customer instead of entire vault?

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?
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.
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.
#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";
}
}
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;
}
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.

Using Coupon Codes with Stripe

I have a site that is using Stripe to process a subscription payments. There is only one type of subscription.
I followed this tutorial on NetTuts to do the initial setup.
Had a form working fine processing subscriptions and everything worked. Client requested a coupon code. Stripe supports this so I set out trying to add a coupon code to the existing form.
I set up coupon codes in Stripe, set my testing keys and switched to test mode in stripe.
I'm performing a couple of checks in my code:
Check to see whether a coupon was entered, if not create a new customer object without a coupon option
Check to see whether the Coupon is valid, if not return an error
If there has been a coupon entered and it is valid, then pass the matching Stripe coupon object as an option when creating a new customer.
if(isset($couponCode) && strlen($couponCode) > 0) {
$using_discount = true;
try {
$coupon = Stripe_Coupon::retrieve($couponCode);
if($coupon !== NULL) {
$cCode = $coupon;
}
// if we got here, the coupon is valid
} catch (Exception $e) {
// an exception was caught, so the code is invalid
$message = $e->getMessage();
returnErrorWithMessage($message);
}
}
try
{
if($using_discount == true) {
$customer = Stripe_Customer::create(array(
"card" => $token,
"plan" => "basic_plan",
"email" => $email,
"coupon" => $cCode
));
}
else {
$customer = Stripe_Customer::create(array(
"card" => $token,
"plan" => "basic_plan",
"email" => $email
));
}
$couponCode is populated with the form field correctly the same way all other fields are populated, I've triple checked that it is being pulled correctly.
When I try to submit the form without a coupon code, it charges the full amount and passes through Stripe correctly.
However if I enter either a valid OR invalid coupon code, it does not pass a coupon object with the customer object when creating a new customer object and charges the full amount when passing through Stripe.
I've looked at the code for hours and can't seem to figure out why it is always failing to recognize the discount code and pass the matching coupon object to Stripe.
This is probably a little out dated and you found a solution for your code. But it would seem all you need to do is pass through your original $couponCode as the array value for Coupon. As stated by codasaurus you are just getting an array back from stripe of the coupon, which you don't need unless you did $cCode->id and pass the ID back to your array to create a customer.
I changed when you set the $using_discount to true, as this would trigger to send a coupon code if the coupon was valid or not.
Once the coupon is actually valid we then send the coupon. You only need the value of your submitted state so $coupon is the reference to the discount in there system. Or you could use the $coupon->id if you wanted to create it that way.
Here is my take on a solution based on your code, it could be better, but I hope it helps others looking for a solution like me.
if($coupon!='' && strlen($coupon) > 0) {
try {
$coupon = Stripe_Coupon::retrieve($coupon); //check coupon exists
if($coupon !== NULL) {
$using_discount = true; //set to true our coupon exists or take the coupon id if you wanted to.
}
// if we got here, the coupon is valid
} catch (Exception $e) {
// an exception was caught, so the code is invalid
$message = $e->getMessage();
returnErrorWithMessage($message);
}
}
if($using_discount==true)
{
$customer = Stripe_Customer::create(array(
"card" => $token,
"plan" => $plan,
"email" => $id,
"coupon"=>$coupon) //original value input example: "75OFFMEMBER" stripe should be doing the rest.
);
}
else
{
$customer = Stripe_Customer::create(array(
"card" => $token,
"plan" => $plan,
"email" => $id)
);
}
Looking at the documentation https://stripe.com/docs/api#create_customer, the Stripe_Customer::create() call is looking for just the coupon code. It looks like you're passing in the entire coupon object.
Unless your first try catch fails, $couponCode already has your coupon code. Also, there are several other checks you need to perform to determine if the coupon is valid. For example, if the coupon->times_redeemed < coupon->max_redemptions, or if the coupon->redeem_by has passed, etc. You might also want to check if the customer is already using the coupon by checking the customer discount object.
If any of these checks fail, just set your $using_discount = false;

Categories