paypal v2

This commit is contained in:
egzosn
2021-01-17 22:38:42 +08:00
parent c6cfd2e8d1
commit b14bdf0d98
13 changed files with 1813 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
package com.egzosn.pay.demo.controller;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.egzosn.pay.common.api.PayService;
import com.egzosn.pay.common.bean.DefaultCurType;
import com.egzosn.pay.common.bean.RefundOrder;
import com.egzosn.pay.common.bean.RefundResult;
import com.egzosn.pay.paypal.api.PayPalConfigStorage;
import com.egzosn.pay.paypal.v2.api.PayPalPayService;
import com.egzosn.pay.paypal.v2.bean.PayPalOrder;
import com.egzosn.pay.paypal.v2.bean.order.AddressPortable;
import com.egzosn.pay.paypal.v2.bean.order.Name;
import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail;
/**
* 发起支付入口
*
* @author: egan
* email egzosn@gmail.com
* date 2018/05/06 10:30
*/
@RestController
@RequestMapping("payPalV2")
public class PayPalV2PayController {
private PayService service = null;
@PostConstruct
public void init() {
PayPalConfigStorage storage = new PayPalConfigStorage();
storage.setClientID("AZDS0IhUZvJTO99unlvSDMfbZIP-p-UecYXZdJoweha9LFuqKXKcQIGZgfVaX6oGiAOJAUuJD7JwyTl1");
storage.setClientSecret("EK2YaOrw3oLSDWIRzvb9BWGTjiPPhY1fFUu5ylhUsGYLc_h_dlpJ0hr_LDEkbO9MyKP2P83YcywbPaem");
storage.setTest(true);
//发起付款后的页面转跳地址
storage.setReturnUrl("http://www.egzosn.com/payPal/payBack.json");
// 注意:这里不是异步回调的通知 IPN 地址设置的路径https://developer.paypal.com/developer/ipnSimulator/
//取消按钮转跳地址,
storage.setCancelUrl("http://www.egzosn.com/pay/cancel");
service = new PayPalPayService(storage);
}
/**
* 跳到支付页面
* 针对实时支付,即时付款
*
* @param price 金额
* @return 跳到支付页面
*/
@RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8")
public String toPay(BigDecimal price) {
//及时收款
PayPalOrder order = new PayPalOrder();
order.setBrandName("该标签将覆盖PayPal网站上PayPal帐户中的公司名称,非必填");
order.setDescription("订单说明");
order.setInvoiceId("非必填 API调用者为该订单提供的外部发票号码。出现在付款人的交易历史记录和付款人收到的电子邮件中。");
order.setCustomId("非必填 api调用中没发现有任何用处 API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中但付款人不可见");
order.setPrice(price);
order.setShippingDetail(new ShippingDetail()
.name(new Name().fullName("RATTA"))
.addressPortable(new AddressPortable()
.addressLine1("梅陇镇")
.addressLine2("集心路168号")
.adminArea2("闵行区")
.adminArea1("上海市")
.postalCode("20000")
.countryCode("CN")));
String toPayHtml = service.toPay(order);
//某些支付下单时无法设置单号,通过下单后返回对应单号,如 paypal友店。
String tradeNo = order.getTradeNo();
System.out.println("支付订单号:" + tradeNo + " 这里可以进行回存");
return toPayHtml;
}
/**
* 申请退款接口
*
* @return 返回支付方申请退款后的结果
*/
@RequestMapping("refund")
public RefundResult refund() {
// TODO 这里需要 refundAmount curType description tradeNo
RefundOrder order = new RefundOrder();
order.setCurType(DefaultCurType.USD);
order.setDescription(" description ");
order.setTradeNo("paypal 平台的单号, 支付下单返回的单号");
order.setRefundAmount(BigDecimal.valueOf(0.01));
return service.refund(order);
}
/**
* 注意:这里不是异步回调的通知 IPN 地址设置的路径https://developer.paypal.com/developer/ipnSimulator/
* PayPal确认付款调用的接口
* 用户确认付款后paypal调用的这个方法执行付款
*
* @param request 请求
* @return 付款成功信息
* @throws IOException IOException
*/
@GetMapping(value = "payBackBefore.json")
public String payBackBefore(HttpServletRequest request) throws IOException {
try (InputStream is = request.getInputStream()) {
if (service.verify(service.getParameter2Map(request.getParameterMap(), is))) {
// TODO 这里进行成功后的订单业务处理
// TODO 返回成功付款页面,这个到时候再做一个漂亮的页面显示,并使用前后端分离的模式
return service.successPayOutMessage(null).toMessage();
}
}
return "failure";
}
/* */
/**
* 支付回调地址
* 注意:这里不是异步回调的通知 IPN 地址设置的路径https://developer.paypal.com/developer/ipnSimulator/
*
* @param request 请求
* @return 结果
* @throws IOException IOException
* 业务处理在对应的PayMessageHandler里面处理在哪里设置PayMessageHandler详情查看{@link PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)}
* <p>
* 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler}
*/
@RequestMapping(value = "payBack.json")
public String payBack(HttpServletRequest request) throws IOException {
//业务处理在对应的PayMessageHandler里面处理在哪里设置PayMessageHandler详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler()
return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage();
}
}

