※ PayPal 伺服器SDK串接

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

※ PayPal SDK的更新速度很快請參考官方網站

※ 安裝 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帳號:

網址:https://developer.paypal.com/dashboard/accounts

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
留言分享你的想法!
avatar-img
奧莉薇走在成為後端工程師之路上
18會員
141內容數
全端網頁開發專業知識分享
2025/04/26
※ 場景: 即時聊天應用: 設計一個支持多房間功能的即時聊天平台,像 WhatsApp、LINE或Facebook Messenger,提供文字、語音、視訊聊天功能,方便管理群組聊天。 功能亮點:加入特別功能,例如可加入多房間功能、使用者名單、表情符號支持、文件分享或訊息已讀未讀狀態。 展示
2025/04/26
※ 場景: 即時聊天應用: 設計一個支持多房間功能的即時聊天平台,像 WhatsApp、LINE或Facebook Messenger,提供文字、語音、視訊聊天功能,方便管理群組聊天。 功能亮點:加入特別功能,例如可加入多房間功能、使用者名單、表情符號支持、文件分享或訊息已讀未讀狀態。 展示
2025/04/26
※ 先建立基本的express後端服務: 1.建立新資料夾:Socket mkdir socket 2.進入資料夾:Socket cd ​bsocket 3. 安裝 Experss 到專案中 npm init -y //初始化專案,建立 package.json 檔 npm insta
Thumbnail
2025/04/26
※ 先建立基本的express後端服務: 1.建立新資料夾:Socket mkdir socket 2.進入資料夾:Socket cd ​bsocket 3. 安裝 Experss 到專案中 npm init -y //初始化專案,建立 package.json 檔 npm insta
Thumbnail
2025/04/10
※ 什麼是 Socket.io:一個基於傳統 WebSocket API 之上的框架。 ※ Socket.io常用功能: Custom Events:在 Socket.io 中,開發者可以創建自己的事件來處理特定的功能或需求。 Rooms:分組的功能。每個連接的用戶(或稱為 socket)可
Thumbnail
2025/04/10
※ 什麼是 Socket.io:一個基於傳統 WebSocket API 之上的框架。 ※ Socket.io常用功能: Custom Events:在 Socket.io 中,開發者可以創建自己的事件來處理特定的功能或需求。 Rooms:分組的功能。每個連接的用戶(或稱為 socket)可
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
R036 Blog API 伺服器的維護更新日誌 (2024/04/30) 開發環境技術 語言: Javascript 環境: Node JS 框架: Express.js 本次維護目的 優化及測試API伺服器程運行 重溫程式碼架構以便日後更新優化 Reac
Thumbnail
R036 Blog API 伺服器的維護更新日誌 (2024/04/30) 開發環境技術 語言: Javascript 環境: Node JS 框架: Express.js 本次維護目的 優化及測試API伺服器程運行 重溫程式碼架構以便日後更新優化 Reac
Thumbnail
本文提供完成訂閱後的相關事項及安裝指引,包括填寫問卷、遠端開通Trading View帳號、指標安裝步驟等。另外也提供影片教學和紙本教學,以及解決安裝問題的方法。
Thumbnail
本文提供完成訂閱後的相關事項及安裝指引,包括填寫問卷、遠端開通Trading View帳號、指標安裝步驟等。另外也提供影片教學和紙本教學,以及解決安裝問題的方法。
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
授權碼模式連線流程 用戶端請求自己的伺服器。 伺服器發現用戶沒登入,就導向認證伺服器。 認證伺服器顯示授權頁面,等待用戶授權。 用戶確認授權後,授權頁面會向認證伺服器請求授權碼。 用戶獲取授權碼。 用戶將授權碼傳給伺服器。 伺服器拿授權碼向認證伺服器取得token。 應用註冊
Thumbnail
授權碼模式連線流程 用戶端請求自己的伺服器。 伺服器發現用戶沒登入,就導向認證伺服器。 認證伺服器顯示授權頁面,等待用戶授權。 用戶確認授權後,授權頁面會向認證伺服器請求授權碼。 用戶獲取授權碼。 用戶將授權碼傳給伺服器。 伺服器拿授權碼向認證伺服器取得token。 應用註冊
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News