Hello,
I鈥檓 trying to implement 3D secure flow with Stripe and I can't manage to complete the checkout with cards that require authentication.
I鈥檓 trying to call the paymentSecureConfirm mutation after completing the 3D secure iframe confirmation step but this error shows up in server console:
web_1 | ERROR saleor.payment.gateway Error encountered while executing payment gateway. [PID:100:Thread-8]
web_1 | Traceback (most recent call last):
web_1 | File "/app/saleor/payment/gateway.py", line 212, in _fetch_gateway_response
web_1 | response = fn(*args, **kwargs)
web_1 | File "/app/saleor/extensions/manager.py", line 250, in confirm_payment
web_1 | return self.__run_payment_method(gateway, method_name, payment_information)
web_1 | File "/app/saleor/extensions/manager.py", line 330, in __run_payment_method
web_1 | raise Exception(
web_1 | Exception: Payment plugin Stripe for confirm_payment payment method is inaccessible!
Thanks in advance
Can you provide more steps to reproduce it? What mutations did you run before trying to run paymentSecureConfirm? We'll try to reproduce and confirm the issue.
Apologies for not providing enough details at the start. I just revisited all the steps I followed to implement the checkout and I actually ran into a few more problems along the way. I'll create separate issues if needed.
My front-end is built with React and Apollo. These are the steps I followed:
No issues here:
View mutations
# Create checkout
mutation(
$email: String!
$shippingAddress: AddressInput!
$billingAddress: AddressInput!
$lines: [CheckoutLineInput]!
) {
checkoutCreate(
input: {
email: $email
shippingAddress: $shippingAddress
billingAddress: $billingAddress
lines: $lines
}
) {
checkout {
id
token
}
errors {
field
message
}
}
}
```graphql
query($checkoutToken: UUID!) {
checkout(token: $checkoutToken) {
availableShippingMethods {
id
}
}
}
```graphql
# Update checkout shipping method and get available payment gateways
mutation($checkoutId: ID!, $shippingMethodId: ID!) {
checkoutShippingMethodUpdate(
checkoutId: $checkoutId
shippingMethodId: $shippingMethodId
) {
checkout {
totalPrice {
gross {
amount
currency
}
}
availablePaymentGateways {
name
config {
field
value
}
}
}
errors {
field
message
}
}
}
Below is a simplified version of my React component, for brevity's sake I removed all error handling.
import React from 'react'
import { CardElement, injectStripe } from 'react-stripe-elements'
import { useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
const CHECKOUT_PAYMENT_CREATE = gql`
mutation($checkoutId: ID!, $token: String!, $amount: Decimal!) {
checkoutPaymentCreate(
checkoutId: $checkoutId
input: { gateway: "Stripe", token: $token, amount: $amount }
) {
payment {
id
chargeStatus
}
}
}
`
const CHECKOUT_COMPLETE = gql`
mutation($checkoutId: ID!) {
checkoutComplete(checkoutId: $checkoutId) {
order {
id
status
}
}
}
`
const CheckoutPaymentForm = ({ amount, checkoutId, stripe }) => {
const [createPayment] = useMutation(CHECKOUT_PAYMENT_CREATE)
const [checkoutComplete] = useMutation(CHECKOUT_COMPLETE)
const handleSubmit = async e => {
e.preventDefault()
const { token } = await stripe.createToken()
createPayment({
variables: {
checkoutId,
amount,
token: token.id,
},
}).then(() => {
checkoutComplete({
variables: {
checkoutId,
},
})
})
}
return (
<form onSubmit={handleSubmit}>
<CardElement hidePostalCode />
<button type="submit">PAY</button>
</form>
)
}
export default injectStripe(CheckoutPaymentForm)
When the user inserts a credit card number into the card input rendered by react-stripe-elements and hits submit, a token is created by stripe.createToken(). This token is passed to the checkoutPaymentCreate mutation. If there are no errors the component calls the checkoutComplete mutation to create the charge on the user's credit card.
However this error shows up in the server console after running checkoutComplete:
web_1 | INFO stripe message='Request to Stripe api' method=post path=https://api.stripe.com/v1/payment_intents [PID:9:Thread-8]
web_1 | INFO stripe message='Stripe API response' path=https://api.stripe.com/v1/payment_intents response_code=400 [PID:9:Thread-8]
web_1 | INFO stripe error_code=None error_message='A token may not be passed in as a PaymentMethod. Instead, use payment_method_data with type=card and card[token]=tok_1G0Z1PDPP6tn2u4HVCUtgOa2.' error_param=payment_method error_type=invalid_request_error message='Stripe API error received' [PID:9:Thread-8]
The error is caused by the following code in this file:
https://github.com/mirumee/saleor/blob/master/saleor/payment/gateways/stripe/__init__.py#L48
The error goes away if I replace payment_method with a payment_method_data object like this:
intent = client.PaymentIntent.create(
payment_method_data={
"type": "card",
"card": {
"token": payment_information.token
}
},
amount=stripe_amount,
currency=currency,
confirmation_method="manual",
confirm=True,
capture_method=capture_method,
setup_future_usage=future_use,
customer=customer_id,
shipping=shipping,
)
After making this change I'm able to complete the checkout succesfully with a credit card that doesn't require authentication such as this one: 4242 4242 4242 4242 (Stripe test card numbers.) The order shows as fully paid in Saleor dashboard and the charge is successful in the Stripe dashboard.
However, the charge is not working with a card that requires 3D Secure authentication such as this one: 4000 0027 6000 3184. It incorrectly shows up as fully paid in Saleor dashboard, while it is displayed as incomplete in Stripe dashboad as the auth step has not been completed.
According to Stripe docs, a PaymentIntent requires a an authentication step if its next_action is redirect_to_url. I was able to get this info by turning off Automatic payment capture in dashboard and inspecting the gatewayResponse JSON object returned by checkoutComplete mutation (I'm not able to find redirect_to_url if auto capture is enabled):
mutation($checkoutId: ID!) {
checkoutComplete(checkoutId: $checkoutId) {
order {
payments {
id
transactions {
kind
gatewayResponse
}
}
}
}
}
Also, according to the Stripe docs a return_url must be set up for the redirect step. For now I just hard coded it in the authorize function in the __init__.py file I mentioned before:
intent = client.PaymentIntent.create(
payment_method_data={
"type": "card",
"card": {
"token": payment_information.token
}
},
amount=stripe_amount,
currency=currency,
confirmation_method="manual",
confirm=True,
capture_method=capture_method,
setup_future_usage=future_use,
customer=customer_id,
shipping=shipping,
return_url="http://localhost:8001/checkout/confirmation",
)
Afterwards, using the info in gatewayResponse I'm able to create an iframe such as the one in the docs with the 3D Secure UI and complete the authentication step.
However, if I try to call the paymentSecureConfirm mutation after completing the 3D Secure auth this exception comes up in the server console:
Exception: Payment plugin Stripe for confirm_payment payment method is inaccessible!
Please tell me if you need any additional details,
Thanks
Thanks for this detailed explanation @nik-s. We'll investigate this issue as soon as we can.
@nik-s
Hey, thanks for reporting issue!
First important thing I see you're doing is using _token_ instead of _payment method_ in Stripe. Our stripe plugin uses new PaymentIntent API, you must use it too.
Secondary, 3D secure flow is unfortunately incomplete in checkout flow due to a missing intermediate state in the _checkoutComplete_ mutation. This is something we are going to add real soon, you could easily detect confirmation needed in returned dataclass from payment plugin and introduce flag or other mean of information to client that confirmation is still needed to complete payment.
Third, for some incomprehensible reason _confirm_ method is missing in stripe plugin class, however it is available in code itself so this fix is very easy.
Hope this helped at least a little 馃槃
I see now that the confirm method is there, and I'll give a try on implementing PaymentIntent on the front-end. I'll post my findings here.
Thank you very much for the help! 鉁岋笍
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@nik-s
Hey, thanks for reporting issue!
First important thing I see you're doing is using _token_ instead of _payment method_ in Stripe. Our stripe plugin uses new PaymentIntent API, you must use it too.
Secondary, 3D secure flow is unfortunately incomplete in checkout flow due to a missing intermediate state in the _checkoutComplete_ mutation. This is something we are going to add real soon, you could easily detect confirmation needed in returned dataclass from payment plugin and introduce flag or other mean of information to client that confirmation is still needed to complete payment.
Third, for some incomprehensible reason _confirm_ method is missing in stripe plugin class, however it is available in code itself so this fix is very easy.
Hope this helped at least a little 馃槃
@salwator The docs (both @ docs.saleor.io and the Playground docs) specify a required token string to be passed along with PaymentInput as part of the checkoutPaymentCreate mutation. Would you help me understand what string from Stripe we're supposed to pass along instead of the token.id we used to get from stripe.createToken()? Is it, perhaps, the client_secret from the Payment Intents API?
Most helpful comment
Apologies for not providing enough details at the start. I just revisited all the steps I followed to implement the checkout and I actually ran into a few more problems along the way. I'll create separate issues if needed.
My front-end is built with React and Apollo. These are the steps I followed:
1. Create checkout object and update shipping method
No issues here:
View mutations
```graphql
Get ids of available shipping methods
query($checkoutToken: UUID!) {
checkout(token: $checkoutToken) {
availableShippingMethods {
id
}
}
}
2. Create Stripe token, create payment object and complete checkout
Below is a simplified version of my React component, for brevity's sake I removed all error handling.
When the user inserts a credit card number into the card input rendered by
react-stripe-elementsand hits submit, a token is created bystripe.createToken(). This token is passed to thecheckoutPaymentCreatemutation. If there are no errors the component calls thecheckoutCompletemutation to create the charge on the user's credit card.However this error shows up in the server console after running
checkoutComplete:The error is caused by the following code in this file:
https://github.com/mirumee/saleor/blob/master/saleor/payment/gateways/stripe/__init__.py#L48
The error goes away if I replace
payment_methodwith apayment_method_dataobject like this:After making this change I'm able to complete the checkout succesfully with a credit card that doesn't require authentication such as this one:
4242 4242 4242 4242(Stripe test card numbers.) The order shows as fully paid in Saleor dashboard and the charge is successful in the Stripe dashboard.However, the charge is not working with a card that requires 3D Secure authentication such as this one:
4000 0027 6000 3184. It incorrectly shows up as fully paid in Saleor dashboard, while it is displayed as incomplete in Stripe dashboad as the auth step has not been completed.According to Stripe docs, a PaymentIntent requires a an authentication step if its
next_actionisredirect_to_url. I was able to get this info by turning off Automatic payment capture in dashboard and inspecting thegatewayResponseJSON object returned bycheckoutCompletemutation (I'm not able to findredirect_to_urlif auto capture is enabled):Also, according to the Stripe docs a
return_urlmust be set up for the redirect step. For now I just hard coded it in theauthorizefunction in the__init__.pyfile I mentioned before:Afterwards, using the info in
gatewayResponseI'm able to create an iframe such as the one in the docs with the 3D Secure UI and complete the authentication step.However, if I try to call the
paymentSecureConfirmmutation after completing the 3D Secure auth this exception comes up in the server console:Please tell me if you need any additional details,
Thanks