使用Java 对接 PayPal 接口,从下单,支付,确认订单、查询、退款、退款查询、回调处理等全流程代码示例,以及图文说明。
PayPal接口调用时序图
PayPal账户注册
PayPal的API接口使用client ID
和client secret
进行身份认证。
账户注册成功后登录开发者平台(https://developer.paypal.com/),然后选择Apps & Credentials菜单,可以看见平台创建了一个默认应用,包含client ID
和client secret
。把它们都复制下来。
1、接口授权,获取AccessToken
curl 格式
curl -v -X POST "https://api-m.sandbox.paypal.com/v1/oauth2/token"\
-u "CLIENT_ID:CLIENT_SECRET"\
-H "Content-Type: application/x-www-form-urlencoded"\
-d "grant_type=client_credentials"
返回示例
{
"scope": "https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller https://uri.paypal.com/services/payments/refund https://api-m.paypal.com/v1/vault/credit-card https://api-m.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://api-m.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks",
"access_token": "A21AAFEpH4PsADK7qSS7pSRsgzfENtu-Q1ysgEDVDESseMHBYXVJYE8ovjj68elIDy8nF26AwPhfXTIeWAZHSLIsQkSYz9ifg",
"token_type": "Bearer",
"app_id": "APP-80W284485P519543T",
"expires_in": 31668,
"nonce": "2020-04-03T15:35:36ZaYZlGvEkV4yVSz8g6bAKFoGSEzuy3CQcz3ljhibkOHg"
}
java 格式
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class PayPalAccessToken {
public static void main(String[] args) {
try {
String clientId = "CLIENT_ID";
String clientSecret = "CLIENT_SECRET";
String credentials = clientId + ":" + clientSecret;
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
URL url = new URL("https://api-m.sandbox.paypal.com/v1/oauth2/token");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", "Basic " + encodedCredentials);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setDoOutput(true);
String postParameters = "grant_type=client_credentials";
try (OutputStream os = connection.getOutputStream()) {
byte[] postParametersBytes = postParameters.getBytes();
os.write(postParametersBytes);
os.flush();
}
int responseCode = connection.getResponseCode();
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 打印结果
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、下单 v2/checkout/orders
@Test
public void order() throws IOException {
URL url = new URL("https://api-m.sandbox.paypal.com/v2/checkout/orders");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("PayPal-Request-Id", "7b92603e-77ed-4896-8e78-5dea2050476kk");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
// 金额
JSONObject amount = new JSONObject();
amount.put("currency_code", "USD");
amount.put("value", "10.00");
JSONObject address = new JSONObject();
address.put("address_line_1", "虹桥镇");
address.put("address_line_2", "茅台路168号");
address.put("admin_area_1", "上海市");
address.put("admin_area_2", "闵行区");
address.put("postal_code", "20000");
address.put("country_code", "CN");
// 客户姓名
JSONObject name = new JSONObject();
name.put("full_name", "Jacky");
// 客户信息机地址
JSONObject shipping = new JSONObject();
shipping.put("name", name);
shipping.put("address", address);
// 购买人信息,数组,最多可以同时传10个
JSONObject purchaseUnit = new JSONObject();
purchaseUnit.put("reference_id", UUID.randomUUID().toString());
purchaseUnit.put("custom_id", UUID.randomUUID().toString());
purchaseUnit.put("amount", amount);
purchaseUnit.put("description", "测试支付");
purchaseUnit.put("shipping", shipping);
JSONArray puprchase_units = new JSONArray();
puprchase_units.add(purchaseUnit);
// 订单上下文信息,取消地址、返回地址设置
JSONObject applicationContext = new JSONObject();
applicationContext.put("cancel_url", "http://localhost/paypalv2/cancel");
applicationContext.put("return_url", "http://localhost/paypalv2/back");
JSONObject json = new JSONObject();
json.put("intent", "CAPTURE");// 用户付款了,商户立即收款
json.put("purchase_units", puprchase_units);
json.put("application_context", applicationContext);
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write(json.toJSONString());
writer.flush();
writer.close();
httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回示例
返回结果中 ref = approve
的链接就是支付链接。
本例是https://www.sandbox.paypal.com/checkoutnow?token=2JF08781K01251707
在浏览器中直接打开链接就可以支付了。
{
"id": "2JF08781K01251707",
"status": "CREATED",
"links": [{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "self",
"method": "GET"
}, {
"href": "https://www.sandbox.paypal.com/checkoutnow?token=2JF08781K01251707",
"rel": "approve",
"method": "GET"
}, {
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "update",
"method": "PATCH"
}, {
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707/capture",
"rel": "capture",
"method": "POST"
}]
}
3、付款
打开支付链接,并登录PayPal账户支付
登录账户,sandbox环境的账户有5000美元的余额,直接使用余额支付,点击“继续查看订单”,会返回到下单时传入的return_url
点击继续,页面跳转到return_url
4、确认付款 /v2/checkout/orders/{order_id}/capture
PayPal的订单在付款后,必须调用确认接口后才能到账。可以在回调事件中处理,收到回调事件时直接调用capture
接口。
java请求
@Test
public void capture() throws IOException {
// 传入订单ID作为路径参数
String api = String.format("https://api-m.sandbox.paypal.com/v2/checkout/orders/%1s/capture","2JF08781K01251707");
URL url = new URL(api);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("PayPal-Request-Id", "7b92603e-77ed-4896-8e78-5dea2050476a");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回示例
其中 purchase_units[0]/payments/captures[0]/id
就是 captureId ,记住这个值,在退款时使用。
本例是 9T136542CK5139010
{
"id": "2JF08781K01251707",
"status": "COMPLETED",
"payment_source": {
"paypal": {
"email_address": "sb-shn43n32414178@personal.example.com",
"account_id": "6MFUB6K8A4CMG",
"account_status": "VERIFIED",
"name": {
"given_name": "John",
"surname": "Doe"
},
"address": {
"country_code": "C2"
}
}
},
"purchase_units": [{
"reference_id": "90f571f6-ad20-4e58-83ca-625f41601188",
"shipping": {
"name": {
"full_name": "Jacky"
},
"address": {
"address_line_1": "虹桥镇",
"address_line_2": "茅台路168号",
"admin_area_2": "闵行区",
"admin_area_1": "上海市",
"postal_code": "20000",
"country_code": "CN"
}
},
"payments": {
"captures": [{
"id": "9T136542CK5139010",
"status": "COMPLETED",
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"final_capture": true,
"seller_protection": {
"status": "ELIGIBLE",
"dispute_categories": ["ITEM_NOT_RECEIVED", "UNAUTHORIZED_TRANSACTION"]
},
"seller_receivable_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "10.00"
},
"paypal_fee": {
"currency_code": "USD",
"value": "0.69"
},
"net_amount": {
"currency_code": "USD",
"value": "9.31"
},
"receivable_amount": {
"currency_code": "HKD",
"value": "70.76"
},
"exchange_rate": {
"source_currency": "USD",
"target_currency": "HKD",
"value": "7.6006905"
}
},
"custom_id": "16803aa5-e2a9-45d5-b00d-7b2919676074",
"links": [{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010",
"rel": "self",
"method": "GET"
}, {
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010/refund",
"rel": "refund",
"method": "POST"
}, {
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "up",
"method": "GET"
}],
"create_time": "2024-09-03T06:02:59Z",
"update_time": "2024-09-03T06:02:59Z"
}]
}
}],
"payer": {
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "sb-shn43n32414178@personal.example.com",
"payer_id": "6MFUB6K8A4CMG",
"address": {
"country_code": "C2"
}
},
"links": [{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "self",
"method": "GET"
}]
}
5、订单查询 /v2/checkout/orders/{order_id}
查询订单详情,如果没有收到回调,可以主动查询订单状态
java请求
@Test
public void query() throws IOException {
// 传入订单ID作为路径参数
String api = String.format("https://api-m.sandbox.paypal.com/v2/checkout/orders/%1s","2JF08781K01251707");
URL url = new URL(api);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("PayPal-Request-Id", "7b92603e-77ed-4896-8e78-5dea2050476a");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回示例
{
"id": "2JF08781K01251707",
"intent": "CAPTURE",
"status": "COMPLETED",
"payment_source": {
"paypal": {
"email_address": "sb-shn43n32414178@personal.example.com",
"account_id": "6MFUB6K8A4CMG",
"account_status": "VERIFIED",
"name": {
"given_name": "John",
"surname": "Doe"
},
"address": {
"country_code": "C2"
}
}
},
"purchase_units": [{
"reference_id": "90f571f6-ad20-4e58-83ca-625f41601188",
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"payee": {
"email_address": "sb-candq32416718@business.example.com",
"merchant_id": "HNKCPAL3PZUUL"
},
"description": "测试支付",
"custom_id": "16803aa5-e2a9-45d5-b00d-7b2919676074",
"shipping": {
"name": {
"full_name": "Jacky"
},
"address": {
"address_line_1": "虹桥镇",
"address_line_2": "茅台路168号",
"admin_area_2": "闵行区",
"admin_area_1": "上海市",
"postal_code": "20000",
"country_code": "CN"
}
},
"payments": {
"captures": [{
"id": "9T136542CK5139010",
"status": "COMPLETED",
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"final_capture": true,
"seller_protection": {
"status": "ELIGIBLE",
"dispute_categories": ["ITEM_NOT_RECEIVED", "UNAUTHORIZED_TRANSACTION"]
},
"seller_receivable_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "10.00"
},
"paypal_fee": {
"currency_code": "USD",
"value": "0.69"
},
"net_amount": {
"currency_code": "USD",
"value": "9.31"
},
"receivable_amount": {
"currency_code": "HKD",
"value": "70.76"
},
"exchange_rate": {
"source_currency": "USD",
"target_currency": "HKD",
"value": "7.6006905"
}
},
"custom_id": "16803aa5-e2a9-45d5-b00d-7b2919676074",
"links": [{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010",
"rel": "self",
"method": "GET"
}, {
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010/refund",
"rel": "refund",
"method": "POST"
}, {
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "up",
"method": "GET"
}],
"create_time": "2024-09-03T06:02:59Z",
"update_time": "2024-09-03T06:02:59Z"
}]
}
}],
"payer": {
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "sb-shn43n32414178@personal.example.com",
"payer_id": "6MFUB6K8A4CMG",
"address": {
"country_code": "C2"
}
},
"create_time": "2024-09-03T04:09:41Z",
"update_time": "2024-09-03T06:02:59Z",
"links": [{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/2JF08781K01251707",
"rel": "self",
"method": "GET"
}]
}
6、退款 /v2/payments/captures/{capture_id}/refund
退款请求必须传入 capture_id
,该参数在上面支付订单确认时返回,本例是 9T136542CK5139010
Java请求
@Test
public void refund() throws IOException {
// 传入订单ID作为路径参数
String api = String.format("https://api-m.sandbox.paypal.com/v2/payments/captures/%1s/refund","9T136542CK5139010");
URL url = new URL(api);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("Prefer", "return=representation");
httpConn.setRequestProperty("PayPal-Request-Id", "7b92603e-77ed-4896-8e78-5dea2050476a");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
httpConn.setDoOutput(true);
JSONObject json = new JSONObject();
// 传入退款金额
JSONObject amount = new JSONObject();
amount.put("value","10.00");
amount.put("currency_code","USD");
json.put("amount",amount);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write(json.toJSONString());
writer.flush();
writer.close();
httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回示例
从结果中取得退款订单ID,用于退款订单查询使用。本例是 73L96122H5742963F
{
"id": "73L96122H5742963F",
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"seller_payable_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "10.00"
},
"paypal_fee": {
"currency_code": "USD",
"value": "0.00"
},
"net_amount": {
"currency_code": "USD",
"value": "10.00"
},
"total_refunded_amount": {
"currency_code": "USD",
"value": "10.00"
}
},
"custom_id": "16803aa5-e2a9-45d5-b00d-7b2919676074",
"status": "COMPLETED",
"create_time": "2024-09-02T23:27:51-07:00",
"update_time": "2024-09-02T23:27:51-07:00",
"links": [{
"href": "https://api.sandbox.paypal.com/v2/payments/refunds/73L96122H5742963F",
"rel": "self",
"method": "GET"
}, {
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010",
"rel": "up",
"method": "GET"
}]
}
7、退款查询 /v2/payments/refunds/{refund_id}
java请求
@Test
public void refundQuery() throws IOException {
// 传入退款订单ID作为路径参数
String api = String.format("https://api-m.sandbox.paypal.com/v2/payments/refunds/%1s","73L96122H5742963F");
URL url = new URL(api);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("Content-Type", "application/json");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回示例
{
"id": "73L96122H5742963F",
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"seller_payable_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "10.00"
},
"paypal_fee": {
"currency_code": "USD",
"value": "0.00"
},
"net_amount": {
"currency_code": "USD",
"value": "10.00"
},
"total_refunded_amount": {
"currency_code": "USD",
"value": "10.00"
}
},
"custom_id": "16803aa5-e2a9-45d5-b00d-7b2919676074",
"status": "COMPLETED",
"create_time": "2024-09-02T23:27:51-07:00",
"update_time": "2024-09-02T23:27:51-07:00",
"payer": {
"email_address": "sb-candq32416718@business.example.com",
"merchant_id": "HNKCPAL3PZUUL"
},
"links": [{
"href": "https://api.sandbox.paypal.com/v2/payments/refunds/73L96122H5742963F",
"rel": "self",
"method": "GET"
}, {
"href": "https://api.sandbox.paypal.com/v2/payments/captures/9T136542CK5139010",
"rel": "up",
"method": "GET"
}]
}
8、回调处理
PayPal的API事件回调是通过webhook请求实现的,首先要在开发者平台上面配置回调地址,以及对应的事件。
登录开发者平台 (https://developer.paypal.com/),点击菜单 “Apps & Credentials”,点击应用-edit,进入应用编辑页面。
页面拖下最下面,可以看见 Sandbox Webhooks ,点击 Add Webhook
输入回调地址(必须是https的请求),以及勾选事件(选择Checkout
以及 Payments & Payouts
即可)
回调时间列表 https://developer.paypal.com/api/rest/webhooks/event-names/
接收Webhook信息
Webhook回调body内容大致如下
{
"event_version": "1.0",
"summary": "An order has been approved by buyer",
"event_type": "CHECKOUT.ORDER.APPROVED",
"create_time": "2024-09-03T07:22:32.983Z",
"resource": {
"create_time": "2024-09-03T07:22:03Z",
"purchase_units": [{
"payee": {
"email_address": "sb-sgxuo32361369@business.example.com",
"merchant_id": "Q348V8XR4TH2N"
},
"amount": {
"value": "0.01",
"currency_code": "HKD"
},
"reference_id": "default",
"shipping": {
"address": {
"country_code": "CN",
"admin_area_1": "上海市",
"address_line_1": "虹桥镇",
"admin_area_2": "闵行区",
"address_line_2": "茅台路168号",
"postal_code": "20000"
},
"name": {
"full_name": "Jacky"
}
},
"custom_id": "f909fb55f28f44e290c50dd048d57266",
"description": "订单说明"
}],
"links": [{
"method": "GET",
"rel": "self",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/69F56626WL954315U"
}, {
"method": "PATCH",
"rel": "update",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/69F56626WL954315U"
}, {
"method": "POST",
"rel": "capture",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/69F56626WL954315U/capture"
}],
"id": "69F56626WL954315U",
"payment_source": {
"paypal": {
"email_address": "sb-9kb7y32414123@personal.example.com",
"account_id": "JEDY926LHZCVL",
"address": {
"country_code": "C2"
},
"name": {
"surname": "Doe",
"given_name": "John"
},
"account_status": "VERIFIED"
}
},
"intent": "CAPTURE",
"payer": {
"email_address": "sb-9kb7y32414123@personal.example.com",
"address": {
"country_code": "C2"
},
"name": {
"surname": "Doe",
"given_name": "John"
},
"payer_id": "JEDY926LHZCVL"
},
"status": "APPROVED"
},
"resource_type": "checkout-order",
"links": [{
"method": "GET",
"rel": "self",
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8MY579521J6808611-3AB13057V80946128"
}, {
"method": "POST",
"rel": "resend",
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8MY579521J6808611-3AB13057V80946128/resend"
}],
"id": "WH-8MY579521J6808611-3AB13057V80946128",
"resource_version": "2.0"
}
同时Webhook回调的header中也有很多数据,如下5个数据需要取出,在验证签名时使用:
"paypal-transmission-sig": ["lqd3Wg4zb/VqOu8CcAbKKcw2teLr+80jMoBh8/QdA+lyP3GqF3PB/2AIMTH9mG6AtZy2f8O+79zXvQu5gOYf1Cw77G8ExEcmAdlgozJOdD9Zc4hEYJUVa8i2BQKwjEcU0MhmuMqTFMkq5yErgPi91t0lfodz3yNoE4Fs8U1bN78s/lHNQ9Rtnxf6oizt02MXR+KTIT9iGiE0GkMLKcjoLv0NsAjYP4BxEy3hE64n3kGWix3Cj7vMM1GfX32PXblRAuurbg/VcIl1fbnLTfYgL401BqaEh/LwSAHBD6VRg84xXCEqBjgJpxpIFwlPTO0zNU9jvJLMu+pOd8dokMqcaw=="],
"paypal-cert-url": ["https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-ab66f33d"],
"paypal-auth-algo": ["SHA256withRSA"],
"paypal-transmission-id": ["9752bb30-69c5-11ef-8631-b5563cb1cd6c"],
"paypal-transmission-time": ["2024-09-03T07:24:44Z"]
回调信息签名验证
参考 https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature_post
java代码
@Test
public void verify() throws IOException {
URL url = new URL("https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "application/json");
// 设置 accessToken
httpConn.setRequestProperty("Authorization", "Bearer A21AAKn5cnLgrxyUuxaQiGeuuA1aQWPZuxyZLBJbXK04-BNm0FJIJZjXef_VFumB9RLCVurmrPh9DXn2KaLwVEau7NqsLTcNA");
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
JSONObject param = new JSONObject();
param.put("auth_algo", "SHA256withRSA");
param.put("cert_url", "https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-ab66f33d");
param.put("transmission_id", "9752bb30-69c5-11ef-8631-b5563cb1cd6c");
param.put("transmission_sig", "lqd3Wg4zb/VqOu8CcAbKKcw2teLr+80jMoBh8/QdA+lyP3GqF3PB/2AIMTH9mG6AtZy2f8O+79zXvQu5gOYf1Cw77G8ExEcmAdlgozJOdD9Zc4hEYJUVa8i2BQKwjEcU0MhmuMqTFMkq5yErgPi91t0lfodz3yNoE4Fs8U1bN78s/lHNQ9Rtnxf6oizt02MXR+KTIT9iGiE0GkMLKcjoLv0NsAjYP4BxEy3hE64n3kGWix3Cj7vMM1GfX32PXblRAuurbg/VcIl1fbnLTfYgL401BqaEh/LwSAHBD6VRg84xXCEqBjgJpxpIFwlPTO0zNU9jvJLMu+pOd8dokMqcaw==");
param.put("transmission_time", "2024-09-03T07:24:44Z");
// The ID of the webhook as configured in your Developer Portal account.
param.put("webhook_id", "14H498652P299021K");
String event_str ="{\"event_version\":\"1.0\",\"summary\":\"Payment completed for HKD 0.01 HKD\",\"event_type\":\"PAYMENT.CAPTURE.COMPLETED\",\"create_time\":\"2024-09-03T07:24:32.968Z\",\"resource\":{\"payee\":{\"email_address\":\"sb-sgxuo32361369@business.example.com\",\"merchant_id\":\"Q348V8XR4TH2N\"},\"amount\":{\"value\":\"0.01\",\"currency_code\":\"HKD\"},\"seller_protection\":{\"dispute_categories\":[\"ITEM_NOT_RECEIVED\",\"UNAUTHORIZED_TRANSACTION\"],\"status\":\"ELIGIBLE\"},\"supplementary_data\":{\"related_ids\":{\"order_id\":\"69F56626WL954315U\"}},\"update_time\":\"2024-09-03T07:24:28Z\",\"create_time\":\"2024-09-03T07:24:28Z\",\"final_capture\":true,\"seller_receivable_breakdown\":{\"paypal_fee\":{\"value\":\"0.01\",\"currency_code\":\"HKD\"},\"gross_amount\":{\"value\":\"0.01\",\"currency_code\":\"HKD\"},\"net_amount\":{\"value\":\"0.00\",\"currency_code\":\"HKD\"}},\"custom_id\":\"f909fb55f28f44e290c50dd048d57266\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"https://api.sandbox.paypal.com/v2/payments/captures/6P662124DH1279613\"},{\"method\":\"POST\",\"rel\":\"refund\",\"href\":\"https://api.sandbox.paypal.com/v2/payments/captures/6P662124DH1279613/refund\"},{\"method\":\"GET\",\"rel\":\"up\",\"href\":\"https://api.sandbox.paypal.com/v2/checkout/orders/69F56626WL954315U\"}],\"id\":\"6P662124DH1279613\",\"status\":\"COMPLETED\"},\"resource_type\":\"capture\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-85V35090TG357611Y-4UY875675G964354R\"},{\"method\":\"POST\",\"rel\":\"resend\",\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-85V35090TG357611Y-4UY875675G964354R/resend\"}],\"id\":\"WH-85V35090TG357611Y-4UY875675G964354R\",\"resource_version\":\"2.0\"}";
JSONObject jsonObject = JSON.parseObject(event_str);
param.put("webhook_event", jsonObject);
writer.write(param.toJSONString());
writer.flush();
writer.close();
httpConn.getOutputStream().close();
InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
}
返回信息
{"verification_status":"SUCCESS"}
参考
- https://developer.paypal.com/
- https://developer.paypal.com/api/rest/
- https://sandbox.paypal.com
- https://developer.paypal.com/docs/api/orders/v2/