paypal-integration

Integrate PayPal payment processing with support for express checkout, subscriptions, and refund management. Use when implementing PayPal payments, processing online transactions, or building e-commerce checkout flows.

View Source
name:paypal-integrationdescription:Integrate PayPal payment processing with support for express checkout, subscriptions, and refund management. Use when implementing PayPal payments, processing online transactions, or building e-commerce checkout flows.

PayPal Integration

Master PayPal payment integration including Express Checkout, IPN handling, recurring billing, and refund workflows.

Do not use this skill when

  • The task is unrelated to paypal integration

  • You need a different domain or tool outside this scope
  • Instructions

  • Clarify goals, constraints, and required inputs.

  • Apply relevant best practices and validate outcomes.

  • Provide actionable steps and verification.

  • If detailed examples are required, open resources/implementation-playbook.md.
  • Use this skill when

  • Integrating PayPal as a payment option

  • Implementing express checkout flows

  • Setting up recurring billing with PayPal

  • Processing refunds and payment disputes

  • Handling PayPal webhooks (IPN)

  • Supporting international payments

  • Implementing PayPal subscriptions
  • Core Concepts

    1. Payment Products


    PayPal Checkout
  • One-time payments

  • Express checkout experience

  • Guest and PayPal account payments
  • PayPal Subscriptions

  • Recurring billing

  • Subscription plans

  • Automatic renewals
  • PayPal Payouts

  • Send money to multiple recipients

  • Marketplace and platform payments
  • 2. Integration Methods


    Client-Side (JavaScript SDK)
  • Smart Payment Buttons

  • Hosted payment flow

  • Minimal backend code
  • Server-Side (REST API)

  • Full control over payment flow

  • Custom checkout UI

  • Advanced features
  • 3. IPN (Instant Payment Notification)


  • Webhook-like payment notifications

  • Asynchronous payment updates

  • Verification required
  • Quick Start

    // Frontend - PayPal Smart Buttons
    <div id="paypal-button-container"></div>

    <script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&currency=USD"></script>
    <script>
    paypal.Buttons({
    createOrder: function(data, actions) {
    return actions.order.create({
    purchase_units: [{
    amount: {
    value: '25.00'
    }
    }]
    });
    },
    onApprove: function(data, actions) {
    return actions.order.capture().then(function(details) {
    // Payment successful
    console.log('Transaction completed by ' + details.payer.name.given_name);

    // Send to backend for verification
    fetch('/api/paypal/capture', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({orderID: data.orderID})
    });
    });
    }
    }).render('#paypal-button-container');
    </script>

    # Backend - Verify and capture order
    from paypalrestsdk import Payment
    import paypalrestsdk

    paypalrestsdk.configure({
    "mode": "sandbox", # or "live"
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
    })

    def capture_paypal_order(order_id):
    """Capture a PayPal order."""
    payment = Payment.find(order_id)

    if payment.execute({"payer_id": payment.payer.payer_info.payer_id}):
    # Payment successful
    return {
    'status': 'success',
    'transaction_id': payment.id,
    'amount': payment.transactions[0].amount.total
    }
    else:
    # Payment failed
    return {
    'status': 'failed',
    'error': payment.error
    }

    Express Checkout Implementation

    Server-Side Order Creation


    import requests
    import json

    class PayPalClient:
    def __init__(self, client_id, client_secret, mode='sandbox'):
    self.client_id = client_id
    self.client_secret = client_secret
    self.base_url = 'https://api-m.sandbox.paypal.com' if mode == 'sandbox' else 'https://api-m.paypal.com'
    self.access_token = self.get_access_token()

    def get_access_token(self):
    """Get OAuth access token."""
    url = f"{self.base_url}/v1/oauth2/token"
    headers = {"Accept": "application/json", "Accept-Language": "en_US"}

    response = requests.post(
    url,
    headers=headers,
    data={"grant_type": "client_credentials"},
    auth=(self.client_id, self.client_secret)
    )

    return response.json()['access_token']

    def create_order(self, amount, currency='USD'):
    """Create a PayPal order."""
    url = f"{self.base_url}/v2/checkout/orders"
    headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {self.access_token}"
    }

    payload = {
    "intent": "CAPTURE",
    "purchase_units": [{
    "amount": {
    "currency_code": currency,
    "value": str(amount)
    }
    }]
    }

    response = requests.post(url, headers=headers, json=payload)
    return response.json()

    def capture_order(self, order_id):
    """Capture payment for an order."""
    url = f"{self.base_url}/v2/checkout/orders/{order_id}/capture"
    headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {self.access_token}"
    }

    response = requests.post(url, headers=headers)
    return response.json()

    def get_order_details(self, order_id):
    """Get order details."""
    url = f"{self.base_url}/v2/checkout/orders/{order_id}"
    headers = {
    "Authorization": f"Bearer {self.access_token}"
    }

    response = requests.get(url, headers=headers)
    return response.json()

    IPN (Instant Payment Notification) Handling

    IPN Verification and Processing


    from flask import Flask, request
    import requests
    from urllib.parse import parse_qs

    app = Flask(__name__)

    @app.route('/ipn', methods=['POST'])
    def handle_ipn():
    """Handle PayPal IPN notifications."""
    # Get IPN message
    ipn_data = request.form.to_dict()

    # Verify IPN with PayPal
    if not verify_ipn(ipn_data):
    return 'IPN verification failed', 400

    # Process IPN based on transaction type
    payment_status = ipn_data.get('payment_status')
    txn_type = ipn_data.get('txn_type')

    if payment_status == 'Completed':
    handle_payment_completed(ipn_data)
    elif payment_status == 'Refunded':
    handle_refund(ipn_data)
    elif payment_status == 'Reversed':
    handle_chargeback(ipn_data)

    return 'IPN processed', 200

    def verify_ipn(ipn_data):
    """Verify IPN message authenticity."""
    # Add 'cmd' parameter
    verify_data = ipn_data.copy()
    verify_data['cmd'] = '_notify-validate'

    # Send back to PayPal for verification
    paypal_url = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr' # or production URL

    response = requests.post(paypal_url, data=verify_data)

    return response.text == 'VERIFIED'

    def handle_payment_completed(ipn_data):
    """Process completed payment."""
    txn_id = ipn_data.get('txn_id')
    payer_email = ipn_data.get('payer_email')
    mc_gross = ipn_data.get('mc_gross')
    item_name = ipn_data.get('item_name')

    # Check if already processed (prevent duplicates)
    if is_transaction_processed(txn_id):
    return

    # Update database
    # Send confirmation email
    # Fulfill order
    print(f"Payment completed: {txn_id}, Amount: ${mc_gross}")

    def handle_refund(ipn_data):
    """Handle refund."""
    parent_txn_id = ipn_data.get('parent_txn_id')
    mc_gross = ipn_data.get('mc_gross')

    # Process refund in your system
    print(f"Refund processed: {parent_txn_id}, Amount: ${mc_gross}")

    def handle_chargeback(ipn_data):
    """Handle payment reversal/chargeback."""
    txn_id = ipn_data.get('txn_id')
    reason_code = ipn_data.get('reason_code')

    # Handle chargeback
    print(f"Chargeback: {txn_id}, Reason: {reason_code}")

    Subscription/Recurring Billing

    Create Subscription Plan


    def create_subscription_plan(name, amount, interval='MONTH'):
    """Create a subscription plan."""
    client = PayPalClient(CLIENT_ID, CLIENT_SECRET)

    url = f"{client.base_url}/v1/billing/plans"
    headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {client.access_token}"
    }

    payload = {
    "product_id": "PRODUCT_ID", # Create product first
    "name": name,
    "billing_cycles": [{
    "frequency": {
    "interval_unit": interval,
    "interval_count": 1
    },
    "tenure_type": "REGULAR",
    "sequence": 1,
    "total_cycles": 0, # Infinite
    "pricing_scheme": {
    "fixed_price": {
    "value": str(amount),
    "currency_code": "USD"
    }
    }
    }],
    "payment_preferences": {
    "auto_bill_outstanding": True,
    "setup_fee": {
    "value": "0",
    "currency_code": "USD"
    },
    "setup_fee_failure_action": "CONTINUE",
    "payment_failure_threshold": 3
    }
    }

    response = requests.post(url, headers=headers, json=payload)
    return response.json()

    def create_subscription(plan_id, subscriber_email):
    """Create a subscription for a customer."""
    client = PayPalClient(CLIENT_ID, CLIENT_SECRET)

    url = f"{client.base_url}/v1/billing/subscriptions"
    headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {client.access_token}"
    }

    payload = {
    "plan_id": plan_id,
    "subscriber": {
    "email_address": subscriber_email
    },
    "application_context": {
    "return_url": "https://yourdomain.com/subscription/success",
    "cancel_url": "https://yourdomain.com/subscription/cancel"
    }
    }

    response = requests.post(url, headers=headers, json=payload)
    subscription = response.json()

    # Get approval URL
    for link in subscription.get('links', []):
    if link['rel'] == 'approve':
    return {
    'subscription_id': subscription['id'],
    'approval_url': link['href']
    }

    Refund Workflows

    def create_refund(capture_id, amount=None, note=None):
    """Create a refund for a captured payment."""
    client = PayPalClient(CLIENT_ID, CLIENT_SECRET)

    url = f"{client.base_url}/v2/payments/captures/{capture_id}/refund"
    headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {client.access_token}"
    }

    payload = {}
    if amount:
    payload["amount"] = {
    "value": str(amount),
    "currency_code": "USD"
    }

    if note:
    payload["note_to_payer"] = note

    response = requests.post(url, headers=headers, json=payload)
    return response.json()

    def get_refund_details(refund_id):
    """Get refund details."""
    client = PayPalClient(CLIENT_ID, CLIENT_SECRET)

    url = f"{client.base_url}/v2/payments/refunds/{refund_id}"
    headers = {
    "Authorization": f"Bearer {client.access_token}"
    }

    response = requests.get(url, headers=headers)
    return response.json()

    Error Handling

    class PayPalError(Exception):
    """Custom PayPal error."""
    pass

    def handle_paypal_api_call(api_function):
    """Wrapper for PayPal API calls with error handling."""
    try:
    result = api_function()
    return result
    except requests.exceptions.RequestException as e:
    # Network error
    raise PayPalError(f"Network error: {str(e)}")
    except Exception as e:
    # Other errors
    raise PayPalError(f"PayPal API error: {str(e)}")

    Usage


    try:
    order = handle_paypal_api_call(lambda: client.create_order(25.00))
    except PayPalError as e:
    # Handle error appropriately
    log_error(e)

    Testing

    # Use sandbox credentials
    SANDBOX_CLIENT_ID = "..."
    SANDBOX_SECRET = "..."

    Test accounts


    Create test buyer and seller accounts at developer.paypal.com

    def test_payment_flow():
    """Test complete payment flow."""
    client = PayPalClient(SANDBOX_CLIENT_ID, SANDBOX_SECRET, mode='sandbox')

    # Create order
    order = client.create_order(10.00)
    assert 'id' in order

    # Get approval URL
    approval_url = next((link['href'] for link in order['links'] if link['rel'] == 'approve'), None)
    assert approval_url is not None

    # After approval (manual step with test account)
    # Capture order
    # captured = client.capture_order(order['id'])
    # assert captured['status'] == 'COMPLETED'

    Resources

  • references/express-checkout.md: Express Checkout implementation guide

  • references/ipn-handling.md: IPN verification and processing

  • references/refund-workflows.md: Refund handling patterns

  • references/billing-agreements.md: Recurring billing setup

  • assets/paypal-client.py: Production PayPal client

  • assets/ipn-processor.py: IPN webhook processor

  • assets/recurring-billing.py: Subscription management
  • Best Practices

  • Always Verify IPN: Never trust IPN without verification

  • Idempotent Processing: Handle duplicate IPN notifications

  • Error Handling: Implement robust error handling

  • Logging: Log all transactions and errors

  • Test Thoroughly: Use sandbox extensively

  • Webhook Backup: Don't rely solely on client-side callbacks

  • Currency Handling: Always specify currency explicitly
  • Common Pitfalls

  • Not Verifying IPN: Accepting IPN without verification

  • Duplicate Processing: Not checking for duplicate transactions

  • Wrong Environment: Mixing sandbox and production URLs/credentials

  • Missing Webhooks: Not handling all payment states

  • Hardcoded Values: Not making configurable for different environments

    1. paypal-integration - Agent Skills