# Guía de integración

Aprenderás cómo:

* Configurar las credenciales del entorno
* Autenticar tu servidor con PayPal
* Crear y capturar órdenes de manera segura desde tu backend

## Descripción general

PayPal Checkout utiliza una integración en dos partes:

1. **Cliente** — Renderiza Smart Payment Buttons
2. **Servidor** — Gestiona llamadas API seguras (crear y capturar órdenes)

Esta guía se centra en la lógica del servidor usando la API Orders v2.

## Prerrequisitos

* Una cuenta de desarrollador de PayPal: <https://developer.paypal.com/>
* Una aplicación sandbox creada en el [Panel de control](https://developer.paypal.com/dashboard/applications)
* Tu **Client ID** y **Secret**

{% hint style="warning" %}
Todos los ejemplos usan los endpoints de \`sandbox\`. Sustituye por \`api.paypal.com\` para producción.
{% endhint %}

## Implementar PayPal Checkout

{% stepper %}
{% step %}

### Configuración del entorno

{% hint style="info" %}
\[Inicia sesión]\(<https://www.paypal.com/signin?returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdashboard%2F%26intent%3Ddeveloper%26ctxId%3Dul1749482563440>) para usar tus propias claves API en estos ejemplos.
{% endhint %}

Establece estos valores como variables de entorno o de forma segura en un archivo de configuración:

```
PAYPAL_CLIENT_ID=tu-client-id-sandbox
PAYPAL_CLIENT_SECRET=tu-client-secret-sandbox
PAYPAL_API=https://api-m.sandbox.paypal.com
```

{% endstep %}

{% step %}

### Autenticación

Obtén un token de acceso de PayPal

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v1/oauth2/token \
  -H "Accept: application/json" \
  -H "Accept-Language: en_US" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "grant_type=client_credentials"
```

{% endtab %}

{% tab title="Node.js" %}

```js
const axios = require('axios');

async function generateAccessToken() {
  const auth = Buffer.from(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`).toString("base64");

  const response = await axios.post(
    `${process.env.PAYPAL_API}/v1/oauth2/token`,
    "grant_type=client_credentials",
    {
      headers: {
        Authorization: `Basic ${auth}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
    }
  );

  return response.data.access_token;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests
import base64
import os

def generate_access_token():
    client_id = os.getenv('PAYPAL_CLIENT_ID')
    client_secret = os.getenv('PAYPAL_CLIENT_SECRET')
    
    auth_string = f"{client_id}:{client_secret}"
    auth_bytes = auth_string.encode('ascii')
    auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
    
    headers = {
        'Authorization': f'Basic {auth_b64}',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    data = 'grant_type=client_credentials'
    
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v1/oauth2/token",
        headers=headers,
        data=data
    )
    
    return response.json()['access_token']
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.http.*;
import java.net.URI;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PayPalAuth {
    private static final String PAYPAL_API = System.getenv("PAYPAL_API");
    private static final String CLIENT_ID = System.getenv("PAYPAL_CLIENT_ID");
    private static final String CLIENT_SECRET = System.getenv("PAYPAL_CLIENT_SECRET");
    
    public static String generateAccessToken() throws Exception {
        String auth = CLIENT_ID + ":" + CLIENT_SECRET;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(PAYPAL_API + "/v1/oauth2/token"))
            .header("Authorization", "Basic " + encodedAuth)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials"))
            .build();
            
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readTree(response.body()).get("access_token").asText();
    }
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
function generateAccessToken() {
    $clientId = $_ENV['PAYPAL_CLIENT_ID'];
    $clientSecret = $_ENV['PAYPAL_CLIENT_SECRET'];
    $paypalApi = $_ENV['PAYPAL_API'];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $paypalApi . '/v1/oauth2/token');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_USERPWD, $clientId . ':' . $clientSecret);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response, true);
    return $data['access_token'];
}
?>
```

{% endtab %}

{% tab title=".NET" %}

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

public class PayPalAuth
{
    private static readonly HttpClient client = new HttpClient();
    private static readonly string PayPalApi = Environment.GetEnvironmentVariable("PAYPAL_API");
    private static readonly string ClientId = Environment.GetEnvironmentVariable("PAYPAL_CLIENT_ID");
    private static readonly string ClientSecret = Environment.GetEnvironmentVariable("PAYPAL_CLIENT_SECRET");
    
    public static async Task<string> GenerateAccessToken()
    {
        var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ClientId}:{ClientSecret}"));
        
        var request = new HttpRequestMessage(HttpMethod.Post, $"{PayPalApi}/v1/oauth2/token");
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);
        request.Content = new StringContent("grant_type=client_credentials", Encoding.UTF8, "application/x-www-form-urlencoded");
        
        var response = await client.SendAsync(request);
        var responseContent = await response.Content.ReadAsStringAsync();
        
        var json = JObject.Parse(responseContent);
        return json["access_token"].ToString();
    }
}
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'net/http'
require 'uri'
require 'base64'
require 'json'

def generate_access_token
  client_id = ENV['PAYPAL_CLIENT_ID']
  client_secret = ENV['PAYPAL_CLIENT_SECRET']
  paypal_api = ENV['PAYPAL_API']
  
  uri = URI("#{paypal_api}/v1/oauth2/token")
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = "Basic #{Base64.strict_encode64("{client_id}:{client_secret}")}"  
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request.body = 'grant_type=client_credentials'
  
  response = http.request(request)
  data = JSON.parse(response.body)
  
  data['access_token']
end
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Crear orden

Crea una nueva orden de PayPal

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -d '{
    "intent": "CAPTURE",
    "purchase_units": [{
      "amount": {
        "currency_code": "USD",
        "value": "10.00"
      }
    }]
  }'
