PowereBay.psm1

Class eBayAPI_ClientCredentials {
    [string]$ClientID
    [string]$ClientSecret
    [string]$RUName

    eBayAPI_ClientCredentials(
        [string]$ClientID,
        [string]$ClientSecret,
        [string]$RUName
    ){
        $this.ClientID = $ClientID
        $this.ClientSecret = $ClientSecret
        $this.RUName = $RUName
    }
}
Class eBayAPI_OauthCode {
    [datetime]$Expires
    [string]$Code

    eBayAPI_OauthCode (
        [int]$expires_in,
        [string]$Code
    ){
        $this.Expires = (Get-Date).AddSeconds($expires_in)
        $this.Code = $Code
    }

    eBayAPI_OauthCode(
        [hashtable]$In
    ){
        If($In.ContainsKey('expires_in') -and $In.ContainsKey('code')){
            $this.Expires = (Get-Date).AddSeconds($In['expires_in'])
            $this.Code = $In['Code']
        }Else{
            Throw 'Improperly formatted hash table'
        }
    }

    [bool]IsExpired(){
        return (Get-Date) -gt $this.Expires
    }

    [string]ToString(){
        return $this.Code
    }
}
Class eBayAPI_OauthUserToken {
    [datetime]$Expires
    [string]$Token
    [string]$RefreshToken
    [string]$RefreshTokenExpires

    eBayAPI_OauthUserToken(
        [string]$access_token,
        [datetime]$expires,
        [string]$refresh_token,
        [datetime]$refresh_token_expires
    ){
        $this.Token = $access_token
        $this.Expires = $expires
        $this.RefreshToken = $refresh_token
        $this.RefreshTokenExpires = $refresh_token_expires
    }

    eBayAPI_OauthUserToken(
        [PSCustomObject]$In
    ){
        If(
            $In.PSObject.Properties.Name -contains 'access_token' -and
            $In.PSObject.Properties.Name -contains 'expires_in' -and
            $In.PSObject.Properties.Name -contains 'refresh_token' -and
            $In.PSObject.Properties.Name -contains 'refresh_token_expires_in'
        ){
            $this.Expires = (Get-Date).AddSeconds($In.expires_in)
            $this.Token = $In.access_token
            $this.RefreshToken = $In.refresh_token
            $this.RefreshTokenExpires = (Get-Date).AddSeconds($In.refresh_token_expires_in)
        }Else{
            Throw 'Improperly formatted object'
        }
    }

    [string]ToString(){
        return $this.Token
    }

    [void]Update(
        # Designed to accept the refresh token response
        [PSCustomObject]$In
    ){
        If(
            $In.PSObject.Properties.Name -contains 'access_token' -and
            $In.PSObject.Properties.Name -contains 'expires_in'
        ){
            $this.Expires = (Get-Date).AddSeconds($In.expires_in)
            $this.Token = $In.access_token
        }Else{
            Throw 'Improperly formatted object'
        }
    }
}
Function Get-eBayLocalToken {
    Param(
        [string]$RegistryPath = 'HKCU:\Software\PowereBay'
    )
    Function ConvertTo-PlainText{
        Param(
            [string]$string
        )
        $ss = ConvertTo-SecureString $string
        $creds = New-Object pscredential('eBay',$ss)
        $creds.GetNetworkCredential().Password
    }
    $properties = 'Expires','RefreshTokenExpires'
    $propertiesToConvert = 'Token','RefreshToken','ClientID','ClientSecret','RUName'
    [string[]]$combinedProperties = $properties+$propertiesToConvert
    $obj = Get-ItemProperty $RegistryPath | Select $combinedProperties
    ForEach($property in $propertiesToConvert){
        $obj."$property" = ConvertTo-PlainText $obj."$property"
    }
    $global:eBayAuthConfig = [PSCustomObject]@{
        UserToken = [eBayAPI_OauthUserToken]::new($obj.Token,$obj.Expires,$obj.RefreshToken,$obj.RefreshTokenExpires)
        ClientCredentials = [eBayAPI_ClientCredentials]::new($obj.ClientID,$obj.ClientSecret,$obj.RUName)
    }
    $eBayAuthConfig
}
#https://developer.ebay.com/api-docs/sell/fulfillment/resources/order/methods/getOrders
#https://developer.ebay.com/api-docs/sell/fulfillment/resources/order/methods/getOrder
Function Get-eBayOrder {
    [cmdletbinding(
        DefaultParameterSetName = 'MultipleOrders'
    )]
    Param(
        [string]$Token = $eBayAuthConfig.UserToken.Token, #should be replaced with some sort of global variable or something
        [Parameter(
            ParameterSetName = 'SingleOrder'
        )]
        [string]$OrderID,
        [Parameter(
            ParameterSetName = 'MultipleOrders'
        )]
        [string[]]$OrderIDs,
        #region filter options
        [datetime]$CreationDateStart,
        [datetime]$CreationDateEnd,
        [datetime]$LastModifiedDateStart,
        [datetime]$LastModifiedDateEnd,
        [ValidateSet('NOT_STARTED','IN_PROGRESS','FULFILLED')]
        [string]$OrderFulfillmentStatus,
        [ValidateRange(1,1000)]
        [int]$Limit
        #endregion
    )
    $baseUri = 'https://api.ebay.com/sell/fulfillment/v1/order'
    $headers = @{
        Authorization = "Bearer $token"
        Accept = 'application/json'
    }
    If($PSCmdlet.ParameterSetName -eq 'SingleOrder'){
        Invoke-RestMethod -Method Get -Uri "$baseUri/$OrderID" -Headers $headers
    }ElseIf($PSCmdlet.ParameterSetName -eq 'MultipleOrders'){
        # %5B and %5D are: [ ]
        # .000Z is added because the API spec requires it but PS doesn't add it by default with '-Format s'
        If($PSBoundParameters.ContainsKey('CreationDateStart')){
        #If($CreationDateStart){
            $creationDateFilter = 'creationdate:%5B{CreationDateStart}..{CreationDateEnd}%5D'
            $CreationDateStartUTC = Get-Date -Date $CreationDateStart.ToUniversalTime() -Format s
            $creationDateFilter = $creationDateFilter -replace '{CreationDateStart}',"$CreationDateStartUTC.000Z"
            If($CreationDateEnd){
                $CreationDateEndUTC = Get-Date -Date $CreationDateEnd.ToUniversalTime() -Format s
                $creationDateFilter = $creationDateFilter -replace '{CreationDateEnd}',"$CreationDateEndUTC.000Z"
            }Else{
                $creationDateFilter = $creationDateFilter -replace '{CreationDateEnd}',''
            }
        }ElseIf($PSBoundParameters.ContainsKey('LastModifiedDateStart')){
            $lastModDateFilter = 'lastmodifieddate:%5B{LastModifiedDateStart}..{LastModifiedDateEnd}%5D'
            $lastModDateStartUTC = Get-Date -Date $LastModifiedDateStart.ToUniversalTime() -Format s
            $lastModDateFilter = $lastModDateFilter -replace '{LastModifiedDateStart}',"$lastModDateStartUTC.000Z"
            If($LastModifiedDateEnd){
                $lastModDateEndUTC = Get-Date -Date $LastModifiedDateEnd.ToUniversalTime() -Format s
                $lastModDateFilter = $lastModDateFilter -replace '{LastModifiedDateEnd}',"$lastModDateEndUTC.000Z"
            }Else{
                $lastModDateFilter = $lastModDateFilter -replace '{LastModifiedDateEnd}',''
            }
        }
        $parameters = @()
        If($creationDateFilter){
            $parameters += "filter=$creationDateFilter"
        }ElseIf($lastModDateFilter){
            $parameters += "filter=$lastModDateFilter"
        }
        If($limit){
            $parameters += "limit=$Limit"
        }
        $strParameters = $parameters -join '&'
        Write-Verbose "$baseUri`?$strParameters"
        $response = Invoke-RestMethod -Uri "$baseUri`?$strParameters" -Headers $headers
        $response.Orders
    }
}
#https://developer.ebay.com/devzone/post-order/post-order_v2_return_search__get.html#overview
Function Get-eBayReturns {
    [cmdletbinding()]
    Param(
        [string]$Token = $eBayAuthConfig.UserToken.Token,
        [ValidateRange(1,200)]
        [int]$Limit
    )
    $baseUri = 'https://api.ebay.com/post-order/v2/return/search'
    $headers = @{
        Authorization = "IAF $token"
        Accept = 'application/json'
        'X-EBAY-C-MARKETPLACE-ID' = 'EBAY_US'
        'Content-Type' = 'application/json'
    }

    Write-Verbose "$baseUri"
    $response = Invoke-RestMethod -Uri "$baseUri" -Headers $headers
    $response.members
}
#https://developer.ebay.com/api-docs/sell/fulfillment/resources/order/shipping_fulfillment/methods/getShippingFulfillments
#https://developer.ebay.com/api-docs/sell/fulfillment/resources/order/shipping_fulfillment/methods/getShippingFulfillment
Function Get-eBayShippingFulfillment {
    Param(
        [string]$OrderID,
        [string]$FulfillmentID,
        [string]$Token = $eBayAuthConfig.UserToken.Token
    )
    $baseUri = 'https://api.ebay.com/sell/fulfillment/v1/order/{orderId}/shipping_fulfillment' -replace '{orderid}',$OrderID
    If($FulfillmentID){
        $baseUri = "$baseUri/$FulfillmentID"
    }
    $headers = @{
        Authorization = "Bearer $token"
        Accept = 'application/json'
    }
    Invoke-RestMethod -Method Get -Uri $baseUri -Headers $headers
}
Function Invoke-eBayAuthentication {
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [string]$ClientID,
        [Parameter(
            Mandatory = $true
        )]
        [string]$ClientSecret,
        [Parameter(
            Mandatory = $true
        )]
        [string]$RUName
    )
    $creds = [eBayAPI_ClientCredentials]::new($ClientID,$ClientSecret,$RUName)
    $code = Get-eBayAuthorizationCode -ClientID $ClientID -RUName $RUName
    $token = Get-eBayUserToken -ClientID $ClientID -ClientSecret $ClientSecret -RuName $RUName -AuthorizationCode $code.ToString()
    Save-eBayToken $token
    Save-eBayCredentials $creds
}
# https://developer.ebay.com/api-docs/static/oauth-refresh-token-request.html
Function Update-eBayUserToken {
    Param(
        [eBayAPI_OauthUserToken]$Token = $eBayAuthConfig.UserToken,
        [eBayAPI_ClientCredentials]$Credentials = $eBayAuthConfig.ClientCredentials,
        [string[]]$Scope = @('https://api.ebay.com/oauth/api_scope/sell.inventory','https://api.ebay.com/oauth/api_scope/sell.fulfillment')
    )
    $baseUri = 'https://api.ebay.com/identity/v1/oauth2/token'

    $encodedAuthorization = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Credentials.ClientID)`:$($Credentials.ClientSecret)"))
    $scopeString = $Scope -join '%20'

    # Build the headers
    $headers = @{
        'Content-Type' = 'application/x-www-form-urlencoded'
        Authorization = "Basic $encodedAuthorization"
    }

    # URL encode the parameters
    $encodedRefreshToken = [System.Web.HttpUtility]::UrlEncode($($Token.RefreshToken))
    $encodedRUName = [System.Web.HttpUtility]::UrlEncode($($Credentials.RUName))

    # Build the body using the URL encoded parameters
    $body = @(
        "grant_type=refresh_token"
        "&refresh_token=$encodedRefreshToken"
        "&scope=$scopeString"
    ) -join ''


    $newToken = Invoke-RestMethod -Method Post -Uri $baseUri -Body $body -Headers $headers
    If($newToken){
        $Token.Update($newToken)
        Save-eBayToken -Token $Token
    }
}
Function Confirm-eBayUserToken {
    Param(
        [ValidateNotNullOrEmpty()]
        [eBayAPI_OauthUserToken]$UserToken = $eBayAuthConfig.UserToken,
        [ValidateNotNullOrEmpty()]
        [eBayAPI_ClientCredentials]$ClientCredentials = $eBayAuthConfig.ClientCredentials
    )
    If(($UserToken.Expires -lt (Get-Date)) -and ($ClientCredentials)){
        Try{
            Update-eBayUserToken -Token $UserToken -Credentials $ClientCredentials
            $true
        }Catch{
            $false
        }
    }Else{
        $true
    }
}
Add-Type -AssemblyName System.Web
Add-Type -AssemblyName System.Windows.Forms
Function Get-eBayAuthorizationCode {
    [cmdletbinding()]
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [string]$ClientID,
        [Parameter(
            Mandatory = $true
        )]
        [string]$RUName
    )
    <#
        Doc on flow: https://developer.ebay.com/api-docs/static/oauth-authorization-code-grant.html
    #>

    # URL encode our parameters
    $encodedClientID = [System.Web.HttpUtility]::UrlEncode($ClientID)
    $encodedRUName = [System.Web.HttpUtility]::UrlEncode($RUName)

    # Other variables
    $baseUri = 'https://auth.ebay.com/oauth2/authorize'
    $scope = 'https://api.ebay.com/oauth/api_scope/sell.inventory%20https://api.ebay.com/oauth/api_scope/sell.fulfillment'

    # Build the logon uri
    $logonUri = "$baseUri`?client_id=$encodedClientID&redirect_uri=$encodedRUName&response_type=code&scope=$scope&prompt=login"
    Write-Verbose "Logon URL: $logonUri"

    # Build a form to use with our web object
    $Form = New-Object -TypeName 'System.Windows.Forms.Form' -Property @{
        Width = 680
        Height = 640
    }

    # Build the web object to brows to the logon uri
    $Web = New-Object -TypeName 'System.Windows.Forms.WebBrowser' -Property @{
        Width = 680
        Height = 640
        Url = $logonUri
    }

    # Add the document completed script to detect when the code is in the uri
    $DocumentCompleted_Script = {
        if ($web.Url.AbsoluteUri -match "error=[^&]*|code=[^&]*") {
            $form.Close()
        }
    }

    # Add controls to the form
    $web.ScriptErrorsSuppressed = $true
    $web.Add_DocumentCompleted($DocumentCompleted_Script)
    $form.Controls.Add($web)
    $form.Add_Shown({ $form.Activate() })

    # Run the form
    [void]$form.ShowDialog()

    # Parse the output
    $QueryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
    $Response = @{ }
    foreach ($key in $queryOutput.Keys) {
        $Response["$key"] = $QueryOutput[$key]
    }

    # Return the output
    # eventually this will be something more secure than just outputting it... I hope.
    If($Response['isAuthSuccessful']){
        [eBayAPI_OAuthCode]::new($Response)
    }Else{
        Throw 'Error'
    }
}
Function Get-eBayUserToken {
    [cmdletbinding()]
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [string]$ClientID,
        [Parameter(
            Mandatory = $true
        )]
        [string]$ClientSecret,
        [Parameter(
            Mandatory = $true
        )]
        [string]$RuName,
        [Parameter(
            Mandatory = $true
        )]
        [string]$AuthorizationCode
    )
    # Base uri for getting user tokens
    $baseUri = 'https://api.ebay.com/identity/v1/oauth2/token/'

    # Format for authorization header
    # Doc on auth format: https://developer.ebay.com/api-docs/static/oauth-base64-credentials.html
    $encodedAuthorization = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$ClientID`:$ClientSecret"))
    Write-Verbose $encodedAuthorization

    # Build the headers
    $headers = @{
        'Content-Type' = 'application/x-www-form-urlencoded'
        Authorization = "Basic $encodedAuthorization"
    }
    Write-Verbose $headers.ToString()

    # URL encode the parameters
    $encodedAuthCode = [System.Web.HttpUtility]::UrlEncode($AuthorizationCode)
    $encodedRUName = [System.Web.HttpUtility]::UrlEncode($RuName)

    # Build the body using the URL encoded parameters
    $body = @(
        "grant_type=authorization_code"
        "&code=$encodedAuthCode"
        "&redirect_uri=$encodedRUName"
    ) -join ''
    Write-Verbose $body.ToString()

    # Send the request
    $response = Invoke-WebRequest -Uri $baseUri -Body $body -Headers $headers -Method Post

    # Return the response
    [eBayAPI_OauthUserToken]::new($($response.Content | ConvertFrom-Json))
}
Function Save-eBayCredentials {
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [eBayAPI_ClientCredentials]$Creds,
        [string]$RegistryPath = 'HKCU:\Software\PowereBay'
    )
    If(-not(Test-Path $RegistryPath)){
        New-Item $RegistryPath
    }
    New-ItemProperty -Path $RegistryPath -Name 'ClientID' -Value (ConvertFrom-SecureString (ConvertTo-SecureString $Creds.ClientID -AsPlainText -Force)) -Force | Out-Null
    New-ItemProperty -Path $RegistryPath -Name 'ClientSecret' -Value (ConvertFrom-SecureString (ConvertTo-SecureString $Creds.ClientSecret -AsPlainText -Force)) -Force | Out-Null
    New-ItemProperty -Path $RegistryPath -Name 'RUName' -Value (ConvertFrom-SecureString (ConvertTo-SecureString $Creds.RUName -AsPlainText -Force)) -Force | Out-Null
}
Function Save-eBayToken {
    Param(
        [Parameter(
            Mandatory = $true
        )]
        [eBayAPI_OauthUserToken]$Token,
        [string]$RegistryPath = 'HKCU:\Software\PowereBay'
    )
    If(-not(Test-Path $RegistryPath)){
        New-Item $RegistryPath
    }
    New-ItemProperty -Path $RegistryPath -Name 'Expires' -Value $Token.Expires -Force | Out-Null
    New-ItemProperty -Path $RegistryPath -Name 'Token' -Value (ConvertFrom-SecureString (ConvertTo-SecureString $Token.Token -AsPlainText -Force)) -Force | Out-Null
    New-ItemProperty -Path $RegistryPath -Name 'RefreshToken' -Value (ConvertFrom-SecureString (ConvertTo-SecureString $Token.RefreshToken -AsPlainText -Force)) -Force | Out-Null
    New-ItemProperty -Path $RegistryPath -Name 'RefreshTokenExpires' -Value $Token.RefreshTokenExpires -Force | Out-Null
    Get-eBayLocalToken
}