I've finally managed to implement the new Stripe Payment Element in Laravel via the Payment Intents API. However, I now need to capture information about the payments and store them in my database - specifically, I need the following data:
Transaction ID
Payment status (failed/pending/successful, etc.)
Payment method type (card/Google Pay/Apple Pay/etc.)
The amount actually charged to the customer
The currency the customer actually paid in
The postcode entered by the user in the payment form
All of this information seems to be available in the Payment Intent object but none of the several Stripe guides specify how to capture them on the server. I want to avoid using webhooks because they seem like overkill for grabbing and persisting data that I'm already retrieving.
It also doesn't help that, thanks to how the Stripe documentation's AJAX/PHP solution is set up, trying to dump and die any variables on the server-side causes the entire client-side flow to break, stopping the payment form from rendering and blocking any debugging information. Essentially, this makes the entire implementation of Payment Intents API impossible to debug on the server.
Does anyone who's been here before know how I would go about capturing this information?
Relevant portion of JavaScript/AJAX:
const stripe = Stripe(<TEST_PUBLISHABLE_KEY>);
const fonts = [
{
cssSrc:
"https://fonts.googleapis.com/css2?family=Open+Sans:wght#300;400;500;600;700&display=swap",
},
];
const appearance = {
theme: "stripe",
labels: "floating",
variables: {
colorText: "#2c2c2c",
fontFamily: "Open Sans, Segoe UI, sans-serif",
borderRadius: "4px",
},
};
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/payment/stripe", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('input[name="_token"]').value,
},
}).then((r) => r.json());
elements = stripe.elements({ fonts, appearance, clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost.rc/success"
},
});
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
Routes file:
Route::post('/payment/stripe', [TransactionController::class, "stripe"]);
TransactionController:
public function stripe(Request $request) {
Stripe\Stripe::setApiKey(env(<TEST_SECRET_KEY>));
header('Content-Type: application/json');
try {
$paymentIntent = Stripe\PaymentIntent::create([
'amount' => 2.99,
'currency' => 'gbp',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
$this->storeStripe($paymentIntent, $output);
echo json_encode($output);
} catch (Stripe\Exception\CardException $e) {
echo 'Error code is:' . $e->getError()->code;
$paymentIntentId = $e->getError()->payment_intent->id;
$paymentIntent = Stripe\PaymentIntent::retrieve($paymentIntentId);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
How can I capture the above information from the Payment Intent to store in my database?
I know you're not going to like this but I'll say it anyway. I honestly think implementing a webhook endpoint, listener and receiver function is your best bet and here is why:
The Stripe Payment Intent captures the lifecycle of a payment as it moves through multiple states. Because the various payment networks outside of Stripe do not guarantee specific response times these transitions can be asynchronous.
Therefore you cannot be certain when is the appropriate time to query the API for your completed Payment Intent unless you are listening for the payment_intent.succeeded event. Additionally, in some cases payment methods can be declined well after initial processing (e.g. suspected fraudulent cards, etc.). Using the webhooks approach keeps you informed of these changes.
Lastly, while you may only care to store this data in your DB now, scope does tend to increase and implementing webhook listeners early means you will have a solution ready if you need to take additional actions like
Sending email notifications to your customers
Adjusting revenue reconciliation
processing fulfillment actions
Other stuff.....
At the recommendation of RyanM I opted instead for the webhook solution, which turned out to be easier than I expected using Spatie's Stripe Webhooks package (although it seems to be run by someone who cares more about closing issues than fixing potential bugs, so opting for Stripe Cashier instead would probably be both easier and a more pleasant developer experience).
Note that by default Stripe webhooks return an Event object that itself contains other objects relevant to the event, such as a PaymentIntent for payment_intent.succeeded, for example, and any associated Charge objects. Therefore it's necessary to drill down a little to get all of the information needed.
$paymentIntent = $this->webhookCall->payload["data"]["object"];
$paymentID = $this->webhookCall->payload["data"]["object"]["id"]; // Transaction ID
$charge = $this->webhookCall->payload["data"]["object"]["charges"]["data"][0];
$transaction = Transaction::where("gateway_payment_id", $paymentID)->first();
$transaction->payment_status = strtoupper($paymentIntent["status"]); // Payment status
$transaction->payment_method = $charge["payment_method_details"]["type"]; // Payment method
$transaction->amount = ($paymentIntent["amount_received"]/100); // Amount charged, in pounds
$transaction->currency = strtoupper($paymentIntent["currency"]); // Currency charged in
$transaction->postcode = $charge["billing_details"]["address"]["postal_code"] ?? "N/A"; // Postcode if entered by the user - otherwise default to N/A
Related
Hello and thanks for your time,
I am using the legacy version of Stripe Checkout, the one with the modal dialog, in a PHP environment.
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<?php echo $stripe['pub_key']; ?>"
data-email="<?php echo $loggedInUser->email; ?>"
data-amount="1000"
data-description="One Credit Purchase ($10.00)"
data-image="img/charge-logo.png"
data-name="Sheet"
data-panel-label="One Credit - "
data-label="One Credit - $10.00"
data-zip-code=TRUE
data-billing-address=TRUE>
</script>
And the charge page:
try {
$charge = \Stripe\Charge::create(array(
'amount' => 1000,
'currency' => 'usd',
'customer' => $_POST['customer_id'],
'description' => "Single Credit Purchase"
));
} catch(Stripe_CardError $e) {
$errors[] = $e->getMessage();
} catch (Stripe_InvalidRequestError $e) {
// Invalid parameters were supplied to Stripe's API
$errors[] = $e->getMessage();
} catch (Stripe_AuthenticationError $e) {
// Authentication with Stripe's API failed
$errors[] = $e->getMessage();
} catch (Stripe_ApiConnectionError $e) {
// Network communication with Stripe failed
$errors[] = $e->getMessage();
} catch (Stripe_Error $e) {
// Display a very generic error to the user, and maybe send
// yourself an email
$errors[] = $e->getMessage();
} catch (Exception $e) {
// Something else happened, completely unrelated to Stripe
$errors[] = $e->getMessage();
}
My Stripe account is in test mode, and I am attempting to test this workflow using their published test cards. Specifically the ones that are designed to decline:
4000000000000002 card_declined
4000000000009995 insufficient_funds
4000000000009987 lost_card
and so on. The problem is, no matter what input I provide, the charge always succeeds. I can put in any of the above card numbers, a bogus expiration date (i.e. 03/1969), and any CVC number, and the charge just goes right through.
The charge.succeeded event shows the charge object (abridged) as successful:
{
"object": {
"object": "charge",
"amount": 1000,
"paid": true,
"status": "succeeded"
}
}
I would expect to see something different. I have tried the new smart payment page hosted by Stripe that creates payments for you, and all of the cards work as expected, providing the appropriate decline reasons. Perhaps it is because the old version is deprecated? Or am I missing something?
Just wondering if anyone still uses the legacy version, or has had experience with the test cards not working as expected, or again, am I missing something?
Thanks for your time!
Cheers!
That code you posted doesn't seem to actually charge the token created by Checkout. You only pass the 'customer' => $_POST['customer_id'], so what happens is the Charge API charges the default card already attached to the particular customer(which is probably something else like a successful test card).
Hard to say without seeing all your code, but probably you don't actually use the token from the card information entered in Checkout(which would be $_POST[stripeToken]) anywhere so you're not actually testing that.
Also you should not use this integration at all and should switch to the new hosted page, Stripe considers it fully deprecated and it doesn't work for 3D Secure which is really important.
I'm new to programming and I am currently doing an internship to improve. That being said, the last days I've spend over 15 hours trying to get Adyen to work on my webpage (for testing purposes), using a test account.
I've first struggled alot with authorization errors, having refilled API Keys, Client Keys, Origin Keys, HMAC Keys and Account Names hundreds of times. Rereading guides over and over without results, nothing worked. Then suddenly, without changing a thing, it worked using curl in terminal to test a first test payment.
I followed the guide: https://docs.adyen.com/online-payments/drop-in-web?tab=redirect_payment_method_1 (Script method), but started getting errors at step 3 (https://gyazo.com/ed4b386035c681e433b0c3616ed90f97). After spending hours without getting a step closer I started creating comments for every step and just copy/pasting everything. Filling in all keys ect. to see if it would get solved by a later step in the code.
I've replaced my account information and keys with 'xxx':
<!-- Adyen CSS, Step 2 -->
<link rel="stylesheet" href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.21.0/adyen.css"
integrity="sha384-Qg5+SF5siQdYOd98ZWyvD7nx35CSLtRdiqlLUQBB5gBSLh45T8kkgiDUgCAsMGkt"
crossorigin="anonymous">
<!-- Adyen provides the SRI hash that you include as the integrity attribute. Refer to our release notes to get the SRI hash for the specific version. https://docs.adyen.com/online-payments/release-notes -->
<?php
require __DIR__ . '/vendor/autoload.php';
// ----- https://docs.adyen.com/online-payments/drop-in-web?utm_source=ca_test&tab=codeBlockRDCd3_4#step-1-get-available-payment-methods ----- //
// ----- Step 1: Get available payment methods ----- //
// Set your X-API-KEY with the API key from the Customer Area.
$client = new \Adyen\Client();
$client->setEnvironment(\Adyen\Environment::TEST);
$client->setXApiKey("xxx");
$service = new \Adyen\Service\Checkout($client);
$params = array(
"merchantAccount" => "xxx",
"countryCode" => "NL",
"shopperLocale" => "nl-NL",
"amount" => array(
"currency" => "EUR",
"value" => 1000
),
"channel" => "Web"
);
$result = $service->paymentMethods($params);
// ----- Step 2: Add Drop-in to your payments form ----- //
// 2.1 Add script to bottom //
// 2.2 Add CSS to head //
// 2.3 Create a DOM element //
echo "<div id='dropin-container'></div>";
// 2.4 Create a configuration object //
?>
<script>
const configuration = {
paymentMethodsResponse: paymentMethodsResponse, // The `/paymentMethods` response from the server.
clientKey: "xxx", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
locale: "en-US",
environment: "test",
onSubmit: (state, dropin) => {
// Your function calling your server to make the `/payments` request
makePayment(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
onAdditionalDetails: (state, dropin) => {
// Your function calling your server to make a `/payments/details` request
makeDetailsCall(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
paymentMethodsConfiguration: {
card: { // Example optional configuration for Cards
hasHolderName: true,
holderNameRequired: true,
enableStoreDetails: true,
hideCVC: false, // Change this to true to hide the CVC field for stored cards
name: 'Credit or debit card'
}
}
};
// 2,5 Use the configuration object to create an instance of Adyen Checkout. Then use the returned value to create and mount the instance of Drop-in: //
const checkout = new AdyenCheckout(configuration);
const dropin = checkout.create('dropin').mount('#dropin-container');
// 2.6 Pass the state.data to your server. //
{
isValid: true,
data: {
paymentMethod: {
type: "scheme",
encryptedCardNumber: "adyenjs_0_1_18$k7s65M5V0KdPxTErhBIPoMPI8HlC..",
encryptedExpiryMonth: "adyenjs_0_1_18$p2OZxW2XmwAA8C1Avxm3G9UB6e4..",
encryptedExpiryYear: "adyenjs_0_1_18$CkCOLYZsdqpxGjrALWHj3QoGHqe+..",
encryptedSecurityCode: "adyenjs_0_1_18$XUyMJyHebrra/TpSda9fha978+.."
holderName: "S. Hopper"
}
}
}
</script>
<?php
// ----- Step 3: Make a payment ----- //
// Set your X-API-KEY with the API key from the Customer Area.
$client = new \Adyen\Client();
$client->setEnvironment(\Adyen\Environment::TEST);
$client->setXApiKey("xxx");
$service = new \Adyen\Service\Checkout($client);
// STATE_DATA is the paymentMethod field of an object passed from the front end or client app, deserialized from JSON to a data structure.
$paymentMethod = STATE_DATA;
$params = array(
"merchantAccount" => "xxx",
"paymentMethod" => $paymentMethod,
"amount" => array(
"currency" => "EUR",
"value" => 1000
),
"reference" => "xxx",
"returnUrl" => "xxx"
);
$result = $service->payments($params); //causing an error
// Check if further action is needed
if (array_key_exists("action", $result)){
// Pass the action object to your front end
// $result["action"]
}
else {
// No further action needed, pass the resultCode to your front end
// $result['resultCode']
}
// ----- Step 4: Perform additional front-end actions ----- //
// 4.1 URL-decode the redirectResult appended to your return URL and pass the parameters to your back end. //
// ----- Step 5: Submit additional payment details ----- //
// Set your X-API-KEY with the API key from the Customer Area.
$client = new \Adyen\Client();
$client->setEnvironment(\Adyen\Environment::TEST);
$client->setXApiKey("xxx");
$service = new \Adyen\Service\Checkout($client);
// STATE_DATA is an object passed from the front end or client app, deserialized from JSON to a data structure.
$params = STATE_DATA;
$result = $service->paymentsDetails($params);
// Check if further action is needed
if (array_key_exists("action", $result)){
// Pass the action object to your frontend.
// $result["action"]
}
else {
// No further action needed, pass the resultCode to your front end
// $result['resultCode']
}
?>
// ----- Step 6: Present the payment result ----- //
<script>
// Show a success message
dropin.setStatus('success');
dropin.setStatus('success', { message: 'Payment successful!' });
// Show an error message
dropin.setStatus('error');
dropin.setStatus('error', { message: 'Something went wrong.'});
// Set a loading state
dropin.setStatus('loading'); // start the loading state
dropin.setStatus('ready'); // set back to the initial state
</script>
<!-- Adyen Script, Step 2 -->
<script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.21.0/adyen.js"
integrity="ha384-XpFdeUhSQQeuZeLjRcNvDBK/16avmyQiiF0t3iXT1Q/4n9b6TKM68T+hv5aZdsvc"
crossorigin="anonymous"></script>
<!-- Adyen provides the SRI hash that you include as the integrity attribute. Refer to our release notes to get the SRI hash for the specific version. https://docs.adyen.com/online-payments/release-notes -->
I've tried:
Checking dependencies,
Changing location of scripts,
Changing different key types,
Contacting Adyen.
At the moment it feels like the more time I spend on this, the further I get from solving the problem. So any help would be appreciated.
So I'm building an webapp that has a shop with Laravel API and Vue as the frontend SPA.
I've been trying to use Strip to enable payments. So far, with the help of Stripe's documentation, I have been able to create a Source in the frontend. for iDEAL, Stripe highly suggests us to make use of webhooks to confirm whether a payment has succeeded. (I'm using Spatie/Laravel-Stripe-Webhook package) This is the current flow of my webapp:
Checkout.vue:
checkout() {
const sourceData = {
type: 'ideal',
amount: this.cart.total,
currency: 'eur',
owner: {
name: this.name + ' ' + this.last_name,
email: this.email,
},
metadata: {
order: JSON.stringify(order),
total_quantity: this.cart.total_quantity,
},
redirect: {
return_url: 'http://example.test/order-confirmation',
},
}
this.stripe.createSource(this.ideal, sourceData).then(function(result) {
if (result.error) {
console.log(error.message)
this.error = error.message
} else {
stripeSourceHandler(result.source)
}
})
const stripeSourceHandler = source => {
document.location.href = source.redirect.url
}
},
After filling in billing address, emails etc. the user starts the payment.
User gets redirected to iDEAL payment page where they can authorize payment.
The Source is now created. Stripe sends source.chargeable webhook:
config/stripe-webhooks.php:
'jobs' => [
'source_chargeable' => \App\Jobs\StripeWebhooks\ProcessPaymentsJob::class,
'charge_succeeded' => \App\Jobs\StripeWebhooks\ChargeSucceededJob::class,
],
ProcessPaymentsJob.php:
public function __construct(WebhookCall $webhookCall)
{
$this->webhookCall = $webhookCall;
}
public function handle()
{
$charge = $this->webhookCall->payload['data']['object'];
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
$user = '';
if(User::find(Auth::id())) {
$user = $user->name;
} else {
$user = 'a guest';
}
$payment = \Stripe\Charge::create([
'amount' => $charge['amount'],
'currency' => 'eur',
'source' => $charge['id'],
'description' => 'New payment from '. $user,
'metadata' => [
'order' => $charge['metadata']['order'],
'total_quantity' => $charge['metadata']['total_quantity'],
]
]);
}
User returns to redirect[return_url]
If all went well, Stripe should send charge.succeeded webhook:
ChargeSucceededJob.php:
public function __construct(WebhookCall $webhookCall)
{
$this->webhookCall = $webhookCall;
}
public function handle()
{
$charge = $this->webhookCall->payload['data']['object'];
$order = Order::create([
'user_id' => Auth::id() ?? null,
'payment_id' => $charge['id'],
'payment_method' => $charge['payment_method_details']['type'],
'billing_email' => $charge['billing_details']['email'],
'billing_name' => $charge['metadata']['name'],
'billing_last_name' => $charge['metadata']['last_name'],
'billing_address' => $charge['metadata']['address'],
'billing_address_number' => $charge['metadata']['address_num'],
'billing_postal_code' => $charge['metadata']['postal_code'],
'billing_city' => $charge['metadata']['city'],
'billing_phone' => strval($charge['billing_details']['phone']),
'order' => json_decode($charge['metadata']['order']),
'total_quantity' => (int) $charge['metadata']['total_quantity'],
'billing_total' => $charge['amount'],
]);
}
This is all going well. However, I do not know how to notify the customer (on the frontend) that the order has been completed. In Stripe's documentation, they explain how to retrieve the Source on the order confirmation page, but they do not explain how to retrieve the Charge, because this is what determines whether the whole order has been completed or not.
OrderConfirmation.vue:
checkPaymentStatus() {
this.stripe = Stripe(this.stripeKey)
// After some amount of time, we should stop trying to resolve the order synchronously:
const MAX_POLL_COUNT = 10;
let pollCount = 0;
let params = new URLSearchParams(location.search)
const pollForSourceStatus = async () => {
const { source } = await this.stripe.retrieveSource({id: params.get('source'), client_secret: params.get('client_secret')})
if (source.status === 'chargeable') {
// Make a request to your server to charge the Source.
// Depending on the Charge status, show your customer the relevant message.
} else if (source.status === 'pending' && pollCount < MAX_POLL_COUNT) {
// Try again in a second, if the Source is still `pending`:
pollCount += 1;
setTimeout(pollForSourceStatus, 1000);
} else {
// Depending on the Source status, show your customer the relevant message.
}
};
pollForSourceStatus();
}
How do I go from here? I am trying to notify the frontend when the Charge has been succeeded. My initial thought process was just to return the Order object, as I would do if it was a Controller, but if I understand correctly, the Job is running asynchronously, so I can't return data. I am also new to Jobs and Queues and stuff, I'm still trying to wrap my head around with it.
Another option I thought of is that I would poll requests from the frontend to the backend to request the last Order, but I have no idea how this would work and/or if this is a good solution.
Any help/tips/helpful resources would be much appreciated!
iDEAL payments are asynchronous, but they luckily do immediately notify you if the payment was successful or not.
When the iDEAL process is complete and your user is redirected to your site, Stripe automatically appends some query parameters to the URL. Meaning your users will be redirected to something like:
https://example.com/checkout/complete?payment_intent=pi_123&payment_intent_client_secret=pi_123_secret_456&source_type=ideal
The next step is to then retrieve the PaymentIntent and check on its status, which you can do by either:
Retrieving the PaymentIntent on the client using stripe.js and the PaymentIntent client secret: https://stripe.com/docs/js/payment_intents/retrieve_payment_intent
Retrieving the PaymentIntent on the server by sending an ajax request to your backend with the PaymentIntend ID: https://stripe.com/docs/api/payment_intents/retrieve
If the status is succeeded, then the payment was completed and you can proceed from there.
I'm sure I'm missing something obvious here, but I can't get my head around how to check for an existing card against a customer.
I'm using the stripe connect api within an laravel app to manage payments on behalf of others, and the basic process is as follows:
a stripe token is created via stripe.js and submitted with the payment form
if the customer exists in the local database, I grab their stripe_id, otherwise a new customer is created using the token as the source/card
a charge is then created using the retrieved or new customer stripe_id
Currently, if the customer returns and uses a different card, as the charge only includes a customer, not source, it'll be charged against their default card regardless.
What I'd like to do is:
create a stripe token
check customer against local database etc
check card fingerprint against customer's cards
if necessary, create new card on customer's record
create charge using both customer and card ids
Simply put: I can't see where in the process a persistent card_id is generated; both those used in the stripe.js response, and when created in the stripe dashboard, appear to be unique, meaning every charge creates a brand-new card object in stripe.
I know I can retrieve a list of cards stored against a customer's account - but where do I get the initial card_id from to search against?
I've seen a question that touches on this here - Can I check whether stripe a card is already existed before going to create new one? - but I don't know Ruby, so can't make head nor tail of it.
EDIT:
Simpler version - is there a way to get the fingerprint as described in the stripe docs here - https://stripe.com/docs/api/php#card_object - without having to first create a card object ?
So the idea here would be to use the fingerprint on the Card object or the Token object and not the id itself as those would be different if you add the same card multiple times.
When you get a new card token you can retrieve it through the Retrieve Token API and look for the fingerprint in the card hash.
You would keep a list of known fingerprints in your database associated with a specific customer and/or card so that you can detect duplicate cards.
NOTE: make sure you are using the secret keys to get those information. otherwise if you are using the publishable key, you might not get the fingerprint value.
I created a function to do this:
$customer is the stripe customer object
$stripe_account is either your account's stripe ID or the connected account's stripe ID
$token comes from stripe.js elements
$check_exp allows you to decide if you want to check the card's expiration date as well, because the fingerprint does not change if the card's number is the same
stripe PHP API 7.0.0
function check_duplicate_card($customer, $stripe_account, $token, $check_exp) {
$loc = "check_duplicate_card >> ";
$debug = true;
if ($debug) {
// see here for an explanation for logging: http://php.net/set_error_handler >> Examples
trigger_error("$loc started", E_USER_NOTICE);
}
try
{
// get token data
$response = \Stripe\Token::retrieve(
$token,
["stripe_account" => $stripe_account]
);
$token_fingerprint = $response->card->fingerprint;
$token_exp_month = $response->card->exp_month;
$token_exp_year = $response->card->exp_year;
if ($debug) {
trigger_error("$loc token_fingerprint = $token_fingerprint; token_exp_month = $token_exp_month; token_exp_year = $token_exp_year", E_USER_NOTICE);
}
// check for duplicate source
if ($debug) {
trigger_error("$loc customer sources = " . json_encode($customer->sources), E_USER_NOTICE);
}
$duplicate_found = false;
foreach ($customer->sources->data as &$value) {
// get data
$fingerprint = $value->fingerprint;
$exp_month = $value->exp_month;
$exp_year = $value->exp_year;
if ($fingerprint == $token_fingerprint) {
if ($check_exp) {
if (($exp_month == $token_exp_month) && ($exp_year == $token_exp_year)) {
$duplicate_found = true;
break;
}
} else {
$duplicate_found = true;
break;
}
}
}
if ($debug) {
trigger_error("$loc duplicate_found = " . json_encode($duplicate_found), E_USER_NOTICE);
}
} catch (Exception $e) {
if ($e instanceof \Stripe\Exception\ApiErrorException) {
$return_array = [
"status" => $e->getHttpStatus(),
"type" => $e->getError()->type,
"code" => $e->getError()->code,
"param" => $e->getError()->param,
"message" => $e->getError()->message,
];
$return_str = json_encode($return_array);
trigger_error("$loc $return_str", E_USER_WARNING);
http_response_code($e->getHttpStatus());
echo $return_str;
} else {
$return_array = [
"message" => $e->getMessage(),
];
$return_str = json_encode($return_array);
trigger_error("$loc $return_str", E_USER_ERROR);
http_response_code(500); // Internal Server Error
echo $return_str;
}
}
if ($debug) {
trigger_error("$loc ended", E_USER_NOTICE);
}
return $duplicate_found;
}
I have successfully integrated a custom payment solution for magento with a lot of help from this tutorial http://www.junaidbhura.com/how-to-make-a-custom-magento-payment-extension-for-an-external-gateway/
I am at the last stage of notifying the website if a payment has been successful or not.
I have a PaymentController.php file below but am not sure how to link this to the payment gateway notification.
The payment gateway provide a notification to the server via an HTTP GET request, letting you know if a payment was accepted or declined. This is below
http://www.websitedomain.co.uk/mygateway/payment/response?Operator=&SessionID=&Note=&Tariff=&Status=&Mobile=
The code for the payment controller where I need to enter the code is below
class Myname_Mygateway_PaymentController extends Mage_Core_Controller_Front_Action {
// The redirect action is triggered when someone places an order
public function redirectAction() {
$this->loadLayout();
$block = $this->getLayout()->createBlock('Mage_Core_Block_Template','mygateway',array('template' => 'mygateway/redirect.phtml'));
$this->getLayout()->getBlock('content')->append($block);
$this->renderLayout();
}
// The response action is triggered when your gateway sends back a response after processing the customer's payment
public function responseAction() {
if($this->getRequest()->isPost()) {
/*
/* Your gateway's code to make sure the reponse you
/* just got is from the gatway and not from some weirdo.
/* This generally has some checksum or other checks,
/* and is provided by the gateway.
/* For now, we assume that the gateway's response is valid
*/
$validated = true;
$orderId = ''; // Generally sent by gateway
if($validated) {
// Payment was successful, so update the order's state, send order email and move to the success page
$order = Mage::getModel('sales/order');
$order->loadByIncrementId($orderId);
$order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true, 'Gateway has authorized the payment.');
$order->sendNewOrderEmail();
$order->setEmailSent(true);
$order->save();
Mage::getSingleton('checkout/session')->unsQuoteId();
Mage_Core_Controller_Varien_Action::_redirect('checkout/onepage/success', array('_secure'=>true));
}
else {
// There is a problem in the response we got
$this->cancelAction();
Mage_Core_Controller_Varien_Action::_redirect('checkout/onepage/failure', array('_secure'=>true));
}
}
else
Mage_Core_Controller_Varien_Action::_redirect('');
}
// The cancel action is triggered when an order is to be cancelled
public function cancelAction() {
if (Mage::getSingleton('checkout/session')->getLastRealOrderId()) {
$order = Mage::getModel('sales/order')->loadByIncrementId(Mage::getSingleton('checkout/session')->getLastRealOrderId());
if($order->getId()) {
// Flag the order as 'cancelled' and save it
$order->cancel()->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Gateway has declined the payment.')->save();
}
}
}
}
You have the line $validated = true;
Here you have to enter your own conditions if the payment has been validated.
So if this is the response from the payment gateway use the variables in the response
http://www.websitedomain.co.uk/mygateway/payment/response?Operator=&SessionID=&Note=&Tariff=&Status=&Mobile=
for example
if ( $_GET('Status') == 'Paid' ){
$validated = true;
}
You have to read the payment gateway documentation on info for their codes. For example the Status variable can have values like "Paid", "Pending", "Expired" or "1", "2", "3". Depends on the gateway.