I have been trying to pass metadata through stripe.checkout.Session.create() like so:
stripe.api_key = STRIPE_SECRET_KEY
payments_blueprint = Blueprint('payments', __name__, url_prefix='/payments')
@payments_blueprint.route('/checkout', methods=['POST'])
def create_checkout_session():
try:
checkout_session = stripe.checkout.Session.create(
metadata=dict(key='val'),
payment_method_types=['card'],
line_items=request.form.get("lineItems", LINE_ITEMS),
success_url=f'{request.environ["HTTP_ORIGIN"]}/success',
cancel_url=f'{request.environ["HTTP_ORIGIN"]}/cancel',
mode='payment'
)
return redirect(checkout_session.url, code=HTTPStatus.SEE_OTHER)
except stripe.error.InvalidRequestError as err:
return redirect(f'{request.environ["HTTP_ORIGIN"]}/error', code=HTTPStatus.MOVED_PERMANENTLY)
and neither the responses from stripe nor the events passing through my webhook contains any metadata, even though the event logs in the stripe console for the request and the response both contain:
"metadata": {
"key": "val"
},...
I am listening to all events using stripe listen --forward-to localhost:8000/hooks/ --print-json and all that the endpoint at /hooks does is print the event to stdout. nothing else.
I would like for this metadata to be passed through my series of booking validation webhooks. referencing this:
https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-metadata
Basically i am following these docs, sending metadata through the call for checkout.Session.create(), and then not seeing this metadata. I have tried using the dict() constructor, using dict syntax instead ({"key":"val"}), creating a variable and setting it to this dict before passing it through the function, and every other way i could think of to pass this metadata dictionary in, but i have not been getting it back from stripe.
Here is the hook i have set up where these events are being forwarded:
class TestHook(Resource):
def post(self):
event = stripe.Event.construct_from(
json.loads(request.data),
stripe.api_key
).to_dict()
print(event['type'])
pprint(event['data']['object'])
And the output to stdout:
payment_intent.created
{'amount': 20000,
'amount_capturable': 0,
'amount_received': 0,
'application': None,
'application_fee_amount': None,
'canceled_at': None,
'cancellation_reason': None,
'capture_method': 'automatic',
'charges': {},
'client_secret': 'pi_3JTYxxxx7t',
'confirmation_method': 'automatic',
'created': 1630184808,
'currency': 'usd',
'customer': None,
'description': None,
'id': 'pi_3JTYxxxxVm4',
'invoice': None,
'last_payment_error': None,
'livemode': False,
'metadata': <StripeObject at 0x105c061d0> JSON: {},
'next_action': None,
'object': 'payment_intent',
'on_behalf_of': None,
'payment_method': None,
'payment_method_options': {'card': {'installments': None,
'network': None,
'request_three_d_secure': 'automatic'}},
'payment_method_types': ['card'],
'receipt_email': None,
'review': None,
'setup_future_usage': None,
'shipping': None,
'source': None,
'statement_descriptor': None,
'statement_descriptor_suffix': None,
'status': 'requires_payment_method',
'transfer_data': None,
'transfer_group': None}
checkout.session.completed
{'allow_promotion_codes': None,
'amount_subtotal': 20000,
'amount_total': 20000,
'automatic_tax': {'enabled': False,
'status': None},
'billing_address_collection': None,
'cancel_url': 'http://localhost:9000/#/guides/cozumel-buzos-del-caribe/trips/7-day-dive?cancelpayment=true',
'client_reference_id': None,
'currency': 'usd',
'customer': 'cus_K7oxxxguu',
'customer_details': {'email': 'abc@gmail.com',
'tax_exempt': 'none',
'tax_ids': []},
'customer_email': None,
'id': 'cs_test_b1Yxxx9dM',
'livemode': False,
'locale': None,
'metadata': <StripeObject at 0x103d64a40> JSON: {},
'mode': 'payment',
'object': 'checkout.session',
'payment_intent': 'pi_3JTYxxxVm4',
'payment_method_options': <StripeObject at 0x103d648b0> JSON: {},
'payment_method_types': ['card'],
'payment_status': 'paid',
'setup_intent': None,
'shipping': None,
'shipping_address_collection': None,
'submit_type': None,
'subscription': None,
'success_url': 'http://localhost:9000/#/payment/success',
'total_details': {'amount_discount': 0,
'amount_shipping': 0,
'amount_tax': 0},
'url': None}
in all these events metadata is 'metadata': <StripeObject at 0x103d64a40> JSON: {}
What exactly do you mean by "session response" here? Can you provide an example?
For the webhook, which exact event type are you subscribed to? If, for example, you're listening to payment_intent.succeeded instead of checkout.session.completed, then it would be expected for the session metadata to not be present. You can optionally provide metadata to the underlying payment intent using payment_intent_data[metadata][key]=val, which would then be included in the payment intent event bodies.
After spending some time with stripe folks on their discord (they are super helpful) i discovered that metadata posted to the checkout session, using the method described in the question, should persist but only in the checkout session flow. it would not be present on any other events (namely charge, paymentIntent, or other flows). After refactoring my code, i managed to see this metadata on the checkout_session.complete event, so as anticipated... i did something dumb. An important thing to note though, is that this metadata would not be seen on any subsequent events other than the checkout session, which is something i did not understand at first. The fact that the metadata was not on subsequent checkout session events was due to a problem between the chair and the keyboard, and i wont ask any further questions for a while.
big thanks to everyone who tried to answer and who directed me to stripe. this actually opened up a can of worms, in a good way.
This question has been answered but since the answers weren't immediately clear to me until further experimentation, I'm just proposing a little extra information for anyone having similar issues.
In the Session creation snippet below, pay attention to the TWO metadata declarations:
stripe.checkout.Session.create(
success_url=success_url,
cancel_url=cancel_url,
payment_method_types=["card"],
mode="payment",
metadata={
"foo": "FOO",
},
payment_intent_data={
"metadata": {
"bar": "BAR",
}
},
line_items=[
{
"name": product_name,
"quantity": quantity,
"currency": currency,
"amount": unit_price,
},
]
)
To test Webhooks, I used the command stripe listen --forward-to localhost:/webhook via the Stripe CLI and obtained the following results from a successful payment transaction with my app. This is what I observed:
My 'FOO' data was available in the payload received from the webhook callback for checkout.session.completed only.
My 'BAR' data was available in the payload received from the webhook callback for payment_intent.created, payment_intent.succeeded, and charge.succeeded only.
Neither 'FOO' nor 'BAR' were available in customer.created or customer.updated.