`提示:因目前条件有限,所以暂时也就只能是paypal沙盒环境进行调试。不过流程走通了之后,生产也是一样的咯。
一、paypal是什么?
可以直接理解成国外的支付宝,说是支持全球200多个国家。然而在中国,这玩意可能没多少知名度。至少在这之前,我是不知道paypal支付的。
有兴趣的可以看一下paypal的官网:paypal官网,如果浏览器打不开,那你可能得借助特殊工具了。
下面咱们直接进入正题
二、使用步骤
1.准备 :注册一个paypa账号
1、注册地址 :https://www.paypal.com/signin?intent=developer&returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdeveloper%2Fapplications。
登录进去之后就长这样了
paypal大致上跟微信支付和支付宝支付一样,要先创建一个APP,因为开发时也需要用到appID和那个秘钥的。
通过上面的截图可以发现,创建应用有两个选项 :商家和平台。这里经过我接口调试后发现,区别也就是官方说的那个标准支付和高级支付。平台也就是商家的升级版,因为想做到无账号实现信用卡快捷支付,那就得是平台才行。所以咱们直接创建平台应用。
然后点击进去拿到clientId 和 secret
然后咱们开始创建一个个人沙盒账号,后面用来支付。沙盒账号咱们就用默认的吧。
到这里咱们就基本上准备就绪了,在撸代码前,还是建议先看一下标准支付和高级支付文档和流程的。
标准支付 : https://www.paypal.com/apex/product-profile/ordersv2/updateOrder
高级支付 :比较麻烦,因为我是后端,所以后面会以后端的方式讲解。不过你们有兴趣可以自行查看官方文档 :https://developer.paypal.com/docs/checkout/
其实除了这两个支付方式,还有一个自定义结账:
这种方式就更加灵活和厉害些了,不过需要前后端配合。目前我也是一知半解的,如果有大佬研究透,记得分享一下。谢谢
2.编写paypal后端接口demo
其实官网是有相关前后端SDK的,据官网所描述V1 和 V2版本,V1据说不进行维护了。这里我就提供一下maven吧,有兴趣可以自己看一下源码。可能有人会想,官网应该是有SDK的说明吧,那我只能说是你想多了,毕竟源码连个注释都没有。
V1 :
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.paypal.sdk/checkout-sdk -->
<!--<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>1.0.5</version>
</dependency>-->
1、创建订单
官网 :
public static void main(String[] args) {
try {
String url = "https://api.sandbox.paypal.com/v2/checkout/orders";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("accept", "application/json");
con.setRequestProperty("content-type", "application/json");
con.setRequestProperty("accept-language", "en_US");
con.setRequestProperty("authorization", "Bearer A21AALAMZY4usVfQCrfOGNgGl0dWHHqtl6SERLSRuuw10_OK2cZgyr_lMm6axSCOTJYPPKKmC15RN1S9Q2y2LVeofLS-GCcSA");
String body = "{" +
" \"intent\": \"CAPTURE\"," +
" \"purchase_units\": [" +
" {" +
" \"reference_id\": \"PUHF\"," +
" \"amount\": {" +
" \"currency_code\": \"USD\"," +
" \"value\": \"100.00\"" +
" }" +
" }" +
" ]," +
" \"application_context\": {" +
" \"return_url\": \"\"," +
" \"cancel_url\": \"\"" +
" }" +
"}";
// Send request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(body);
wr.flush();
wr.close();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// Print the response
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
SDK :
//创建订单
@Test
public void createOrder() throws Exception{
String amount = "100.00";
PayPalHttpClient client = new PayPalHttpClient(new PayPalEnvironment.Sandbox(clientId, secret));
// 配置请求参数
OrderRequest orderRequest = new OrderRequest();
orderRequest.checkoutPaymentIntent("CAPTURE");
List<PurchaseUnitRequest> purchaseUnits = new ArrayList<>();
purchaseUnits.add(new PurchaseUnitRequest().amountWithBreakdown(new AmountWithBreakdown().currencyCode("USD").value(amount)));
orderRequest.purchaseUnits(purchaseUnits);
orderRequest.applicationContext(new ApplicationContext().returnUrl(notifyURLSuccess).cancelUrl(notifyURLFail));
OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);
HttpResponse<Order> response;
try {
response = client.execute(request);
Order order = response.result();
String token = order.id();
//log.debug("payPal 支付操作返回结果: " + order);
System.out.printf("payPal 支付操作返回结果: " + order);
String payHref = null;
String status = order.status();
if (status.equals("CREATED")) {
List<LinkDescription> links = order.links();
for (LinkDescription linkDescription : links) {
if (linkDescription.rel().equals("approve")) {
payHref = linkDescription.href();
}
}
}
Map<String, String> resultMap = new HashMap<String, String>();
resultMap.put("token", token);
resultMap.put("payHref", payHref);
System.out.printf(resultMap.toString());
} catch (IOException ioe) {
if (ioe instanceof HttpException) {
HttpException he = (HttpException) ioe;
}
}
}
创建订单之后。完整的返回报文 :
{
"links": [
{
"method": "GET",
"rel": "self",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/1RY52286985553701"
},
{
"method": "GET",
"rel": "approve",
"href": "https://www.sandbox.paypal.com/checkoutnow?token=1RY52286985553701"
},
{
"method": "PATCH",
"rel": "update",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/1RY52286985553701"
},
{
"method": "POST",
"rel": "capture",
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/1RY52286985553701/capture"
}
],
"id": "1RY52286985553701",
"status": "CREATED"
}
2、授权订单(客户授权订单)
这里其实就是客户登录一下paypal账号对这笔订单进行授权,就是前端做的事情。找到创建订单之后返回的报文,拿到approve下的那里url,用浏览器访问打开一下,再使用上面创建的个人沙盒账号登录授权一下,就可以进行下一步扣款了。
3、更新订单
官网 :
public static void main(String[] args) {
try {
// APEX examples for PATCH calls use Unirest library as HttpsURLConnection does not support PATCH method without a workaround
HttpResponse<String> response = Unirest.patch("https://api.sandbox.paypal.com/v2/checkout/orders/83849099T1607350D")
.header("accept", "application/json")
.header("content-type", "application/json")
.header("accept-language", "en_US")
.header("authorization", "Bearer A21AALAMZY4usVfQCrfOGNgGl0dWHHqtl6SERLSRuuw10_OK2cZgyr_lMm6axSCOTJYPPKKmC15RN1S9Q2y2LVeofLS-GCcSA")
.body("[" +
" {" +
" \"op\": \"replace\"," +
" \"path\": \"/purchase_units/@reference_id=='PUHF'/amount\"," +
" \"value\": {" +
" \"currency_code\": \"USD\"," +
" \"value\": \"200.00\"," +
" \"breakdown\": {" +
" \"item_total\": {" +
" \"currency_code\": \"USD\"," +
" \"value\": \"180.00\"" +
" }," +
" \"shipping\": {" +
" \"currency_code\": \"USD\"," +
" \"value\": \"20.00\"" +
" }" +
" }" +
" }" +
" }" +
"]")
.asString();
System.out.println(response.getBody());
System.out.println(response.getStatus());
} catch (UnirestException e) {
throw new IllegalStateException(e);
}
}
SDK : 这个我就没去写测试了,感觉有点鸡肋
4、捕获订单(就是商家扣款)
请求地址需要带一个ID,这个ID就是前面创建订单时返回的那个ID
官网 :
public static void main(String[] args) {
try {
String url = "https://api.sandbox.paypal.com/v2/checkout/orders/83849099T1607350D/capture";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("content-type", "application/json");
con.setRequestProperty("authorization", "Bearer A21AALAMZY4usVfQCrfOGNgGl0dWHHqtl6SERLSRuuw10_OK2cZgyr_lMm6axSCOTJYPPKKmC15RN1S9Q2y2LVeofLS-GCcSA");
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// Print the response
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
SDK :
//捕获订单
@Test
public void captureOrder() {
String token = "21P67705TC475012Y";
PayPalHttpClient client = new PayPalHttpClient(new PayPalEnvironment.Sandbox(clientId,
OrdersCaptureRequest request = new OrdersCaptureRequest(token);
try {
HttpResponse<Order> response = client.execute(request);
Order order = response.result();
System.out.printf(order.status());
String status = order.status();
if (status.equals("COMPLETED")) {
System.out.printf("成功");
}
System.out.printf("成功");
} catch (IOException ioe) {
if (ioe instanceof HttpException) {
HttpException he = (HttpException) ioe;
System.out.printf("PayPal支付捕获失败"+ioe);
}
System.out.printf("捕获订单异常" + ioe);
}
}
官网 :
public static void main(String[] args) {
try {
String url = "https://api.sandbox.paypal.com/v1/oauth2/token";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("accept", "application/json");
con.setRequestProperty("accept-language", "en_US");
con.setRequestProperty("content-type", "application/x-www-form-urlencoded");
con.setRequestProperty("authorization", "basic QVRt**********WXU=");
String body = "grant_type=client_credentials";
// Send request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(body);
wr.flush();
wr.close();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// Print the response
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/*****
* paypal支付通用请求头
* ***/
public static Map<String, String> getBaseHeaders(String accessToken,String clientId,String secret) {
Map<String, String> headers = new HashMap<>(3);
headers.put("Content-Type", ContentType.JSON.toString());
String authorization = "";
String encodedAuth;
if (StringUtils.isNotEmpty(accessToken))
{
encodedAuth = Base64.encode(accessToken.getBytes(Charset.forName("US-ASCII")));
authorization = "Bearer " + encodedAuth;
}else{
String auth = clientId + ":" + secret;
encodedAuth = Base64.encode(auth.getBytes(Charset.forName("US-ASCII")));
authorization = "Basic " + encodedAuth;
}
headers.put("Authorization", authorization);
return headers;
}
到这里之后流程就很清楚了吧,回到咱们开始说到的无账号实现信用卡快捷支付,按现有的流程是不是就没办法实现?毕竟创建订单之后,肯定是需要客户进行订单授权的。
要想实现这个,那就得用到paypal的高级支付了。客户只需要在页面输入信用卡卡号等几个属性,就可以代替订单授权这一步骤了。后端接收到信用卡的几个属性后,直接调用创建订单接口后,再调一个属于高级支付的接口将这几个属性传过去,之后就可以直接调接口进行扣款了。
@Test
public void test08() throws UnsupportedEncodingException {
// 客户端接口请求路径
String url = "https://cors.api.sandbox.paypal.com/v2/checkout/orders/16E92398U6411971K/validate-payment-method";
//String body = "";
String body = "{\"payment_source\":{\"card\":{\"number\":\"4032032482415913\",\"expiry\":\"2024-06\",\"security_code\":\"359\",\"name\":\"Lee Vincen\",\"billing_address\":{\"address_line_1\":\"beijing\",\"address_line_2\":\"CAD\",\"admin_area_1\":\"1\",\"admin_area_2\":\"beijign\",\"postal_code\":\"518000\",\"country_code\":\"CA\"}}},\"application_context\":{\"vault\":false}}";
// 创建请求
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//设置连接超时时间(非必要设置)
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(3000)
.setConnectTimeout(3000).build();
//创建post方式请求对象
HttpPost post = new HttpPost(url);
// post.setConfig(requestConfig);
//设置对应请求接口要求设置的请求头
post.setHeader("Content-Type", "application/json");
// post.setHeader("Accept", "application/json;charset=utf-8");
post.setHeader("Authorization", getHeader());
StringEntity stringEntity = new StringEntity(body);
post.setEntity(stringEntity);
// 执行请求并拿到结果
CloseableHttpResponse response = null;
String result = null;
try {
response = httpClient.execute(post);
//判断返回状态是否正常
int state = response.getStatusLine().getStatusCode();
if (state != HttpStatus.SC_OK) {
System.out.println("connection faild 错误代码:"+state);
}
//获取结果实体并返回结果
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity,"UTF-8");
//返回json处理见下面testGet方法里对json数据的解析处理
//打印结果
System.out.println(result);
//释放所有由httpEntity所持有的资源
EntityUtils.consume(entity);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(response != null){
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(httpClient != null){
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
再贴一个paypal 的 ipn通知吧
/****
* IPN实时付款通知
* ***/
@PostMapping(value = "/notifyUrl")
public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception{
try{
//获取paypal请求参数,并拼接验证参数
Enumeration<String> en = request.getParameterNames();
String str = "cmd=_notify-validate";
while (en.hasMoreElements()) {
String paramName = en.nextElement();
String paramValue = request.getParameter(paramName);
//此处的编码一定要和自己的网站编码一致,不然会出现乱码,paypal回复的通知为‘INVALID’
str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "utf-8");
}
//建议在此将接受到的信息 str 记录到日志文件中以确认是否收到 IPN 信息
log.info("=========================================================================================");
log.info("paypal传递过来的交易信息:"+str);
PayPalApiConfig config = getConfig();
IJPayHttpResponse reData = PayPalApi.validationIPN(config,str);
String verifled = reData.getBody();
log.info("回调回复之后 :" + verifled);
/**
* 将 POST 信息分配给本地变量,可以根据您的需要添加
*/
// 交易状态 Completed 代表交易成功
String paymentStatus = request.getParameter("payment_status");
log.info("交易状态 :" + paymentStatus);//Completed :交易完成已付款
// 交易时间
String paymentDate = request.getParameter("payment_date");
log.info("交易时间 :" + paymentDate);
// 交易id
String txnId = request.getParameter("txn_id");
log.info("交易id :" + txnId);
// 父交易id
String parentTxnId = request.getParameter("parent_txn_id");
log.info("父交易id :" + parentTxnId);
// 收款人email0
String receiverEmail = request.getParameter("receiver_email");
log.info("收款人email :" + receiverEmail);
// 收款人id
String receiverId = request.getParameter("receiver_id");
log.info("收款人id :" + receiverId);
// 付款人email
String payerEmail = request.getParameter("payer_email");
log.info("付款人email :" + payerEmail);
// 付款人id
String payerId = request.getParameter("payer_id");
log.info("付款人id :" + payerId);
// 交易金额
String mcGross = request.getParameter("mc_gross");
log.info("交易金额 :" + mcGross);
// 自定义字段,我们存放的订单ID
String custom = request.getParameter("custom");
log.info("自定义字段,我们存放的订单ID :" + custom);
/**
* 获取 PayPal 对回发信息的回复信息,判断刚才的通知是否为 PayPal 发出的
*/
if("VERIFIED" .equalsIgnoreCase(verifled)){
log.info("请求是真的 :" + verifled + "可进行一些想过逻辑处理");
}else if ("INVALID".equalsIgnoreCase(verifled)) {
//非法信息,可以将此记录到您的日志文件中以备调查
log.error("paypal完成支付发送IPN通知返回状态非法,请联系管理员,请求参数:" + str);
} else {
//处理其他错误
log.error("paypal完成支付发送IPN通知发生其他异常,请联系管理员,请求参数:" + str);
}
}catch (Exception e){
}
}
国外的api文档,真的对咱们很不友好。以上都是刚开始对接paypal时,写的测试类。如果有什么不足的,欢迎大家提出来。