SpringBoot实现PayPal-JavaScript SDK(REST APIs v2)订单支付/退款

        在这记录并分享一下最近学习的PayPal接口使用,PayPal提供了非常多的API接口,我这里只是实现了订单的支付以及退款功能(Sandbox模式下的实现)

具体API可以看PayPal官方接口文档:https://developer.paypal.com/api/rest/

 一、注册PayPal账号

        这个过程这里就不过多介绍了

二、PayPal开发者中心准备(可选)

开发者中心地址:https://developer.paypal.com/dashboard/

        1. 在开发者中心登录账号,之后在 Testing Tools =》Sandbox Accounts 中创建一个商家账号和一个用户账号(默认会各有一个商家账号和用户账号,不新建直接用这个也可以)

        两种类型各创建一个

        点击进入用户页面可以看到用户的邮箱、密码(这个用于后续支付登录使用的);以及可以在这里修改当前账户的账户余额

        2.然后在 Apps & Credentials 中新建自己的项目(默认会有一个 Default Application,不新建直接用这个也可以)

        输入自己的项目名称,选择 Merchant类型,再选择一个商家账号,就创建成功了;

        点击进入项目页面,在 API credentials 中的 Client IDSecret key 1 在后续代码实现中会使用用到

三、前端代码实现

前端是使用的模板引擎实现的HTML,注意${}是模板引擎语言

        在支付的前端HTML中直接引用JavaScript SDK的js,并在增加一个div用来展示支付按钮(CLIENT_ID是PayPal开发者中心项目页面中对应的 Client ID 

<script src="https://www.paypal.com/sdk/js?client-id=${CLIENT_ID}&components=buttons"></script>

        增加PayPal按钮的初始化代码,(页面中需要增加一个显示PayPal支付按钮的div,并且设置有id为paypal-button-container,也可以按自己的想法换成别的值)添加完打开页面就会有PayPal的按钮显示了

paypal.Buttons({
    style: {
        layout: 'vertical',
        color: 'gold',
        shape: 'pill',
        label: 'paypal'
    }
}).render('#paypal-button-container')

        需要完成支付,我们还需要一些别的代码,createOrder()、onApprove(data)一定要重写的方法,不然无法完成支付

paypal.Buttons({
	style: {
		layout: 'vertical',
		color: 'gold',
		shape: 'pill',
		label: 'paypal'
	},
	// 创建订单
	createOrder() {
		return fetch("/自己的后端创建订单接口", {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
			},
			body: JSON.stringify({
				// 传入订单信息,我这传入的是SKU、数量、收货信息等等
			})
		})
			.then((response) => response.json())
			.then((order) => {
				return order.id;
			});
	},
	// 确认支付
	onApprove(data) {
		return fetch("/自己的后端确认支付接口", {
			method: "POST",
			body: data.orderID
		})
			.then((response) => response.json())
			.then((details) => {
				console.log("付款成功");
			});
	},
	// 取消支付,按自己的业务决定是否实现,我这里是和PayPal接口无关的业务处理
	onCancel(data) {
		$.ajax({
			type: 'POST',
			url: '/自己后端的取消支付接口',
			data: {
				orderID: data.orderID
			},
			success: function (result) {
				if (result.status !== 1) {
					alert(result.message);
				}
			}
		});
	},
	// 支付报错
	onError(err) {
		alert(err);
	}
}).render('#paypal-button-container')

createOrder():

        点击PayPal的支付按钮就会调用这个函数,根据接口返回信息会打开一个PayPal的支付弹框页面,使用第二步创建的用户账号登录,然后支付就好了(接口返回的order.id是PayPal生成的订单ID)

onApprove(data):

        弹框页面中支付完成了,就会调用这个函数;这不很重要,如果不确认支付,那只会扣除用户账号中的金额,并不会增加商家账号中的金额(函数入口的data.orderID就是PayPal的订单ID)

onCancel(data):

        弹框页面没有支付关闭了页面,就会调用到这个函数,按自己的业务决定是否实现,我这里是和PayPal接口无关的本地业务处理(没有业务逻辑需要实现,可以不重写该函数)

 四、后端代码实现

        1.鉴权接口

        所有PayPal接口请求前都需要先获取PayPal的访问令牌才能调用别的接口,其中CLIENT_ID是PayPal开发者中心项目页面中对应的 Client IDCLIENT_SECRET是PayPal开发者中心项目页面中对应的 Secret key 1

import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

public String getAccessToken() throws Exception {
	String url = "https://api-m.sandbox.paypal.com/v1/oauth2/token";

	HttpHeaders headers = new HttpHeaders();
	headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET);
	headers.set("Content-Type", "application/x-www-form-urlencoded");

	String requestBody = "grant_type=client_credentials";
	HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

	RestTemplate restTemplate = new RestTemplate();
	ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
	
	JSONObject jsonBody = new JSONObject(Objects.requireNonNull(response.getBody()));

	return jsonBody.getString("access_token");
}

        2.创建订单

        创建订单接口需要先调用鉴权接口获取请求token,再根据自己的业务情况创建具体的请求对象,进行请求(这里最好是把PayPal的订单号和本地订单号的关联关系保存起来)

        接口返回的body内容就是需要返回给前端createOrder()调用的信息了

