【支付】PayPal支付通道 Java对接 (下单 付款 确认 退款 查询 回调)

news2025/1/14 18:43:15

使用Java 对接 PayPal 接口,从下单,支付,确认订单、查询、退款、退款查询、回调处理等全流程代码示例,以及图文说明。

PayPal接口调用时序图

后台服务 PayPal 【授权】 返回AccessToken,后续每个请求后腰携带Token 【下单】(v2/checkout/orders) 返回订单信息,包含支付链接(links)和订单id 浏览器打开支付链接,输入PayPal账户,【完成支付】 支付完成,跳转到完成页面 【支付成功回调】(webhook请求,CHECKOUT.ORDER.APPROVED事件) 【支付确认】(/v2/checkout/orders/{order_id}/capture) 返回确定信息(得到captureId,退款使用) 【确认回调】(PAYMENT.CAPTURE.COMPLETED事件)修改订单状态置为成功 【查询订单】(/v2/checkout/orders/{order_id}) 返回订单信息 【退款】(/v2/payments/captures/{capture_id}/refund) 返回退款订单信息(退款ID refund_id) 【查询退款】订单(/v2/payments/refunds/{refund_id}) 返回退款订单信息 后台服务 PayPal

PayPal账户注册

PayPal的API接口使用client IDclient secret进行身份认证。

账户注册成功后登录开发者平台(https://developer.paypal.com/),然后选择Apps & Credentials菜单,可以看见平台创建了一个默认应用,包含client IDclient 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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2104793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习的发展历程

深度学习的起源 在机器学习中,我们经常使用两种方式来表示特征:局部表示(Local Representation)和分布式表示(Distributed Representation)。以颜色表示为例,见下图: 要学习到一种好…

探索NVIDIA RTX 4060 8G与RTX 3060 12G:性能与适用场景的深度解析

在考虑NVIDIA RTX 4060 8G和RTX 3060 12G两种配置时,我们需要根据具体的应用场景来做出选择。RTX 4060 8G显卡以其较快的处理速度而著称,适合需要快速响应的任务,如实时渲染、视频编辑和部分机器学习任务。而RTX 3060 12G显卡则因其较大的显存…

依托自研力量,给共享集群存储服务一个优选

YashanDB共享集群有三大关键组件,崖山集群服务(YCS)、崖山集群文件系统(YFS)、DB组件。上一篇共享集群系列文章《为何共享集群的高可用能力被频频称赞,它的机制有何不同?》深入解析了关键组件的…

第九届“创客中国”生成式人工智能中小企业创新创业大赛复赛圆满落幕

9月3日,第九届“创客中国”生成式人工智能(AIGC)中小企业创新创业大赛复赛在南昌高新区艾溪湖畔圆满落幕,共有11组企业项目和10组创客项目凭借其卓越的项目展示、深刻的市场洞察以及前瞻的技术应用,脱颖而出,成功晋级至激动人心的决赛阶段。 本次大赛由工业和信息化信息中心主办…

多池化策略揭秘:PVAFN如何实现更精准的3D物体识别?

更多优质内容,请关注公众号:智驾机器人技术前线 1.论文信息 论文标题:PVAFN: Point-Voxel Attention Fusion Network with Multi-Pooling Enhancing for 3D Object Detection 作者:Yidi Li, Jiahao Wen, Bin Ren, Wenhao Li, Zh…

传统CV算法——基于Sift算法实现特征点检测

图像尺度空间 在一定的范围内,无论物体是大还是小,人眼都可以分辨出来,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个统一的认知,就需要考虑图像在不同的尺度下都存在的特点。 尺度空间的…

【练习5】简写单词

地址:简写单词_牛客题霸_牛客网 (nowcoder.com) 分析: 1.用到in.charAt(i)方法,可以实现读取下一个由空白字符分隔的输入字符串,并返回这个字符串的第一个字符。 2.ch-32可以实现小写字母转大写字母。 public static void main(St…

微积分复习笔记 Calculus Volume 1 -1.4 Inverse Functions

1.4 Inverse Functions - Calculus Volume 1 | OpenStax

[数据集][目标检测]汽油检泄漏检测数据集VOC+YOLO格式237张2类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):237 标注数量(xml文件个数):237 标注数量(txt文件个数):237 标注类别…

最新影视直播CMS源码/基于苹果CMS系统/粉红色UI/附搭建教程/支付已接+会员中心

源码简介: 最新影视直播CMS源码,它是基于苹果CMS系统框架,外观是超可爱的粉红色UI设计。里面还附带了详细的教程,轻松上手。而且,支付功能已经完美接入,会员中心也一应俱全。 这套源码是基于苹果CMS v10影…

网络编程详解

什么是网络编程 网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输) 发送端和接收端 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源…

