做跨境业务的团队几乎都绕不开PayPal,而退款进度查询又是客服场景里出现频率最高的问题之一。用户发来一句“我的钱到底退到哪一步了”,如果客服人员每次都要登录商家后台手动翻记录,效率会非常低。其实PayPal开放了完善的REST API,可以程序化查询交易与退款状态,而Dify恰好擅长把这类外部接口封装成对话能力。这篇文章就带你完整走一遍:从PayPal的应用创建、OAuth令牌获取,到在Dify工作流中查询账单、解析退款状态,最后把结果推送给用户。

准备工作:创建PayPal应用并理解鉴权流程
在调用任何PayPal接口之前,你需要先到PayPal Developer平台创建一个应用。登录后进入My Apps & Credentials页面,这里要注意区分Sandbox和Live两个标签页。Sandbox是沙箱环境,用于开发测试,对应的API地址是api-m.sandbox.paypal.com;Live是生产环境,地址是api-m.paypal.com。强烈建议先用Sandbox跑通全流程,再切换到Live,因为退款类操作在生产环境失误的代价很高。
创建应用后你会拿到两个关键凭据:Client ID和Client Secret。PayPal使用OAuth 2.0的Client Credentials模式鉴权,也就是先用这两个凭据换取一个access_token,后续所有请求都把这个token放在Authorization请求头里。token有效期通常是32400秒(约9小时),过期后需要重新获取。
获取令牌的请求很简单,向/v1/oauth2/token发起POST请求,Basic认证携带凭据,请求体为grant_type=client_credentials。用curl表示如下:
curl -v -X POST "https://api-m.sandbox.paypal.com/v1/oauth2/token" \ -u "你的ClientID:你的ClientSecret" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials"
返回的JSON中会包含access_token字段。一个容易踩的坑是:如果你在请求头里手动拼接了Basic认证串,务必记得是“ClientID:Secret”整体做Base64编码,冒号不能丢。在Dify的HTTP请求节点里,更推荐直接使用它的认证配置功能填入凭据,让节点自动处理编码,避免手写出错。
在Dify中编排查询账单的工作流
拿到令牌后,下一步是在Dify里搭建工作流。推荐的结构是:开始节点接收用户输入(比如交易ID或订单号),然后依次经过“获取令牌”、“查询交易详情”、“解析退款状态”三个HTTP或代码节点,最后用回复节点输出结果。
查询某笔交易详情的接口是GET /v2/payments/captures/{capture_id},如果你想按退款单查询,则使用GET /v2/payments/refunds/{refund_id}。如果只知道原始交易ID,可以先调用/v2/checkout/orders/{order_id}拿到关联的capture_id,再顺着链路往下查。以查询退款为例,请求如下:
curl -v -X GET "https://api-m.sandbox.paypal.com/v2/payments/refunds/退款ID" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的access_token"
返回的JSON里,最需要关注的字段是status。退款状态常见的取值有PENDING(处理中,通常意味着资金在途或等待原始付款方式确认)、COMPLETED(已完成,资金已退回用户付款账户)。此外还有seller_payable_refund字段,里面包含退款金额、币种以及平台手续费明细,做跨境账单核对时这些信息非常关键。
在Dify中,你可以用HTTP请求节点直接发起上述调用:URL填接口地址,认证方式选Bearer Token,值用上一步令牌节点的输出变量。拿到响应后,接一个代码节点用Python解析JSON,把status、amount、update_time等字段提取出来转成结构化变量,方便后续格式化输出。这里建议做异常兜底:如果返回的HTTP状态码是401,说明令牌过期,工作流应回退到令牌获取节点重新执行,Dify支持用条件分支节点实现这个循环逻辑。
解析退款状态并推送给用户
原始API返回的结构对普通用户来说太生硬,直接展示JSON体验很差。比较好的做法是在Dify里加一个LLM节点,把解析后的结构化数据交给大模型,让它根据状态生成一段自然的中文回复。比如同样是PENDING状态,跨境卡退款可能要等5至7个工作日,PayPal余额退款则几乎是实时的,这些业务知识可以写进LLM节点的系统提示词里,让回复更有温度也更准确。
如果需要主动推送而不是被动问答,可以把工作流的末尾接一个Webhook或邮件通知节点。例如用户提交退款申请后,系统定时触发工作流查询状态,一旦发现状态从PENDING变为COMPLETED,就调用推送接口通知用户。下面是一段在代码节点中做状态判断并生成推送文案的示例:
def main(refund_data: dict) -> dict:
status = refund_data.get("status", "UNKNOWN")
amount = refund_data.get("amount", {})
if status == "COMPLETED":
msg = f"您的退款 {amount.get('value')} {amount.get('currency_code')} 已完成,请注意查收。"
elif status == "PENDING":
msg = "您的退款正在处理中,预计3-5个工作日内到账,请耐心等待。"
else:
msg = f"退款当前状态为 {status},如需帮助请联系人工客服。"
return {"push_message": msg}
推送渠道的选择上,Webhook对接企业内部IM最灵活,邮件则适合面向终端消费者。无论哪种方式,都要注意不要在推送文案里暴露完整的交易ID和账户信息,做适当的脱敏处理,比如只展示交易号后四位。
常见错误与生产环境的几个建议
调试过程中你大概率会遇到几个高频错误。401错误基本是令牌问题,要么过期了,要么是Client ID和Secret不匹配;404错误通常是退款ID或capture_id写错,注意Sandbox和Live环境的数据完全隔离,用Live令牌查Sandbox的单号必然404;429错误表示触发了限流,PayPal对令牌接口的调用频率有较严格的限制,千万不要每次查询都重新获取令牌。
针对令牌复用,建议在Dify里用环境变量或外部缓存存一份access_token和它的过期时间戳,每次请求前先判断是否过期,只有过期才重新获取。这样既能避免限流,也能明显降低整体响应延迟。另外,Client Secret务必配置在Dify的环境变量中,不要硬编码在节点参数里,防止泄露。
最后一点经验:跨境场景下币种和时区容易出岔子。PayPal返回的金额是原始交易币种,update_time是UTC时间,展示给国内用户时记得做时区转换和币种说明,避免用户产生“退的钱怎么变少了”的误会。把这些细节处理到位,一条稳定可用的退款进度查询链路就真正落地了。
DifyPayPal API退款进度推送修改时间:2026-09-10 22:32:44