View File

@@ -0,0 +1,26 @@
package com.egzosn.pay.paypal.v2.api;
import java.util.Map;
import com.egzosn.pay.common.bean.outbuilder.TextBuilder;
/**
* @author Egan
* <pre>
* email egzosn@gmail.com
* date 2021/1/17
* </pre>
*/
public class PayPalOutMessageBuilder extends TextBuilder {
public PayPalOutMessageBuilder(Map<String, Object> message) {
StringBuilder out = new StringBuilder();
for (Map.Entry<String, Object> entry : message.entrySet()) {
out.append(entry.getKey()).append('=').append(entry.getValue()).append("<br>");
}
super.content(out.toString());
}
}

View File

@@ -0,0 +1,437 @@
package com.egzosn.pay.paypal.v2.api;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import org.apache.http.Header;
import org.apache.http.entity.ContentType;
import org.apache.http.message.BasicHeader;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.egzosn.pay.common.api.BasePayService;
import com.egzosn.pay.common.bean.CurType;
import com.egzosn.pay.common.bean.DefaultCurType;
import com.egzosn.pay.common.bean.MethodType;
import com.egzosn.pay.common.bean.PayMessage;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.common.bean.PayOutMessage;
import com.egzosn.pay.common.bean.RefundOrder;
import com.egzosn.pay.common.bean.RefundResult;
import com.egzosn.pay.common.bean.TransactionType;
import com.egzosn.pay.common.bean.result.PayException;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.http.HttpHeader;
import com.egzosn.pay.common.http.HttpStringEntity;
import com.egzosn.pay.common.http.UriVariables;
import com.egzosn.pay.common.util.Util;
import com.egzosn.pay.common.util.str.StringUtils;
import com.egzosn.pay.paypal.api.PayPalConfigStorage;
import com.egzosn.pay.paypal.v2.bean.PayPalRefundResult;
import com.egzosn.pay.paypal.v2.bean.PayPalTransactionType;
import com.egzosn.pay.paypal.v2.bean.order.ApplicationContext;
import com.egzosn.pay.paypal.v2.bean.order.Money;
import com.egzosn.pay.paypal.v2.bean.order.OrderRequest;
import com.egzosn.pay.paypal.v2.bean.order.PurchaseUnitRequest;
import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail;
/**
* 贝宝支付配置存储
*
* @author egan
* <p>
* email egzosn@gmail.com
* date 2021-1-16 22:15:09
*/
public class PayPalPayService extends BasePayService<PayPalConfigStorage> {
/**
* 沙箱环境
*/
private static final String SANDBOX_REQ_URL = "https://api.sandbox.paypal.com/";
/**
* 正式测试环境
*/
private static final String REQ_URL = "https://api.paypal.com/";
private static final String NOTIFY_VALIDATE_URL = "https://ipnpb.paypal.com/cgi-bin/webscr?cmd=_notify-validate&";
private static final String SANDBOX_NOTIFY_VALIDATE_URL = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&";
/**
* 获取对应的请求地址
*
* @return 请求地址
*/
@Override
public String getReqUrl(TransactionType transactionType) {
return (payConfigStorage.isTest() ? SANDBOX_REQ_URL : REQ_URL) + transactionType.getMethod();
}
/**
* 获取通知校验对应的请求地址
*
* @param params 回调参数
* @return 请求地址
*/
public String getNotifyReqUrl(Map<String, Object> params) {
return (payConfigStorage.isTest() ? SANDBOX_NOTIFY_VALIDATE_URL : NOTIFY_VALIDATE_URL) + UriVariables.getMapToParameters(params);
}
public PayPalPayService(PayPalConfigStorage payConfigStorage) {
super(payConfigStorage);
}
/**
* 获取请求token
*
* @return 授权令牌
*/
public String getAccessToken() {
return getAccessToken(false);
}
/**
* 获取授权令牌
*
* @param forceRefresh 是否重新获取, true重新获取
* @return 新的授权令牌
* @throws PayErrorException 支付异常
*/
public String getAccessToken(boolean forceRefresh) throws PayErrorException {
Lock lock = payConfigStorage.getAccessTokenLock();
try {
lock.lock();
if (forceRefresh) {
payConfigStorage.expireAccessToken();
}
if (payConfigStorage.isAccessTokenExpired()) {
Map<String, String> header = new HashMap<>();
header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppid(), getPayConfigStorage().getKeyPrivate()));
header.put("Accept", "application/json");
header.put("Content-Type", "application/x-www-form-urlencoded");
try {
HttpStringEntity entity = new HttpStringEntity("grant_type=client_credentials", header);
JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.AUTHORIZE), entity, JSONObject.class);
payConfigStorage.updateAccessToken(String.format("%s %s", resp.getString("token_type"), resp.getString("access_token")), resp.getIntValue("expires_in"));
}
catch (UnsupportedEncodingException e) {
throw new PayErrorException(new PayException("failure", e.getMessage()));
}
return payConfigStorage.getAccessToken();
}
}
finally {
lock.unlock();
}
return payConfigStorage.getAccessToken();
}
/**
* IPN 地址设置的路径https://developer.paypal.com/developer/ipnSimulator/
* 1.Check that the payment_status is Completed.
* 2.If the payment_status is Completed, check the txn_id against the previous PayPal transaction that you processed to ensure the IPN message is not a duplicate.
* 3.Check that the receiver_email is an email address registered in your PayPal account.
* 4.Check that the price (carried in mc_gross) and the currency (carried in mc_currency) are correct for the item (carried in item_name or item_number).
*
* @param params 回调回来的参数集
* @return
*/
@Override
public boolean verify(Map<String, Object> params) {
Object paymentStatus = params.get("payment_status");
if (!"Completed".equals(paymentStatus)) {
LOG.warn("状态未完成:" + paymentStatus);
return false;
}
String resp = getHttpRequestTemplate().getForObject(getNotifyReqUrl(params), authHeader(), String.class);
return "VERIFIED".equals(resp);
}
/**
* 获取授权请求头
*
* @return 授权请求头
*/
private HttpHeader authHeader() {
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Authorization", getAccessToken()));
headers.add(new BasicHeader("PayPal-Request-Id", UUID.randomUUID().toString()));
return new HttpHeader(headers);
}
/**
* 页面转跳支付, 返回对应页面重定向信息
*
* @param order 订单信息
* @return 对应页面重定向信息
*/
@Override
public String toPay(PayOrder order) {
order.setTransactionType(PayPalTransactionType.CHECKOUT);
return super.toPay(order);
}
private ApplicationContext initUrl(ApplicationContext applicationContext, PayOrder order) {
String cancelUrl = (String) order.getAttr("cancelUrl");
if (StringUtils.isEmpty(cancelUrl)) {
cancelUrl = payConfigStorage.getCancelUrl();
}
String returnUrl = (String) order.getAttr("returnUrl");
if (StringUtils.isEmpty(returnUrl)) {
returnUrl = payConfigStorage.getReturnUrl();
}
applicationContext
.cancelUrl(cancelUrl)
.returnUrl(returnUrl);
return applicationContext;
}
private ApplicationContext createApplicationContext(PayOrder order) {
ApplicationContext applicationContext = new ApplicationContext();
initUrl(applicationContext, order);
String brandName = (String) order.getAttr("brandName");
if (StringUtils.isEmpty(brandName)) {
applicationContext.setBrandName(brandName);
}
String landingPage = (String) order.getAttr("landingPage");
if (StringUtils.isEmpty(landingPage)) {
applicationContext.setLandingPage(landingPage);
}
String shippingPreference = (String) order.getAttr("shippingPreference");
if (StringUtils.isEmpty(shippingPreference)) {
applicationContext.setShippingPreference(shippingPreference);
}
String userAction = (String) order.getAttr("userAction");
if (StringUtils.isEmpty(userAction)) {
applicationContext.setUserAction(userAction);
}
return applicationContext;
}
/**
* 返回创建的订单信息
*
* @param order 支付订单
* @return 订单信息
* @see PayOrder 支付订单信息
*/
@Override
public Map<String, Object> orderInfo(PayOrder order) {
if (null == order.getTransactionType()) {
order.setTransactionType(PayPalTransactionType.CHECKOUT);
}
OrderRequest orderRequest = new OrderRequest();
orderRequest.setCheckoutPaymentIntent("CAPTURE");
orderRequest.setApplicationContext(createApplicationContext(order));
List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<PurchaseUnitRequest>();
CurType curType = order.getCurType();
if (null == curType) {
curType = DefaultCurType.USD;
}
PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
.description(order.getSubject())
.invoiceId((String) order.getAttr("invoiceId"))
.customId(order.getOutTradeNo())
.money(new Money()
.currencyCode(curType.getType())
.value(Util.conversionAmount(order.getPrice()).toString()));
Object shippingDetail = order.getAttr("shippingDetail");
if (shippingDetail instanceof ShippingDetail) {
purchaseUnitRequest.setShippingDetail((ShippingDetail) shippingDetail);
}
else {
ShippingDetail shippingDetail1 = JSON.parseObject(JSON.toJSONString(shippingDetail), ShippingDetail.class);
purchaseUnitRequest.setShippingDetail(shippingDetail1);
}
purchaseUnitRequests.add(purchaseUnitRequest);
orderRequest.setPurchaseUnits(purchaseUnitRequests);
HttpStringEntity entity = new HttpStringEntity(JSON.toJSONString(orderRequest), ContentType.APPLICATION_JSON);
HttpHeader header = authHeader();
header.addHeader(new BasicHeader("prefer", "return=representation"));
entity.setHeaders(header);
JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class);
if ("created".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) {
order.setTradeNo(resp.getString("id"));
}
return preOrderHandler(resp, order);
}
@Override
public PayOutMessage getPayOutMessage(String code, String message) {
String out = "The response from IPN was: <b>" + code + "</b>";
return PayOutMessage.TEXT().content(out).build();
}
@Override
public PayOutMessage successPayOutMessage(PayMessage payMessage) {
Map<String, Object> message = payMessage.getPayMessage();
return new PayPalOutMessageBuilder(message).build();
}
@Override
public String buildRequest(Map<String, Object> orderInfo, MethodType method) {
if (orderInfo instanceof JSONObject) {
JSONObject resp = (JSONObject) orderInfo;
JSONArray links = resp.getJSONArray("links");
for (int i = 0; i < links.size(); i++) {
JSONObject link = links.getJSONObject(i);
if ("approve".equals(link.getString("rel"))) {
return String.format("<script type=\"text/javascript\">location.href=\"%s\"</script>", link.getString("href"));
}
}
}
return "<script type=\"text/javascript\">location.href=\"/\"</script>";
}
@Override
public String getQrPay(PayOrder order) {
return null;
}
@Override
public Map<String, Object> microPay(PayOrder order) {
return null;
}
/**
* 交易查询接口
*
* @param tradeNo 支付平台订单号
* @param outTradeNo 商户单号
* @return 返回查询回来的结果集,支付方原值返回
*/
@Override
public Map<String, Object> query(String tradeNo, String outTradeNo) {
JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, tradeNo);
return resp;
}
@Override
public Map<String, Object> close(String tradeNo, String outTradeNo) {
return null;
}
/**
* 申请退款接口
*
* @param refundOrder 退款订单信息
* @return 返回支付方申请退款后的结果
*/
@Override
public RefundResult refund(RefundOrder refundOrder) {
JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, refundOrder.getTradeNo());
if (!"COMPLETED".equals(ordersCaptureInfo.getString("status"))) {
return new PayPalRefundResult(ordersCaptureInfo, refundOrder.getTradeNo());
}
String captureId = ordersCaptureInfo.getJSONArray("purchaseUnits").getJSONObject(0).getJSONObject("payments").getJSONArray("captures").getJSONObject(0).getString("id");
JSONObject request = new JSONObject();
Money amount = new Money();
if (null == refundOrder.getCurType()) {
refundOrder.setCurType(DefaultCurType.USD);
}
amount.setCurrencyCode(refundOrder.getCurType().getType());
amount.value(Util.conversionAmount(refundOrder.getRefundAmount()).toString());
request.put("amount", amount);
request.put("note_to_payer", refundOrder.getDescription());
request.put("invoiceId", refundOrder.getOutTradeNo());
HttpStringEntity httpEntity = new HttpStringEntity(request.toJSONString(), ContentType.APPLICATION_JSON);
httpEntity.setHeaders(authHeader());
JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, captureId);
PayPalRefundResult payPalRefundResult = new PayPalRefundResult(resp, refundOrder.getTradeNo());
refundOrder.setRefundNo(payPalRefundResult.getRefundNo());
return payPalRefundResult;
}
/**
* 查询退款
*
* @param refundOrder 退款订单单号信息
* @return 返回支付方查询退款后的结果
*/
@Override
public Map<String, Object> refundquery(RefundOrder refundOrder) {
JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.REFUND_GET), authHeader(), JSONObject.class, refundOrder.getRefundNo());
return resp;
}
@Override
public Map<String, Object> downloadbill(Date billDate, String billType) {
return Collections.emptyMap();
}
/**
* 将请求参数或者请求流转化为 Map
*
* @param parameterMap 请求参数
* @param is 请求流
* @return 获得回调的请求参数
*/
@Override
public Map<String, Object> getParameter2Map(Map<String, String[]> parameterMap, InputStream is) {
Map<String, Object> params = new LinkedHashMap<>();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String name = entry.getKey();
String[] values = entry.getValue();
String valueStr = "";
for (int i = 0, len = values.length; i < len; i++) {
valueStr += (i == len - 1) ? values[i] : values[i] + ",";
}
if (StringUtils.isNotEmpty(payConfigStorage.getInputCharset()) && !valueStr.matches("\\w+")) {
try {
if (valueStr.equals(new String(valueStr.getBytes("iso8859-1"), "iso8859-1"))) {
valueStr = new String(valueStr.getBytes("iso8859-1"), payConfigStorage.getInputCharset());
}
}
catch (UnsupportedEncodingException e) {
LOG.error(e);
}
}
params.put(name, valueStr);
}
return params;
}
}

