---
# System prepended metadata

title: PayPal 筆記
tags: [筆記]

---

## PayPal 筆記

### :pushpin: 需要先知道的事
- 呼叫PayPal的API，header都要加上Bearer {{ACCESS-TOKEN}}
ACCESS-TOKEN取得方式：呼叫POST:/v1/oauth2/token取得
參數需帶入ClientId和Secret
:::spoiler PayPal金鑰查詢方式
1. 登入[PayPal開發者中心](https://developer.paypal.com)
2. 導覽列 > Apps & Credentials，即可確認
:::
### webhook
讓兩個網站間互相溝通的機制，當訂閱的事件發生時立即呼叫指定的Url
使用PayPal舉例：
- **step1**：
 建立webhook，PayPal API 參考文件：https://developer.paypal.com/docs/api/webhooks/v1/#webhooks_post
 
     內容如下：
    ```C=1
     {
        "Url": "https://tenji.money-link.com.tw/PayPalTest/api/PayPal/order/capture",
        "EventName": "CHECKOUT.ORDER.APPROVED" // 此事件為買家同意訂單
     }
     ```
- **step2**:
 當CHECKOUT.ORDER.APPROVED事件發生後會立即觸發webhook，呼叫指定Url並且body會帶該事件的詳細資訊
 
     body範例：
    ```C=1
    {
      "id": "WH-6W380007F74365212-2VC02462WS389632N",
      "create_time": "2024-02-26T05:42:49.363Z",
      "resource_type": "checkout-order",
      "event_type": "CHECKOUT.ORDER.APPROVED",
      "summary": "An order has been approved by buyer",
      "resource": {
        "create_time": "2024-02-26T05:36:36Z",
        "id": "9CJ84087S75111404", // 訂單編號
        "status": "APPROVED" // 訂單狀態
      }
    }
    ```
- 使用到的webhook event：

	單次購買：
	|    Event Name             | 觸發時機
	| ------------------------- |--------- 
	| CHECKOUT.ORDER.APPROVED   |使用者同意訂單
	| PAYMENT.CAPTURE.COMPLETED |成功將款項移動到商家帳號

	訂閱：
	|    Event Name                      | 觸發時機
	| ---------------------------------- |--------- 
	| PAYMENT.SALE.COMPLETED             |訂閱付款完成
	| BILLING.SUBSCRIPTION.CANCELLED     |取消訂閱
    | BILLING.SUBSCRIPTION.PAYMENT.FAILED|訂閱付款失敗

- webhook事件清單：https://developer.paypal.com/api/rest/webhooks/event-names/


**補充**：
- PayPal的Webhook必須使用<font color=red>https</font>，http以及localhost被視為無效連結
- PayPal Retry機制：
    若PayPal的Webhook接受到的回應不是HTTP 200，會在<font color=red>3天內</font>重新發送請求，<font color=red>最多25次</font>
- webhook是<font color=red>非同步</font>的，無法確定執行順序
- 不同webhook設定的Url不能重複
- 可以在同一個webhook中訂閱多個事件


### 單次購買流程
![PayPal單次購買時序圖](https://hackmd.io/_uploads/H1GDI0Mfee.jpg)


API：
- 建立訂單
	- PayPal API參考文件：https://developer.paypal.com/docs/api/orders/v2/#orders_create
	- 功能：
 取得跳轉至PayPal付款頁面的連結
	- 較重要的Response參數(PayPal回傳的)：
```C#=1
 "id":"9BC48837FS518181G" // PayPal訂單編號,
 "links":[{
     "href":"https://www.sandbox.paypal.com/checkoutnow?token=9BC48837FS518181G", // 導向PayPal付款頁面的連結
     "rel":"approve",
     "method":"GET"
},...]
```
- 向PayPal要求款項 (讓webhook呼叫的)
	- PayPal API參考文件：
https://developer.paypal.com/docs/api/orders/v2/#orders_capture
	- 使用時機：當使用者同意訂單後呼叫此API (CHECKOUT.ORDER.APPROVED 事件觸發)
	- 功能：修改PayPal的訂單狀態並將款項撥至商家帳戶

- 取得PayPal訂單詳情
	- PayPal API參考文件：
https://developer.paypal.com/docs/api/orders/v2/#orders_get
	- query參數須帶入PayPal訂單編號


### 定期定額訂單流程
參考文件：https://developer.paypal.com/docs/subscriptions/
第一次付款流程：
![messageImage_1748325278832](https://hackmd.io/_uploads/r10kBRfzxe.jpg)

第二次以後流程：
![PayPal定期定額非首次付款時序圖](https://hackmd.io/_uploads/H1crr0fzxe.jpg)


| 名稱 | 內容
| ---- | -------
| 商品 | 商品名稱、簡介、類型
| 計畫 | 價格(可更改)、訂閱週期(不能更改)

API：
- 建立商品
 	- PayPal API參考文件：
 https://developer.paypal.com/docs/api/catalog-products/v1/#products_create
 	- 較重要的Response參數(PayPal回傳的)：
```C=1
 "id":"1708592390" // 商品編號，建立計畫須帶入
```
- 建立計畫
 	- PayPal API參考文件：  https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create
 	- 較重要的Response參數(PayPal回傳的)：

```C=1
 "id":"P-1TP44450WC6357503MXLQ4SY" // 計畫編號，建立訂閱須帶入
```

- 建立訂閱 (產生定期定額訂單)
 	- PayPal API參考文件：
https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
 	- 較重要的Response參數(PayPal回傳的)：
```C=1
"id":"I-3X4FDVF4N2DP" // 訂閱編號(類似綠界訂單編號)，取消訂閱時會用到
"links":[{
  "href":"https://www.sandbox.paypal.com/webapps/billing/subscriptions?ba_token=BA-2BP71128M6394474E", // 導向PayPal付款頁面的連結
  "rel":"approve",
  "method":"GET"
},...]
```
- 取消訂閱 (讓webhook呼叫的)
 	- PayPal API參考文件：https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
 使用時機：使用者從PayPal執行取消訂閱動作，webhook呼叫此API執行取消訂閱後續流程

- 取消訂閱 (一般API)
 	- PayPal API參考文件：https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
 	- 功能：跟PayPal說要取消訂閱，下期不再扣款
 	- 使用時機：使用者從我們的站台執行取消訂閱動作

 
 - 停用計畫 
 	- PayPal API參考文件：
https://developer.paypal.com/docs/api/subscriptions/v1/#plans_deactivate
 	- 功能：為了避免使用者在取消訂閱後到PayPal介面重新訂閱，需要把計畫停用讓使用者無法重新訂閱
 	- 使用時機：使用者取消訂閱、銷包下架
 
 - 取得訂閱詳情
  	- PayPal API參考文件：
https://developer.paypal.com/docs/api/subscriptions/v1/#plans_deactivate
 	- 功能：由於停用計畫時需要計畫Id，在只記錄PayPal定期定額訂單編號的情況下需要此API取得
	
 - 取得訂閱付款結果(讓webhook呼叫的)
 	- 功能：透過webhook通知站台付款結果

### 訂閱制
#### 前置條件
1. 支援國家有限制，詳情請參考[官方文件]
(https://developer.paypal.com/docs/checkout/save-payment-methods/during-purchase/orders-api/cards/)

2. 商業帳號需要有 advanced credit and debit card payments 功能 ([官方文件](https://developer.paypal.com/docs/multiparty/checkout/advanced/))
3. 需要PayPal審核通過才能使用

#### 步驟
1. 使用者設定未來扣款的方式，不會扣款 (建立setup-token)
![image](https://hackmd.io/_uploads/r1bCodeZlx.png)
    - 使用者直接在PayPal的頁面上進行授權動作
    - 若未來站台的前端會收集信用卡資訊，可以跳過此步驟
        > 需符合 PCI DSS 規範 (較嚴格的信用卡管理方式) 或
        > 使用 PayPal JS SDK
    :::spoiler 串接API
    POST:https://api-m.sandbox.paypal.com/v3/vault/setup-tokens (PayPal測試環境 API)
    取得Response Data的Id參數
    :::
2. 建立 Payment Tokens
    - 已建立的Payment Token只能透過API取得，無法從商業帳號後台查詢
    :::spoiler 串接API
    POST:https://api-m.sandbox.paypal.com/v3/vault/payment-tokens (PayPal測試環境 API)，payment_source:token:id 帶入步驟1的Id
    取得Response Data的Id參數
    ```c=0
    Request Sample：
    {
      "payment_source": {
        "token": {
          "id": "0AR382999B0832256",
          "type": "SETUP_TOKEN"
        }
      }
    }
    ```
    :::
3. 需要扣款時由我們的站台呼叫建立訂單API (可以在任意時間進行)
    :::spoiler 串接API
    POST:https://api-m.sandbox.paypal.com/v2/checkout/orders
    payment_source:paypal:vault_id帶入步驟二得到的Id
    ```c=0
    Request Sample：
    {
      "intent": "CAPTURE",
      "purchase_units": [
        {
          "amount": {
            "currency_code": "USD",
            "value": "12.34"
          }
        }
      ],
      "payment_source": {
         "paypal": {
             "vault_id":"8vm1526934563590h"
         }          
      }
    }
    ```
    :::

#### 流程
``` mermaid
graph TD;
前端呼叫產生訂單API-->有Payment_Token;
前端呼叫產生訂單API-->沒有Payment_Token;
有Payment_Token-->產生PayPal訂單;
沒有Payment_Token-->建立setup_tokens;
建立setup_tokens-->建立Payment_Token;
建立Payment_Token-->產生PayPal訂單;
```

### 幣別
PayPal的商家帳號可以設定收款幣別，共有三種設定
::: spoiler 設定方式
![螢幕擷取畫面 2025-01-03 160932](https://hackmd.io/_uploads/rJaCsfHUkl.png)
附圖為2025-01-03版本
:::
1. 接受所有付款並轉換成首選幣種
2. 只接受商家擁有幣種的付款，拒收其他幣種的支付
3. 接受所有付款，當付款的幣種為商家未擁有自動增加幣種和餘額


### 補充
- webhook可以透過API或是PayPal開發者中心(PayPal Developer Dashboard)增加
- PayPal有提供介面可以更改計畫的價格，訂閱週期無法更改，更改價格PayPal沒有規定需要使用者重新授權
- 需要有計畫Id才可以停用計畫，若不紀錄該Id需要call PayPal取得訂閱詳情API
    >取得訂閱詳情 API - 開發文件：https://developer.paypal.com/docs/api/subscriptions/v1/#plans_get
- 若更改計畫金額，PayPal沒有規定要使用者重新批准，對於原先的用戶在價格變更的10天內會按照舊價格計費

### 測試工具整理
- PayPal 整合模擬器：https://developer.paypal.com/integration-builder/
- 信用卡產生器：https://developer.paypal.com/api/rest/sandbox/card-testing/
- webhook模擬器：https://developer.paypal.com/dashboard/webhooksSimulator/
- webhook事件清單：https://developer.paypal.com/api/rest/webhooks/event-names/
- PayPal貨幣代碼：https://developer.paypal.com/api/rest/reference/currency-codes/

### 待研究
- [ ] 由使用者觸發訂單爭議的處理
- [x] ACCSEE_TOKEN儲存方式 - 維持不儲存
    > 目前未儲存，因此每次呼叫PayPal API前需要先呼叫取得ACCSEE_TOKEN API
- [x] 定期定額訂單付款成功的通知
    > 目前透過Webhook事件-PAYMENT.SALE.COMPLETED通知
~~需要再驗證此事件的觸發條件是否為訂單付款成功~~
- [ ] 驗證webhook 