paypal-integration ยท diff

git:20260307.47a5dbc to git:20260522.be57c0b

2 added, 296 removed. Audit B to B.

---
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
```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)}")
+ ## Detailed patterns and worked examples
- # Usage
- try:
- order = handle_paypal_api_call(lambda: client.create_order(25.00))
- except PayPalError as e:
- # Handle error appropriately
- log_error(e)
- ```
+ Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
## 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'
```