-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(integrations): add support for sage-member #6717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
adb6adb
b5ee4dc
2bf5f33
35c10dc
431055d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| --- | ||
| title: 'SAGE Member' | ||
| sidebarTitle: 'SAGE Member' | ||
| description: 'Integrate your application with the SAGE Member API' | ||
| --- | ||
|
|
||
| ## Quickstart | ||
|
|
||
| Connect to SAGE Member with Nango and see data flow in 2 minutes. | ||
|
|
||
| <Steps> | ||
| <Step title="Create the integration"> | ||
| In Nango ([free signup](https://app.nango.dev)), go to [Integrations](https://app.nango.dev/dev/integrations) -> _Configure New Integration_ -> _SAGE Member_. | ||
| </Step> | ||
| <Step title="Authorize SAGE Member"> | ||
| Go to [Connections](https://app.nango.dev/dev/connections) -> _Add Test Connection_ -> _Authorize_, then enter your Account Number, Login ID, and Authentication Key. Later, you'll let your users do the same directly from your app. | ||
| </Step> | ||
| <Step title="Call the SAGE Member API"> | ||
| Let's make your first request to the SAGE Member API. Replace the placeholders below with your [secret key](https://app.nango.dev/dev/environment-settings), [integration ID](https://app.nango.dev/dev/integrations), and [connection ID](https://app.nango.dev/dev/connections): | ||
| <Tabs> | ||
| <Tab title="cURL"> | ||
|
|
||
| ```bash | ||
| curl -X POST "https://api.nango.dev/proxy/ConnectAPI" \ | ||
| -H "Authorization: Bearer <NANGO-SECRET-KEY>" \ | ||
| -H "Provider-Config-Key: <INTEGRATION-ID>" \ | ||
| -H "Connection-Id: <CONNECTION-ID>" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"serviceId": 101, "apiVer": 130}' | ||
| ``` | ||
|
|
||
| </Tab> | ||
|
|
||
| <Tab title="Node"> | ||
|
|
||
| Install Nango's backend SDK with `npm i @nangohq/node`. Then run: | ||
|
|
||
| ```typescript | ||
| import { Nango } from '@nangohq/node'; | ||
|
|
||
| const nango = new Nango({ secretKey: '<NANGO-SECRET-KEY>' }); | ||
|
|
||
| const res = await nango.post({ | ||
| endpoint: '/ConnectAPI', | ||
| providerConfigKey: '<INTEGRATION-ID>', | ||
| connectionId: '<CONNECTION-ID>', | ||
| data: { | ||
| serviceId: 101, | ||
| apiVer: 130 | ||
| } | ||
| }); | ||
|
|
||
| console.log(res.data); | ||
| ``` | ||
| </Tab> | ||
|
|
||
| </Tabs> | ||
| Nango automatically injects your Account Number, Login ID, and Authentication Key into the `auth` object of every proxied request body — you only need to supply the `serviceId` and other fields for the specific SAGE Member module you're calling. | ||
|
|
||
| Or fetch credentials with the [Node SDK](/reference/sdks/node#get-a-connection-with-credentials) or [API](/reference/api/connection/get). | ||
|
|
||
| You're connected! Check the [Logs](https://app.nango.dev/dev/logs) tab in Nango to inspect requests. | ||
| </Step> | ||
|
|
||
| <Step title="Implement Nango in your app"> | ||
| Follow our [Auth implementation guide](/guides/primitives/auth) to integrate Nango in your app. | ||
|
|
||
| To obtain your own production credentials, follow the setup guide linked below. | ||
| </Step> | ||
| </Steps> | ||
|
|
||
| ## SAGE Member Integration Guides | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| Nango-maintained guides for common use cases. | ||
|
|
||
| - [How do I link my account?](/api-integrations/sage-member/connect) | ||
| Connect your SAGE Member account using the Connect UI | ||
|
|
||
| ## Pre-built syncs & actions for SAGE Member | ||
|
|
||
| Enable them in your dashboard. Extend and customize to fit your needs. | ||
|
|
||
| import PreBuiltUseCases from "/snippets/generated/sage-member/PreBuiltUseCases.mdx" | ||
|
|
||
| <PreBuiltUseCases /> | ||
|
|
||
| --- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| --- | ||
| title: SAGE Member - How do I link my account? | ||
| sidebarTitle: SAGE Member | ||
| --- | ||
|
|
||
| # Overview | ||
|
|
||
| To authenticate with SAGE Member, you need: | ||
| 1. **Account Number** — Your SAGE Member account number. | ||
| 2. **Login ID** — The login ID of the SAGE Member user account making the requests. | ||
| 3. **Authentication Key** — The key associated with that user's login, used to authenticate every request. | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - An active SAGE Workplace subscription or Supplier Advantage membership. | ||
| - A SAGE Member module enabled on your account (enable modules on the Services tab). | ||
|
|
||
| ### Step 1: Finding your credentials | ||
|
|
||
| SAGE Member authenticates on a per-user basis, so each set of credentials is tied to one user on your account. | ||
|
|
||
| 1. Log in to your SAGE account and go to **Developer APIs** > **SAGE Connect**. | ||
| 2. Go to the **Users** tab. | ||
| 3. Click your profile in the top right to find your **Account Number**. | ||
| 4. Select the user that will be making API requests and note their **Login ID** and **Authentication Key**. | ||
|
|
||
| <img src="/api-integrations/sage-member/users_tab.png"/> | ||
|
hassan254-prog marked this conversation as resolved.
|
||
|
|
||
|
|
||
| ### Step 2: Enter credentials in the Connect UI | ||
|
|
||
| Once you have your credentials: | ||
| 1. Open the form where you need to authenticate with SAGE Member. | ||
| 2. Enter your **Account Number**, **Login ID**, and **Authentication Key** in their designated fields. | ||
| 3. Submit the form, and you should be successfully authenticated. | ||
|
|
||
| <img src="/api-integrations/sage-member/form.png" style={{maxWidth: "450px" }}/> | ||
|
hassan254-prog marked this conversation as resolved.
|
||
|
|
||
|
|
||
| You are now connected to SAGE Member. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| _No pre-built syncs or actions available yet._ | ||
|
|
||
| <Tip>Not seeing the integration you need? [Build your own](/guides/functions/functions-guide) independently.</Tip> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17633,6 +17633,46 @@ sage-intacct: | |
| description: Your Sage Intacct User Password | ||
| secret: true | ||
|
|
||
| sage-member: | ||
| display_name: SAGE Member | ||
| categories: | ||
| - marketing | ||
| - e-commerce | ||
| auth_mode: API_KEY | ||
| proxy: | ||
| base_url: https://www.promoplace.com/ws/ws.dll | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that doesn't look like SAGE related?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| body: | ||
| auth: | ||
| acctId: ${connectionConfig.acctId} | ||
| loginId: ${connectionConfig.loginId} | ||
| key: ${apiKey} | ||
| docs: https://nango.dev/docs/api-integrations/sage-member | ||
| docs_connect: https://nango.dev/docs/api-integrations/sage-member/connect | ||
| credentials: | ||
| apiKey: | ||
| type: string | ||
| title: Authentication Key | ||
| example: 56edbbe3********************141a | ||
| pattern: '^[a-f0-9]{32}$' | ||
| description: The authentication key for your SAGE Member user. | ||
| secret: true | ||
| doc_section: '#step-1-finding-your-credentials' | ||
| connection_config: | ||
| acctId: | ||
| type: string | ||
| title: Account Number | ||
| description: Your SAGE Member account number. | ||
| pattern: '^[0-9]+$' | ||
| example: '4353478' | ||
| order: 1 | ||
| doc_section: '#step-1-finding-your-credentials' | ||
| loginId: | ||
| type: string | ||
| title: Login ID | ||
| description: The login ID for your SAGE Member user. | ||
| order: 2 | ||
| doc_section: '#step-1-finding-your-credentials' | ||
|
|
||
| sage-people: | ||
| display_name: Sage People | ||
| categories: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -509,25 +509,47 @@ export function buildProxyBody({ | |
| }: { | ||
| config: ApplicationConstructedProxyConfiguration; | ||
| connection: ConnectionForProxy; | ||
| }): Record<string, string> | null { | ||
| }): Record<string, string | Record<string, string>> | null { | ||
| if (!config.provider?.proxy?.body) { | ||
| return null; | ||
| } | ||
|
|
||
| const body: Record<string, string> = {}; | ||
| const replacers = { | ||
| connectionConfig: connection.connection_config, | ||
| credentials: connection.credentials, | ||
| ...(connection.credentials as unknown as Record<string, string>) | ||
| }; | ||
|
|
||
| const interpolateLeaf = (value: string): string | null => { | ||
| const interpolated = interpolateIfNeeded(value, replacers); | ||
| return interpolated.includes('${') ? null : interpolated; | ||
| }; | ||
|
|
||
| const body: Record<string, string | Record<string, string>> = {}; | ||
| for (const [key, value] of Object.entries(config.provider.proxy.body)) { | ||
| if (typeof value !== 'string') { | ||
| if (typeof value === 'string') { | ||
| const interpolated = interpolateLeaf(value); | ||
| if (interpolated !== null) { | ||
| body[key] = interpolated; | ||
| } | ||
| continue; | ||
| } | ||
| const interpolated = interpolateIfNeeded(value, replacers); | ||
| if (!interpolated.includes('${')) { | ||
| body[key] = interpolated; | ||
|
|
||
| if (!isPlainObject(value)) { | ||
| continue; | ||
| } | ||
| const nested: Record<string, string> = {}; | ||
| for (const [nestedKey, nestedValue] of Object.entries(value)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if there is more than one level of nesting?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated this in the latest commit to handle recursive object mapping instead of only supporting a single level of nested objects. |
||
| if (typeof nestedValue !== 'string') { | ||
| continue; | ||
| } | ||
| const interpolated = interpolateLeaf(nestedValue); | ||
| if (interpolated !== null) { | ||
| nested[nestedKey] = interpolated; | ||
| } | ||
| } | ||
| if (Object.keys(nested).length > 0) { | ||
| body[key] = nested; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -545,15 +567,19 @@ function isPlainObject(value: unknown): value is Record<string, unknown> { | |
| ); | ||
| } | ||
|
|
||
| function mergeInjectedBody(existing: unknown, injected: Record<string, string>): unknown { | ||
| function mergeInjectedBody(existing: unknown, injected: Record<string, string | Record<string, string>>): unknown { | ||
| if (!existing) return injected; | ||
| if (isPlainObject(existing)) return { ...existing, ...injected }; | ||
| if (existing instanceof FormData) { | ||
| for (const [key, value] of Object.entries(injected)) existing.append(key, value); | ||
| for (const [key, value] of Object.entries(injected)) { | ||
| if (typeof value === 'string') existing.append(key, value); | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| return existing; | ||
| } | ||
| if (existing instanceof URLSearchParams) { | ||
| for (const [key, value] of Object.entries(injected)) existing.append(key, value); | ||
| for (const [key, value] of Object.entries(injected)) { | ||
| if (typeof value === 'string') existing.append(key, value); | ||
| } | ||
| return existing; | ||
| } | ||
| return existing; | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.