这里的Order是我本地的订单对象

import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

public String createOrderToPayPal(Order order, BillDetail billDetail) throws Exception {
	String url = "https://api-m.sandbox.paypal.com/v2/checkout/orders";
	// 鉴权接口获取的token值
    String accessToken = this.getAccessToken();

	HttpHeaders headers = new HttpHeaders();
	headers.set("Content-Type", "application/json");
	headers.set("Authorization", "Bearer " + accessToken);

    // 根据本地订单信息和收货信息生成请求数据(按各自项目业务来)
	String requestBody = createOrder.buildRequestBody(order, billDetail).toString();
	HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

	RestTemplate restTemplate = new RestTemplate();
	ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
    String post = response.getBody();

    JSONObject jsonBody = new JSONObject(Objects.requireNonNull(post));
    // PayPal订单号
    String paypalOrderNo = jsonBody.getString("id");
    // todo 保存本地订单和PayPal订单号关系,以便后续操作

    // 返回给前端createOrder()函数的信息
	return post;
}

         我这里是按我所需设置创建订单的请求数据,大家可以按官方文档定制自己业务所需要的值(官方文档地址:https://developer.paypal.com/docs/api/orders/v2/#orders_create

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * CreateOrder
 *
 * @author Peterxie
 */
@Component
public class CreateOrder {

    /**
     * 收款方邮箱地址
     */
    @Value("${paypal.email-address}")
    private String payeeEmailAddress;

    /**
     * 收款方账号ID
     */
    @Value("${paypal.merchant-id}")
    private String payeeMerchantId;

    public JSONObject buildRequestBody(Order order) {
        Map<String, Object> requestBodyMap = new HashMap<>();
        requestBodyMap.put("purchase_units", this.buildPurchaseUnits(order));
        // 立即捕获付款
        requestBodyMap.put("intent", "CAPTURE");
        requestBodyMap.put("payment_source", this.buildPaymentSource());

        return new JSONObject(requestBodyMap);
    }

    private List<Map<String, Object>> buildPurchaseUnits(Order order) {
        Map<String, Object> purchaseUnitMap = new HashMap<>();
        // 本地订单号
        purchaseUnitMap.put("reference_id", order.getOrderCode());
        // 支付金额
        purchaseUnitMap.put("amount", this.buildAmount(order));
        purchaseUnitMap.put("payee", this.buildPayee());

        return Lists.newArrayList(purchaseUnitMap);
    }

    private Map<String, Object> buildAmount(Order order) {
        Map<String, Object> amountMap = new HashMap<>();
        // 货币类型
        amountMap.put("currency_code", "USD");
        // 支付金额
        amountMap.put("value", order.getAmount().setScale(2, RoundingMode.HALF_UP).toString());

        return amountMap;
    }

    private Map<String, Object> buildPayee() {
        Map<String, Object> payeeMap = new HashMap<>();
        payeeMap.put("email_address", payeeEmailAddress);
        payeeMap.put("merchant_id", payeeMerchantId);

        return payeeMap;
    }

    private Map<String, Object> buildPaymentSource() {
        Map<String, Object> paymentSourceMap = new HashMap<>();
        paymentSourceMap.put("paypal", this.buildPayPal());

        return paymentSourceMap;
    }

    private Map<String, Object> buildPayPal() {
        Map<String, Object> payPalMap = new HashMap<>();
        payPalMap.put("experience_context", this.buildExperienceContext());

        return payPalMap;
    }

    private Map<String, Object> buildExperienceContext() {
        Map<String, Object> experienceContextMap = new HashMap<>();
        // 公司名称,自定义
        experienceContextMap.put("brand_name", "XZ-Shopping");
        // 不在PayPal页面中设置收货地址信息,只付款
        experienceContextMap.put("shipping_preference", "NO_SHIPPING");
        experienceContextMap.put("landing_page", "LOGIN");
        experienceContextMap.put("user_action", "PAY_NOW");
        experienceContextMap.put("payment_method_preference", "IMMEDIATE_PAYMENT_REQUIRED");
        experienceContextMap.put("locale", "en-US");

        return experienceContextMap;
    }
}

        3.确认支付

        同样在请求前需要先调用鉴权接口获取请求token;请求返回成功时,在返回数据中会有一个当前订单对应的capture_id需要保存起来,在后续的退款中会需要用上

        接口返回的body内容就是需要返回给前端onApprove(data)调用的信息了

方法入参orderID为PayPal的订单号

import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

public String capture(String orderID) throws Exception {
	String url = "https://www.sandbox.paypal.com/v2/checkout/orders/" + orderID + "/capture";
	// 鉴权接口获取的token值
    String accessToken = this.getAccessToken();
	
	HttpHeaders headers = new HttpHeaders();
	headers.set("Content-Type", "application/json");
	headers.set("Authorization", "Bearer " + accessToken);

	HttpEntity<String> entity = new HttpEntity<>(headers);

	RestTemplate restTemplate = new RestTemplate();
	ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

	JSONObject jsonBody = new JSONObject(Objects.requireNonNull(response.getBody()));
	if (!jsonBody.getString("status").equals("COMPLETED")) {
		// 请求失败结果处理
	} else {
        // 获取capture_id
		String captureId = ((JSONObject) ((JSONArray) ((JSONObject) ((JSONObject) ((JSONArray) jsonBody.get("purchase_units")).get(0)).get("payments")).get("captures")).get(0)).get("id").toString();
		// todo 保存capture_id
	}
	return response.getBody();
}

        4.退款

         同样在请求前需要先调用鉴权接口获取请求token;该接口只获取PayPal退款处理结果,退款页面根据自己的项目设计处理

 这里的Order是我本地的订单对象,order.getCaptureId()是确认支付时保存的capture_id,order.getRefundAmount()是订单退款金额

import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

public boolean refund(Order order) throws Exception {
	String url = "https://api-m.sandbox.paypal.com/v2/payments/captures/" + order.getCaptureId() + "/refund";
	// 鉴权接口获取的token值
	String accessToken = this.getAccessToken();
	
	HttpHeaders headers = new HttpHeaders();
	headers.set("Content-Type", "application/json");
	headers.set("Authorization", "Bearer " + accessToken);
	headers.set("Prefer", "return=representation");

	// 根据退款金额生成请求数据
	String requestBody = RefundOrder.buildRequestBody(order.getRefundAmount()).toString();
	HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

	RestTemplate restTemplate = new RestTemplate();
	ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

	JSONObject jsonBody = new JSONObject(Objects.requireNonNull(response.getBody()));
	if (!jsonBody.getString("status").equals("COMPLETED")) {
		// 请求失败结果处理
	}

	return true;
}

         我这里是按我所需设置订单退款的请求数据,大家可以按官方文档定制自己业务所需要的值(官方文档地址:https://developer.paypal.com/docs/api/payments/v2/#captures_refund

import org.json.JSONObject;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * RefundOrder
 *
 * @author Peterxie
 */
public class RefundOrder {

    public static JSONObject buildRequestBody(BigDecimal refundAmount) {
        Map<String, Object> requestBodyMap = new HashMap<>();
        requestBodyMap.put("amount", buildAmount(refundAmount));
        // 退款理由
        requestBodyMap.put("note_to_payer", "客户要求退款");

        return new JSONObject(requestBodyMap);
    }

    private static Map<String, Object> buildAmount(BigDecimal refundAmount) {
        Map<String, Object> amountMap = new HashMap<>();
        // 货币类型
        amountMap.put("currency_code", "USD");
        // 退款金额
        amountMap.put("value", refundAmount);

        return amountMap;
    }
}

五、总结

        我这就是最简单的一种PayPal接口的应用,只是单纯得实现了单个订单的付款、退款;但其实在PayPal的REST APIs还有非常多的接口,比如SKU接口、发票接口等等可以去学习;

        我这也是从各处参考,加上自己的理解得出来的结果,如果有哪些地方有问题的,希望可以帮我指出来,谢谢!

  • 18
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot 是一款基于 Spring 框架的开源应用程序框架,它旨在简化和加速Java应用程序的开发和部署。PayPal 则是一家全球领先的在线支付解决方案提供商。两者结合在一起,意味着我们可以使用 Spring Boot 来集成 PayPal支付功能。 通过 Spring BootPayPal 的集成,我们可以轻松地向我们的应用程序添加支付功能。我们可以使用 PayPalAPI 来创建付款,接收付款以及管理退款。通过集成 PayPal,我们可以为用户提供一种方便,安全和可靠的在线支付方式。 在实现这个集成过程中,需要引入 PayPalJava SDK,它提供了与 PayPal 平台进行通信的各种方法和功能。可以使用 Maven 或 Gradle 管理工具来添加相应的依赖项。然后,我们可以在 Spring Boot 应用程序中使用这些 SDK 提供的 API实现支付功能。 在开发过程中,我们需要配置 PayPalAPI 密钥和其他相关参数,以便与 PayPal 平台进行通信。这些配置可以在应用程序的配置文件中设置,以便在运行时动态加载。 一旦集成完成,我们就可以通过调用相应的 API 来处理付款请求和响应。例如,我们可以创建一个付款订单,设置收款人和订单金额,然后使用 PayPalAPI 请求来提交付款。成功的付款将触发回调通知,我们可以使用这些通知来更新订单状态或进行其他后续操作。 总而言之,Spring BootPayPal 的集成为我们提供了一个简单和方便的方式来实现在线支付功能。它使我们能够更快速地构建和部署应用程序,并为用户提供安全可靠的支付体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值