※ PayPal 伺服器SDK串接

更新於 發佈於 閱讀時間約 21 分鐘


※ 安裝 PayPal 伺服器 SDK:在伺服器端處理訂單和付款

網址https://developer.paypal.com/studio/checkout/standard/getstarted

raw-image


 //在開發和測試階段模擬或測試 PayPal 結帳流程
npm install --save-dev @paypal/checkout-server-sdk
raw-image


※ PayPal adapter(技術模組)實作:adapters-->paypal -->index.ts

//導入 PaymentPayload:
import { PaymentPayload } from "@/dispatcher"
//引入 PayPal SDK:
const paypal = require("@paypal/paypal-server-sdk");
//定義 IPaypalAdapter 介面:
export interface IPaypalAdapter {
  createOrder({
    totalPrice,
    details,
  }: Omit<PaymentPayload, "desc" | "returnURL">): Promise<string>;
}

程式碼解說:

1.導入 PaymentPayload:從 "@/dispatcher" 模組中導入 PaymentPayload 介面,這個介面定義了支付資料的結構。

2.引入 PayPal SDK:引入了 @paypal/checkout-server-sdk,用來與 PayPal 的 REST API 進行互動。

3.定義 IPaypalAdapter 介面:

  • 包含了一個 createOrder 方法。
  • 接收一個不包含 descreturnURL 屬性的 PaymentPayload 物件,並且返回一個 Promise<string>
  • 非同步處理: 返回 Promise 使得 createOrder 方法可以處理非同步操作。
  • 最終會返回一個字串(通常是訂單 ID)。

※ 定義 PaypalAdapter轉接器:adapters-->paypal -->index.ts

export class PaypalAdapter implements IPaypalAdapter {

  private paypalClient: any;
  constructor() {
  const Environment = process.env.NODE_ENV === "production"
? paypal.core.LiveEnvironment
: paypal.core.SandboxEnvironment;


  }

}

程式碼解說:

1.宣告了一個名為 PaypalAdapter 的類別,實現了 IPaypalAdapter 介面,並使其可在其他模組中引用。

2.宣告了一個名為 paypalClient 的私有變數,使用 any 類型,並且只能在類別內部訪問。

3.構造函數初始化 PaypalAdapter 類別,並從環境變數中取得 NODE_ENV 的值以設置應用程式的運行環境。例如 "development"(開發環境)或 "production"(生產環境)。

NODE_ENV="development"//設為開發環境
raw-image

4.選擇環境

  • 如果 NODE_ENV 等於 "production",則選擇 paypal.core.LiveEnvironment,這是 PayPal 的生產環境,用於處理真實交易。
  • 如果 NODE_ENV 不等於 "production"(例如,它可能是 "development" 或其他值),則選擇 paypal.core.SandboxEnvironment,這是 PayPal 的沙盒環境,用於測試交易而不會影響真實資金。

※ 建一個 PayPalHttpClient 實例,並將其初始化為特定的環境:adapters-->paypal -->index.ts

export class PaypalAdapter implements IPaypalAdapter {
private paypalClient: any;

constructor() {
...
//PayPalHttpClient 實例
this.paypalClient = new paypal.core.PayPalHttpClient(
new Environment(
process.env.PAYPAL_CLIENT_ID,
process.env.PAYPAL_CLIENT_SECRET));
}
}

程式碼解說:

1.paypal.core.PayPalHttpClient:這是 PayPal SDK 中的一個類,用於處理與 PayPal 伺服器的 HTTP 請求和回應。

2.new Environment():這是一個函數調用,用來設置與 PayPal API 互動的環境。這個環境可以是沙盒環境(Sandbox)或是生產環境(Live)。沙盒環境用於測試支付流程,而生產環境則用於實際支付操作。

3.process.env.PAYPAL_CLIENT_ID:這是從環境變數中讀取 PayPal 的客戶端 ID (PAYPAL_CLIENT_ID)。這個 ID 是你的應用程式在 PayPal 上的唯一標識,用於認證請求。

4.process.env.PAYPAL_CLIENT_SECRET:這是從環境變數中讀取 PayPal 的客戶端密鑰 (PAYPAL_CLIENT_SECRET)。這個密鑰是用來保證 API 請求的安全性,只有你和 PayPal 知道。

※ 定義一個符合 IPaypalAdapter 接口的 createOrder 方法:adapters-->paypal -->index.ts