信创实践(2):利用Leapp工具迁移CentOS至AnolisOS,实现系统升级与自主可控

1. 引言 为了满足用户在CentOS退出后对操作系统使用的诉求,OpenAnolis龙蜥社区正式发布了Anolis OS。越来越多的CentOS客户期望能够迁移到Anolis OS上来。操作系统迁移是一个复杂工程,手工迁移技术要求高,操作复杂度强,需要耗费大…

Windows 欺骗主机 Burn-In 的自动化工具

Sinon 是一款开源的模块化工具,用于自动测试基于 Windows 的欺骗主机。 它旨在降低大规模编排欺骗主机的难度,同时通过生成功能实现多样性和随机性。 Sinon 旨在通过执行模拟实际用户活动的各种操作来自动设置欺骗主机。 目标是创建一个可以欺骗潜在入…

Python3.8绿色便携版安装版制作

Python 的绿色便携版有两种:官方 Embeddable 版本(嵌入式版);安装版制作的绿色版。Embeddable 版适用于需要将 Python 集成到其他应用程序或项目中的情况,它不包含图形界面的安装程序,只提供了 Python 解释器和必要的库…

国企民企协同共进,让长沙永远是当打之年

一提到长沙,大多都会跟“网红”二字联系在一起,随之而来的是巨大关注度与经济效应,但与此同时,争议也随之而来:这样的网红城市依赖单一的“网红”元素或流量效应,经济增长缺乏内生动力,十分不禁…

大数据-118 - Flink DataSet 基本介绍 核心特性 创建、转换、输出等

点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…

JVM 垃圾回收机制:GC

目录 一、死亡对象的判断算法 1.1 引用计数算法 1.2 可达性分析算法 二、垃圾回收算法 2.1 标记-清除算法 2.2 复制算法 2.3 标记-整理算法 2.4 分代算法 三、垃圾收集器 3.1 CMS收集器(老年代收集器,并发GC) 3.2 G1收集器(唯一一…

项目实战 - 贪吃蛇

目录 1. 基本功能 2. 技术要点 3. 环境 4. 效果演示 5. 控制台设置 6. Win32 API介绍 6.1 Win32 API 6.2 程序台控制(Console) 6.3 控制台屏幕上的坐标(COORD) 6.4 GetStdHandle 6.5 GetConsoleCursorInfo 6.5.1 CONSOLE_CURSOR_INFO 6.6 SetConsoleCursorInfo 6…

云原生架构概念

云原生架构概念 云原生架构(Cloud Native Architechtrue)作为一种现代软件开发的革新力量,正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构,使得应用程序能够分解为独立、松耦合的服务&#xf…

详解si5338 si53xx 设计使用及STM32 iic驱动设计

背景 在实际项目中经常使用si5338 si53xx,进行多路时钟的倍频以生成想要的时钟信号,但是针对si5338 si53xx设计使用缺少相关的资料,本文详解si5338 si53xx 设计使用及STM32 iic驱动设计,本文使用工程在项目中得到测试&#xff0c…