| name | paypal-integration |
| description | 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.
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
<div id="paypal-button-container"></div>
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=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) {
console.log('Transaction completed by ' + details.payer.name.given_name);
fetch('/api/paypal/capture', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({: data.})
});
});
}
}).();
from paypalrestsdk import Payment
import paypalrestsdk
paypalrestsdk.configure({
"mode": "sandbox",
"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}):
return {
'status': 'success',
'transaction_id': payment.id,
'amount': payment.transactions[0].amount.total
}
else:
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",
:
}
payload = {
: ,
: [{
: {
: currency,
: (amount)
}
}]
}
response = requests.post(url, headers=headers, json=payload)
response.json()
():
url =
headers = {
: ,
:
}
response = requests.post(url, headers=headers)
response.json()
():
url =
headers = {
:
}
response = requests.get(url, headers=headers)
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."""
ipn_data = request.form.to_dict()
if not verify_ipn(ipn_data):
return 'IPN verification failed', 400
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."""
verify_data = ipn_data.copy()
verify_data['cmd'] = '_notify-validate'
paypal_url = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
response = requests.post(paypal_url, data=verify_data)
return response.text ==
():
txn_id = ipn_data.get()
payer_email = ipn_data.get()
mc_gross = ipn_data.get()
item_name = ipn_data.get()
is_transaction_processed(txn_id):
()
():
parent_txn_id = ipn_data.get()
mc_gross = ipn_data.get()
()
():
txn_id = ipn_data.get()
reason_code = ipn_data.get()
()
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",
"name": name,
"billing_cycles": [{
"frequency": {
"interval_unit": interval,
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 1,
"total_cycles": 0,
"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",
:
}
}
response = requests.post(url, headers=headers, json=payload)
response.json()
():
client = PayPalClient(CLIENT_ID, CLIENT_SECRET)
url =
headers = {
: ,
:
}
payload = {
: plan_id,
: {
: subscriber_email
},
: {
: ,
:
}
}
response = requests.post(url, headers=headers, json=payload)
subscription = response.json()
link subscription.get(, []):
link[] == :
{
: subscription[],
: link[]
}
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:
raise PayPalError(f"Network error: {str(e)}")
except Exception as e:
raise PayPalError(f"PayPal API error: {str(e)}")
try:
order = handle_paypal_api_call(lambda: client.create_order(25.00))
except PayPalError as e:
log_error(e)
Testing
SANDBOX_CLIENT_ID = "..."
SANDBOX_SECRET = "..."
def test_payment_flow():
"""Test complete payment flow."""
client = PayPalClient(SANDBOX_CLIENT_ID, SANDBOX_SECRET, mode='sandbox')
order = client.create_order(10.00)
assert 'id' in order
approval_url = next((link['href'] for link in order['links'] if link['rel'] == 'approve'), None)
assert approval_url is not None
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