export class PaypalAdapter implements IPaypalAdapter {

  private paypalClient: any;
  ...

  //實作createOrder
  public createOrder: IPaypalAdapter["createOrder"] = async ({
    billId,
    totalPrice,
    details,
  }) => { };
}

程式碼解說:

1.public createOrder: IPaypalAdapter["createOrder"]

  • public 關鍵字表示這個方法是公開的,可以從類的外部調用。
  • createOrder 是這個方法的名稱。
  • IPaypalAdapter["createOrder"] 表示這個方法的類型。IPaypalAdapter 是一個接口,包含了 createOrder 方法的定義。

2.= async ({ billId, totalPrice, details }) => { };

= async 關鍵字表示這個方法是非同步的,意味著它會返回一個 Promise,允許在函數內使用 await 關鍵字來處理非同步操作。

3.({ billId, totalPrice, details }) 表示這個方法接受一個對象作為參數,這個對象包含了 billIdtotalPricedetails 這些屬性

※ 和PayPal客戶服務設立一個交易或進行有關交易:adapters-->paypal -->index.ts

 public createOrder: IPaypalAdapter["createOrder"] = async ({
    ...
  }) => {
// call PayPal to set up a transaction
const request = new paypal.orders.OrdersCreateRequest();
};
}

程式碼解說:

建立了一個 OrdersCreateRequest 對象,該對象用於建立和發送訂單建立請求給PayPal。這是在整合PayPal的API時,處理支付訂單的一部分。

※ 訂單請求的首選回應格式:adapters-->paypal -->index.ts

public createOrder: IPaypalAdapter["createOrder"] = async ({
    ...
  }) => {
// call PayPal to set up a transaction
...
//訂單請求的首選回應格式
    request.prefer("return=representation");
};

}

程式碼解說:

  • request.prefer("return=representation");:設定了 PayPal 訂單請求的首選回應格式。
  • prefer 方法用於設置 HTTP 請求首選項標頭,當發送請求給 PayPal 時,它會告訴 PayPal 您希望以什麼樣的格式接收回應。
  • "return=representation" 表示在成功建立訂單後,PayPal 返回訂單的完整表示(representation),而不是只返回一些簡要的訊息(例如訂單 ID)。

※ 設置 PayPal 訂單請求的詳細內容,特別是針對一個購物車中的多項目進行設置:adapters-->paypal -->index.ts

 request.requestBody({

      intent: "CAPTURE",

      purchase_units: [{
        custom_id: billId,
        amount: {
          currency_code: 'USD',
          value: totalPrice,
          breakdown: {
            item_total: {
              currency_code: 'USD',
              value: totalPrice,
            },
          },
        },

        items: details.map((item) => ({
          name: item.name,
description: item.desc,
          unit_amount: {
            currency_code: 'USD',
            value: item.price,
          },
          quantity: item.amount,
        }))
      },],
    });

程式碼解說:

1.intent: "CAPTURE": 表示訂單的目標是進行資金擷取,即完成支付。

2.purchase_units: 這是一個包含所有購買單位的陣列。

3.custom_id: billId: custom_id 是您設置的自定義 ID,可能用於追踪或標識訂單。billId 是一個變數,應該在程式碼的其他地方進行定義。

4.amount: 定義了整個購買單位的金額和貨幣類型。

  • currency_code: 'USD': 設定貨幣為美金。
  • value: totalPrice: 設定金額為 totalPrice,這應該是在程式碼的其他地方定義的變數。

5.breakdown: 這提供了金額的詳細分解。

  • item_total: 這是所有項目的總價。
    • currency_code: 'USD': 設定貨幣為美金。
    • value: totalPrice: 設定總價為 totalPrice。

6.items: 這是一個物品的列表,每個物品代表購物車中的一件商品。details.map(item => ( ... )) 這段程式碼將 details 陣列中的每個物品轉換為所需格式。

  • name: item.name: 商品名稱。
  • description: item.desc : 取自 itemdesc 屬性,商品說明
  • unit_amount:
    • currency_code: 'USD': 設定貨幣為美金。
    • value: item.price: 商品單價。
  • quantity: item.amount: 商品數量。
  • category: "PHYSICAL_GOODS": 商品類別,這裡表示是實物商品。

※ 定義OrderDetail中新增amount和description:adapters-->paypal -->index.ts

//定義OrderDetail

