境外支付订阅(Paypal)原生API总结 (已上线)
最近这周业务不忙了, 花点时间把前段时间干的事总结一下吧, 老师我太想进步了 🍓.
1. 背景/历程/收获/展望
-
时间线: Q1季度中吧2月底了,具体时间忘了。
-
背景: 在Q1季度最近webtoapp业务上要集成PayPal订阅/支付,主要用户购买订阅类型的商品,下发我们App会员的权益,其实在webtoapp端我们已经有Stripe stripe.com/ 信用卡支付了,结果ab实验整体跑下来的效果/数据是不好的,为了优化流程,去抄topx的产品形态,产品看到一年前以前的版本有papal支付现在又要加回来,业务需求是: 免费试用X天/周付/季付类型,进行续费 用户退订,过期的操作进行回调地址监听。
-
历程:
- Paypal操作流程特别麻烦,
- 第一次接手什么都看不到,没办法 直接去看官方文档,找相关文档和相关接口阅读理解,Paypal后台开发不能进行登录,没有权限(风控严重)。
- 我这边主要是用Python脚本进行调用Paypal官网提供的Api,根据自己需求进行创建对应规则的ProductId,再调用他们提供的订阅接口,Paypal返回一个购买的url给你,对刚刚创建的ProductId订阅,你再用沙盒的Paypal账号进行购买,订阅成功的话,你可以再paypal账户后台看到相关订单信息通过transactionId。
- 中间遇到了很多坑,
- 续费只会回调给我事件类型是 PAYMENT.SALE.COMPLETED 我还以为回调的是BILLING.SUBSCRIPTION.UPDATED (第一版本的回调使用的是这个,说明这个bug已经存在1年多了),没办法 还是官网找修改webhook内容的接口,添加新的类型在回调接口上面,沙盒环境和线上环境都调用一遍,这才解决问题。
- 还有它上面写的下次扣费时间,其实是不准确的,上面写的下午6点扣费,等到它到了晚上9点才进行扣费,没办法不可能让用户扣了钱 结果还不是会员,先送一天会员处理下这个场景吧(都不知道以前怎么测试通过的,可能没什么用户量吧,没人反馈这些问题)。
- 还有一个危险操作,购买是放在前端流程,前端代码里面存储了key/secret直接进行plan的购买,购买完成之后调用服务器接口进购买订单校验并且下发会员权益。
- 费时费力(对我来说是有成长的),在一月份上线之后这个功能上线了3个月后,加上广告投入,4月份看了一次数据,好像有一个人购买了,不太记得是通过paypal支付还是stripe支付,这些都不太重要(已经换项目组了), 唉 最近一年做的东西一点成就感都没有,做一个功能垮一个,数据一直很差, 自己缺少产品思维,把自己的角色定义的太死了, 给不了产品好的意见。
- Paypal操作流程特别麻烦,
-
收获:
- 开源自己封装好的脚本,由于Paypal账号风控非常严重,生产环境web页面操作容易封禁, 研发没有权限操作,我这边测试和线上都是使用Api创建Plan/Subscription/webhook 进行对接调用实现。主要总结Paypal-Api总结 订阅/Plan/webhook python3实现 github.com/hakusai22/P…。
- 快速阅读/理解文档/解决问题的能力有所提升,而不是以前那样有问题就找leader, 他们也不清楚这个业务啊,他们帮不了你 又不是出架构方案,只能靠自己去翻文档解决问题。
- 对于支付订单这块的丢单逻辑没有考虑/没有写(失败就失败 用户自己重试/回调异常看paypal会不会重试),线上真实用户量太少了,给不了你发挥的空间。这种东西写一次就没意思(刚开始还觉得挺有趣的),全是没有用户量的架子,效果不好 说不定下个版本就换别的了。
-
未来展望: 支付架构设计(越抽象越好😁),支付异常处理方案,配置和获取支付地址逻辑放在服务器端(安全)。
- 拿到沙盒/线上环境 client.id/secret/url
- 沙盒账号周付/季付 免费试用3天 python3脚本创建。
- Paypal 免费试用/直接付费逻辑添加
- Paypal 后台回调/paypal/webhook 接口设置的是三个数据中心
1. Paypal 订阅API文档
官方文档
一个产品ID 用户免费试用3天后 再进行周付的扣款
下面都是一些接口调用的记录和使用模版。
1.1. 创建plan
1.1.1. 接口调用鉴权 header
if __name__ == '__main__':
def get_http_headers():
# 设置请求头
headers = {
"Content-Type": "application/json"
}
authorization = "xxxx-xx:xxx-xx-xxx"
auth_encode = base64.b64encode(authorization.encode()).decode()
final_auth = f"Basic {auth_encode}"
headers["Authorization"] = final_auth
return headers
1.1.1.1. 详细解析创建订阅body里面每个字段含义
{
"product_id": "PROD-XXCD1234QWER65782", //sku id
"name": "Video Streaming Service Plan", //name
"description": "Video Streaming Service basic plan", //desc
"status": "ACTIVE", //状态 CREATED/INACTIVE/ACTIVE
//订单周期
"billing_cycles": [
{
"frequency": {
"interval_unit": "MONTH", //月 DAY/WEEK/MONTH/YEAR
"interval_count": 1 // 1个月计费一次/
},
"tenure_type": "TRIAL", // 试用订阅 TRIAL/REGULAR
"sequence": 1, //顺序
"total_cycles": 2, //这个计费执行的次数
"pricing_scheme": {
"fixed_price": {
"value": "3", //金额
"currency_code": "USD" //货币单位
}
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "TRIAL", // 试用订阅
"sequence": 2, //顺序
"total_cycles": 3,
"pricing_scheme": {
"fixed_price": {
"value": "6",
"currency_code": "USD"
}
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR", //常规订阅
"sequence": 3, //顺序
"total_cycles": 12,
"pricing_scheme": {
"fixed_price": {
"value": "10",
"currency_code": "USD"
}
}
}
],
"payment_preferences": {
"auto_bill_outstanding": true, //是否自动扣费
//服务初始费用
"setup_fee": {
"value": "10",
"currency_code": "USD"
},
"setup_fee_failure_action": "CONTINUE", //如果设置的初始付款失败,则继续订阅
"payment_failure_threshold": 3 // 暂停订阅前的最大付款失败次数
},
"taxes": {
"percentage": "10", //税的百分比
"inclusive": false // 指示该税是否已包含在开单金额中
}
}
1.2. 批量获取plan列表
import requests
headers = {
'Authorization': 'Bearer A21AAGHr9qtiRRXH4oYcQokQgV99rGqEIfgrr8xHCclP0OzmD9KVgg5ppIIg1jzJgQkV4wd02svIvBJyg6cLFJjFow_SjBhxQ',
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
}
params = (
('sort_by', 'create_time'),
('sort_order', 'desc'),
)
response = requests.get('https://api-m.sandbox.paypal.com/v1/billing/plans', headers=headers, params=params)
请求数据
{
"plans": [
{
"id": "P-9CT60829WM695623HL7QGYOI", //paypal 生成plan的唯一id
"name": "Netflix Plan 17012019", //name
"status": "ACTIVE", //plan的状态 CREATED/INACTIVE/ACTIVE
"description": "Netflix basic plan",
"usage_type": "LICENSED",
"create_time": "2020-12-23T07:08:40Z", //创建时间
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-9CT60829WM695623HL7QGYOI",
"rel": "self",
"method": "GET"
}
]
},
{
"id": "P-7CE83846EJ264184CL7QHL3I",
"name": "Netflix Plan 17012019",
"status": "CREATED",
"description": "Netflix basic plan",
"usage_type": "LICENSED",
"create_time": "2020-12-23T07:06:08Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-7CE83846EJ264184CL7QHL3I",
"rel": "self",
"method": "GET"
}
]
}
],
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/plans?page_size=10&page=1",
"rel": "self",
"method": "GET"
}
]
}
1.3. 根据id查出具体plan的信息
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
response = requests.get('https://api-m.sandbox.paypal.com/v1/billing/plans/P-5ML4271244454362WXNWU5NQ', headers=headers)
具体plan的信息 信息已脱敏
{
"id": "xxxxxQ",
"product_id": "xxxxx2",
"name": "Basic Plan",
"description": "Basic Plan",
"status": "ACTIVE",
"billing_cycles": [
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "TRIAL",
"sequence": 1,
"total_cycles": 2,
// 价格
"pricing_scheme": {
"fixed_price": {
"currency_code": "USD",
"value": "3"
},
"version": 1,
"create_time": "2020-05-27T12:13:51Z",
"update_time": "2020-05-27T12:13:51Z"
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "TRIAL",
"sequence": 2,
"total_cycles": 3,
"pricing_scheme": {
"fixed_price": {
"currency_code": "USD",
"value": "6"
},
"version": 1,
"create_time": "2020-05-27T12:13:51Z",
"update_time": "2020-05-27T12:13:51Z"
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 3,
"total_cycles": 12,
"pricing_scheme": {
"fixed_price": {
"value": "10",
"currency_code": "USD"
},
"status": "ACTIVE",
"version": 1,
"create_time": "2020-05-27T12:13:51Z",
"update_time": "2020-05-27T12:13:51Z"
}
}
],
"taxes": {
"percentage": "10",
"inclusive": false
},
"create_time": "2020-05-27T12:13:51Z",
"update_time": "2020-05-27T12:13:51Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-zzz",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-zzz",
"rel": "edit",
"method": "PATCH"
},
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-zzz/deactivate",
"rel": "deactivate",
"method": "POST"
},
{
"href": "https://api-m.paypal.com/v1/billing/plans/P-zzz/update-pricing-schemes",
"rel": "edit",
"method": "POST"
}
]
}
1.4. 更新plan
import requests
headers = {
'Authorization': 'Bearer xxxxx',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '[
{
"op": "replace",
"path": "/payment_preferences/payment_failure_threshold",
"value": 7
},
{
"op": "replace",
"path": "/name",
"value": "Updated Video Streaming Service Plan"
}
]'
response = requests.patch('https://api-m.sandbox.paypal.com/v1/billing/plans/P-xxx', headers=headers, data=data)
1.5. 激活plan
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":xx,"merchantId":"xxx"},"merchant":{"accountNumber":xxx,"merchantId":"xxx"},"apiCaller":{"clientId":"xx-xx","appId":"APP-xx","payerId":"xxx","accountNumber":"xx"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/plans/P-7GL4271244454362WXNWU5NQ/activate', headers=headers)
1.6. 停用plan
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":xx,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/plans/P-7GL4271244454362WXNWU5NQ/deactivate', headers=headers)
1.7. 更新订阅价格
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '{
"plan_id": "P-5ML4271244454362WXNWU5NQ",
"start_time": "2018-11-01T00:00:00Z",
"quantity": "20",
"shipping_amount": {
"currency_code": "USD",
"value": "10.00"
},
"subscriber": {
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "customer@example.com",
"shipping_address": {
"name": {
"full_name": "John Doe"
},
"address": {
"address_line_1": "2211 N First Street",
"address_line_2": "Building 17",
"admin_area_2": "San Jose",
"admin_area_1": "CA",
"postal_code": "95131",
"country_code": "US"
}
}
},
"application_context": {
"brand_name": "walmart",
"locale": "en-US",
"shipping_preference": "SET_PROVIDED_ADDRESS",
"user_action": "SUBSCRIBE_NOW",
"payment_method": {
"payer_selected": "PAYPAL",
"payee_preferred": "IMMEDIATE_PAYMENT_REQUIRED"
},
"return_url": "https://example.com/returnUrl",
"cancel_url": "https://example.com/cancelUrl"
}
}'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/plans/P-7GL4271244454362WXNWU5NQ/update-pricing-schemes', headers=headers, data=data)
1.8. 通过plan创建订阅
import requests
headers = {
'Authorization': 'Bearer A21AAGHr9qtiRRXH4oYcQokQgV99rGqEIfgrr8xHCclP0OzmD9KVgg5ppIIg1jzJgQkV4wd02svIvBJyg6cLFJjFow_SjBhxQ',
'Content-Type': 'application/json',
'Accept': 'application/json',
'PayPal-Request-Id': 'SUBSCRIPTION-21092019-001',
'Prefer': 'return=representation',
}
data = '{ "plan_id": "P-5ML4271244454362WXNWU5NQ", "start_time": "2018-11-01T00:00:00Z", "quantity": "20", "shipping_amount": { "currency_code": "USD", "value": "10.00" }, "subscriber": { "name": { "given_name": "John", "surname": "Doe" }, "email_address": "customer@example.com", "shipping_address": { "name": { "full_name": "John Doe" }, "address": { "address_line_1": "2211 N First Street", "address_line_2": "Building 17", "admin_area_2": "San Jose", "admin_area_1": "CA", "postal_code": "95131", "country_code": "US" } } }, "application_context": { "brand_name": "walmart", "locale": "en-US", "shipping_preference": "SET_PROVIDED_ADDRESS", "user_action": "SUBSCRIBE_NOW", "payment_method": { "payer_selected": "PAYPAL", "payee_preferred": "IMMEDIATE_PAYMENT_REQUIRED" }, "return_url": "https://example.com/returnUrl", "cancel_url": "https://example.com/cancelUrl" } }'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions', headers=headers, data=data)
1.9. 通过订阅id查询订阅详情
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
response = requests.get('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G', headers=headers)
订阅详情 已脱敏
{
"id": "I-xxx",
"plan_id": "P-xx",
"start_time": "2019-04-10T07:00:00Z",
"quantity": "20",
"shipping_amount": {
"currency_code": "USD",
"value": "10.0"
},
"subscriber": {
"shipping_address": {
"name": {
"full_name": "John Doe"
},
"address": {
"address_line_1": "2211 N First Street",
"address_line_2": "Building 17",
"admin_area_2": "San Jose",
"admin_area_1": "CA",
"postal_code": "95131",
"country_code": "US"
}
},
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "customer@example.com",
"payer_id": "2J6QB8YJQSJRJ"
},
"billing_info": {
"outstanding_balance": {
"currency_code": "USD",
"value": "1.0"
},
"cycle_executions": [
{
"tenure_type": "TRIAL",
"sequence": 1,
"cycles_completed": 0,
"cycles_remaining": 2,
"total_cycles": 2
},
{
"tenure_type": "TRIAL",
"sequence": 2,
"cycles_completed": 0,
"cycles_remaining": 3,
"total_cycles": 3
},
{
"tenure_type": "REGULAR",
"sequence": 3,
"cycles_completed": 0,
"cycles_remaining": 12,
"total_cycles": 12
}
],
"last_payment": {
"amount": {
"currency_code": "USD",
"value": "1.15"
},
"time": "2019-04-09T10:27:20Z"
},
"next_billing_time": "2019-04-10T10:00:00Z",
"failed_payments_count": 0
},
"create_time": "2019-04-09T10:26:04Z",
"update_time": "2019-04-09T10:27:27Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-xx/cancel",
"rel": "cancel",
"method": "POST"
},
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-xxx",
"rel": "edit",
"method": "PATCH"
},
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-xxx",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-xxx/suspend",
"rel": "suspend",
"method": "POST"
},
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-xxx/capture",
"rel": "capture",
"method": "POST"
}
],
"status": "ACTIVE",
"status_update_time": "2019-04-09T10:27:27Z"
}
1.10. 更新订阅
import requests
headers = {
'Authorization': 'Bearer xxx',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = {
'[
{
"op": "replace",
"path": "/plan/billing_cycles/@sequence': '=1/pricing_scheme/fixed_price",
"value": {
"currency_code": "USD",
"value": "50.00"
}
},
{
"op": "replace",
"path": "/plan/billing_cycles/@sequence==2/pricing_scheme/tiers",
"value": [
{
"starting_quantity": "1",
"ending_quantity": "1000",
"amount": {
"value": "500",
"currency_code": "USD"
}
},
{
"starting_quantity": "1001",
"amount": {
"value": "2000",
"currency_code": "USD"
}
}
]
},
{
"op": "replace",
"path": "/plan/payment_preferences/auto_bill_outstanding",
"value": true
},
{
"op": "replace",
"path": "/plan/payment_preferences/payment_failure_threshold",
"value": 1
},
{
"op": "replace",
"path": "/plan/taxes/percentage",
"value": "10"
}
]'
}
response = requests.patch('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G', headers=headers, data=data)
1.11. 修改订阅plan或者数量
import requests
headers = {
'Authorization': 'Bearer A21AAGHr9qtiRRXH4oYcQokQgV99rGqEIfgrr8xHCclP0OzmD9KVgg5ppIIg1jzJgQkV4wd02svIvBJyg6cLFJjFow_SjBhxQ',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '{ "plan_id": "P-5ML4271244454362WXNWU5NQ", "shipping_amount": { "currency_code": "USD", "value": "10.00" }, "shipping_address": { "name": { "full_name": "John Doe" }, "address": { "address_line_1": "2211 N First Street", "address_line_2": "Building 17", "admin_area_2": "San Jose", "admin_area_1": "CA", "postal_code": "95131", "country_code": "US" } }, "application_context": { "brand_name": "walmart", "locale": "en-US", "shipping_preference": "SET_PROVIDED_ADDRESS", "payment_method": { "payer_selected": "PAYPAL", "payee_preferred": "IMMEDIATE_PAYMENT_REQUIRED" }, "return_url": "https://example.com/returnUrl", "cancel_url": "https://example.com/cancelUrl" } }'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/revise', headers=headers, data=data)
1.12. 暂停订阅
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '{ "reason": "Item out of stock" }'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/suspend', headers=headers, data=data)
1.13. 取消订阅
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '{ "reason": "Not satisfied with the service" }'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/cancel', headers=headers, data=data)
1.14. 激活订阅
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
data = '{ "reason": "Reactivating the subscription" }'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/activate', headers=headers, data=data)
1.15. 在订阅上捕获来自订阅者的授权付款
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Authorization': 'Bearer access_token6V7rbVwmlM1gFZKW_8QtzWXqpcwQ6T5vhEGYNJDAAdn3paCgRpdeMdVYmWzgbKSsECednupJ3Zx5Xd-g',
'Content-Type': 'application/json',
'Accept': 'application/json',
'PayPal-Request-Id': 'CAPTURE-160919-A0051',
}
data = '{
"note": "Charging as the balance reached the limit",
"capture_type": "OUTSTANDING_BALANCE",
"amount": {
"currency_code": "USD",
"value": "100"
}
}'
response = requests.post('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/capture', headers=headers, data=data)
1.16. 通过订阅id查询出所有的交易列表 时间范围
import requests
headers = {
'X-PAYPAL-SECURITY-CONTEXT': '{"consumer":{"accountNumber":1181198218909172527,"merchantId":"5KW8F2FXKX5HA"},"merchant":{"accountNumber":1659371090107732880,"merchantId":"2J6QB8YJQSJRJ"},"apiCaller":{"clientId":"AdtlNBDhgmQWi2xk6edqJVKklPFyDWxtyKuXuyVT-OgdnnKpAVsbKHgvqHHP","appId":"APP-6DV794347V142302B","payerId":"2J6QB8YJQSJRJ","accountNumber":"1659371090107732880"},"scopes":["https://api-m.paypal.com/v1/subscription/.*","https://uri.paypal.com/services/subscription","openid"]}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
params = (
('start_time', '2018-01-21T07:50:20.940Z'),
('end_time', '2018-08-21T07:50:20.940Z'),
)
response = requests.get('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/transactions', headers=headers, params=params)
#NB. Original query string below. It seems impossible to parse and
#reproduce query strings 100% accurately so the one below is given
#in case the reproduced version is not "correct".
# response = requests.get('https://api-m.sandbox.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/transactions?start_time=2018-01-21T07:50:20.940Z&end_time=2018-08-21T07:50:20.940Z', headers=headers)
{
"transactions": [
{
"id": "TRFGHNJKOIIOJKL",
"status": "COMPLETED",
"payer_email": "customer@example.com",
"payer_name": {
"given_name": "John",
"surname": "Doe"
},
"amount_with_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "10.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "1.00"
},
"net_amount": {
"currency_code": "USD",
"value": "9.00"
}
},
"time": "2018-03-16T07:40:20.940Z"
},
{
"id": "VDFG45345FFGS",
"status": "COMPLETED",
"payer_email": "customer2@example.com",
"payer_name": {
"given_name": "Jhonny",
"surname": "Cat"
},
"amount_with_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "15.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "1.00"
},
"net_amount": {
"currency_code": "USD",
"value": "14.00"
}
},
"time": "2018-08-21T07:50:20.940Z"
}
],
"links": [
{
"href": "https://api-m.paypal.com/v1/billing/subscriptions/I-BW452GLLEP1G/transactions?start_time=2018-01-21T07:50:20.940Z&end_time=2018-08-21T07:50:20.940Z",
"rel": "self",
"method": "GET"
}
]
}
1.17. WebHook (用户取消订阅/续费)
-
我们需要在Paypal后台设置 webhook 回调自己服务器地址,我这边直接忽略了回调数据的鉴权.
1.17.1. 获取后台设置的回调接口列表
import base64
import json
import requests
# -*- coding: utf-8 -*-
# @Author : hakusai
# @Time : 2024/03/05 10:10
if __name__ == '__main__':
def get_http_headers():
# 设置请求头
headers = {
"Content-Type": "application/json"
}
authorization = "xxx-xxxx:xxx-xxx-xxx"
auth_encode = base64.b64encode(authorization.encode()).decode()
final_auth = f"Basic {auth_encode}"
headers["Authorization"] = final_auth
return headers
response = requests.get('https://api-m.sandbox.paypal.com/v1/notifications/webhooks', headers=get_http_headers())
print(response.status_code)
print(response.text)
回调Body数据
{
"id": "8PT597110X687430LKGECATA",
"create_time": "2013-06-25T21:41:28Z",
"resource_type": "authorization",
"event_type": "PAYMENT.AUTHORIZATION.CREATED", //事件类型
"summary": "A payment authorization was created",//回调说明
"resource": {
"id": "2DC87612EK520411B", //订单id
"create_time": "2013-06-25T21:39:15Z",
"update_time": "2013-06-25T21:39:17Z",
"state": "authorized",
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"parent_payment": "PAY-36246664YD343335CKHFA4AY",
"valid_until": "2013-07-24T21:39:15Z",
"links": [
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B/capture",
"rel": "capture",
"method": "POST"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B/void",
"rel": "void",
"method": "POST"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/payment/PAY-36246664YD343335CKHFA4AY",
"rel": "parent_payment",
"method": "GET"
}
]
},
"links": [
{
"href": "https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/8PT597110X687430LKGECATA",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/8PT597110X687430LKGECATA/resend",
"rel": "resend",
"method": "POST"
}
]
}
首次订阅成功会回调两个事件:
1.17.2. BILLING.SUBSCRIPTION.ACTIVATED
{
"id": "WH-2JK13221SH228131A-85M888883CF992091V",
"event_version": "1.0",
"create_time": "2022-08-02T09:58:37.896Z",
"resource_type": "subscription",
"resource_version": "2.0",
"event_type": "BILLING.SUBSCRIPTION.ACTIVATED",
"summary": "Subscription activated",
"resource": {
"quantity": "1",
"subscriber": {
"email_address": "wwww@personal.example.com",
"payer_id": "BNMR2YZQJ9Q2L",
"name": {
"given_name": "John",
"surname": "Doe"
}
},
"create_time": "2022-08-02T09:58:05Z",
"plan_overridden": false,
"shipping_amount": {
"currency_code": "USD",
"value": "0.0"
},
"start_time": "2022-08-02T09:57:17Z",
"update_time": "2022-08-02T09:58:06Z",
"billing_info": {
"outstanding_balance": {
"currency_code": "USD",
"value": "0.0"
},
"cycle_executions": [
{
"tenure_type": "TRIAL",
"sequence": 1,
"cycles_completed": 1,
"cycles_remaining": 0,
"current_pricing_scheme_version": 1,
"total_cycles": 1
},
{
"tenure_type": "REGULAR",
"sequence": 2,
"cycles_completed": 0,
"cycles_remaining": 0,
"current_pricing_scheme_version": 1,
"total_cycles": 0
}
],
"last_payment": {
"amount": {
"currency_code": "USD",
"value": "0.99"
},
"time": "2022-08-02T09:58:05Z"
},
"next_billing_time": "2022-08-03T10:00:00Z",
"failed_payments_count": 0
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/billing/subscriptions/I-DL5L53KM3N/cancel",
"rel": "cancel",
"method": "POST",
"encType": "application/json"
},
{
"href": "https://api.sandbox.paypal.com/v1/billing/subscriptions/I-DL5L53KM3N",
"rel": "edit",
"method": "PATCH",
"encType": "application/json"
},
{
"href": "https://api.sandbox.paypal.com/v1/billing/subscriptions/I-DL5L53KM3N",
"rel": "self",
"method": "GET",
"encType": "application/json"
},
{
"href": "https://api.sandbox.paypal.com/v1/billing/subscriptions/I-DL5L53KM3N/suspend",
"rel": "suspend",
"method": "POST",
"encType": "application/json"
},
{
"href": "https://api.sandbox.paypal.com/v1/billing/subscriptions/I-DL5L53KM3N/capture",
"rel": "capture",
"method": "POST",
"encType": "application/json"
}
],
"id": "I-DL5L53KM3N",
"plan_id": "P-4G238388SD824MLUPJNY",
"status": "ACTIVE",
"status_update_time": "2022-08-02T09:58:06Z"
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-2JK13221SH228131A-85M80963CF992091V",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-2JK13221SH228131A-85M80963CF992091V/resend",
"rel": "resend",
"method": "POST"
}
]
}
1.17.3. 续订时只会回调:PAYMENT.SALE.COMPLETED
续费只会回调给我事件=PAYMENT.SALE.COMPLETED 我还以为回调的是BILLING.SUBSCRIPTION.UPDATED
{
"id": "WH-4AU27166UL181633V-3BU78225VU943751T",
"event_version": "1.0",
"create_time": "2022-08-02T09:58:43.670Z",
"resource_type": "sale",
"event_type": "PAYMENT.SALE.COMPLETED",
"summary": "Payment completed for $ 0.99 USD",
"resource": {
"billing_agreement_id": "I-DL5L53KM3N",
"amount": {
"total": "0.99",
"currency": "USD",
"details": {
"subtotal": "0.99"
}
},
"payment_mode": "INSTANT_TRANSFER",
"update_time": "2022-08-02T09:58:05Z",
"create_time": "2022-08-02T09:58:05Z",
"protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
"transaction_fee": {
"currency": "USD",
"value": "0.33"
},
"protection_eligibility": "ELIGIBLE",
"links": [
{
"method": "GET",
"rel": "self",
"href": "https://api.sandbox.paypal.com/v1/payments/sale/4DN9460xxx61205Y"
},
{
"method": "POST",
"rel": "refund",
"href": "https://api.sandbox.paypal.com/v1/payments/sale/4DN9460xxx61205Y/refund"
}
],
"id": "4DN9460xxx61205Y",
"state": "completed",
"invoice_number": ""
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4AU27166UL181633V-3BU78225VU943751T",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4AU27166UL181633V-3BU78225VU943751T/resend",
"rel": "resend",
"method": "POST"
}
]
}
2. PayPal SDK 接入文档 (废弃)
- Java 集成SDK 我们项目在2022年之前就集成了SDK, 后面paypal官方不进行维护,我们项目里面都是使用原生Api 自己写鉴权逻辑使用webclient进行对接,既然SDK都过时了,我就不进行文档总结了。
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>${paypal.sdk.version}</version>
</dependency>
最后写完了 10点半了 陈伯直播了, 打麻将去了 八木唯
3. 参考
转载自:https://juejin.cn/post/7373529708262981642