diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d9000a7..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,109 +0,0 @@ -version: 2.1 - -orbs: - slack: circleci/slack@4.1 - -slack/notify: &slack_notify - branch_pattern: master - event: fail - channel: ci-build-status - template: SLACK_TAG_CI_FAILURE_TEMPLATE - -commands: - export_slack_id: - steps: - - run: - name : Exporting circleci username as slack id. - command: echo 'export SLACK_PARAM_MENTIONS="$CIRCLE_USERNAME"' >> "$BASH_ENV" - - run: - name : CircleCi To Slack user mapping. - command: | - echo $GITHUB_SLACK_USERMAPPING | base64 --decode > github_slack - while read -r line || [[ -n $line ]]; - do - [[ ${line//[[:space:]]/} =~ ^#.* || -z "$line" ]] && continue - echo "$line" | tr "=" "\n" | while read -r key; do - read -r value - if [ "$CIRCLE_USERNAME" = "${key}" ]; then - echo "export SLACK_PARAM_MENTIONS='<@${value}>'" >> $BASH_ENV - fi - done - done < github_slack - rm github_slack - -context: &context - - slack-templates - - slack_Oauth - - Github_Slack_UserMapping - -"-": &build7 - working_directory: ~/sift-php - steps: - - checkout - - export_slack_id - - run: sudo composer self-update - - restore_cache: - keys: - - composer-v1-{{ checksum "composer.lock" }} - - composer-v1- - - run: composer install -n --prefer-dist - - save_cache: - key: composer-v1-{{ checksum "composer.lock" }} - paths: - - vendor - - run: composer exec phpunit -v - - when: - condition: - equal: [ master, << pipeline.git.branch >> ] - steps: - - slack/notify: - <<: *slack_notify - -jobs: - build71: - <<: *build7 - docker: - - image: circleci/php:7.1.27-stretch - - build72: - <<: *build7 - docker: - - image: circleci/php:7.2.16-stretch - - build73: - <<: *build7 - docker: - - image: circleci/php:7.3.3-stretch - - build74: - <<: *build7 - docker: - - image: circleci/php:7.4.25-cli-buster-node - - run-integration-tests-php74: - docker: - - image: circleci/php:7.4.25-cli-buster-node - steps: - - checkout - - run: - name: Install the lib and run tests - command: | - composer install -n --prefer-dist - php test_integration_app/main.php - -workflows: - version: 2 - check_compile: - jobs: - - build71: - context: *context - - build72: - context: *context - - build73: - context: *context - - build74: - context: *context - - run-integration-tests-php74: - filters: - branches: - only: master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d6c4510 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: PHP Composer + +on: + push: + branches: + - master + pull_request: + branches: + - master + +env: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + API_KEY: ${{ secrets.API_KEY }} + +jobs: + build: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ ubuntu-latest ] + php-versions: [ '7.1.27', '7.2.16', '7.3.3', '7.4.25' ] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + - name: Validate composer.json and composer.lock + run: | + sudo composer self-update + - run: | + composer install --prefer-dist --no-progress + - name: siftPhpTest + run: | + composer exec phpunit -v + + run-integration-tests-php74: + runs-on: ubuntu-latest + if: ${{ github.ref == 'refs/heads/master' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: setup php74 + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4.25' + - run: | + composer install --prefer-dist --no-progress + - run: | + php test_integration_app/main.php diff --git a/.jenkins/Jenkinsfile b/.jenkins/Jenkinsfile deleted file mode 100644 index 68bde66..0000000 --- a/.jenkins/Jenkinsfile +++ /dev/null @@ -1,113 +0,0 @@ -// Load Jenkins shared library -jenkinsBranch = 'v0.15.0' -sharedLib = library("shared-lib@${jenkinsBranch}") - -def siftPhpWorkflow = sharedLib.com.sift.ci.SiftPhpWorkflow.new() -def ciUtil = sharedLib.com.sift.ci.CIUtil.new() -def stackdriver = sharedLib.com.sift.ci.StackDriverMetrics.new() - -// Default GitHub status context for automatically triggered builds -def defaultStatusContext = 'Jenkins:auto' - -// GitHub repo name -def repoName = 'sift-php' - -pipeline { - agent none - options { - timestamps() - skipDefaultCheckout() - disableConcurrentBuilds() - disableRestartFromStage() - parallelsAlwaysFailFast() - buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '30', numToKeepStr: '') - timeout(time: 1, unit: 'HOURS') - } - environment { - GIT_BRANCH = "${env.CHANGE_BRANCH != null? env.CHANGE_BRANCH : env.BRANCH_NAME}" - } - stages { - stage('Initialize') { - steps { - script { - statusContext = defaultStatusContext - // Get the commit sha for the build - commitSha = ciUtil.commitHashForBuild() - ciUtil.updateGithubCommitStatus(repoName, statusContext, 'Started', 'pending', commitSha) - } - } - } - stage ('Build and Test Workflows') { - steps { - script { - def workflows = [:] - - // Unit tests - def unitTestStages = [ - 'Tests - php 7.1':'7.1', - 'Tests - php 7.2':'7.2', - 'Tests - php 7.3':'7.3', - 'Tests - php 7.4':'7.4' - ] - - unitTestStages.each { stageName, phpVersion -> - workflows[stageName] = { - stage(stageName) { - ciUtil.updateGithubCommitStatus(repoName, stageName, 'Started', 'pending', commitSha) - try { - siftPhpWorkflow.runSiftPhpTest("php-${phpVersion.replace('.', '-')}-pod-template.yaml", "php${phpVersion}-${BUILD_TAG}", phpVersion) - ciUtil.updateGithubCommitStatus(repoName, stageName, 'SUCCESS', 'success', commitSha) - } catch (Exception e) { - ciUtil.updateGithubCommitStatus(repoName, stageName, 'FAILURE', 'failure', commitSha) - print("${stageName} failed") - throw e - } - } - } - } - - // Integration tests - def phpIntegrationTestsVersion = "7.4" - def integrationTestsStageName = "Integration Tests - php ${phpIntegrationTestsVersion}" - workflows[integrationTestsStageName] = { - stage(integrationTestsStageName) { - if (env.GIT_BRANCH.equals('master')) { - ciUtil.updateGithubCommitStatus(repoName, integrationTestsStageName, 'Started', 'pending', commitSha) - try { - siftPhpWorkflow.runSiftPhpIntegration("php-${phpIntegrationTestsVersion.replace('.','-')}-pod-template.yaml", - "php${phpIntegrationTestsVersion}-${BUILD_TAG}", - phpIntegrationTestsVersion) - ciUtil.updateGithubCommitStatus(repoName, integrationTestsStageName, 'SUCCESS', 'success', commitSha) - } catch (Exception e) { - ciUtil.updateGithubCommitStatus(repoName, integrationTestsStageName, 'FAILURE', 'failure', commitSha) - print("${integrationTestsStageName} failed") - throw e - } - } - } - } - - parallel workflows - } - } - } - } - post { - success { - script { - ciUtil.updateGithubCommitStatus(repoName, statusContext, currentBuild.currentResult, 'success', commitSha) - } - } - unsuccessful { - script { - ciUtil.updateGithubCommitStatus(repoName, statusContext, currentBuild.currentResult, 'failure', commitSha) - ciUtil.notifySlack(repoName, commitSha) - } - } - always { - script { - stackdriver.updatePipelineStatistics(this) - } - } - } -} diff --git a/CHANGES.RST b/CHANGES.RST index a393c65..990a93c 100644 --- a/CHANGES.RST +++ b/CHANGES.RST @@ -1,3 +1,7 @@ +4.8.0 (2024-05-27) +================ +- Added support for warnings in Events API + 4.7.0 (2024-02-22) ================ - Webhooks API diff --git a/README.md b/README.md index 97c7741..8d9e381 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,19 @@ $sift = new SiftClient(); ## Usage ### Track an event -Here's an example that sends a `$transaction` event to sift. +To learn more about the Events API visit our [developer docs](https://developers.sift.com/docs/php/events-api/overview). + +**Optional Params** +- `return_score`: `:true` or `:false` +- `return_action`: `:true` or `:false` +- `return_workflow_status`: `:true` or `:false` +- `return_route_info`: `:true` or `:false` +- `force_workflow_run`: `:true` or `:false` +- `include_score_percentiles`: `:true` or `:false` +- `warnings`: `:true` or `:false` +- `abuse_types`: `["payment_abuse", "content_abuse", "content_abuse", "account_abuse", "legacy", "account_takeover"]` + +Here's an example that sends a `$transaction` event to Sift. ```php $sift = new SiftClient(['api_key' => 'my_api_key', 'account_id' => 'my_account_id']); $response = $sift->track('$transaction', [ diff --git a/composer.json b/composer.json index 8217b9d..4773562 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "keywords": ["sift", "sift science", "php", "fraud"], "homepage": "https://github.com/SiftScience/sift-php", "license": "MIT", - "version": "4.7.0", + "version": "4.8.0", "authors": [ { "name": "Alex Lopatin" diff --git a/lib/Sift.php b/lib/Sift.php index c9548e3..ecf472b 100644 --- a/lib/Sift.php +++ b/lib/Sift.php @@ -12,7 +12,7 @@ abstract class Sift */ public static $account_id; - const VERSION = '4.7.0'; + const VERSION = '4.8.0'; public static function setApiKey($api_key) { diff --git a/lib/SiftClient.php b/lib/SiftClient.php index 56cf0fc..57de07c 100644 --- a/lib/SiftClient.php +++ b/lib/SiftClient.php @@ -105,7 +105,9 @@ function __construct($opts = []) { * version of the Events API is used. * - include_score_percentiles(optional) : Whether to add new parameter in the query parameter. * - if include_score_percentiles is true then add a new parameter called fields in the query parameter - * + * - include_warnings(optional) : Whether to add list of warnings (if any) to response. + * - if include_warnings is true then add 'warnings' to the 'fields' query parameter. + * * @return null|SiftResponse */ public function track($event, $properties, $opts = []) { @@ -119,7 +121,8 @@ public function track($event, $properties, $opts = []) { 'path' => null, 'timeout' => $this->timeout, 'version' => $this->version, - 'include_score_percentiles' => false + 'include_score_percentiles' => false, + 'include_warnings' => false ]); $this->validateArgument($event, 'event', 'string'); @@ -146,8 +149,16 @@ public function track($event, $properties, $opts = []) { $params['force_workflow_run'] = 'true'; if ($opts['abuse_types']) $params['abuse_types'] = implode(',', $opts['abuse_types']); - if($opts['include_score_percentiles']) - $params['fields'] = 'SCORE_PERCENTILES'; + if ($opts['include_score_percentiles'] || $opts['include_warnings']) { + $fields = []; + if ($opts['include_score_percentiles']) { + $fields[] = 'SCORE_PERCENTILES'; + } + if ($opts['include_warnings']) { + $fields[] = 'WARNINGS'; + } + $params['fields'] = implode(',', $fields); + } try { $request = new SiftRequest( diff --git a/test/SiftClientTest.php b/test/SiftClientTest.php index 02575b1..057c16a 100644 --- a/test/SiftClientTest.php +++ b/test/SiftClientTest.php @@ -1417,6 +1417,31 @@ public function testGetUserScoreWithScorePercentiles(): void ); } + public function testSuccessfulTrackEventWithWarnings(): void { + $mockUrl = 'https://api.sift.com/v205/events?fields=WARNINGS'; + $mockResponse = new SiftResponse(' + { + "status": 0, "error_message": "OK", + "warnings": { + "count": 1, + "items": [ + { + "message": "Invalid field value" + } + ] + } + }', 200, null); + SiftRequest::setMockResponse($mockUrl, SiftRequest::POST ,$mockResponse); + $response = $this->client->track('$transaction', $this->transaction_properties, [ + "version" => "205", "include_warnings" => true + ]); + + $this->assertTrue($response->isOk()); + $this->assertEquals('OK', $response->apiErrorMessage); + $this->assertEquals(1, $response->body["warnings"]["count"]); + $this->assertEquals('Invalid field value', $response->body["warnings"]["items"][0]["message"]); + } + public function testPostWebhook(): void { $mockUrl = diff --git a/test_integration_app/events_api/test_events_api.php b/test_integration_app/events_api/test_events_api.php index 5dadde4..9889a1b 100644 --- a/test_integration_app/events_api/test_events_api.php +++ b/test_integration_app/events_api/test_events_api.php @@ -1400,12 +1400,32 @@ function verification() '$verified_event' => '$login', '$reason' => '$automated_rule', // Verification was triggered based on risk score '$verification_type' => '$sms', - '$verified_value' => '14155551212' + '$verified_value' => '14155551212', + '$verified_entity_id' => $GLOBALS['session_id'] ); return $this->client->track('$verification', $properties); } + function verification_with_warnings() + { + // Sample $verification event + $properties = array( + // Required Fields + '$user_id' => $GLOBALS['user_id'], + '$session_id' => $GLOBALS['session_id'], + '$status' => '$pending', + + // $verified_entity_id is not provided to generate warnings + '$verified_event' => '$login', + '$reason' => '$automated_rule', + '$verification_type' => '$sms', + '$verified_value' => '14155551212' + ); + + return $this->client->track('$verification', $properties, ["include_warnings" => true]); + } + } ?> diff --git a/test_integration_app/main.php b/test_integration_app/main.php index 8427753..94e3b3b 100644 --- a/test_integration_app/main.php +++ b/test_integration_app/main.php @@ -50,6 +50,7 @@ public function test_all_methods(){ $this->assertEquals(1, $objUtil->isOk($objEvents->update_order())); $this->assertEquals(1, $objUtil->isOk($objEvents->update_password())); $this->assertEquals(1, $objUtil->isOk($objEvents->verification())); + $this->assertEquals(1, $objUtil->hasWarnings($objEvents->verification_with_warnings())); print("Events API Tested \n"); // Decisions API @@ -113,6 +114,16 @@ public function isOkCheck($response) { return false; } } + + public function hasWarnings($response) { + // expect http status 200, api status 0, and warnings present + if (isset($response->apiStatus)){ + return ($response->apiStatus == 0) + && (200 === $response->httpStatusCode) + && ($response->body["warnings"]); + } + return false; + } } $objMain = new Main();