interface OrderDetail {
  name: string;
  price: number;
  amount: number;//新增
  desc: string;//新增
}

程式碼解說:

  1. amount: number; : 這個新屬性是一個數字,表示訂單的數量或數額。
  2. desc: string; :這個新屬性是一個字串,表示訂單的描述。

※ 修改設定details的contents:controller-->orderController.ts

const contentInfos = contents.map((content) => {
          const product = products?.find((p) => p.id === content.productId);
          return {
            name: product?.name || "",
            price: content.price,
            amount: content.amount,
            desc: product?.description || "",
          };
        });

程式碼解說:

1.遍歷 contents 陣列map 方法對 contents 陣列中的每個 content 元素執行函式,產生一個新的陣列 contentInfos

2.尋找對應的 productproducts 陣列中尋找 idcontent.productId 相符的 product 元素。如果 productsnullundefined,則結果為 undefined

3.回傳一個新的物件

  • name: product?.name || "" : 如果找到 product,則取 product.name,否則為空字串。
  • price: content.price : 取自 contentprice 屬性。
  • amount: content.amount : 取自 contentamount 屬性。
  • desc: product?.description : 如果找到 product,則取 product.description,否則為空字串。

※ 執行 PayPal 客戶端的請求並處理結果或錯誤:adapters-->paypal -->index.ts

//實作createOrder
  public createOrder: IPaypalAdapter["createOrder"] = async ({
    billId,
    totalPrice,
    details,
  }) => {
...​ 
    let order;
    try {
      order = await this.paypalClient.execute(request);
      //Return a successful response to the client with the order ID
      return order.result.id;
    } catch (err: any) {
      //Handle any errors from thr call
      console.error(err);
      throw new Error(err);
    }
  };

程式碼解說:

1.宣告 order 變數宣告了一個 order 變數,但尚未賦值。

2.執行請求

  • await this.payPalClient.execute(request);: 使用 await 等待 PayPal 客戶端執行請求(request)包括訂單 ID、付款細節等。這是個非同步操作,並將結果賦值給 order 變數。
  • 回傳訂單 ID如果請求成功,程式會回傳 order.result.id 作為成功響應。

3.捕捉和處理錯誤

  • catch (err: any): 如果在執行請求時出現任何錯誤,程式會進入 catch 區塊。
  • console.error(err);: 將錯誤訊息輸出到控制台,以便除錯。
  • throw new Error(err);: 拋出一個新的錯誤,並將捕捉到的錯誤訊息傳遞給新的錯誤。

※ 使用 PayPal 支付流程:dispatcher-->index.ts

//建立發派function
export const paymentDispatcher = async ({

  paymentProvider,

  paymentWay,

  payload,

}: {
...
}) => {

  const ecpay = new ECPayAdapter();

  if (paymentProvider === PaymentProvider.ECPAY) {
...
  } else if (paymentProvider === PaymentProvider.PAYPAL) {
    //TODO
const paypal = new PaypalAdapter();
const id = await paypal.createOrder({
billId: payload.billId,
totalPrice: payload.totalPrice,
details: payload.details,
})
}
  }

程式碼解說:

1.條件檢查:使用 else if 語句檢查 paymentProvider 是否為 PAYPAL

2.建立 PayPal 適配器建立一個新的 PaypalAdapter 實例並將其存儲在 paypal 變量中。

3.建立訂單調用 paypal.createOrder 方法,傳入包含 billIdtotalPricedetailspayload 物件。這個方法應該會與 PayPal 的 API 進行連線,以建立一個新訂單,並返回訂單的編號。

※ 付款頁面結果:

raw-image


※ 進入Sandbox帳號:

raw-image

Business就是default App的Sandbox帳號,也就是開店用的虛擬帳號。

raw-image

※ 查詢帳戶裡面有多少錢:

選擇View/Edit account

raw-image

PayPal balance:顯示的金額就表示有多少錢

raw-image

※ 帳號密碼登入:

raw-image

※ 登入結果:

raw-image

※ 按下完成購買:

raw-image

※ 付款成功畫面:

raw-image



全端網頁開發專業知識分享
留言
avatar-img
留言分享你的想法!

































































