I have set up a webhook where events sent from stripe are handled. I, however, have noticed that not all events share the same structure.
I am currently retrieving the customer this way:
$input = #file_get_contents("php://input");
$event_json = json_decode($input);
$customerId = $event_json->data->object->customer
After a day or two I found out that not all events contain the customer under $event_json->data->object->customer. Here is an example response:
{
"object": {
"id": "cus_Ac3Sx3Bn7cuvqB",
"object": "customer",
"account_balance": -3099,
"created": 1494163341,
"currency": "usd",
"default_source": "card_1AGpL4ByNDe65wcFOfqQZGCc",
"delinquent": false,
"description": "John John",
"discount": null,
"email": "john.john#john.com",
"livemode": false,
"metadata": {
},
"shipping": null,
"sources": {
"object": "list",
"data": [
{
"id": "card_1AGpL4ByNDe65wcFOfqQZGCc",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_Ac3Sx3Bn7cuvqB",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 7,
"exp_year": 2017,
"fingerprint": "lI2tl3FOGKOG7PcZ",
"funding": "credit",
"last4": "4242",
"metadata": {
},
"name": "John John",
"tokenization_method": null
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/customers/cus_Ac3Sx3Bn7cuvqB/sources"
},
"subscriptions": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/customers/cus_Ac3Sx3Bn7cuvqB/subscriptions"
}
}
}
What I am asking is where I can see an example of all possible event structures so that I can make sure my webhook doesn't return Could not determine which URL to request: Stripe\Customer instance has invalid ID: (500 Internal Server Error)?
Note: I did see this question - How to get a customer ID from event object in stripe but there the only given way is $event_json->data->object->customer
Stripe webhooks share the same structure as the API endpoints for the relevant objects. In the example above, the object field is set to customer, so you can see the structure in the API docs at https://stripe.com/docs/api#customer_object . If you go to https://dashboard.stripe.com/account/webhooks and add an endpoint, and choose "Select types to send", you can see all the types of webhooks that will be sent to you.
There are a lot of events where it doesn't really make sense to look for a customer on the event, so it would probably be useful to only subscribe to the event types you want.
Related
I'm integrating Stripe for my platform which uses Next.js and Laravel.
I can't figure out how can I get the data and store it in MY Platform database.
For example, a user buys a 50$ keyboard. How can I get that item_name and price information in Laravel.
Below is my code. Thank you.
next.js : items page
<form action="/api/checkout_sessions/buy" method="POST">
<button type="submit">
Pay $50
</button>
</form>
So, if the user clicks Pay $50 button, he will be redirected to stript-hosted checkout page. And below is pages/api/checkout_sessions/buy.js.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
// Provide the exact Price ID (for example, pr_1234) of the product you want to sell
price: 'price_random',
quantity: 1,
},
],
mode: 'payment',
success_url: `${req.headers.origin}/?success=true`,
cancel_url: `${req.headers.origin}/?canceled=true`,
});
res.redirect(303, session.url);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
Till now, I'm just following these instructions: https://stripe.com/docs/checkout/quickstart.
After filling necessary info in the stript-hosted checkout page, to test the payment is successful or not, I wrote web hooks in my backend and test it with stripe cli. And it is below. Again, I'm just follwing these instructions.
https://stripe.com/docs/payments/checkout/fulfill-orders
api.php
Route::post('webhook', function() {
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
\Stripe\Stripe::setApiKey('sk_test_');
function print_log($val) {
return file_put_contents('php://stderr', print_r($val, TRUE));
}
// You can find your endpoint's secret in your webhook settings
$endpoint_secret = 'whsec_';
$payload = #file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = null;
try {
$event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
}
catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
function fulfill_order($session) {
// TODO: fill me in
print_log("Fulfilling order...");
print_log($session);
}
// Handle the checkout.session.completed event
if ($event->type == 'checkout.session.completed') {
$session = $event->data->object;
Log::info('session----');
Log::info($session);
Log::info('payload----');
Log::info($payload);
Log::info('event----');
Log::info($event);
// Fulfill the purchase...
fulfill_order($session);
}
if ($event->type == 'payment_intent.succeeded') {
Log::info('payment successful');
}
http_response_code(200);
});
While running the below command in Strip cli,
stripe listen --forward-to localhost:3000/api/webhook (which will execute the above function when I clicked pay in stripe-hosted checkout page)
I got following result in the stript-cli console.
payment_intent.created [evt_random]
2022-05-05 21:46:35 <-- [200] POST http://localhost:3000/api/webhook [evt_random]
2022-05-05 21:46:49 --> payment_intent.created [evt_random]
2022-05-05 21:46:49 <-- [200] POST http://localhost:3000/api/webhook [evt_random]
2022-05-05 21:48:50 --> payment_intent.created [evt_random]
2022-05-05 21:48:50 <-- [200] POST http://localhost:3000/api/webhook [evt_random]
2022-05-05 21:50:55 --> payment_intent.created [evt_random]
2022-05-05 21:50:55 <-- [200] POST http://localhost:3000/api/webhook [evt_random]
I got following result in the laravel console.
[2022-05-05 20:38:58] local.INFO: payment successful
[2022-05-05 20:38:59] local.INFO: session----
[2022-05-05 20:38:59] local.INFO: Stripe\Checkout\Session JSON: {
"id": "cs_test_random",
"object": "checkout.session",
"after_expiration": null,
"allow_promotion_codes": null,
"amount_subtotal": 50,
"amount_total": 50,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://...?canceled=true",
"client_reference_id": null,
"consent": null,
"consent_collection": null,
"currency": "usd",
"customer": "cus_",
"customer_creation": "always",
"customer_details": {
"address": {
"city": null,
"country": "MM",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": "sample#gm.com",
"name": "er",
"phone": null,
"tax_exempt": "none",
"tax_ids": []
},
"customer_email": null,
"expires_at": 1651836625,
"livemode": false,
"locale": null,
"metadata": [],
"mode": "payment",
"payment_intent": "pi_random",
"payment_link": null,
"payment_method_options": [],
"payment_method_types": [
"card"
],
"payment_status": "paid",
"phone_number_collection": {
"enabled": false
},
"recovered_from": null,
"setup_intent": null,
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
"shipping_rate": null,
"status": "complete",
"submit_type": null,
"subscription": null,
"success_url": "http:...?success=true",
"total_details": {
"amount_discount": 0,
"amount_shipping": 0,
"amount_tax": 0
},
"url": null
}
[2022-05-05 20:38:59] local.INFO: payload----
[2022-05-05 20:38:59] local.INFO: {
"id": "evt_random",
"object": "event",
"api_version": "2018-02-06",
"created": 1651754338,
"data": {
"object": {
"id": "cs_test_random",
"object": "checkout.session",
"after_expiration": null,
"allow_promotion_codes": null,
"amount_subtotal": 50,
"amount_total": 50,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_address_collection": null,
"cancel_url": "http:...?canceled=true",
"client_reference_id": null,
"consent": null,
"consent_collection": null,
"currency": "usd",
"customer": "cus_random",
"customer_creation": "always",
"customer_details": {
"address": {
"city": null,
"country": "US",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": "sample#gm.com",
"name": "er",
"phone": null,
"tax_exempt": "none",
"tax_ids": [
]
},
"customer_email": null,
"expires_at": 1651836625,
"livemode": false,
"locale": null,
"metadata": {
},
"mode": "payment",
"payment_intent": "pi_random",
"payment_link": null,
"payment_method_options": {
},
"payment_method_types": [
"card"
],
"payment_status": "paid",
"phone_number_collection": {
"enabled": false
},
"recovered_from": null,
"setup_intent": null,
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [
],
"shipping_rate": null,
"status": "complete",
"submit_type": null,
"subscription": null,
"success_url": "http:...?success=true",
"total_details": {
"amount_discount": 0,
"amount_shipping": 0,
"amount_tax": 0
},
"url": null
}
},
"livemode": false,
"pending_webhooks": 2,
"request": {
"id": null,
"idempotency_key": null
},
"type": "checkout.session.completed"
}
[2022-05-05 23:13:09] local.INFO: event----
[2022-05-05 23:13:09] local.INFO: Stripe\Event JSON: {
"id": "evt_random",
"object": "event",
"api_version": "2018-02-06",
"created": 1651763589,
"data": {
"object": {
"id": "cs_test_random",
"object": "checkout.session",
"after_expiration": null,
"allow_promotion_codes": null,
"amount_subtotal": 50,
"amount_total": 50,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_address_collection": null,
"cancel_url": "http:...?canceled=true",
"client_reference_id": null,
"consent": null,
"consent_collection": null,
"currency": "usd",
"customer": "cus_random",
"customer_creation": "always",
"customer_details": {
"address": {
"city": null,
"country": "NL",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": "sample#gm.com",
"name": "sample",
"phone": null,
"tax_exempt": "none",
"tax_ids": []
},
"customer_email": null,
"expires_at": 1651849960,
"livemode": false,
"locale": null,
"metadata": [],
"mode": "payment",
"payment_intent": "pi_random",
"payment_link": null,
"payment_method_options": [],
"payment_method_types": [
"card"
],
"payment_status": "paid",
"phone_number_collection": {
"enabled": false
},
"recovered_from": null,
"setup_intent": null,
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
"shipping_rate": null,
"status": "complete",
"submit_type": null,
"subscription": null,
"success_url": "http:...?success=true",
"total_details": {
"amount_discount": 0,
"amount_shipping": 0,
"amount_tax": 0
},
"url": null
}
},
"livemode": false,
"pending_webhooks": 2,
"request": {
"id": null,
"idempotency_key": null
},
"type": "checkout.session.completed"
}
Question:
The payment is successful! But, how can I get those keyboard and 50 to save into my database and where to write those code so that I know that user have purchased that item with that price.
Till now, I'm just following these instructions: https://stripe.com/docs/checkout/quickstart.
If so, in the method stripe.checkout.sessions.create you should
// Provide the exact Price ID (for example, pr_1234) of the product you want to sell
as the example for your environment suggests (along with the doc How to use product and prices when creating Checkout Session). This is the key for the answer to your question:
how can I get those keyboard and 50
So, what should you do to get price id? The common way is to submit a lookup_key from your form. At the server side you can search for price using this key:
$priceSearchResult = \Stripe\Price::search([
"query" => "lookup_key:'" . $priceLookupKey . "'"
]);
and create the checkout session with the proper price id:
"line_items" => [
[
"price" => $priceSearchResult->data[0]->id,
"quantity" => 1,
]
]
Within the event's handler code (either checkout.session.completed or payment_intent.succeeded), you can grab the fields you need from the event object and store them to variables. Then, you can use your Laravel database library and insert that data into your database within the event handler code block.
I am using Stripe's API to pay for orders.
My plan is generally the following:
Creation of the payment form with Stripe Elements
Card authentication (3d secure security SCA) through the use of a PaymentIntent then the creation of a PaymentMethod.
Creation of the Customer object, to which I attach the payment method, and which I define as default
$customer = $this->stripeConnector->updateCustomer($customer->id, [
'invoice_settings' => [
'default_payment_method' => $paymentMethod->id,
],
]);
Creation of the subscription, or of a single payment.
Normally it works fine.
But not for the Order.
I create Order Object following the documentation : https://stripe.com/docs/api/orders/create with the orders items and the associated Customer
And I pay it following the following documentation https://stripe.com/docs/api/orders/pay
So, just by passing the Order id (because the customer is already associated to the order).
But I've the following error : Cannot charge a customer that has no active card
Why ? And how can I 'activate' the customer card ?
This is an example of a Customer data that I have :
{
"id": "cus_JocNLLNhqHuOD6",
"object": "customer",
"address": {
"city": "paris",
"country": "FR",
"line1": "5 rue pompidou",
"line2": null,
"postal_code": "75000",
"state": null
},
"balance": 0,
"created": 1625758082,
"currency": null,
"default_source": null,
"delinquent": false,
"description": null,
"discount": null,
"email": "admin#mail.fr",
"invoice_prefix": "D46F6A15",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1JAz8uGgCQgXBLKX2hoxLQHr",
"footer": null
},
"livemode": false,
"metadata": {
},
"name": "admin admin",
"next_invoice_sequence": 1,
"phone": null,
"preferred_locales": [
"fr"
],
"shipping": null,
"tax_exempt": "none"
}
And my PaymentMethod response :
{
"id": "pm_1JAz8uGgCQgXBLKX2hoxLQHr",
"object": "payment_method",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": "qsvsf",
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 5,
"exp_year": 2025,
"fingerprint": "oV7uH07M2JEz7jQm",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1625758080,
"customer": "cus_JocNLLNhqHuOD6",
"livemode": false,
"metadata": {
},
"type": "card"
}
Looking at the doc on the payment of an Order object, I wonder if I should instead use the 'source' option and therefore use a Source object instead of a PaymentMethod. But the problem is, the Source Object is deprecated in favor of the PaymentMethod to favor 3d secure, so I have to use it.
Orders is a long deprecated Stripe API, so I would recommend moving away from it, to PaymentIntents or one-off Invoices.
Since it is a legacy API, it only works with Cards/Sources and not PaymentMethods.
Right now your integration is attaching a PaymentMethod to your Customer.
Orders doesn't look for your Customer's PaymentMethods but instead it looks under the sources: param on your Customer.
Orders also don't support SCA, so if a payment requires authentication, that would just surface as a decline and not give you the authentication life-cycle functionality that PaymentIntents give you.
That is why it works as you said, when you save a "Source" object under the Customer's source:.
The right approach would be to move away from using deprecated Card/Source objects and the Orders API, and integrate PaymentMethods (which you already have integrated) and PaymentIntents or Invoices or CheckoutSessions.
The webhook pings successfully, but I'm unable to parse it, I spent 4 hours still no luck, I tried all methods but still failed,I just don't know why is this happening , i have used it same for stripe webhook, it works but this is working for razor pay
Below is the header and response I receive on my webhook:
Source: https://webhook.site/
Headers
connection close
accept-encoding gzip
x-razorpay-signature f0087994b59ee5ec1b1828a87f794c89bb398033a428bb79971875e5f295322e
x-razorpay-event-id Gb5kMCdcAZ8jJ8
request-id Gb5kMCdcAZ8jJ8
content-type application/json
content-length 1053
user-agent Razorpay-Webhook/v1
host webhook.site
Raw content
{
"entity": "event",
"account_id": "acc_DOXUp08NQX6Sr9",
"event": "payment.captured",
"contains": [
"payment"
],
"payload": {
"payment": {
"entity": {
"id": "pay_Gb5kLMBMjON6So",
"entity": "payment",
"amount": 1132,
"currency": "USD",
"base_amount": 82174,
"base_currency": "INR",
"status": "captured",
"order_id": "order_Gb5kEv5w8pveU5",
"invoice_id": null,
"international": false,
"method": "card",
"amount_refunded": 0,
"refund_status": null,
"captured": true,
"description": null,
"card_id": "card_Gb5kLUZEfKlXF4",
"card": {
"id": "card_Gb5kLUZEfKlXF4",
"entity": "card",
"name": "231231231231231231",
"last4": "8826",
"network": "Visa",
"type": "prepaid",
"issuer": "SBIN",
"international": false,
"emi": false,
"sub_type": "business"
},
"bank": null,
"wallet": null,
"vpa": null,
"email": "niteshkmr366#gmail.com",
"contact": "+918169179450",
"notes": {
"ytsubme_account_email": "niteshkmr366#gmail.com",
"phone": "8169179450"
},
"fee": 1644,
"tax": 0,
"error_code": null,
"error_description": null,
"error_source": null,
"error_step": null,
"error_reason": null,
"acquirer_data": {
"auth_code": null
},
"created_at": 1613226609
}
}
},
"created_at": 1613226609
}
My PHP Code:
<?
$data = file_get_contents('php://input');
$obj = json_decode($data);
file_put_contents('data.txt',$data);
//data.txt file is still empty
?>
Thanks to #IMSoP,
For reminding me to sort out why it's empty before you worry about parsing it.
My webhook was pinging to the URL where login was necessity,
)more info : SESSIONS : i.e. if session wasn't set it was redirecting them to / page.)
Due to which I was getting empty data ,feeling so dumb about this.
I am working with Shopify Webhook for order fulfillment(in PHP) now i want the payment details of "Information from the gateway" which are available in the shopify admin but not showing in Webhook details so anyone can please give any solution for this. following is the screenshot of details which i want to get in order.
These following are details which I want when any new order is confirmed:
1) Authorization key
2) Name on Credit card
3) Exp month
4) Exp year
details which i want to get in order
For this information you needs to use following shopify method
GET /admin/orders/#{id}/transactions.json
And following response you get:
HTTP/1.1 200 OK
{
"transactions": [
{
"id": 179259969,
"order_id": 450789469,
"amount": "209.00",
"kind": "refund",
"gateway": "bogus",
"status": "success",
"message": null,
"created_at": "2005-08-05T12:59:12-04:00",
"test": false,
"authorization": "authorization-key",
"currency": "USD",
"location_id": null,
"user_id": null,
"parent_id": null,
"device_id": null,
"receipt": {},
"error_code": null,
"source_name": "web"
},
{
"id": 389404469,
"order_id": 450789469,
"amount": "409.94",
"kind": "authorization",
"gateway": "bogus",
"status": "success",
"message": null,
"created_at": "2005-08-01T11:57:11-04:00",
"test": false,
"authorization": "authorization-key",
"currency": "USD",
"location_id": null,
"user_id": null,
"parent_id": null,
"device_id": null,
"receipt": {
"testcase": true,
"authorization": "123456"
},
"error_code": null,
"source_name": "web",
"payment_details": {
"credit_card_bin": null,
"avs_result_code": null,
"cvv_result_code": null,
"credit_card_number": "•••• •••• •••• 4242",
"credit_card_company": "Visa"
}
},
{
"id": 801038806,
"order_id": 450789469,
"amount": "250.94",
"kind": "capture",
"gateway": "bogus",
"status": "success",
"message": null,
"created_at": "2005-08-05T10:22:51-04:00",
"test": false,
"authorization": "authorization-key",
"currency": "USD",
"location_id": null,
"user_id": null,
"parent_id": null,
"device_id": null,
"receipt": {},
"error_code": null,
"source_name": "web"
}
]
}
And you want to more details than please refer the below link:
https://help.shopify.com/api/reference/transaction
I'm trying to do a simple count of how many refunds are in my Stripe Response but count() isn't working and I don't really know any other way of achieving this.
Could anyone point me in the right direction?
$retrieve_event = Stripe_Event::retrieve("evt_00000000000000");
$event_json_id = json_decode($retrieve_event);
$refund_array = $event_json_id->{'data'}->{'object'}->{'refunds'};
die(count($refund_array));
This is the response of $retrieve_event
{
"created": 1326853478,
"livemode": false,
"id": "evt_00000000000000",
"type": "charge.refunded",
"object": "event",
"request": null,
"data": {
"object": {
"id": "ch_00000000000000",
"object": "charge",
"created": 1402433517,
"livemode": false,
"paid": true,
"amount": 1000,
"currency": "usd",
"refunded": true,
"card": {
"id": "card_00000000000000",
"object": "card",
"last4": "0028",
"type": "Visa",
"exp_month": 8,
"exp_year": 2015,
"fingerprint": "a5KWlTcrmCYk5DIYa",
"country": "US",
"name": "First Last",
"address_line1": "null",
"address_line2": null,
"address_city": "null",
"address_state": "null",
"address_zip": "null",
"address_country": "US",
"cvc_check": null,
"address_line1_check": "fail",
"address_zip_check": "pass",
"customer": "cus_00000000000000"
},
"captured": true,
"refunds": [
{
"id": "re_104CKt4uGeYuVLAahMwLA2TK",
"amount": 100,
"currency": "usd",
"created": 1402433533,
"object": "refund",
"charge": "ch_104CKt4uGeYuVLAazSyPqqLV",
"balance_transaction": "txn_104CKt4uGeYuVLAaSNZCR867",
"metadata": {}
},
{
"id": "re_104CKt4uGeYuVLAaDIMHoIos",
"amount": 200,
"currency": "usd",
"created": 1402433539,
"object": "refund",
"charge": "ch_104CKt4uGeYuVLAazSyPqqLV",
"balance_transaction": "txn_104CKt4uGeYuVLAaqSwkNKPO",
"metadata": {}
},
{
"id": "re_4CL6n1r91dY5ME",
"amount": 700,
"currency": "usd",
"created": 1402434306,
"object": "refund",
"charge": "ch_4CL6FNWhGzVuAV",
"balance_transaction": "txn_4CL6qa4vwlVaDJ"
}
],
"balance_transaction": "txn_00000000000000",
"failure_message": null,
"failure_code": null,
"amount_refunded": 1000,
"customer": "cus_00000000000000",
"invoice": null,
"description": "this is a description",
"dispute": null,
"metadata": {},
"statement_description": "this is a description",
"fee": 0
}
}
}
It's just the way you're trying to output the count. This example outputs 3:
echo count($refund_array);
exit;
Whereas this example doesn't:
die(count($refund_array));
The reason is because you're simply passing in an integer into die(). From the manual:
If status is an integer, that value will be used as the exit status and not printed. Exit statuses should be in the range 0 to 254, the exit status 255 is reserved by PHP and shall not be used. The status 0 is used to terminate the program successfully.
This example works because the message is a string:
die('Count: ' . count($refund_array)); // Count: 3
...or:
die((string) count($refund_array)); // 3
The count() just needs to be cast as a string:
die((string) count($refund_array));
As per new version of Stripe API which was released on 2014-06-17, Refunds object is modified for Charges Method.
Now you can directly get refund count using total_count parameter in refunds object.
data->object->refund->total_count