Payments
Advanced Guide - prior knowledge required
In order to follow this guide properly, we recommend that you get familiar with the payment flow and payment API concepts first.
- Payments Concept - especially
asynchronous
andsynchronous
chapters. - Payment API
Synchronous Payment
Due to the fact the order can be placed without giving any additional payment information (only allowed data is a customer comment
and affiliate code
), the synchronous payment strongly depends on the specific implementation, and that's why it does not affect the way how to deal it in the headless client application.
In this case, the flow looks as follows:
// the cart contains at least one item added
const { createOrder } = useCheckout();
// create an order from the current Cart
const order = await createOrder(/** optional params omitted */);
// order object on success, unhandled rejection otherwise
Under the hood, once the order is placed, a PaymentHandler is being invoked to process the payment right away:
- Execute the payment logic (may vary for every payment method / provider)
- Change the payment status according the result from previous step
In general, the client side does not have any direct control on the sync payment process.
Asynchronous Payment
Contrary to the sync flow, the asynchronous payment has more options and thus, more control of the payment process.
This is a better option for those payment providers that would need to pass additional data (like credentials, one time tokens) to complete the payment process.
External gateway
To give an example, let's say we need to implement a payment method which redirects a customer to the external payment gateway. Depending on success or failure, we need to be redirected to success page in case of payment was done properly, otherwise display an error page to the user in our shop page.
Create an order
jsconst { createOrder } = useCheckout(); const { refreshCart } = useCart(); // create an order const order = await createOrder();
Utilize
useOrderPayment
composable to proceed the payment process once order is placedjs// utilize useOrderPayment to proceed on the provided order const { paymentUrl, handlePayment, isAsynchronous, state, paymentMethod } = useOrderPayment(ref(order));
Initialize a payment handler
This is the moment, when any additional information can be passed (if a payment extension allows to do so). Payment handler can communicate with an external service to init some additional process, like preparation of external gateway session to process the payment for specific order.
js// where to redirect an user when payment is done correctly const SUCCESS_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/paid`; // go to this page otherwise const FAILURE_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/unpaid`; const handlePaymentResponse = await handlePayment( SUCCESS_PAYMENT_URL, FAILURE_PAYMENT_URL, { /** * here goes additional information required by payment provider * can be payment intent token */ } )
Note that, this is an example, does not show how to create success/failure pages.
Do the action on processed payment handler
If payment provider (shipped via app/plugin/extension) has external payment gateway, you will probably get the URL to go to.
jsconst handlePaymentResponse = await handlePayment(); /* parameters omitted, see previous point */ const redirectUrl = handlePaymentResponse?.redirectUrl; // URL or undefined
Then you are ready to perform a redirection of an user to the URL in order to finish the payment. If succeed, the customer will be redirected back to
SUCCESS_PAYMENT_URL
defined before. Otherwise,FAILURE_PAYMENT_URL
will be displayed.
Credit cards
Flow for the credit cards may vary between providers, nevertheless there is a general rule: asynchronous payment flow applies also in this case. Because there is always additional data to be sent, like one time tokens, hash and other security solutions.
Sometimes the external authorization is needed and the external gateway can be used, or a popup to interact with payment provider.
However, if there are no plugin-specific endpoints to interact with, the handlePayment
method (or /store-api/handle-payment
endpoint) is always a good choice.
See what can be achieved on Express Checkout example for PayPal provider.
App server integration
When a payment method uses an app server, for example as a gateway or middleware, there are some key information needed to identify the client source and the store related to the app itself.
In detached API consumer like headless app, the mentioned information can be obtained by using a tailored endpoint:
⚠️ works only for logged-in customers
const { apiClient } = useShopwareContext(); // or use an instance of @shopware/api-client library
const tokenResponse = await apiClient.invoke(
"generateJWTAppSystemAppServer post /app-system/{name}/generate-token",
{
pathParams: {
name: "MyPaymentApp",
},
},
);
The response may look like this:
// tokenResponse:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJVZXF4S1RtSHBKVHZmZkRQIiwiaWF0IjoxNzMzNDA5NTM3LjQ1NzYxMSwibmJmIjoxNzMzNDA5NTM3LjQ1NzYxMywiZXhwIjoxNzMzNDEwMTM3LjQ1BzUzOSwic2FsZXNDaGFubmVsSWQiOiI4ODQzMmRlZjM5ZmM0NjI0YjMzMjEzYTU2YjhjOTQ0ZCJ9.M2GZ6hFFBgQAgoAQAVC--aIG2pl5wytEBBwpCN0UFCw",
"expires": "2024-12-05T14:48:57+00:00",
"shopId": "QeqxZlmHpJBvfvDP"
}
Since the endpoint returns a jwt
token containing all required data to identify the further requests: salesChannelId
and shopId
. Therefore using the jwt
token should be the only way of authorization, in a request's header. The token is valid for 10 minutes by default.
For example:
await fetch("https://shopware.mypaymentgateway.com/api/store/card", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokenResponse.data?.token}`, // jwt token from the sample code above
},
body: JSON.stringify({
cardId: "card_123",
tokenId: "some-secret-token_123",
}),
});