※ 前端串接: 第一步設定paypal JavaScript SDKconfiguration:views --> index.ejs 網址:https://developer.paypal.com/sdk/js/configuration/ <!--顯示paypal 按鈕-->     <d
※ PayPal官方文件教學: 網址:https://developer.paypal.com/home/ 第一步:註冊帳號 第二步:註冊商業帳號 第三步:註冊成功 ※ PayPal REST APIs說明: 登入開發者平台:在Sandbox模式開發 網址:https://de
※ 安裝ECPAY SDK(軟體開發工具包): 載點:https://github.com/ECPay/ECPayAIO_Node.js 方法: 1.把code dowonload下來。 在專案中下載ECPAY SDK代碼的原因如下: 參考學習:開發時,可以直接參考 SDK 內的內容。
※ 在前端畫面顯示ECPay: ● 優化前端:views → index.ejs <!-- 新增按鈕 --> <div> <button>綠界支付</button> </div> ● 在Vue 組件的資料定義中,新增buyItem來存儲購買的項目和數量:java
※ 綠界科技API平台: https://www.ecpay.com.tw/ ● 進到開發者中心: ● API串接規格文件: ● 使用SDK來串接Node.JS 的WEB SERVICE: ● 綠界 全方位(All In One)金流介接Node.js 第一版: 載點:htt
※ 利用transactionHandler將資料寫入database ● 在orderController.ts檔案中使用 try...catch 區塊:  try { } catch (err) { // 處理錯誤 res.status(500).json({ errors: e
※ 前端串接: 第一步設定paypal JavaScript SDKconfiguration:views --> index.ejs 網址:https://developer.paypal.com/sdk/js/configuration/ <!--顯示paypal 按鈕-->     <d
※ PayPal官方文件教學: 網址:https://developer.paypal.com/home/ 第一步:註冊帳號 第二步:註冊商業帳號 第三步:註冊成功 ※ PayPal REST APIs說明: 登入開發者平台:在Sandbox模式開發 網址:https://de
※ 安裝ECPAY SDK(軟體開發工具包): 載點:https://github.com/ECPay/ECPayAIO_Node.js 方法: 1.把code dowonload下來。 在專案中下載ECPAY SDK代碼的原因如下: 參考學習:開發時,可以直接參考 SDK 內的內容。
※ 在前端畫面顯示ECPay: ● 優化前端:views → index.ejs <!-- 新增按鈕 --> <div> <button>綠界支付</button> </div> ● 在Vue 組件的資料定義中,新增buyItem來存儲購買的項目和數量:java
※ 綠界科技API平台: https://www.ecpay.com.tw/ ● 進到開發者中心: ● API串接規格文件: ● 使用SDK來串接Node.JS 的WEB SERVICE: ● 綠界 全方位(All In One)金流介接Node.js 第一版: 載點:htt
※ 利用transactionHandler將資料寫入database ● 在orderController.ts檔案中使用 try...catch 區塊:  try { } catch (err) { // 處理錯誤 res.status(500).json({ errors: e
你可能也想看
Google News 追蹤
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
※ 補充說明: ※ npm 常用指令: ◦ npm init–y:快速初始化一個新的 Node.js 並建立一個 package.json 文件的命令。 ◦ npm info 套件名稱 version:快速查詢指定 npm 套件的最新版本號。 ◦ npm install套件名稱:用來安裝
Thumbnail
R036 Blog API 伺服器的維護更新日誌 (2024/04/30) 開發環境技術 語言: Javascript 環境: Node JS 框架: Express.js 本次維護目的 優化及測試API伺服器程運行 重溫程式碼架構以便日後更新優化 Reac
Thumbnail
本文提供完成訂閱後的相關事項及安裝指引,包括填寫問卷、遠端開通Trading View帳號、指標安裝步驟等。另外也提供影片教學和紙本教學,以及解決安裝問題的方法。
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
※ 補充說明: ※ npm 常用指令: ◦ npm init–y:快速初始化一個新的 Node.js 並建立一個 package.json 文件的命令。 ◦ npm info 套件名稱 version:快速查詢指定 npm 套件的最新版本號。 ◦ npm install套件名稱:用來安裝
Thumbnail
R036 Blog API 伺服器的維護更新日誌 (2024/04/30) 開發環境技術 語言: Javascript 環境: Node JS 框架: Express.js 本次維護目的 優化及測試API伺服器程運行 重溫程式碼架構以便日後更新優化 Reac
Thumbnail
本文提供完成訂閱後的相關事項及安裝指引,包括填寫問卷、遠端開通Trading View帳號、指標安裝步驟等。另外也提供影片教學和紙本教學,以及解決安裝問題的方法。
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信