View File

@@ -0,0 +1,189 @@
package com.egzosn.pay.paypal.v2.bean;
import com.egzosn.pay.common.bean.CurType;
import com.egzosn.pay.common.bean.DefaultCurType;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail;
/**
* PayPal付款订单
*
* @author Egan
* <pre>
* email egzosn@gmail.com
* date 2021/1/12
* </pre>
*/
public class PayPalOrder extends PayOrder {
/**
* 该标签将覆盖PayPal网站上PayPal帐户中的公司名称
*/
private String brandName;
/**
* 支付成功之后回调的页面
*/
private String returnUrl;
/**
* 取消支付的页面
* <pre>
* 注意:这里不是异步回调的通知
* IPN 地址设置的路径https://developer.paypal.com/developer/ipnSimulator/
* </pre>
*/
private String cancelUrl;
/**
* LOGIN。当客户单击PayPal Checkout时客户将被重定向到页面以登录PayPal并批准付款。
* BILLING。当客户单击PayPal Checkout时客户将被重定向到一个页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息
* NO_PREFERENCE。当客户单击“ PayPal Checkout”时将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。
* 默认值NO_PREFERENCE
*/
private String landingPage = "NO_PREFERENCE";
/**
* GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。
* NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品
* SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址
*/
private String shippingPreference = "NO_SHIPPING";
/**
* CONTINUE。将客户重定向到PayPal付款页面后将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。
* PAY_NOW。将客户重定向到PayPal付款页面后出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。
*/
private String userAction = "CONTINUE";
private ShippingDetail shippingDetail;
/**
* API调用者为购买单元提供的外部ID。当您必须通过“补丁”更新订单时需要多个购买单位。如果忽略该值且订单只包含一个购买单元PayPal将该值设置为' default '。
*/
private String referenceId;
/**
* API调用者为该订单提供的外部发票号码。出现在付款人的交易历史记录和付款人收到的电子邮件中
*/
private String invoiceId;
/**
* API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中但付款人不可见。
*
* @return 外部ID
*/
public String getCustomId() {
return super.getOutTradeNo();
}
/**
* /**
* API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中但付款人不可见。
*
* @param customId 外部ID
*/
public void setCustomId(String customId) {
super.setOutTradeNo(customId);
}
public String getDescription() {
return super.getSubject();
}
public void setDescription(String description) {
super.setSubject(description);
}
public String getCurrencyCode() {
CurType curType = super.getCurType();
if (null == curType) {
curType = DefaultCurType.USD;
}
return curType.getType();
}
public void setCurrencyCode(CurType currencyCode) {
super.setCurType(currencyCode);
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
super.addAttr("brandName", brandName);
this.brandName = brandName;
}
public String getReturnUrl() {
return returnUrl;
}
public void setReturnUrl(String returnUrl) {
super.addAttr("returnUrl", returnUrl);
this.returnUrl = returnUrl;
}
public String getCancelUrl() {
return cancelUrl;
}
public void setCancelUrl(String cancelUrl) {
super.addAttr("cancelUrl", cancelUrl);
this.cancelUrl = cancelUrl;
}
public String getLandingPage() {
return landingPage;
}
public void setLandingPage(String landingPage) {
super.addAttr("landingPage", landingPage);
this.landingPage = landingPage;
}
public String getShippingPreference() {
return shippingPreference;
}
public void setShippingPreference(String shippingPreference) {
super.addAttr("shippingPreference", shippingPreference);
this.shippingPreference = shippingPreference;
}
public String getUserAction() {
return userAction;
}
public void setUserAction(String userAction) {
super.addAttr("userAction", userAction);
this.userAction = userAction;
}
public ShippingDetail getShippingDetail() {
return shippingDetail;
}
public void setShippingDetail(ShippingDetail shippingDetail) {
super.addAttr("shippingDetail", shippingDetail);
this.shippingDetail = shippingDetail;
}
public String getReferenceId() {
return referenceId;
}
public void setReferenceId(String referenceId) {
super.addAttr("referenceId", referenceId);
this.referenceId = referenceId;
}
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(String invoiceId) {
super.addAttr("invoiceId", referenceId);
this.invoiceId = invoiceId;
}
}