```

{% endtab %}

{% tab title="Node.js" %}

```js
app.post("/api/orders", async (req, res) => {
  const accessToken = await generateAccessToken();

  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders`,
    {
      intent: "CAPTURE",
      purchase_units: [{ amount: { currency_code: "USD", value: "10.00" } }]
    },
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      }
    }
  );

  res.json({ id: response.data.id });
});
```

{% endtab %}

{% tab title="Python" %}

```python
from flask import Flask, request, jsonify
import requests, os

app = Flask(__name__)

@app.route('/api/orders', methods=['POST'])
def create_order():
    access_token = generate_access_token()
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    order_data = {
        'intent': 'CAPTURE',
        'purchase_units': [{
            'amount': {
                'currency_code': 'USD',
                'value': '10.00'
            }
        }]
    }
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v2/checkout/orders",
        headers=headers,
        json=order_data
    )
    return jsonify({'id': response.json()['id']})
```

{% endtab %}

{% tab title="Java" %}

```java
@PostMapping("/api/orders")
public ResponseEntity<Map<String, String>> createOrder() throws Exception {
    String accessToken = PayPalAuth.generateAccessToken();
    String orderData = """
    {
        "intent": "CAPTURE",
        "purchase_units": [{
            "amount": {
                "currency_code": "USD",
                "value": "10.00"
            }
        }]
    }
    """;
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(PAYPAL_API + "/v2/checkout/orders"))
        .header("Authorization", "Bearer " + accessToken)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(orderData))
        .build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    ObjectMapper mapper = new ObjectMapper();
    String orderId = mapper.readTree(response.body()).get("id").asText();
    return ResponseEntity.ok(Map.of("id", orderId));
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
if (\$_SERVER['REQUEST_METHOD'] === 'POST' && \$_SERVER['REQUEST_URI'] === '/api/orders') {
    \$accessToken = generateAccessToken();
    \$orderData = [
        'intent' => 'CAPTURE',
        'purchase_units' => [[
            'amount' => [
                'currency_code' => 'USD',
                'value' => '10.00'
            ]
        ]]
    ];
    \$ch = curl_init();
    curl_setopt(\$ch, CURLOPT_URL, \$_ENV['PAYPAL_API'] . '/v2/checkout/orders');
    curl_setopt(\$ch, CURLOPT_POST, 1);
    curl_setopt(\$ch, CURLOPT_POSTFIELDS, json_encode(\$orderData));
    curl_setopt(\$ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . \$accessToken,
        'Content-Type: application/json'
    ]);
    curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
    \$response = curl_exec(\$ch);
    curl_close(\$ch);
    \$data = json_decode(\$response, true);
    echo json_encode(['id' => \$data['id']]);
}
?>
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Capturar orden

Captura el pago de una orden aprobada

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders/ORDER_ID/capture \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

{% endtab %}

{% tab title="Node.js" %}

```js
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const accessToken = await generateAccessToken();
  const { orderID } = req.params;
  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders/${orderID}/capture`,
    {},
    { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }
  );
  res.json(response.data);
});
```

{% endtab %}

{% tab title="Python" %}

```python
@app.route('/api/orders/<order_id>/capture', methods=['POST'])
def capture_order(order_id):
    access_token = generate_access_token()
    headers = { 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json' }
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v2/checkout/orders/{order_id}/capture",
        headers=headers
    )
    return jsonify(response.json())
```

{% endtab %}

{% tab title="Java" %}

```java
@PostMapping("/api/orders/{orderID}/capture")
public ResponseEntity<String> captureOrder(@PathVariable String orderID) throws Exception {
    String accessToken = PayPalAuth.generateAccessToken();
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(PAYPAL_API + "/v2/checkout/orders/" + orderID + "/capture"))
        .header("Authorization", "Bearer " + accessToken)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString("{}"))
        .build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    return ResponseEntity.ok(response.body());
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
if (preg_match('/^\/api\/orders\/([^\/]+)\/capture$/', \$_SERVER['REQUEST_URI'], \$matches)) {
    \$orderID = \$matches[1];
    \$accessToken = generateAccessToken();
    \$ch = curl_init();
    curl_setopt(\$ch, CURLOPT_URL, \$_ENV['PAYPAL_API'] . '/v2/checkout/orders/' . \$orderID . '/capture');
    curl_setopt(\$ch, CURLOPT_POST, 1);
    curl_setopt(\$ch, CURLOPT_POSTFIELDS, '{}');
    curl_setopt(\$ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . \$accessToken,
        'Content-Type: application/json'
    ]);
    curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
    \$response = curl_exec(\$ch);
    curl_close(\$ch);
    echo \$response;
}
?>
```

{% endtab %}

{% tab title=".NET" %}

```csharp
[HttpPost("api/orders/{orderID}/capture")]
public async Task<IActionResult> CaptureOrder(string orderID)
{
    var accessToken = await PayPalAuth.GenerateAccessToken();
    var request = new HttpRequestMessage(HttpMethod.Post, $"{PayPalApi}/v2/checkout/orders/{orderID}/capture");
    request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
    var response = await client.SendAsync(request);
    return Ok(await response.Content.ReadAsStringAsync());
}
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Próximos pasos

* Validar el estado del pago tras la captura
* Almacenar los detalles del pago en tu base de datos
* Añadir listeners de webhooks para manejar eventos asíncronos
* Implementar manejo de errores y registro de logs
* Añadir validación de órdenes y controles de seguridad

## Enlaces útiles

* [API Orders v2](https://paypal.gitbook.com/online-payments/es/checkout/referencia-api)
* [Guía de OAuth 2.0](https://developer.paypal.com/api/rest/authentication/)
* [Patrón de integración backend](https://paypal.gitbook.com/online-payments/es/checkout/guia-de-integracion)
