PayPal集成技能Skill paypal-integration

这个技能用于集成PayPal支付系统,支持快速结账、订阅管理和退款处理,适用于电子商务和在线交易平台。关键词:PayPal集成、支付处理、快速结账、订阅、退款管理、IPN处理、定期账单、金融科技。

支付系统 0 次安装 0 次浏览 更新于 3/16/2026

名称: paypal-integration 描述: 集成PayPal支付处理,支持快速结账、订阅和退款管理。在实现PayPal支付、处理在线交易或构建电子商务结账流程时使用。

PayPal集成

掌握PayPal支付集成,包括快速结账、IPN处理、定期账单和退款工作流。

何时使用此技能

  • 将PayPal作为支付选项集成
  • 实现快速结账流程
  • 设置PayPal的定期账单
  • 处理退款和支付争议
  • 处理PayPal网络钩子(IPN)
  • 支持国际支付
  • 实现PayPal订阅

核心概念

1. 支付产品

PayPal结账

  • 一次性支付
  • 快速结账体验
  • 游客和PayPal账户支付

PayPal订阅

  • 定期账单
  • 订阅计划
  • 自动续订

PayPal付款

  • 向多个收款人发送资金
  • 市场和平台支付

2. 集成方法

客户端(JavaScript SDK)

  • 智能支付按钮
  • 托管支付流程
  • 最少后端代码

服务器端(REST API)

  • 完全控制支付流程
  • 自定义结账界面
  • 高级功能

3. IPN(即时支付通知)

  • 类似网络钩子的支付通知
  • 异步支付更新
  • 需要验证

快速开始

// 前端 - PayPal智能按钮
<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) {
        // 支付成功
        console.log('交易完成者 ' + details.payer.name.given_name);

        // 发送到后端进行验证
        fetch('/api/paypal/capture', {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({orderID: data.orderID})
        });
      });
    }
  }).render('#paypal-button-container');
</script>
# 后端 - 验证并捕获订单
from paypalrestsdk import Payment
import paypalrestsdk

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

def capture_paypal_order(order_id):
    """捕获PayPal订单。"""
    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
        }

快速结账实现

服务器端订单创建

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):
        """获取OAuth访问令牌。"""
        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'):
        """创建PayPal订单。"""
        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):
        """为订单捕获支付。"""
        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):
        """获取订单详情。"""
        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(即时支付通知)处理

IPN验证和处理

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

app = Flask(__name__)

@app.route('/ipn', methods=['POST'])
def handle_ipn():
    """处理PayPal IPN通知。"""
    # 获取IPN消息
    ipn_data = request.form.to_dict()

    # 用PayPal验证IPN
    if not verify_ipn(ipn_data):
        return 'IPN验证失败', 400

    # 根据交易类型处理IPN
    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已处理', 200

def verify_ipn(ipn_data):
    """验证IPN消息的真实性。"""
    # 添加'cmd'参数
    verify_data = ipn_data.copy()
    verify_data['cmd'] = '_notify-validate'

    # 发送回PayPal进行验证
    paypal_url = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'  # 或生产URL

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

    return response.text == 'VERIFIED'

def handle_payment_completed(ipn_data):
    """处理已完成的支付。"""
    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')

    # 检查是否已处理(防止重复)
    if is_transaction_processed(txn_id):
        return

    # 更新数据库
    # 发送确认邮件
    # 履行订单
    print(f"支付完成: {txn_id}, 金额: ${mc_gross}")

def handle_refund(ipn_data):
    """处理退款。"""
    parent_txn_id = ipn_data.get('parent_txn_id')
    mc_gross = ipn_data.get('mc_gross')

    # 在您的系统中处理退款
    print(f"退款处理: {parent_txn_id}, 金额: ${mc_gross}")

def handle_chargeback(ipn_data):
    """处理支付反转/拒付。"""
    txn_id = ipn_data.get('txn_id')
    reason_code = ipn_data.get('reason_code')

    # 处理拒付
    print(f"拒付: {txn_id}, 原因: {reason_code}")

订阅/定期账单

创建订阅计划

def create_subscription_plan(name, amount, interval='MONTH'):
    """创建订阅计划。"""
    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",
            "payment_failure_threshold": 3
        }
    }

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

def create_subscription(plan_id, subscriber_email):
    """为客户创建订阅。"""
    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()

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

退款工作流

def create_refund(capture_id, amount=None, note=None):
    """为已捕获的支付创建退款。"""
    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):
    """获取退款详情。"""
    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()

错误处理

class PayPalError(Exception):
    """自定义PayPal错误。"""
    pass

def handle_paypal_api_call(api_function):
    """PayPal API调用的包装器,带有错误处理。"""
    try:
        result = api_function()
        return result
    except requests.exceptions.RequestException as e:
        # 网络错误
        raise PayPalError(f"网络错误: {str(e)}")
    except Exception as e:
        # 其他错误
        raise PayPalError(f"PayPal API错误: {str(e)}")

# 用法
try:
    order = handle_paypal_api_call(lambda: client.create_order(25.00))
except PayPalError as e:
    # 适当处理错误
    log_error(e)

测试

# 使用沙盒凭据
SANDBOX_CLIENT_ID = "..."
SANDBOX_SECRET = "..."

# 测试账户
# 在developer.paypal.com创建测试买家和卖家账户

def test_payment_flow():
    """测试完整支付流程。"""
    client = PayPalClient(SANDBOX_CLIENT_ID, SANDBOX_SECRET, mode='sandbox')

    # 创建订单
    order = client.create_order(10.00)
    assert 'id' in order

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

    # 批准后(使用测试账户手动步骤)
    # 捕获订单
    # captured = client.capture_order(order['id'])
    # assert captured['status'] == 'COMPLETED'

资源

  • references/express-checkout.md: 快速结账实现指南
  • references/ipn-handling.md: IPN验证和处理
  • references/refund-workflows.md: 退款处理模式
  • references/billing-agreements.md: 定期账单设置
  • assets/paypal-client.py: 生产PayPal客户端
  • assets/ipn-processor.py: IPN网络钩子处理器
  • assets/recurring-billing.py: 订阅管理

最佳实践

  1. 始终验证IPN: 未经验证绝不信任IPN
  2. 幂等处理: 处理重复IPN通知
  3. 错误处理: 实现鲁棒的错误处理
  4. 日志记录: 记录所有交易和错误
  5. 全面测试: 广泛使用沙盒
  6. 网络钩子备份: 不要仅依赖客户端回调
  7. 货币处理: 总是明确指定货币

常见陷阱

  • 未验证IPN: 接受未经验证的IPN
  • 重复处理: 未检查重复交易
  • 错误环境: 混合沙盒和生产URL/凭据
  • 缺失网络钩子: 未处理所有支付状态
  • 硬编码值: 不针对不同环境进行配置