View File

@@ -0,0 +1,119 @@
package com.egzosn.pay.paypal.v2.bean;
import java.math.BigDecimal;
import java.util.Map;
import com.egzosn.pay.common.bean.BaseRefundResult;
import com.egzosn.pay.common.bean.CurType;
/**
* @author Egan
* <pre>
* email egzosn@gmail.com
* date 2021/1/16
* </pre>
*/
public class PayPalRefundResult extends BaseRefundResult {
/**
* 支付平台订单号,交易号
*/
private String tradeNo;
public PayPalRefundResult(Map<String, Object> attrs, String tradeNo) {
super(attrs);
this.tradeNo = tradeNo;
}
/**
* 获取退款请求结果状态码
*
* @return 状态码
*/
@Override
public String getCode() {
return getAttrString("state");
}
/**
* 获取退款请求结果状态提示信息
*
* @return 提示信息
*/
@Override
public String getMsg() {
return null;
}
/**
* 返回业务结果状态码
*
* @return 业务结果状态码
*/
@Override
public String getResultCode() {
return getAttrString("state");
}
/**
* 返回业务结果状态提示信息
*
* @return 业务结果状态提示信息
*/
@Override
public String getResultMsg() {
return null;
}
/**
* 退款金额
*
* @return 退款金额
*/
@Override
public BigDecimal getRefundFee() {
return null;
}
/**
* 退款币种信息
*
* @return 币种信息
*/
@Override
public CurType getRefundCurrency() {
return null;
}
/**
* 支付平台交易号
* 发起支付时 支付平台(如支付宝)返回的交易订单号
*
* @return 支付平台交易号
*/
@Override
public String getTradeNo() {
return tradeNo;
}
/**
* 支付订单号
* 发起支付时,用户系统的订单号
*
* @return 支付订单号
*/
@Override
public String getOutTradeNo() {
return null;
}
/**
* 商户退款单号
*
* @return 商户退款单号
*/
@Override
public String getRefundNo() {
return getAttrString("id");
}
}

