paypal braintree支付对接

paypal本身有sdk,不过这里选择使用braintree服务进行对接,paypal本身也比较推荐这种方式。

准备工作

paypal账号

braintree账号(包括正式账号和沙盒账号)

申请流程这里不做说明了。

配置过程
  1. 登陆paypal开发后台,点击右上角的Dashboard,左边菜单栏中找到Sandbox–>Account,在这里可以创建测试账号,创建账号时注意选择类型personal,账号密码注意修改,密码修改后看不到。
  2. 左边菜单栏中找到Dashboard–>My Apps & Credentials,可以看到Sandbox和Live两个选项,分别时沙盒和正式环境的app配置。两边配置的步骤是一样的,选择Create App,填写相关内容完成创建。点击刚刚创建的app可以看到Sandbox account、Client ID、Secret,等下需要用到。往下滑,可以看到Add Webhook按钮,点击创建,这里需要准备webhook的接收地址,注意必须是https地址。
  3. 使用braintree沙盒账号登陆braintree沙盒后台,点击右上角齿轮,选择Processing进入,下方Payment Methods中找到paypal并开启,点击Options,配置PayPal Email,PayPal Client Id,PayPal Client Secret。相关参数由第2步获取。(正式环境配置过程类似,需要使用正式账号登陆barintree正式环境后台)
  4. braintree后台首页点击右上角齿轮,选择Fraud Management进入,可以设置相关信用卡支付安全校验,相关配置说明可以查看braintree,部分配置需要谨慎,可能会导致用户信用卡无法使用。
  5. braintree后台首页点击右上角齿轮,选择API,可以看到Keys、Webhooks、Security,其中Keys中的API Keys下可以点击Private Key的VIEW拿到Merchant ID、Public Key、Private Key,是请求braintree api时所必须的东西。Webhooks中需要配置对应的webhook地址,接受消息通知,配置以后可以使用Check URL进行地址校验。Security可以开启IP和主机名限制。
  6. 额外需要注意的一点, braintree后台首页点击右上角齿轮,选择Business,在Merchant Accounts中可以看到Merchant Account,如果有多个,使用braintree时需要进行指定,另外注意每个Merchant Account对应的Currency,即货币,如果使用时,用到了不对应的货币,将无法成功完成支付流程。
  7. 配置Plan(如果有订阅订单,会用到),braintree后台首页顶部选择Subscriptions,跳转后选择Plans,点击New Plan,进行Plan创建,填写相关内容,其中Plan ID是调用api时所需参数,如果需要添加试用期,选中Trial Period,请求api时需要开启试用。
开发流程
  1. 引入braintree的jar包(maven项目),开发参考braintree开发文档
<dependency>
    <groupId>com.braintreepayments.gateway</groupId>
    <artifactId>braintree-java</artifactId>
    <version>2.87.0</version>
</dependency>
  1. 获取braintree的token,交给客户端,由客户端braintreeSDK换取paymentMethodNonce,如果使用了braintree的订阅服务,还需要客户端获取用户账户的firstName和lastName,服务端代码如下:
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. 客户端获取到相关参数后,服务端通过api,进行订单创建、结算。

(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. 普通transaction会通过paypal的webhook进行通知。subscription会分别通过paypal和braintree进行webhook通知。

(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;
}
发布了21 篇原创文章 · 获赞 20 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章