paypal-integration
upstream

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.

ID: paypal-integration
Preview
Codigo

PayPal Integration


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


When to Use This Skill


  • 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


``javascript

// 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>

`


`python

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

`python

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

`python

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

`python

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


`python

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


`python

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


`python

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


1. Always Verify IPN: Never trust IPN without verification

2. Idempotent Processing: Handle duplicate IPN notifications

3. Error Handling: Implement robust error handling

4. Logging: Log all transactions and errors

5. Test Thoroughly: Use sandbox extensively

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

7. 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

Informacion

Estado
Activo
Origen upstream
Usos 3
Ultimo uso 23/01 16:57
Actualizado 09/01/2026 02:14

Archivo origen

Z:\Repositorios\core\upstream\plugins\payment-processing\skills\paypal-integration\SKILL.md