View File

@@ -0,0 +1,74 @@
package com.egzosn.pay.paypal.v2.bean;
import com.egzosn.pay.common.bean.TransactionType;
/**
* 贝宝交易类型
* <pre>
* 说明交易类型主要用于支付接口调用参数所需
*
*
*
* </pre>
*
* @author egan
* <p>
* email egzosn@gmail.com
* date 2018/04/28 11:10
*/
public enum PayPalTransactionType implements TransactionType {
/**
* 获取token
*/
AUTHORIZE("v1/oauth2/token"),
/**
* 付款 网页支付
*/
CHECKOUT("v2/checkout/orders"),
/**
* 获取订单信息
*/
ORDERS_GET("/v2/checkout/orders/{order_id}"),
/**
* 获取订单信息
*/
ORDERS_CAPTURE("/v2/checkout/orders/{order_id}/capture"),
/**
* 获取订单信息
*/
CAPTURE("/v2/payments/captures/{capture_id}"),
/**
* 退款
*/
REFUND("/v2/payments/captures/{capture_id}/refund"),
/**
* 退款查询
*/
REFUND_GET("/v2/payments/refunds/{refund_id}"),
;
private String method;
private PayPalTransactionType(String method) {
this.method = method;
}
@Override
public String getType() {
return this.name();
}
/**
* 获取接口名称
*
* @return 接口名称
*/
@Override
public String getMethod() {
return this.method;
}
}

