1C-Bitrix Integration with PayPal Payment System
PayPal is one of the most common payment systems for international trade. In 1C-Bitrix, there is no official current PayPal module: the old PayPal Standard module is outdated, PayPal Express Checkout has been replaced with PayPal Commerce Platform (PPCP). Therefore, integration requires custom development for the current API.
Current PayPal Product for Merchants
PayPal Commerce Platform (PPCP) combines several payment methods:
- Visa/Mastercard/AmEx card payments without PayPal account (Guest Checkout);
- payment via customer's PayPal account;
- Pay Later (BNPL — buy now, pay later) in supported countries;
- Venmo (USA).
To connect, the store registers as a merchant via PayPal Partner Program or directly via business.paypal.com.
Two Technical Implementation Options
PayPal Checkout (hosted buttons / Smart Buttons). JavaScript SDK from PayPal is embedded on the checkout page. The customer clicks the PayPal button, goes through authorization in a popup window, returns to the site. Card data does not touch the merchant's server. Suitable for most stores.
Orders API (server-side). The order is created on the server via POST /v2/checkout/orders, the customer authorizes it, the server captures funds via POST /v2/checkout/orders/{id}/capture. More control, necessary for custom flows and subscriptions.
Module Architecture in Bitrix
The payment system handler inherits \Bitrix\Sale\PaySystem\ServiceHandler. Parameters are stored in b_sale_pay_system_action: CLIENT_ID, CLIENT_SECRET, ENVIRONMENT (sandbox/live).
Getting an access token:
$response = $httpClient->post(
$baseUrl . '/v1/oauth2/token',
['grant_type' => 'client_credentials'],
['Authorization' => 'Basic ' . base64_encode($clientId . ':' . $clientSecret)]
);
$accessToken = $response['access_token'];
Creating an order (Orders API v2):
$order = $httpClient->post($baseUrl . '/v2/checkout/orders', [
'intent' => 'CAPTURE',
'purchase_units' => [[
'reference_id' => 'order_' . $bitrixOrderId,
'amount' => [
'currency_code' => 'USD',
'value' => '99.00',
],
]],
'application_context' => [
'return_url' => $returnUrl,
'cancel_url' => $cancelUrl,
],
]);
The customer is redirected via links[rel=approve].href. After returning, we call POST /v2/checkout/orders/{id}/capture.
JavaScript Smart Buttons
For the Checkout approach, the JS SDK is added to the sale.order.ajax component page:
<script src="https://www.paypal.com/sdk/js?client-id=CLIENT_ID¤cy=USD"></script>
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
createOrder: function(data, actions) {
return fetch('/api/paypal/create-order', {method: 'POST'})
.then(r => r.json()).then(d => d.id);
},
onApprove: function(data, actions) {
return fetch('/api/paypal/capture-order/' + data.orderID, {method: 'POST'})
.then(r => r.json()).then(d => {
if (d.status === 'COMPLETED') window.location = '/order/success/';
});
}
}).render('#paypal-button-container');
</script>
On the Bitrix side, two AJAX endpoints are created: create-order (initiates order in PayPal, returns id) and capture-order (captures funds, updates status in b_sale_order).
Webhooks (Webhooks IPN)
PayPal sends notifications via Webhooks v2. Subscription to events — in PayPal Developer Dashboard. Required events:
-
PAYMENT.CAPTURE.COMPLETED— confirmation of fund capture -
PAYMENT.CAPTURE.DENIED— rejection -
PAYMENT.CAPTURE.REFUNDED— refund
Webhook signature verification:
$result = \PayPalHttp\HttpClient::verifyWebhookSignature(
$webhookId, $headers, $body, $accessToken
);
Refunds
Refund via POST /v2/payments/captures/{captureId}/refund. captureId is saved in b_sale_payment.PS_INVOICE_ID when captured.
$refund = $httpClient->post(
$baseUrl . '/v2/payments/captures/' . $captureId . '/refund',
['amount' => ['value' => '25.00', 'currency_code' => 'USD']]
);
Full refund — request body is empty, PayPal will return the entire capture amount.
Sandbox Testing
In PayPal Developer Dashboard, create sandbox accounts: one merchant (business), one customer (personal). Test cards are available in Sandbox → Accounts → customer → Credit Cards. The sandbox is fully isolated — transactions do not affect real money.
| Scenario | How to Check |
|---|---|
| Successful payment | Sandbox personal account, balance > order amount |
| Insufficient funds | Sandbox account with zero balance |
| Cancellation by customer | Close PayPal window, check cancel_url |
| Refund | Via API after successful capture |
Timeline
| Option | Scope | Timeline |
|---|---|---|
| Smart Buttons + capture | Module + JS widget + testing | 4–6 days |
| Fully server-side (Orders API) | Without JS dependency, API only | 4–5 days |
| + Subscriptions | Recurring billing via Subscriptions API | +3–4 days |
| + Multi-currency | Currency selection logic, PayPal setup | +1–2 days |







