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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章