{"openapi":"3.1.0","info":{"title":"NewPay 招商進件 API（經銷商）","version":"1.0.0","description":"NewPay 招商進件 API。經銷商自己設計表單、從**自家伺服器**把進件送進來。\n\n一把金鑰綁**一條招商連結**。連結上設定的「可申請哪些金流」、「收法人還是個人」、\n配號用哪一組前置碼，走 API 這條路完全一樣 ——\n**API 不是另一條路，是那條連結的另一個入口**。\n所以那條連結被停用的話，API 也會被擋（NPA-1006）。\n\n## 開始之前\n\n1. 在 NewPay 經銷商後台的「送件串接」頁面發一把金鑰、登記你伺服器的對外 IP。\n2. 先打 `GET /api/dealer/apply/terms` 取得目前生效的法定告知。\n3. 送件時把告知的版本帶回來。\n\n## ★★★ 法定告知的版本一定要即時取得\n\n同意紀錄裡存的版本是**我們伺服器算的**（告知內容的雜湊）。\n如果你把告知文字抄一份寫死在表單裡，而我們之後修了內容：\n\n> 我們會記成「申請人同意了新版」，而他在你的表單上看到的是舊版。\n\n那筆紀錄在有爭議時什麼也證明不了，**而且沒有任何人會發現** ——\n程式照跑、進件照建、畫面一切正常。\n所以請每次載入表單都呼叫 `/api/dealer/apply/terms`，把 `text` 顯示給申請人。\n\n## 錯誤代碼\n\n| 代碼 | HTTP | 意思 | 要做什麼 |\n|---|---|---|---|\n| `NPA-1001` | 401 | 驗證失敗 | 我們回的 401 一律是這一個代碼，不會說是哪一關 —— 那會變成幫人偵錯。真正的原因在「送件串接」頁面上那一把金鑰的「最近被擋」欄位，會寫出時間、來源 IP 與細代碼（NPA-1001.x）。 |\n| `NPA-1002` | 400 | 沒帶法定告知的版本 | 先呼叫 GET /api/dealer/apply/terms 取得目前生效的告知內容與版本，把內容顯示給申請人，再把版本隨這一件帶回來。 |\n| `NPA-1003` | 409 | 法定告知已改版，你帶的版本過期了 | 重新呼叫 GET /api/dealer/apply/terms 取得最新內容，讓申請人看過之後再送出。★ 不要把那兩段文字抄進你的表單寫死 —— 抄了之後我們改版，同意紀錄就會指到申請人沒看過的版本，而且沒有人會發現。 |\n| `NPA-1004` | 422 | 欄位不符 | 回應的 errors 會逐欄說明，照著修。欄位名與最小可通過的範例見這一頁下面的「送件格式」。 |\n| `NPA-1005` | 403 | 這家經銷商目前沒有可用的前置碼 | 這一件要我們處理 —— 請聯絡 NewPay。 |\n| `NPA-1006` | 404 | 金鑰綁的招商連結無效或已停用 | 如果是停用：到「招商進件」把那條連結啟用。如果是刪掉了：在這一頁重新發一把金鑰、綁現有的連結。 |\n\n★ **401 一律是 `NPA-1001`**，不會說是哪一關沒過 —— 那會變成幫人偵錯。\n真正的原因（細代碼 `NPA-1001.x`，含被擋的時間與來源 IP）在你的\n「送件串接」頁面上那一把金鑰的「最近被擋」欄位。\n回報問題時只要告訴我們「什麼時候、從哪個 IP 打的」。\n\n## 目前開放的權限\n\n`apply:submit`（送出招商進件）、`apply:read`（查自己送過的進件狀態）","contact":{"name":"NewPay","url":"https://core.newpay.com.tw"}},"servers":[{"url":"https://core.newpay.com.tw","description":"正式環境"}],"security":[{"ApiKey":[],"Timestamp":[],"Nonce":[],"Sign":[]}],"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"每一個請求都要帶四個標頭：\n\n| 標頭 | 內容 |\n|---|---|\n| `X-Api-Key` | NewPay 給的 api_key（明文識別） |\n| `X-Timestamp` | Unix 秒（整數字串）。容許 ±300 秒 |\n| `X-Nonce` | 每個請求都不一樣的隨機值（建議 16 bytes 轉 hex） |\n| `X-Sign` | 下面的公式，**小寫 hex** |\n\n簽章公式：\n\n```\npayload = METHOD + \"\\n\" + PATH + \"\\n\" + TIMESTAMP + \"\\n\" + NONCE + \"\\n\" + sha256(BODY)\nX-Sign  = hex( HMAC-SHA256( api_secret, payload ) )\n```\n\n四個最容易錯的地方（實際回報的順序）：\n\n1. **PATH 不含查詢字串，也不含主機名稱** —— 就是 `/api/dealer/applications` 這一串。\n2. **BODY 要用實際送出去的原始位元算 sha256**。不要先 decode 再 encode ——\n   空白或鍵的順序一變，雜湊就不一樣。\n3. **GET 沒有 body 時，BODY 當空字串**。注意 `sha256(\"\")` 是一個固定值，不是空的。\n4. **METHOD 要大寫**。\n\n★ `api_secret` 只用來算簽章，**不要**放進請求裡。\n★★ 只能從伺服器端呼叫：我們沒有開 CORS，瀏覽器的 JavaScript 打不進來 ——\n這是刻意的，secret 不該出現在前端。\n★★★ 來源 IP 要先登記（在 NewPay 經銷商後台的「送件串接」自己設）。"},"Timestamp":{"type":"apiKey","in":"header","name":"X-Timestamp","description":"Unix 秒。容許 ±300 秒。"},"Nonce":{"type":"apiKey","in":"header","name":"X-Nonce","description":"每個請求都要不一樣。重複會被當成重送而擋掉。"},"Sign":{"type":"apiKey","in":"header","name":"X-Sign","description":"hex(HMAC-SHA256(api_secret, METHOD\\nPATH\\nTIMESTAMP\\nNONCE\\nsha256(BODY)))"}},"schemas":{"Terms":{"type":"object","properties":{"consent":{"$ref":"#/components/schemas/TermsBlock"},"pci_terms":{"$ref":"#/components/schemas/TermsBlock"},"note":{"type":"string","description":"怎麼用這份回應的提醒"}}},"TermsBlock":{"type":"object","properties":{"version":{"type":"string","description":"內容的雜湊前 12 碼。送件時要帶回來","example":"5fc952345e54"},"text":{"type":"string","description":"要顯示給申請人看的全文"}}},"ApplicationRequest":{"type":"object","required":["member","store","consent","pci_terms","consent_version","pci_terms_version"],"properties":{"member":{"$ref":"#/components/schemas/Member"},"store":{"$ref":"#/components/schemas/Store"},"consent":{"type":"boolean","description":"申請人已閱讀並同意個資蒐集告知（個資法第 8 條）。必須是他在你的表單上實際看過、確認過的"},"pci_terms":{"type":"boolean","description":"申請人已確認支付卡資料安全責任申明（PCI DSS 12.9.1）"},"consent_version":{"type":"string","description":"從 /api/dealer/apply/terms 取得的 consent.version"},"pci_terms_version":{"type":"string","description":"從 /api/dealer/apply/terms 取得的 pci_terms.version"}}},"Member":{"type":"object","description":"申請人（會員）。★ 欄位名是 `type` 不是 `member_type`。","required":["type","company_name","tax_id","company_owner_name","company_owner_id_number","company_owner_phone","company_owner_email","contact_city","contact_district","contact_address_line","email"],"properties":{"type":{"type":"string","enum":["company","personal"],"description":"company=法人／personal=個人。那條招商連結允許收哪一種由 NewPay 設定"},"company_name":{"type":"string","description":"公司登記名稱（法人）"},"tax_id":{"type":"string","description":"統一編號（法人）","example":"12345675"},"company_owner_name":{"type":"string","description":"負責人姓名"},"company_owner_id_number":{"type":"string","description":"負責人身分證號；外籍填新式統一證號（1 碼英文＋9 碼數字）"},"company_owner_phone":{"type":"string","description":"負責人聯絡電話"},"company_owner_email":{"type":"string","description":"負責人電子郵件"},"contact_city":{"type":"string","description":"聯絡地址—縣市","example":"臺北市"},"contact_district":{"type":"string","description":"聯絡地址—鄉鎮市區","example":"內湖區"},"contact_address_line":{"type":"string","description":"聯絡地址—街道門牌"},"name":{"type":"string","description":"聯絡人姓名"},"mobile_phone":{"type":"string","description":"聯絡人手機"},"email":{"type":"string","description":"聯絡人電子郵件"},"id_number":{"type":"string","description":"身分證號（個人申請時用，對應 type=personal）"}}},"Store":{"type":"object","description":"商店資料。","required":["store_name","foreign_statement_name","store_city","store_district","store_address","store_description","average_order_amount","store_email","contact_email","contact_name","contact_mobile","contact_line"],"properties":{"store_name":{"type":"string","description":"商店名稱。★ 會送去上游建店，並顯示在消費者的信用卡帳單上"},"foreign_statement_name":{"type":"string","description":"英文帳單名稱","example":"STORE NAME"},"store_city":{"type":"string","description":"商店地址—縣市"},"store_district":{"type":"string","description":"商店地址—鄉鎮市區"},"store_address":{"type":"string","description":"商店地址—街道門牌"},"store_postal":{"type":"string","description":"郵遞區號"},"store_description":{"type":"string","description":"營運說明。**至少 50 字** —— 太短金流公司看不出在賣什麼"},"average_order_amount":{"type":"string","description":"平均客單價","example":"1000"},"store_email":{"type":"string","description":"商店客服信箱"},"contact_email":{"type":"string","description":"商店聯絡人信箱"},"contact_name":{"type":"string","description":"商店聯絡人"},"contact_mobile":{"type":"string","description":"商店聯絡人手機"},"contact_line":{"type":"string","description":"商店聯絡人 Line ID —— 審核有問題時這是最快找得到人的方式"},"store_url":{"type":"string","description":"營業網址","example":"https://example.com"},"store_url_type":{"type":"string","description":"`url`＝有網站。沒有網站是合法的，另有做法請洽 NewPay"},"business_type":{"type":"string","description":"行業別"},"product_type":{"type":"string","description":"販售商品類別"},"mcc":{"type":"string","description":"MCC 代碼","example":"84"},"payment_tools":{"type":"array","items":{"type":"string"},"description":"要申請的支付工具","example":["一次付清（國內卡）"]},"delivery_period":{"type":"integer","description":"履約期間"},"ratio_prepaid":{"type":"integer","description":"預收款比例（%）"},"ratio_non_prepaid":{"type":"integer","description":"非預收比例（%）"},"ratio_deferred":{"type":"integer","description":"延後出貨比例（%）"},"ratio_voucher":{"type":"integer","description":"禮券比例（%）"},"agent_id":{"type":"integer","description":"介紹人（你底下的業務員 id）。★ 不屬於你的會被忽略"}}},"Created":{"type":"object","properties":{"message":{"type":"string","description":"給人看的訊息"},"id":{"type":"integer","description":"進件編號"}}},"Error":{"type":"object","properties":{"code":{"type":"string","description":"錯誤代碼（NPA-xxxx）。查詳細說明請到「送件串接」頁面","example":"NPA-1001"},"message":{"type":"string","description":"給人看的訊息"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"只有 422 會有：欄位 → 哪裡不對"},"consent_version":{"type":"string","description":"只有 400／409 會有：目前生效的版本"},"pci_terms_version":{"type":"string","description":"只有 400／409 會有：目前生效的版本"}}}},"responses":{"Unauthorized":{"description":"NPA-1001　驗證失敗\n\n金鑰、時間戳、簽章、IP 白名單、權限、重送防護，這幾關有一關沒過。\n\n**下一步**：我們回的 401 一律是這一個代碼，不會說是哪一關 —— 那會變成幫人偵錯。真正的原因在「送件串接」頁面上那一把金鑰的「最近被擋」欄位，會寫出時間、來源 IP 與細代碼（NPA-1001.x）。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1001","message":"驗證失敗"}}}},"MissingTermsVersion":{"description":"NPA-1002　沒帶法定告知的版本\n\n送件時沒有 consent_version 或 pci_terms_version。\n\n**下一步**：先呼叫 GET /api/dealer/apply/terms 取得目前生效的告知內容與版本，把內容顯示給申請人，再把版本隨這一件帶回來。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1002","message":"沒帶法定告知的版本"}}}},"TermsStale":{"description":"NPA-1003　法定告知已改版，你帶的版本過期了\n\n我們修過個資蒐集告知或支付卡資料安全責任申明的內容。版本是內容的雜湊，內容一變版本就變。\n\n**下一步**：重新呼叫 GET /api/dealer/apply/terms 取得最新內容，讓申請人看過之後再送出。★ 不要把那兩段文字抄進你的表單寫死 —— 抄了之後我們改版，同意紀錄就會指到申請人沒看過的版本，而且沒有人會發現。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1003","message":"法定告知已改版，你帶的版本過期了"}}}},"FieldErrors":{"description":"NPA-1004　欄位不符\n\n必填沒帶、格式不對、或長度不足（例如營運說明至少 50 字）。規則跟我們提供的公開表單共用同一份。\n\n**下一步**：回應的 errors 會逐欄說明，照著修。欄位名與最小可通過的範例見這一頁下面的「送件格式」。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1004","message":"欄位不符"}}}},"NoPrefix":{"description":"NPA-1005　這家經銷商目前沒有可用的前置碼\n\n前置碼決定配什麼商店代號、送哪一家上游。沒有可用的就無法建件。\n\n**下一步**：這一件要我們處理 —— 請聯絡 NewPay。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1005","message":"這家經銷商目前沒有可用的前置碼"}}}},"TokenInvalid":{"description":"NPA-1006　金鑰綁的招商連結無效或已停用\n\n一把金鑰綁一條招商連結。那條連結被停用或刪掉之後，金鑰就沒有歸屬。\n\n**下一步**：如果是停用：到「招商進件」把那條連結啟用。如果是刪掉了：在這一頁重新發一把金鑰、綁現有的連結。","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":"NPA-1006","message":"金鑰綁的招商連結無效或已停用"}}}}}},"paths":{"/api/dealer/apply/terms":{"get":{"summary":"取得目前生效的法定告知與版本","description":"**每次載入表單都要呼叫。** 回來的 `text` 要顯示給申請人，`version` 要隨送件帶回來。版本不符會回 409（NPA-1003）。","operationId":"getApplyTerms","responses":{"200":{"description":"目前生效的內容與版本","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Terms"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/dealer/applications":{"post":{"summary":"送出一件招商進件","description":"欄位的必填與格式規則跟 NewPay 提供的公開表單**共用同一份** —— 表單上問什麼、API 就要帶什麼。缺的話 422 會逐欄告訴你。\n\n進來的件狀態是「待經銷商初審」，你要在 NewPay 後台完成初審再送交審核。","operationId":"createApplication","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationRequest"}}}},"responses":{"201":{"description":"已收到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Created"}}}},"400":{"$ref":"#/components/responses/MissingTermsVersion"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/NoPrefix"},"404":{"$ref":"#/components/responses/TokenInvalid"},"409":{"$ref":"#/components/responses/TermsStale"},"422":{"$ref":"#/components/responses/FieldErrors"}}}}}}