View File

@@ -0,0 +1,220 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
/**
* The portable international postal address. Maps to [AddressValidationMetadata](https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata) and HTML 5.1 [Autofilling form controls: the autocomplete attribute](https://www.w3.org/TR/html51/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
*/
public class AddressPortable {
public AddressPortable() {
}
/**
* The first line of the address. For example, number or street. For example, `173 Drury Lane`. Required for data entry and compliance and risk checks. Must contain the full address.
*/
@JSONField(name = "address_line_1")
private String addressLine1;
public String addressLine1() {
return addressLine1;
}
public AddressPortable addressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
return this;
}
/**
* The second line of the address. For example, suite or apartment number.
*/
@JSONField(name = "address_line_2")
private String addressLine2;
public String addressLine2() {
return addressLine2;
}
public AddressPortable addressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
return this;
}
/**
* The third line of the address, if needed. For example, a street complement for Brazil, direction text, such as `next to Walmart`, or a landmark in an Indian address.
*/
@JSONField(name = "address_line_3")
private String addressLine3;
public String addressLine3() {
return addressLine3;
}
public AddressPortable addressLine3(String addressLine3) {
this.addressLine3 = addressLine3;
return this;
}
/**
* The highest level sub-division in a country, which is usually a province, state, or ISO-3166-2 subdivision. Format for postal delivery. For example, `CA` and not `California`. Value, by country, is:<ul><li>UK. A county.</li><li>US. A state.</li><li>Canada. A province.</li><li>Japan. A prefecture.</li><li>Switzerland. A kanton.</li></ul>
*/
@JSONField(name = "admin_area_1")
private String adminArea1;
public String adminArea1() {
return adminArea1;
}
public AddressPortable adminArea1(String adminArea1) {
this.adminArea1 = adminArea1;
return this;
}
/**
* A city, town, or village. Smaller than `admin_area_level_1`.
*/
@JSONField(name = "admin_area_2")
private String adminArea2;
public String adminArea2() {
return adminArea2;
}
public AddressPortable adminArea2(String adminArea2) {
this.adminArea2 = adminArea2;
return this;
}
/**
* A sub-locality, suburb, neighborhood, or district. Smaller than `admin_area_level_2`. Value is:<ul><li>Brazil. Suburb, bairro, or neighborhood.</li><li>India. Sub-locality or district. Street name information is not always available but a sub-locality or district can be a very small area.</li></ul>
*/
@JSONField(name = "admin_area_3")
private String adminArea3;
public String adminArea3() {
return adminArea3;
}
public AddressPortable adminArea3(String adminArea3) {
this.adminArea3 = adminArea3;
return this;
}
/**
* The neighborhood, ward, or district. Smaller than `admin_area_level_3` or `sub_locality`. Value is:<ul><li>The postal sorting code for Guernsey and many French territories, such as French Guiana.</li><li>The fine-grained administrative levels in China.</li></ul>
*/
@JSONField(name = "admin_area_4")
private String adminArea4;
public String adminArea4() {
return adminArea4;
}
public AddressPortable adminArea4(String adminArea4) {
this.adminArea4 = adminArea4;
return this;
}
/**
* REQUIRED
* The [two-character ISO 3166-1 code](/docs/integration/direct/rest/country-codes/) that identifies the country or region.<blockquote><strong>Note:</strong> The country code for Great Britain is <code>GB</code> and not <code>UK</code> as used in the top-level domain names for that country. Use the `C2` country code for China worldwide for comparable uncontrolled price (CUP) method, bank card, and cross-border transactions.</blockquote>
*/
@JSONField(name = "country_code")
private String countryCode;
public String countryCode() {
return countryCode;
}
public AddressPortable countryCode(String countryCode) {
this.countryCode = countryCode;
return this;
}
/**
* The postal code, which is the zip code or equivalent. Typically required for countries with a postal code or an equivalent. See [postal code](https://en.wikipedia.org/wiki/Postal_code).
*/
@JSONField(name = "postal_code")
private String postalCode;
public String postalCode() {
return postalCode;
}
public AddressPortable postalCode(String postalCode) {
this.postalCode = postalCode;
return this;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getAddressLine3() {
return addressLine3;
}
public void setAddressLine3(String addressLine3) {
this.addressLine3 = addressLine3;
}
public String getAdminArea1() {
return adminArea1;
}
public void setAdminArea1(String adminArea1) {
this.adminArea1 = adminArea1;
}
public String getAdminArea2() {
return adminArea2;
}
public void setAdminArea2(String adminArea2) {
this.adminArea2 = adminArea2;
}
public String getAdminArea3() {
return adminArea3;
}
public void setAdminArea3(String adminArea3) {
this.adminArea3 = adminArea3;
}
public String getAdminArea4() {
return adminArea4;
}
public void setAdminArea4(String adminArea4) {
this.adminArea4 = adminArea4;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
}

View File

@@ -0,0 +1,147 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
public class ApplicationContext {
/**
* 该标签将覆盖PayPal网站上PayPal帐户中的公司名称
*/
@JSONField(name = "brand_name")
private String brandName;
@JSONField(name = "cancel_url")
private String cancelUrl;
/**
* LOGIN。当客户单击PayPal Checkout时客户将被重定向到页面以登录PayPal并批准付款。
* BILLING。当客户单击PayPal Checkout时客户将被重定向到一个页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息
* NO_PREFERENCE。当客户单击“ PayPal Checkout”时将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。
* 默认值NO_PREFERENCE
*/
@JSONField(name = "landing_page")
private String landingPage = "NO_PREFERENCE";
@JSONField(name = "return_url")
private String returnUrl;
/**
* GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。
* NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品
* SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址
*/
@JSONField(name = "shipping_preference")
private String shippingPreference = "NO_SHIPPING";
/**
* CONTINUE。将客户重定向到PayPal付款页面后将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。
* PAY_NOW。将客户重定向到PayPal付款页面后出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。
*/
@JSONField(name = "user_action")
private String userAction = "CONTINUE";
public ApplicationContext() {
}
public String brandName() {
return brandName;
}
public ApplicationContext brandName(String brandName) {
this.brandName = brandName;
return this;
}
public String cancelUrl() {
return this.cancelUrl;
}
public ApplicationContext cancelUrl(String cancelUrl) {
this.cancelUrl = cancelUrl;
return this;
}
public String landingPage() {
return this.landingPage;
}
public ApplicationContext landingPage(String landingPage) {
this.landingPage = landingPage;
return this;
}
public String returnUrl() {
return this.returnUrl;
}
public ApplicationContext returnUrl(String returnUrl) {
this.returnUrl = returnUrl;
return this;
}
public String shippingPreference() {
return this.shippingPreference;
}
public ApplicationContext shippingPreference(String shippingPreference) {
this.shippingPreference = shippingPreference;
return this;
}
public String userAction() {
return this.userAction;
}
public ApplicationContext userAction(String userAction) {
this.userAction = userAction;
return this;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCancelUrl() {
return cancelUrl;
}
public void setCancelUrl(String cancelUrl) {
this.cancelUrl = cancelUrl;
}
public String getLandingPage() {
return landingPage;
}
public void setLandingPage(String landingPage) {
this.landingPage = landingPage;
}
public String getReturnUrl() {
return returnUrl;
}
public void setReturnUrl(String returnUrl) {
this.returnUrl = returnUrl;
}
public String getShippingPreference() {
return shippingPreference;
}
public void setShippingPreference(String shippingPreference) {
this.shippingPreference = shippingPreference;
}
public String getUserAction() {
return userAction;
}
public void setUserAction(String userAction) {
this.userAction = userAction;
}
}

View File

@@ -0,0 +1,37 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
public class Money {
@JSONField(name = "currency_code")
private String currencyCode;
@JSONField(name = "value")
private String value;
public String getCurrencyCode() {
return currencyCode;
}
public void setCurrencyCode(String currencyCode) {
this.currencyCode = currencyCode;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Money currencyCode(String currencyCode) {
this.currencyCode = currencyCode;
return this;
}
public Money value(String value) {
this.value = value;
return this;
}
}

View File

@@ -0,0 +1,174 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
/**
* The name of the party.
*/
public class Name {
// Required default constructor
public Name() {
}
/**
* DEPRECATED. The party's alternate name. Can be a business name, nickname, or any other name that cannot be split into first, last name. Required when the party is a business.
*/
@JSONField(name = "alternate_full_name")
private String alternateFullName;
public String alternateFullName() {
return alternateFullName;
}
public Name alternateFullName(String alternateFullName) {
this.alternateFullName = alternateFullName;
return this;
}
/**
* When the party is a person, the party's full name.
*/
@JSONField(name = "full_name")
private String fullName;
public String fullName() {
return fullName;
}
public Name fullName(String fullName) {
this.fullName = fullName;
return this;
}
/**
* When the party is a person, the party's given, or first, name.
*/
@JSONField(name = "given_name")
private String givenName;
public String givenName() {
return givenName;
}
public Name givenName(String givenName) {
this.givenName = givenName;
return this;
}
/**
* When the party is a person, the party's middle name. Use also to store multiple middle names including the patronymic, or father's, middle name.
*/
@JSONField(name = "middle_name")
private String middleName;
public String middleName() {
return middleName;
}
public Name middleName(String middleName) {
this.middleName = middleName;
return this;
}
/**
* The prefix, or title, to the party's name.
*/
@JSONField(name = "prefix")
private String prefix;
public String prefix() {
return prefix;
}
public Name prefix(String prefix) {
this.prefix = prefix;
return this;
}
/**
* The suffix for the party's name.
*/
@JSONField(name = "suffix")
private String suffix;
public String suffix() {
return suffix;
}
public Name suffix(String suffix) {
this.suffix = suffix;
return this;
}
/**
* When the party is a person, the party's surname or family name. Also known as the last name. Required when the party is a person. Use also to store multiple surnames including the matronymic, or mother's, surname.
*/
@JSONField(name = "surname")
private String surname;
public String surname() {
return surname;
}
public Name surname(String surname) {
this.surname = surname;
return this;
}
public String getAlternateFullName() {
return alternateFullName;
}
public void setAlternateFullName(String alternateFullName) {
this.alternateFullName = alternateFullName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getGivenName() {
return givenName;
}
public void setGivenName(String givenName) {
this.givenName = givenName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}

View File

@@ -0,0 +1,43 @@
package com.egzosn.pay.paypal.v2.bean.order;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
public class OrderRequest {
@JSONField(name = "application_context")
private ApplicationContext applicationContext;
@JSONField(name = "intent")
private String checkoutPaymentIntent;
@JSONField(name =
"purchase_units"
)
private List<PurchaseUnitRequest> purchaseUnits;
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public String getCheckoutPaymentIntent() {
return checkoutPaymentIntent;
}
public void setCheckoutPaymentIntent(String checkoutPaymentIntent) {
this.checkoutPaymentIntent = checkoutPaymentIntent;
}
public List<PurchaseUnitRequest> getPurchaseUnits() {
return purchaseUnits;
}
public void setPurchaseUnits(List<PurchaseUnitRequest> purchaseUnits) {
this.purchaseUnits = purchaseUnits;
}
}

View File

@@ -0,0 +1,141 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
public class PurchaseUnitRequest {
@JSONField(name = "amount")
private Money money;
@JSONField(name = "custom_id")
private String customId;
@JSONField(name = "description")
private String description;
@JSONField(name = "invoice_id")
private String invoiceId;
@JSONField(name = "reference_id")
private String referenceId;
@JSONField(name = "soft_descriptor")
private String softDescriptor;
/**
* The shipping details.
*/
@JSONField(name = "shipping")
private ShippingDetail shippingDetail;
public Money money() {
return this.money;
}
public PurchaseUnitRequest money(Money money) {
this.money = money;
return this;
}
public String customId() {
return this.customId;
}
public PurchaseUnitRequest customId(String customId) {
this.customId = customId;
return this;
}
public String description() {
return this.description;
}
public PurchaseUnitRequest description(String description) {
this.description = description;
return this;
}
public String invoiceId() {
return this.invoiceId;
}
public PurchaseUnitRequest invoiceId(String invoiceId) {
this.invoiceId = invoiceId;
return this;
}
public String referenceId() {
return this.referenceId;
}
public PurchaseUnitRequest referenceId(String referenceId) {
this.referenceId = referenceId;
return this;
}
public String softDescriptor() {
return this.softDescriptor;
}
public PurchaseUnitRequest softDescriptor(String softDescriptor) {
this.softDescriptor = softDescriptor;
return this;
}
public PurchaseUnitRequest shippingDetail(ShippingDetail shippingDetail) {
this.shippingDetail = shippingDetail;
return this;
}
public Money getMoney() {
return money;
}
public void setMoney(Money money) {
this.money = money;
}
public String getCustomId() {
return customId;
}
public void setCustomId(String customId) {
this.customId = customId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(String invoiceId) {
this.invoiceId = invoiceId;
}
public String getReferenceId() {
return referenceId;
}
public void setReferenceId(String referenceId) {
this.referenceId = referenceId;
}
public String getSoftDescriptor() {
return softDescriptor;
}
public void setSoftDescriptor(String softDescriptor) {
this.softDescriptor = softDescriptor;
}
public ShippingDetail getShippingDetail() {
return shippingDetail;
}
public void setShippingDetail(ShippingDetail shippingDetail) {
this.shippingDetail = shippingDetail;
}
}

View File

@@ -0,0 +1,59 @@
package com.egzosn.pay.paypal.v2.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
/**
* The shipping details.
*/
public class ShippingDetail {
// Required default constructor
public ShippingDetail() {
}
/**
* The portable international postal address. Maps to [AddressValidationMetadata](https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata) and HTML 5.1 [Autofilling form controls: the autocomplete attribute](https://www.w3.org/TR/html51/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
*/
@JSONField(name = "address")
private AddressPortable addressPortable;
public AddressPortable addressPortable() {
return addressPortable;
}
public ShippingDetail addressPortable(AddressPortable addressPortable) {
this.addressPortable = addressPortable;
return this;
}
/**
* The name of the party.
*/
@JSONField(name = "name")
private Name name;
public Name name() {
return name;
}
public ShippingDetail name(Name name) {
this.name = name;
return this;
}
public AddressPortable getAddressPortable() {
return addressPortable;
}
public void setAddressPortable(AddressPortable addressPortable) {
this.addressPortable = addressPortable;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
}