Accept PayPal Express with UI components
Use Unzer UI component to add Unzer PayPal Express payment to your checkout page.
Overview
Using UI components v2 for PayPal Express you get a ready-made form with the fields necessary to make this type of payment. Basic steps for integrating using UI components are the same for all payment methods and you can read about them here.
Legacy integration
This page describes the most recent integration of the UI Components, introduced in Jan 2025. For the Legacy integration guide go here.
Note for WebView- If you are integrating for mobile devices, please read the Guidelines for mobile integrations.
Before you begin
Before you begin- Check the basic integration requirements.
- Familiarize yourself with general guide on integrating using UI components.
Step 1: Add UI components v2 to your payment pageclient side
Step 1: Add UI components v2 to your payment page [client side]First, you need to initiate our UI components v2 library and add the needed payment type component to your payment page.
Initiate UI Components
Initiate UI Components v2Load the Unzer JS script
Load the Unzer JS scriptInclude the Unzer JS script on your website. This will load all the Unzer custom UI components with unzer-
prefixed. For example, <unzer-paylater-invoice>
.
Make sure to always include the script directly from the Unzer domain https://static-v2.unzer.com
.
<script
type="module"
src="https://static-v2.unzer.com/v2/ui-components/index.js"
></script>
To make your website load faster, import the unzer script at the bottom of your HTML document.
Make sure to import the script as type=“module”.
To learn which URLs must be added to the allowlist for Unzer UI components in your content security policy, please refer to Content security policy section.
It is a good practice to put your website in loading state until the Unzer script is loaded, and the UI components are ready to use.
// Make sure initially your application is in loading state
// until all Unzer components are loaded
Promise.all([
customElements.whenDefined("unzer-payment"),
// Probably add any other Unzer components used here. For example:
// customElements.whenDefined("unzer-paylater-invoice"),
]).then(() => {
// Hide the loading state and proceed with next steps
}).catch((error) => {
// Handle any error that might occur during the
// loading process and initialization
});
UI setup and configuration
UI setup and configurationTo securely collect payment data from your customer, you need to add the <unzer-payment>
component, inside which you insert the needed payment type components.
<unzer-checkout>
element.
This will provide automatic handling for enabling/disabling the submit button depending on the current status,
and showing/hiding of brand icons.<unzer-payment
id="unzer-payment"
publicKey="s-pub-xyz"
locale="de-DE">
<!-- ... Here you will need to add the Unzer payment type tag, so the form UI elements will be inserted -->
<!-- e.g <unzer-paylater-invoice></unzer-paylater-invoice> -->
</unzer-payment>
<unzer-checkout id='unzer-checkout'>
<button type="submit" id="yourPaymentButtonId">Pay</button>
</unzer-checkout>
Following parameters need to be passed.
Parameter | Type | Description | Default value |
---|---|---|---|
publicKey (required) | String | The merchant public key. | - |
locale | String | The used locale. For more information on supported locales, see Localization. | Browser user defined locale. |
To implement a customer form, check customer UI components page for details.
Customized checkout flow
Customized checkout flowTo simplify the integration, you can use the <unzer-checkout>
custom web component. This tag encapsulates core functionality needed to manage the checkout process efficiently.
At a minimum, <unzer-checkout>
should be used with a checkout button as its immediate child.
If you’re using a custom button, it’s important that the button supports the disabled
property. This allows <unzer-checkout>
to dynamically control the button’s state based on the current status of the checkout form. For example:
myButton.disabled = true; // should disable the button
myButton.disabled = false; // should enable the button
If the disabled
property is not present or supported, <unzer-checkout>
will not be able to control the button’s interactivity.
<unzer-checkout id='unzer-checkout'>
<button type="submit">Pay</button>
</unzer-checkout>
Manual control
If you prefer to implement your own logic for enabling or disabling the checkout button, you can pass the manualCheckoutButtonControl
attribute to the tag.
In this mode, <unzer-checkout>
will dispatch events to indicate when the button should be enabled or disabled, allowing you to implement your own logic and criteria for managing the button state.
<unzer-payment
id="unzer-payment"
publicKey="s-pub-xyz"
locale="de-DE">
<unzer-card></unzer-card>
</unzer-payment>
<unzer-checkout id='unzer-checkout' manualCheckoutButtonControl>
<button type="submit">Pay</button>
</unzer-checkout>
Promise.all([
customElements.whenDefined("unzer-payment"),
customElements.whenDefined("unzer-checkout"),
customElements.whenDefined("unzer-card"),
])
.then(() => {
const unzerCheckout = document.getElementById("unzer-checkout");
const unzerPayment = document.getElementById("unzer-payment");
unzerPayment.addEventListener("stateChanged", (event) => {
const enableCheckout = event.detail.enableCheckout;
// Add custom logic here and apply the button state
});
})
.catch((error) => {
/* Handle loading and initialization error */
})
If you prefer not to use <unzer-checkout>
at all, events will be dispatched in a similar manner
<unzer-payment
id="unzer-payment"
publicKey="s-pub-xyz"
locale="de-DE">
<unzer-card></unzer-card>
</unzer-payment>
<div>
<button type="submit">Pay</button>
</div>
Promise.all([
customElements.whenDefined("unzer-payment"),
customElements.whenDefined("unzer-card"),
])
.then(() => {
const unzerCheckout = document.getElementById("unzer-checkout");
const unzerPayment = document.getElementById("unzer-payment");
unzerPayment.addEventListener("stateChanged", (event) => {
const enableCheckout = event.detail.enableCheckout;
// Add custom logic here and apply the button state
})
.catch((error) => {
/* Handle loading and initialization error */
})
Displaying Click to Pay branding
For payment types such as Click to Pay that require a dynamic brand logo to appear beneath the checkout button, <unzer-checkout>
handles this automatically. If you are not using <unzer-checkout>
and still need to display Click To Pay branding, you can instead use the <unzer-brands>
component:
<unzer-payment
id="unzer-payment"
publicKey="s-pub-xyz"
locale="de-DE">
<unzer-card></unzer-card>
</unzer-payment>
<div>
<button type="submit">Pay</button>
<unzer-brands></unzer-brands>
</div>
This will automatically manage the visibility of the required logos based on the current checkout context.
Optional: Customize UI components
Optional: Customize UI componentsThe UI components comes with a default theme that contains basic styles. You can easily change these styles by overriding the CSS variables listed below. These variables are global, so they can be used for general customization.
Variable name | Description | Affected components |
---|---|---|
--unzer-font | Replaces all fonts with the given font. Users can define any font-face and provide the name of the custom font-face. | typography |
--unzer-text-color | The font color use for typography. | typography, icons |
--unzer-brand-color | The main color of the application. | button, checkbox, radio, loader |
--unzer-background-color | The page background. | |
--unzer-link-color | The color of links. | link |
--unzer-corner-radius | Whether controls will have rounded corners or not. Values can be either 0 or 1. | button, tags, chips |
--unzer-shadows | Whether shadows are enabled or not. Values can be either 0 or 1. | button, card |
<style>
:root {
--unzer-font: SFMono; /* Font family */
--unzer-brand-color: #ee1818; /* Brand color */
--unzer-text-color: #f19316; /* Main text color */
--unzer-background-color: #6a9472; /* Background Color */
--unzer-link-color: #1330ef; /* Link color */
--unzer-corner-radius: 0; /* Corner radius in pixels */
--unzer-shadows: 1 /* Enables or disables shadows (1 or 0) */
}
.box {
max-width: 300px; /* Width of the box */
margin: auto; /* Margin allignment */
}
</style>
Add Paypal Express payment method
To add the Paypal Express payment method insert unzer-paypal-express
in the unzer-payment
container.
<unzer-payment
publicKey="s-pub-xxxxxxxxxx"
locale="de-DE">
<unzer-paypal-express></unzer-paypal-express>
</unzer-payment>
<unzer-checkout>
<button type="submit" id="yourPaymentButtonId">Pay</button>
</unzer-checkout>
<unzer-checkout>
element.
This will provide automatic handling of the submit whenever the user clicks the submit button, and passing back the
creation payload.Optional: Customize the UI components
You can easily customize the color of the button by setting the color
attribute. Here you can set the following colors: blue
(by default), white
, silver
, black
, and gold
.
<unzer-payment
publicKey="s-pub-xxxxxxxxxx"
locale="de-DE">
<unzer-paypal-express color="silver"></unzer-paypal-express>
</unzer-payment>
<unzer-checkout>
<button type="submit" id="yourPaymentButtonId">Pay</button>
</unzer-checkout>
Step 2: Create a basket resourceserver side
The basket
resource stores information about the purchased products, used vouchers, and the shipment costs.
POST https://api.unzer.com/v2/baskets
Body:
{
"currencyCode": "EUR",
"orderId": "YourOrderId1234",
"totalValueGross":195.90,
"note": "basket note",
"basketItems": [
{
"title": "Notebook pro",
"basketItemReferenceId": "item-1",
"quantity": 1,
"amountPerUnitGross": 200.00,
"amountDiscountPerUnitGross": 10.00,
"vat": 1,
"unit": "pc",
"subTitle": "basket item 2",
"type": "goods"
},
{
"title": "Shipment costs",
"basketItemReferenceId": "item-2",
"quantity": 1,
"amountPerUnitGross": 5.90,
"vat": 1,
"subTitle": "Insured standard shipment",
"type": "shipment"
}
]
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$basketItem = (new BasketItem())
->setBasketItemReferenceId('Item-d030efbd4963')
->setQuantity(10)
->setUnit('m')
->setAmountPerUnitGross(20.00)
->setAmountDiscountPerUnitGross(1.00)
->setVat(19.0)
->setTitle('SDM 6 CABLE')
->setSubTitle('This is brand new Mid 2019 version')
->setImageUrl('https://a.storyblok.com/f/91629/x/1ba8deb8cc/unzer_primarylogo__white_rgb.svg')
->setType(BasketItemTypes::GOODS);
$basket = (new Basket())
->setTotalValueGross(190.00)
->setCurrencyCode('EUR')
->setOrderId('Order-12345')
->setNote('Test Basket')
->addBasketItem($basketItem);
$unzer->createBasket($basket);
BasketItem basketItem = new BasketItem()
.setBasketItemReferenceId("Item-xxxxxxxxxx")
.setQuantity(10)
.setUnit("m")
.setAmountPerUnitGross(BigDecimal.valueOf(20.00))
.setAmountDiscountPerUnitGross(BigDecimal.valueOf(1.00))
.setVat(BigDecimal.valueOf(19.0))
.setTitle("SDM 6 CABLE")
.setSubTitle("This is brand new Mid 2019 version")
.setImageUrl(new URL("https://a.storyblok.com/f/91629/x/1ba8deb8cc/unzer_primarylogo__white_rgb.svg"))
.setType(BasketItem.Type.GOODS);
Basket basket = new Basket()
.setTotalValueGross(BigDecimal.valueOf(190.00))
.setCurrencyCode(Currency.getInstance("EUR"))
.setOrderId("Order-12345")
.setNote("Test Basket")
.addBasketItem(basketItem);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
unzer.createBasket(basket);
The response looks similar to the following example:
{
"id": "s-bsk-12345"
}
For a full description of basket
resource, refer to the relevant server-side-integration documentation page.
Create payment type and customer resources
Create payment type and customer resourcesAfter the submit button is clicked, the customer and payment data are automatically submitted. You will then need
to query the unzer-checkout
element and handle the response inside its onPaymentSubmit
event listener.
The following code example shows how to read customer and payment data from the response.
// Make sure that this code is executed after the elements are rendered
const unzerCheckout = document.getElementById('unzer-checkout');
unzerCheckout.onPaymentSubmit = (response) => {
if (response.submitResponse && response.customerResponse) {
if (response.customerResponse.success) {
// Optional: Only in case a new customer is created in the frontend.
const customerId = response.customerResponse.data.id;
}
if (response.submitResponse.success) {
const paymentTypeId = response.submitResponse.data.id;
}
// Submit all IDs to your server-side integration to perform the payment transaction.
}
};
To try the customization yourself, go to the Hands-on example.
Step 3: Make a payment [server side]Step 3: Make a paymentserver side
For PayPal Express, an extended 2-step process for PayPal Express is applicable. This is different from the standard PayPal transaction flow.
In a first step, you are forwarding the customer to the PayPal payment page, where the customer authenticates himself and approves the order initially. Once this is successfully done, PayPal provides the customer’s full address data, that you can use to complete the customer’s order in your shop. With the address data, you can recalculate the shipping costs according to the now known shipping address.
In a second step, you are finalizing the payment in a separate request to Unzer by providing the final transaction amount.
Depending on your use cases, you have the option to use two different transaction flows.
Option 1: You can charge the customer’s wallet directly and the payment is done once the customer confirmed the order, and you’ve successfully finalized the payment immediately during customer’s checkout. For more details, see Order fulfillment.
Option 2: For a delayed fulfillment, you can authorize the payment first and the amount is blocked on the customer’s wallet. When you ship the customer’s order, you can then charge the blocked amount and charge the customer’s wallet. For more details, see Order fulfillment.
For more details on managing PayPal payments, such as refunding them, see Manage PayPal payments.
Option 1: Make a charge transaction
Make a POST /payments/charges
Now, make a charge
transaction with the Paypal
typeId that you created and the returnUrl
leading back to your shop after the customer has successfully approved the order on the PayPal payment page. With the charge
transaction, an amount is reserved but is not yet transferred. Initially the transaction is pending and a payment
resource is created.
POST https://api.unzer.com/v1/payments/charges
Body:
{
"amount": "195.90",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"paymentReference": "Example Reference",
"orderId": "ExampleOrderId1234",
"resources": {
"typeId": "s-ppl-twq0jsaafyny",
"basketId": "s-bsk-12345"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
}
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$charge = (new Charge(195.9, 'EUR', 'https://unzer.com/'))
->setCheckoutType('express', $paypal)
->setPaymentReference("Example Reference")
->setOrderId("ExampleOrderId1234");
$unzer->performCharge($charge, $paypal, null, null, $basket);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Charge charge = (Charge) new Charge()
.setAmount(BigDecimal.valueOf(195.9))
.setCurrencyCode(Currency.getInstance("EUR"))
.setReturnUrl("https://unzer.com/")
.setPaymentReference("Example Reference")
.setOrderId("ExampleOrderId1234");
.setAdditionalTransactionData(
new AdditionalTransactionData()
.setPaypal(new PaypalData().setCheckoutType(PaypalData.CheckoutType.EXPRESS))
);
unzer.charge(charge);
The response looks similar to the following example:
{
"id": "s-chg-1",
"isSuccess": false,
"isPending": true,
"isError": false,
"isResumed": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypal/s-ky7ZyEmKNjiC",
"message": {
"code": "COR.000.200.000",
"merchant": "Transaction pending",
"customer": "Your payment is currently pending. Please contact us for more information."
},
"amount": "195.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:48:12",
"resources": {
"customerId": "",
"paymentId": "s-pay-8537",
"basketId": "s-bsk-12345",
"metadataId": "",
"traceId": "f24acee84b5d28324489a7860bc7ffb2",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
},
"orderId": "YourOrderId1234",
"paymentReference": "Example Reference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A41E1EADC47133",
"shortId": "5311.4688.9455",
"traceId": "f24acee84b5d28324489a7860bc7ffb2"
}
}
Fetch the result of POST /payments/charges
We recommend subscribing to the charge
event to receive notifications about any changes to the charge
transaction. As soon as the event is triggered, you should fetch the charge
and update the order status in your shop according to its status.
Transaction status isResumed:true
indicates, customer has successfully approved the order on PayPal payment page, and you can proceed on this transaction.
GET /payments/s-pay-8538/charges/s-chg-1
Body:
{
}
$unzer = new UnzerSDK\Unzer('s-priv-xxxxxxxxxx');
$charge = $unzer->fetchChargeById($paymentId, $chargeId);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Charge charge = unzer.fetchCharge(paymentId, chargeId);
The response looks similar to the following example:
{
"id": "s-chg-1",
"isSuccess": true,
"isPending": false,
"isResumed": true,
"isError": false,
"card3ds": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypalot/s-jjlC9FrQrZ7q",
"message": {
"code": "COR.000.100.112",
"merchant": "Request successfully processed in 'Merchant in Connector Test Mode'",
"customer": "Your payments have been successfully processed in sandbox mode."
},
"amount": "195.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:59:07",
"resources": {
"customerId": "s-cst-0b5fbac8e1fb",
"paymentId": "s-pay-8538",
"basketId": "s-bsk-5e612a68be0b",
"traceId": "e8664e249367acee81480e9ae4f74b1f",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
},
"orderId": "YourOrderId1234",
"paymentReference": "YourReference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A44DC072DB429E",
"shortId": "5311.4754.5055",
"traceId": "e8664e249367acee81480e9ae4f74b1f"
}
}
Fetch customer data
Fetch the customer resource, which is now linked to the charges transaction. The customer resource is containing complete customer billing and shipping address data provided by PayPal.
For a full description of customer resource, refer to the relevant server-side-integration documentation page: Manage customer.
GET /customers/s-cst-0b5fbac8e1fb
Body:
{
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$unzer->fetchCustomer($customerId);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Customer customer = unzer.fetchCustomer(customerId);
The response looks similar to the following example:
{
"id": "s-cst-0b5fbac8e1fb",
"lastname": "Doe",
"firstname": "John",
"salutation": "mr",
"company": "",
"customerId": "",
"birthDate": "1987-12-20",
"email": "John.Doe@unzer.com",
"phone": "+4962216471100",
"mobile": "",
"language": "de",
"billingAddress": {
"name": "John Doe",
"street": "Hugo-Junkers-Str. 5",
"state": "DE",
"zip": "60386",
"city": "Frankfurt am Main",
"country": "DE"
},
"shippingAddress": {
"name": "John Doe",
"street": "Hugo-Junkers-Str. 5",
"state": "DE",
"zip": "60386",
"city": "Frankfurt am Main",
"country": "DE",
"shippingType": "equals-billing"
},
"geoLocation": {
"clientIp": "87.163.144.231",
"countryIsoA2": "DE"
}
}
(Optional) Recalculate the charge amount
With customer’s address data you can now recalculate the charge amount, for example, if due to customer’s shipping address higher shipping costs are applied.
Optional: Update the basket and the charge amount
If you want to change the initial amount of your charges transaction, update the basket you’ve used in the POST /payments/charges
request, display the updated basket and final charge amount to the customer. Ask the customer to finally confirm the changes.
Refer to the Update a basket guide to learn more on how to update basket
resources.
Make a PATCH /payments/{paymentId}/charges
Once the customer has finally confirmed the changed amount in your shop, make a PATCH on the initially created charge transaction to complete the PayPal Express transaction.
Provide the updated charge
amount in the PATCH /payments/{paymentId}/charges
request.
An updated basket doesn’t need to be provided again, the basketId includes the changes that you’ve applied. These will be considered automatically.
With a successful PATCH /payments/{paymentId}/charges
the payment status is set on completed now.
PATCH /payments/{paymentId}/charges
with an empty body.PATCH /payments/s-pay-8538/charges/s-chg-1
Body:
{
"amount": "210.90"
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$charge = new Charge(210.9);
$unzer->updateCharge($paymentId, $charge);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Charge charge = unzer.fetchCharge(paymentId, chargeId);
charge.setAmount(BigDecimal.valueOf(210.9));
unzer.updateCharge(charge);
The response looks similar to the following example:
{
"id": "s-chg-1",
"isSuccess": true,
"isPending": false,
"isError": false,
"card3ds": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypalot/s-jjlC9FrQrZ7q",
"message": {
"code": "COR.000.100.112",
"merchant": "Request successfully processed in 'Merchant in Connector Test Mode'",
"customer": "Your payments have been successfully processed in sandbox mode."
},
"amount": "210.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:59:07",
"resources": {
"customerId": "s-cst-0b5fbac8e1fb",
"paymentId": "s-pay-8538",
"basketId": "s-bsk-5e612a68be0b",
"metadataId": "",
"payPageId": "",
"traceId": "e8664e249367acee81480e9ae4f74b1f",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
}
"orderId": "YourOrderId1234",
"paymentReference": "YourReference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A44DC072DB429E",
"shortId": "5311.4754.5055",
"paypalBuyerId": "JHQEVB7FYWUHW",
"traceId": "e8664e249367acee81480e9ae4f74b1f"
}
}
Option 2: Make an authorize transaction
Make a POST /payments/authorize
Now, make an authorize
transaction with the Paypal
typeId
and basketId
that you created and the returnUrl
leading back to your shop after the customer has successfully approved the order on the PayPal payment page. With authorize
, the transaction is created, but the amount is not reserved yet in the wallet. Initially, the transaction is pending and a payment
resource is created.
POST /payments/authorize
Body:
{
"amount": "195.90",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"paymentReference": "Example Reference",
"orderId": "ExampleOrderId1234",
"resources": {
"typeId": "s-ppl-twq0jsaafyny",
"basketId": "s-bsk-12345"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
}
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$authorize = (new Authorization(195.9, 'EUR', 'https://unzer.com/'))
->setCheckoutType('express', $paypal)
->setPaymentReference("Example Reference")
->setOrderId("ExampleOrderId1234");
$unzer->performAuthorization($authorize, $paypal, null, null, $basket);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Authorization authorization = (Authorization) new Authorization()
.setAmount(BigDecimal.valueOf(195.9))
.setCurrencyCode(Currency.getInstance("EUR"))
.setReturnUrl("https://unzer.com/")
.setPaymentReference("Example Reference")
.setOrderId("ExampleOrderId1234");
.setAdditionalTransactionData(
new AdditionalTransactionData()
.setPaypal(new PaypalData().setCheckoutType(PaypalData.CheckoutType.EXPRESS))
);
unzer.authorize(authorization);
The response looks similar to the following example:
{
"id": "s-aut-1",
"isSuccess": false,
"isPending": true,
"isError": false,
"isResumed": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypal/s-ky7ZyEmKNjiC",
"message": {
"code": "COR.000.200.000",
"merchant": "Transaction pending",
"customer": "Your payment is currently pending. Please contact us for more information."
},
"amount": "195.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:48:12",
"resources": {
"customerId": "",
"paymentId": "s-pay-8537",
"basketId": "s-bsk-12345",
"metadataId": "",
"traceId": "f24acee84b5d28324489a7860bc7ffb2",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
},
"orderId": "YourOrderId1234",
"paymentReference": "Example Reference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A41E1EADC47133",
"shortId": "5311.4688.9455",
"traceId": "f24acee84b5d28324489a7860bc7ffb2"
}
}
Fetch the result of POST /payments/authorize
We recommend subscribing to the authorize
event to receive notifications about any changes to the authorize
transaction. As soon as the event is triggered, you should fetch the authorize
transaction and update the order status in your shop according to its status.
Transaction status isResumed:true
indicates, customer has successfully approved the order on PayPal payment page, and you can proceed on this transaction.
GET /payments/s-pay-8538/authorize/s-aut-1
Body:
{
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$unzer->fetchAuthorization($paymentId);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Charge charge = unzer.fetchAuthorization(paymentId);
The response looks similar to the following example:
{
"id": "s-aut-1",
"isSuccess": true,
"isPending": false,
"isResumed": true,
"isError": false,
"card3ds": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypalot/s-jjlC9FrQrZ7q",
"message": {
"code": "COR.000.100.112",
"merchant": "Request successfully processed in 'Merchant in Connector Test Mode'",
"customer": "Your payments have been successfully processed in sandbox mode."
},
"amount": "195.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:59:07",
"resources": {
"customerId": "s-cst-0b5fbac8e1fb",
"paymentId": "s-pay-8538",
"basketId": "s-bsk-5e612a68be0b",
"traceId": "e8664e249367acee81480e9ae4f74b1f",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
},
"orderId": "YourOrderId1234",
"paymentReference": "YourReference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A44DC072DB429E",
"shortId": "5311.4754.5055",
"traceId": "e8664e249367acee81480e9ae4f74b1f"
}
}
Fetch the customer data
Fetch the customer resource, which is now linked to the charges transaction. The customer resource is containing complete customer billing and shipping address data provided by PayPal.
For a full description of customer resource, refer to the relevant server-side-integration documentation page: Manage customer.
GET /customers/s-cst-0b5fbac8e1fb
Body:
{
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$unzer->fetchCustomer($customerId);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Customer customer = unzer.fetchCustomer(customerId);
The response looks similar to the following example:
{
"id": "s-cst-0b5fbac8e1fb",
"lastname": "Doe",
"firstname": "John",
"salutation": "mr",
"company": "",
"customerId": "",
"birthDate": "1987-12-20",
"email": "John.Doe@unzer.com",
"phone": "+4962216471100",
"mobile": "",
"language": "de",
"billingAddress": {
"name": "John Doe",
"street": "Hugo-Junkers-Str. 5",
"state": "DE",
"zip": "60386",
"city": "Frankfurt am Main",
"country": "DE"
},
"shippingAddress": {
"name": "John Doe",
"street": "Hugo-Junkers-Str. 5",
"state": "DE",
"zip": "60386",
"city": "Frankfurt am Main",
"country": "DE",
"shippingType": "equals-billing"
},
"geoLocation": {
"clientIp": "87.163.144.231",
"countryIsoA2": "DE"
}
}
(Optional) Recalculate the authorize amount
With customer’s address data you can now recalculate the authorize amount, for example, if due to customer’s shipping address higher shipping costs are applied.
Optional: Update basket and authorize amount
If you want to change the initial amount of your authorize transaction, update the basket you’ve used in the POST /payments/authorize
request, display the updated basket and final authorize amount to the customer. Ask the customer to finally confirm the changes.
Refer to the Update a basket guide to learn more on how to update basket resources.
Make a PATCH /payments/{paymentId}/authorize
Once the customer has finally confirmed the changed amount in your shop, make a PATCH on the initially created authorize transaction to complete the PayPal Express transaction.
Provide the updated authorize
amount in the PATCH /payments/{paymentId}/authorize
request.
An updated basket don’t need to be provided again, the basketId incl changes you’ve applied will be considered automatically.
With a successful PATCH /payments/{paymentId}/authorize
the transaction status is set on success now - the payment status remains on pending as long as no charge has been initialized.
PATCH /payments/{paymentId}/charges
with an empty body.PATCH /payments/s-pay-8538/authorize
Body:
{
"amount": "210.90"
}
$unzer = new Unzer('s-priv-xxxxxxxxxx');
$authorize = new Authorization(210.9);
$unzer->updateAuthorization($paymentId, $authorize);
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Authorization authorization = unzer.fetchAuthorization(paymentId);
authorization.setAmount(BigDecimal.valueOf(210.9));
unzer.updateAuthorization(authorization);
The response looks similar to the following example:
{
"id": "s-aut-1",
"isSuccess": true,
"isPending": false,
"isError": false,
"card3ds": false,
"redirectUrl": "https://sbx-payment.unzer.com/v1/redirect/paypalot/s-jjlC9FrQrZ7q",
"message": {
"code": "COR.000.100.112",
"merchant": "Request successfully processed in 'Merchant in Connector Test Mode'",
"customer": "Your payments have been successfully processed in sandbox mode."
},
"amount": "210.9000",
"currency": "EUR",
"returnUrl": "https://unzer.com/",
"date": "2022-10-31 12:59:07",
"resources": {
"customerId": "s-cst-0b5fbac8e1fb",
"paymentId": "s-pay-8538",
"basketId": "s-bsk-5e612a68be0b",
"metadataId": "",
"payPageId": "",
"traceId": "e8664e249367acee81480e9ae4f74b1f",
"typeId": "s-ppl-skki29gw2fue"
},
"additionalTransactionData": {
"paypal": {
"checkoutType": "express"
}
}
"orderId": "YourOrderId1234",
"paymentReference": "YourReference",
"processing": {
"uniqueId": "31HA07BC8137FFAF17A44DC072DB429E",
"shortId": "5311.4754.5055",
"paypalBuyerId": "JHQEVB7FYWUHW",
"traceId": "e8664e249367acee81480e9ae4f74b1f"
}
}
For a full description of the authorize
transaction, see the relevant server-side-integration documentation page: Authorize a payment (direct API calls), Authorize a payment (PHP SDK), Authorize a payment (Java SDK).
Charge the authorization
Charge the authorizationBecause the customer already accepted the order with the authorize transaction, you can now charge
the payment to transfer the money.
When you authorize before charge, the authorized amount is on hold for 29 days. Paypal however, recommends to charge the funds within the three-day honor period. The success of the charge is subject to risk and availability of funds on the authorized payment instrument.
POST https://api.unzer.com/v1/payments/s-pay-1/charges
{
"amount": 20,
"paymentReference": "Test charge transaction"
}
$unzer = new Unzer('s-priv-xxxxxxxxx');
$charge = $unzer->performChargeOnPayment('s-pay-1', new Charge());
Unzer unzer = new Unzer("s-priv-xxxxxxxxxx");
Charge charge = unzer.chargeAuthorization("s-pay-1");
The response looks similar to the following example:
{
"id": "s-chg-1",
"isSuccess": true,
"isPending": false,
"isError": false,
"message": {
"code": "COR.000.100.112",
"merchant": "Request successfully processed in 'Merchant in Connector Test Mode'",
"customer": "Your payments have been successfully processed in sandbox mode."
},
"amount": "20.0000",
"currency": "EUR",
"date": "2021-06-04 15:33:16",
"resources": {
"customerId": "s-cst-d6ae94482612",
"paymentId": "s-pay-1",
"basketId": "",
"metadataId": "",
"payPageId": "",
"traceId": "4ebfbfcae640ff2823e2e65febd54037",
"typeId": "s-ppl-4x3zn7i4bxyh"
},
"paymentReference": "Test charge transaction",
"processing": {
"uniqueId": "31HA07BC8198C2F9107E38AF14C45201",
"shortId": "4867.4719.2103",
"traceId": "4ebfbfcae640ff2823e2e65febd54037"
}
}
Step 4: Check status of the paymentserver side
Once the customer is redirected to the returnUrl
, you can fetch the payment details from the API, by using the resources.paymentId
from the charge
response above to handle the payment
according to its status. If the status of the payment
is completed
, the payment process has been finished successfully and can be considered as paid. Check all possible payment states here.
GET https://api.unzer.com/v1/payments/{payment_ID}
{
"id": "s-pay-131937",
"state": {
"id": 1,
"name": "completed"
},
"amount": {
"total": "20.0000",
"charged": "20.0000",
"canceled": "0.0000",
"remaining": "0.0000"
},
"currency": "EUR",
"orderId": "",
"invoiceId": "",
"resources": {
"customerId": "",
"paymentId": "s-pay-131937",
"basketId": "",
"metadataId": "",
"payPageId": "",
"traceId": "70ddf3152a798c554d9751a6d77812ae",
"typeId": "s-ppl-grpucjmy5zrk"
},
"transactions": [
{
"date": "2021-05-10 00:51:03",
"type": "charge",
"status": "success",
"url": "https://api.unzer.com/v1/payments/s-pay-131937/charges/s-chg-1",
"amount": "20.0000"
}
]
}
Step 5: Display the payment resultclient side
Step 5: Display the payment result [client side]Use the information from the Check status of the payment step to show payment result to your customer.
This can be the success or error page of your shop. If something went wrong you can use the client message from the API response and show it to the customer.
Notifications
NotificationsWe recommend subscribing to the payment
event to receive notifications about any changes to the payment
resource. As soon as the event is triggered you should fetch the payment
and update the order status in your shop according to its status.
{
"event":"payment.pending",
"publicKey":"s-pub-xxxxxxxxxx",
"retrieveUrl":"https://api.unzer.com/v1/payments/s-pay-774",
"paymentId":"s-pay-774"
}
For more details on implementing webhooks
to receive notifications, see Notifications page.
Error handling
Error handlingAll requests to the API can result in an error that should be handled. Refer to the Error handling guide to learn more about Unzer API (and other) errors and handling them.
Hands-on example
The following example contains a sample integration.
Select Edit in JSFiddle to try out the checkout component for yourself. Just replace the publicKey="INSERT YOUR SANDBOX PUBLIC KEY"
with your sandbox public key and then select Run.
You can find your sandbox keys in Unzer Insights.
Once you submit the payment, you can view the payment ID in the console of the browser. For example, --- success paymentId s-ppl-00naqzqml7ex
.
<script
type="module"
src="https://static-v2.unzer.com/v2/ui-components/index.js"
></script>
<div class="box">
<!-- id: Unique HTML identifier for the payment component. -->
<!-- publicKey: Merchant public key provided by Unzer. Replace with your own Unzer public key. -->
<!-- locale: Defines the language of the payment component interface and error messages(e.g., "de-DE" for German, "en-EN" for English). If not set, defaults to browser language. -->
<unzer-payment
id="unzer-payment"
publicKey="INSERT YOUR SANDBOX PUBLIC KEY"
locale="de-DE"
>
<unzer-paypal-express></unzer-paypal-express>
</unzer-payment>
<div class="pay-button">
<unzer-checkout id="unzer-checkout"
><button type="submit" id="yourPaymentButtonId">
Pay
</button></unzer-checkout
>
</div>
</div>
Promise.all([customElements.whenDefined('unzer-payment'), customElements.whenDefined('unzer-paypal-express')]).then(() => {
const unzerCheckout = document.getElementById('unzer-checkout');
unzerCheckout.onPaymentSubmit = response => {
if (response.submitResponse && response.submitResponse.success) {
/* Submit the ID of the created payment type resource to your server side integration to perform the payment transaction. */
const paymentId = response.submitResponse.data.id;
console.log("--- success paymentId", paymentId);
const saveInfoValue = response.saveInfoValue; /* If saveInfoValue == true, you can use the masked submitResponseData for storage in your server side integration and reuse for future transactions for this customer. */
} else {
/* Handle resource creation error */ }
};
}).catch(error => {
/* Handle loading and initialization error */
console.log("--- initialization error", error);
});
:root {
/* Font family */
--unzer-font: SFMono;
/* Brand color */
--unzer-brand-color: #ee1818;
/* Main text color */
--unzer-text-color: #f19316;
/* Background Color */
--unzer-background-color: #6a9472;
/* Link color */
--unzer-link-color: #1330ef;
/* Corner radius in pixels */
--unzer-corner-radius: 0;
/* Enables or disables shadows (1 or 0) */
--unzer-shadows: 1;
}
.box {
/* Changes the box width */
max-width: 300px;
/* Sets horizontal alignment */
margin: auto;
}
.pay-button {
/* Makes the button container full width and centers its content horizontally */
width: 100%;
display: flex;
justify-content: center;
margin-top: 8px;
}
#yourPaymentButtonId {
/* Button background color */
background: #2986e2;
/* Button text color */
color: #fff;
/* Removes border */
border: none;
/* Makes button corners fully rounded */
border-radius: 24px;
/* Button padding */
padding: 10px 32px;
/* Button font size */
font-size: 1rem;
/* Button font weight */
font-weight: 600;
/* Button font family */
font-family: 'Segoe UI', 'Arial', 'Helvetica Neue', 'sans-serif';
/* Shows pointer cursor on hover */
cursor: pointer;
/* Removes outline on focus */
outline: none;
/* Minimum button width */
min-width: 100px;
/* Minimum button height */
min-height: 40px;
/* Makes the button respect width/height and allows centering in flex */
display: inline-block;
}
#yourPaymentButtonId:hover {
/* Button background color on hover */
background: #1565c0;
}
Test & go live
Test & go liveYou should always test your integration before going live. First perform test transactions using test data. Next, check against Integration checklist and Go-live checklist to make sure the integration is complete and you’re ready to go live.