paypal本身有sdk,不过这里选择使用braintree服务进行对接,paypal本身也比较推荐这种方式。
paypal账号
braintree账号(包括正式账号和沙盒账号)
申请流程这里不做说明了。
<dependency>
<groupId>com.braintreepayments.gatewaygroupId>
<artifactId>braintree-javaartifactId>
<version>2.87.0version>
dependency>
public String getBraintreeToken() {
try {
BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
PayConfig.BraintreeMerchantId,
PayConfig.BraintreePublicKey,
PayConfig.BraintreePrivateKey
);
return gateway.clientToken().generate();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
(1). 普通订单:
public void braintreeTransaction(String nonce) {
try {
BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
PayConfig.BraintreeMerchantId,
PayConfig.BraintreePublicKey,
PayConfig.BraintreePrivateKey
);
TransactionRequest request = new TransactionRequest()
.amount(new BigDecimal(100))
//客户端根据服务器返回的accesstoken获取的paymentMethodNonce
.paymentMethodNonce(nonce)
//映射到PayPal的发票号码
.orderId(yourServerOrderId)
.descriptor()
//描述符显示在客户CC报表中。22个字符马克斯
// .name("Descriptor displayed in customer CC statements. 22 char max")
.done()
.shippingAddress()
//对应商家账号的firstName
.firstName(PayConfig.BraintreeFirstName)
//对应商家账号的lastName
.lastName(PayConfig.BraintreeLastName)
//公司名
// .company("Braintree")
// .streetAddress("1 E 1st St")
// .extendedAddress("Suite 403")
// .locality("Bartlett")
// .region("IL")
// .postalCode("60103")
// .countryCodeAlpha2("US")
.done()
.options()
//是否结算。如果不结算,用户的金额不会被扣除,需要手工去后台确认收款
.submitForSettlement(true)
.paypal()
//PayPal自定义字段
.customField("PayPal custom field")
//PayPal电子邮件说明
.description("")
.done()
//If you want to create a new payment method in the Vault upon a successful transaction, use the this
.storeInVaultOnSuccess(true)
.done();
Result<Transaction> saleResult = gateway.transaction().sale(request);
if (saleResult.isSuccess()) {
Transaction transaction = saleResult.getTarget();
transactionId = transaction.getId();
System.out.println("Success ID: " + transaction.getId());
System.out.println("transaction ====== "+JSON.toJSON(transaction));
} else {
//支付失败的情况
logger.error("Message: {}",saleResult.getMessage());
logger.error("Error: {}",saleResult.getErrors().toString());
logger.error("Error-JSON: {}",JSON.toJSON(saleResult.getErrors()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2). 订阅订单,需要注意braintree本身允许用户重复订阅,相关逻辑需要自行实现:
public void braintreeSubscription(String nonce,String firstName,String lastName) {
try {
BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
PayConfig.BraintreeMerchantId,
PayConfig.BraintreePublicKey,
PayConfig.BraintreePrivateKey
);
CustomerRequest customerRequest = new CustomerRequest()
.firstName(firstName)
.lastName(lastName)
.paymentMethodNonce(nonce);
Result<Customer> customerResult = gateway.customer().create(customerRequest);
//顾客信息不必每次获取,可以将顾客id或者token缓存起来下次使用,但需要注意用户本次使用的paypal账户是否与服务器缓存的一致,以防扣错账户。
Customer customer = customerResult.getTarget();
if (customer==null){ logger.error("获取用户买家信息失败");
return;
}
SubscriptionRequest request = new SubscriptionRequest()
.paymentMethodToken(customer.getPaymentMethods().get(0).getToken())
.planId(yourPlanId)
//启用试用期,启用以后,不能设置开始时间,会冲突
// .trialPeriod(isFreeFirst)
.options()
//设置立即开始,如果启用了试用期,就不能设置本项
.startImmediately(true)
.paypal()
.description("")
.done()
.done();
Result<Subscription> subscriptionResult = gateway.subscription().create(request);
try {
System.out.println(JSON.toJSON(subscriptionResult));
} catch (Exception e) {
e.printStackTrace();
}
if (subscriptionResult.isSuccess()) {
orderSuccess = true;
Subscription subscription = subscriptionResult.getTarget();
List<Transaction> transactionList = subscription.getTransactions();
String subscriptionId = subscription.getId();
} else {
//支付失败的情况
logger.error("Message: {}",subscriptionResult.getMessage());
logger.error("Error: {}",subscriptionResult.getErrors().toString());
logger.error("Error-JSON: {}",JSON.toJSON(subscriptionResult.getErrors()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
(1). paypal的webhook可以参考WebhooksManagementAPI文档
public String paypalNotify(@RequestBody(required = false) byte[] body, HttpServletRequest request, HttpServletResponse response) {
PrintWriter out = null;
JSONObject rsJson = new JSONObject();
try {
String bodyStr = null;
JSONObject paramJson = null;
try {
bodyStr = new String(body, "utf-8");
paramJson = JSONObject.parseObject(bodyStr);
} catch (Exception e) {
e.printStackTrace();
}
/**
* PAYMENT.SALE.COMPLETED
*/
if (paramJson!=null){
String eventType = paramJson.getString("event_type");
if ("PAYMENT.SALE.COMPLETED".equalsIgnoreCase(eventType)){
//支付完成处理逻辑
JSONObject resourceJson = paramJson.getJSONObject("resource");
String orderNo = resourceJson.getString("invoice_number");
String paymentId = resourceJson.getString("parent_payment");
}
}
out = response.getWriter();
rsJson.put("status", "200");
} catch (Exception e) {
e.printStackTrace();
rsJson.put("status", "500");
}
out.println(rsJson.toString());
out.flush();
out.close();
return null;
}
(2). braintree的Webhook回调处理,配置webhook时可以使用Check URL进行测试,braintree的webhook回调携带参数为bt_signature、bt_payload,可以参考braintreeWebhook文档:
public String braintreeNotify(HttpServletRequest request, HttpServletResponse response)
PrintWriter out = null;
JSONObject rsJson = new JSONObject();
try {
String signature = null;
String payload = null;
Object btSignatureObj = request.getParameter("bt_signature");
Object btPayloadObj = request.getParameter("bt_payload");
if (btSignatureObj!=null){
signature = btSignatureObj.toString();
}
if (btPayloadObj!=null){
payload = btPayloadObj.toString();
}
CErrorData cErrorData = null;
if (!CStr.isEmpty(signature)&&!CStr.isEmpty(payload)){
BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
PayConfig.BraintreeMerchantId,
PayConfig.BraintreePublicKey,
PayConfig.BraintreePrivateKey
);
WebhookNotification webhookNotification = gateway.webhookNotification().parse(signature,payload);
if ("CHECK".equals(webhookNotification.getKind().name())){
//测试
return;
}
Subscription subscription = webhookNotification.getSubscription();
if (subscription==null){
//没有订阅信息
return;
}
//如果是试用订阅,则不包含transactions
List<Transaction> transactionList = subscription.getTransactions();
if ("SUBSCRIPTION_WENT_ACTIVE".equals(webhookNotification.getKind().name())) {
//创建订阅的第一个授权交易,或者成功的交易将订阅从“ 过期”状态转移到“ 活动”状态。具有试用期的订阅从试用期进入第一个计费周期后不会触发此通知。
}else if ("SUBSCRIPTION_CHARGED_SUCCESSFULLY".equals(webhookNotification.getKind().name())){
//订阅成功进入下一个计费周期,即续订成功
}
}
out = response.getWriter();
rsJson.put("status", "200");
} catch (Exception e) {
rsJson.put("status", "500");
e.printStackTrace();
}
out.println(rsJson.toString());
out.flush();
out.close();
return null;
}