From 09283b4aec353c7f953fd73f77974203b94b45f7 Mon Sep 17 00:00:00 2001 From: Joshua Greben Date: Fri, 13 Oct 2023 09:09:47 -0700 Subject: [PATCH] Card payment report using accounts data from FOLIO, run as rake task --- .github/workflows/ruby.yml | 44 +++++- .gitignore | 2 + Capfile | 12 -- .../download_payment_batch_detail_report.rb | 49 ------- Dockerfile | 20 +++ Gemfile | 12 +- Gemfile.lock | 137 ++++++++---------- README.md | 90 +++++++----- Rakefile | 11 ++ config/deploy.rb | 24 --- config/deploy/dev.rb | 6 - config/deploy/prod.rb | 6 - config/settings/test.yml | 11 -- cronjob.yaml | 54 +++++++ .../download_payment_batch_detail_report.rb | 121 ++++++++++++++++ debug.yaml | 50 +++++++ download_report.rb | 8 - {config/settings => files}/.keep | 0 helpers/write_to_csv.rb | 18 --- helpers/year_or_prev_year.rb | 8 - pv-volume.yaml | 11 ++ sendmail.yaml | 124 ++++++++++++++++ ...wnload_payment_batch_detail_report_spec.rb | 49 ++++--- spec/fixtures/report_file.csv | 6 +- spec/spec_helper.rb | 2 + tasks/download_report.rake | 31 ++++ 26 files changed, 611 insertions(+), 295 deletions(-) delete mode 100644 Capfile delete mode 100644 CyberSource/download_payment_batch_detail_report.rb create mode 100644 Dockerfile create mode 100644 Rakefile delete mode 100644 config/deploy.rb delete mode 100644 config/deploy/dev.rb delete mode 100644 config/deploy/prod.rb delete mode 100644 config/settings/test.yml create mode 100644 cronjob.yaml create mode 100644 cyber_source/download_payment_batch_detail_report.rb create mode 100644 debug.yaml delete mode 100755 download_report.rb rename {config/settings => files}/.keep (100%) delete mode 100644 helpers/write_to_csv.rb delete mode 100644 helpers/year_or_prev_year.rb create mode 100644 pv-volume.yaml create mode 100644 sendmail.yaml create mode 100644 tasks/download_report.rake diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index a9aa4c5..42e0773 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -1,16 +1,18 @@ -name: CI +name: PushImage -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] +on: push + +permissions: + contents: read + packages: write + +env: + OKAPI_URL: http://okapi:9130 + SLEEP: 0 jobs: test: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Ruby @@ -23,3 +25,29 @@ jobs: run: bundle exec rspec - name: Verify output file run: cat spec/fixtures/report_file.csv + + build-and-push-image: + runs-on: ubuntu-latest + steps: + + - name: Extract branch name + id: extract_branch + run: echo "::set-output name=branch::$(echo ${GITHUB_REF#refs/heads/})" + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Log in to the Container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image and push to Docker Hub and GitHub Container Registry + uses: docker/build-push-action@v2 + with: + push: true + tags: ghcr.io/sul-dlss/cybersource-rest-ruby:${{ steps.extract_branch.outputs.branch }} + file: Dockerfile diff --git a/.gitignore b/.gitignore index a553f14..fae062e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /config/settings/dev.yml /config/settings/prod.yml /log/* +*.csv +secret.yaml diff --git a/Capfile b/Capfile deleted file mode 100644 index 32bfabf..0000000 --- a/Capfile +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -# Load DSL and set up stages -require 'capistrano/setup' - -# Include default deployment tasks -require 'capistrano/deploy' -require 'capistrano/bundler' -require 'dlss/capistrano' - -# Load custom tasks from `lib/capistrano/tasks` if you have any defined -Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } diff --git a/CyberSource/download_payment_batch_detail_report.rb b/CyberSource/download_payment_batch_detail_report.rb deleted file mode 100644 index 4280db4..0000000 --- a/CyberSource/download_payment_batch_detail_report.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'cybersource_rest_client' -require 'csv' -require_relative '../helpers/write_to_csv' -require_relative '../helpers/year_or_prev_year' -require 'config' -Config.load_and_set_settings(Config.setting_files('config', ENV.fetch('STAGE', nil))) - -# class to download the PaymentbatchDetailReport from CyberSource -class DownloadPaymentBatchDetailReport - attr_accessor :date - - def initialize(date) - # Date format: e.g. '2001-02-03' - @date = date ? Date.parse(date) : Date.today - end - - def main - first_day = Date.new(YearOrPrev.year(@date), @date.prev_month.month, 1) - last_day = Date.new(YearOrPrev.year(@date), @date.prev_month.month, -1) - - (first_day..last_day).each do |date| - report_date = date.strftime('%Y-%m-%d') - puts report_date - begin - data, status_code, headers = download_report(report_date) - puts report_date, status_code, headers - rescue StandardError => e - puts report_date - puts e.message - next - end - - WriteToCsv.file(data, date) - end - rescue StandardError => e - puts e.message - end - - def download_report(report_date) - CyberSource::ReportDownloadsApi.new( - CyberSource::ApiClient.new, Settings.configurationDictionary.to_h.transform_keys(&:to_s) - ).download_report( - report_date, - 'PaymentBatchDetailReport_Daily_Classic', organization_id: 'wfgsulair' - ) - end -end diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6acda3d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM ruby:2.7.1 + +USER root + +# Install and setup mail +RUN apt update +RUN apt-get install -y bsd-mailx +RUN apt-get install -y sendmail + +WORKDIR /etc/mail + +RUN echo \"AuthInfo:smtp.domain.com "U:USERNAME" "P:PASSWORD" "M:PLAIN"\" > authinfo +RUN makemap hash authinfo < authinfo +RUN make + +WORKDIR /home/cybersource + +COPY . . + +RUN bundle install diff --git a/Gemfile b/Gemfile index 2d819d0..1399f61 100644 --- a/Gemfile +++ b/Gemfile @@ -1,20 +1,18 @@ source 'https://rubygems.org' ruby '2.7.1' -gem 'config' gem 'cybersource_rest_client', '0.0.31' +gem "folio_client", "~> 0.13.0" + +gem "mail", "~> 2.8" + group :development, :test do gem 'byebug' gem 'rspec' gem 'rubocop' gem 'rubocop-performance' gem 'rubocop-rspec' + gem 'webmock' end -group :deployment do - gem 'capistrano' - gem 'capistrano-bundler' - gem 'capistrano-shared_configs' - gem 'dlss-capistrano' -end diff --git a/Gemfile.lock b/Gemfile.lock index 1d78ad7..e0f3510 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,34 +7,14 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - airbrussh (1.4.1) - sshkit (>= 1.6.1, != 1.7.0) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) base64 (0.1.1) - bundler-audit (0.9.1) - bundler (>= 1.2.0, < 3) - thor (~> 1.0) byebug (11.1.3) - capistrano (3.17.3) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundle_audit (0.4.0) - bundler-audit (~> 0.5) - capistrano (~> 3.0) - capistrano-bundler (>= 1.4) - capistrano-bundler (2.1.0) - capistrano (~> 3.1) - capistrano-one_time_key (0.1.0) - capistrano (~> 3.0) - capistrano-shared_configs (0.2.2) concurrent-ruby (1.2.2) - config (4.2.1) - deep_merge (~> 1.2, >= 1.2.1) - dry-validation (~> 1.0, >= 1.0.0) + crack (0.4.5) + rexml cybersource_rest_client (0.0.31) activesupport (~> 6.0, >= 6.0.3.2) addressable (~> 2.3, >= 2.3.0) @@ -42,81 +22,78 @@ GEM json (~> 2.1, >= 2.1.0) jwt (~> 2.1.0) typhoeus (~> 1.0, >= 1.0.1) - deep_merge (1.2.2) + date (3.3.3) diff-lcs (1.5.0) - dlss-capistrano (4.4.0) - capistrano (~> 3.0) - capistrano-bundle_audit (>= 0.3.0) - capistrano-one_time_key - capistrano-shared_configs - dry-configurable (1.0.1) - dry-core (~> 1.0, < 2) - zeitwerk (~> 2.6) dry-core (1.0.0) concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) - dry-inflector (1.0.0) - dry-initializer (3.1.1) - dry-logic (1.5.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - zeitwerk (~> 2.6) - dry-schema (1.13.3) - concurrent-ruby (~> 1.0) - dry-configurable (~> 1.0, >= 1.0.1) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-logic (>= 1.4, < 2) - dry-types (>= 1.7, < 2) - zeitwerk (~> 2.6) - dry-types (1.7.1) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0) - dry-inflector (~> 1.0) - dry-logic (~> 1.4) - zeitwerk (~> 2.6) - dry-validation (1.10.0) + dry-monads (1.6.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-schema (>= 1.12, < 2) zeitwerk (~> 2.6) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.15.5) + faraday (2.7.11) + base64 + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.16.3) + folio_client (0.13.0) + activesupport (>= 4.2, < 8) + dry-monads + faraday + marc + zeitwerk + hashdiff (1.0.1) i18n (1.14.1) concurrent-ruby (~> 1.0) interface (1.0.5) json (2.6.3) jwt (2.1.0) language_server-protocol (3.17.0.3) - minitest (5.19.0) - net-scp (4.0.0) - net-ssh (>= 2.6.5, < 8.0.0) - net-ssh (7.1.0) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marc (1.2.0) + rexml + scrub_rb (>= 1.0.1, < 2) + unf + mini_mime (1.1.5) + minitest (5.20.0) + net-imap (0.3.7) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.1) + timeout + net-smtp (0.4.0) + net-protocol parallel (1.23.0) parser (3.2.2.4) ast (~> 2.4.1) racc - public_suffix (4.0.6) + public_suffix (5.0.3) racc (1.7.1) rainbow (3.1.1) - rake (13.0.6) regexp_parser (2.8.2) rexml (3.2.6) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) rspec-mocks (~> 3.12.0) - rspec-core (3.12.0) + rspec-core (3.12.2) rspec-support (~> 3.12.0) - rspec-expectations (3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-mocks (3.12.0) + rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-support (3.12.0) + rspec-support (3.12.1) rubocop (1.57.1) base64 (~> 0.1.1) json (~> 2.3) @@ -143,15 +120,21 @@ GEM rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) - sshkit (1.21.4) - net-scp (>= 1.1.2) - net-ssh (>= 2.8.0) - thor (1.2.1) + ruby2_keywords (0.0.5) + scrub_rb (1.0.1) + timeout (0.4.0) typhoeus (1.4.0) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) unicode-display_width (2.5.0) + webmock (3.19.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) zeitwerk (2.6.12) PLATFORMS @@ -159,19 +142,17 @@ PLATFORMS DEPENDENCIES byebug - capistrano - capistrano-bundler - capistrano-shared_configs - config cybersource_rest_client (= 0.0.31) - dlss-capistrano + folio_client (~> 0.13.0) + mail (~> 2.8) rspec rubocop rubocop-performance rubocop-rspec + webmock RUBY VERSION ruby 2.7.1p83 BUNDLED WITH - 2.1.4 + 2.3.22 diff --git a/README.md b/README.md index 488c11e..be3c268 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Download the Payment Batch Detail Report using the CyberSource SDK -This LibSys repository is a hard fork and heavily modified and pruned version of the [CyberSource working code sample](https://github.com/CyberSource/cybersource-rest-samples-ruby) which demonstrates Ruby +This LibSys repository is a hard fork and heavily modified and pruned version of the [CyberSource working code sample](https://github.com/CyberSource/cybersource-rest-samples-ruby) which demonstrates Ruby integration with the CyberSource REST APIs through the [CyberSource Ruby SDK](https://github.com/CyberSource/cybersource-rest-client-ruby). ## Requirements @@ -13,56 +13,68 @@ integration with the CyberSource REST APIs through the [CyberSource Ruby SDK](ht ``` $ git clone https://github.com/sul-dlss/cybersource-rest-ruby ``` -* Install the cybersource-rest-client-ruby (from RubyGems.org) +* Install the gems ``` - $ gem install cybersource_rest_client + $ bundle ``` -* Run the report: +* Run the test report: ``` - $ STAGE=dev ruby CyberSource/download_payment_batch_detail_report.rb + $ SLEEP=0 rspec +``` + +## Create Persistent Volume +``` +kubectl -n ${namespace} apply -f pv-volume.yaml ``` ## Setting the API credentials for an API request -Configure the following information in the settings file: - - * Http Signature -```yml -configurationDictionary: - merchantID: 'wfgsulair' - runEnvironment: 'cybersource.environment.production' - timeout: 1000 # In Milliseconds - authenticationType: 'http_signature' - enableLog: false - merchantsecretKey: 'your_key_serial_number' - merchantKeyId: 'your_key_shared_secret' -``` - -Obtain the merchantsecretKey and merchantKeyId from the CyberSource Business Center. +Configure the `MERCHANT_KEY_ID` and `MERCHANT_SECRET_KEY` variables in the environment The variables for `STAGE`, `APP_PASSWORD` (for folio) and `APP_USERNAME` are configured using the `db-connect-modules` folio secret. + +Obtain the `merchantsecretKey` and `merchantKeyId` from the CyberSource Business Center. You will need permissions from merchants@stanford.edu to access the CyberSource Business Center with the option to generate security keys. -## Switching between the sandbox environment and the production environment -CyberSource maintains a complete sandbox environment for testing and development purposes. This sandbox environment is an exact -duplicate of our production environment with the transaction authorization and settlement process simulated. By default, this sample code is -configured to communicate with the sandbox environment. To switch to the production environment, set the appropriate environment -constant in config/settings/#{stage} file. For example: - -#### For PRODUCTION use -```yml -configurationDictionary: - merchantID: 'wfgsulair' - runEnvironment: 'cybersource.environment.production' - ... -``` -#### For TESTING use -```yml -configurationDictionary: - merchantID: 'wfgsulair' - runEnvironment: 'cybersource.environment.sandbox' - ... +### Create secret with the Cybersource credentials +``` +apiVersion: v1 +kind: Secret +metadata: + name: cybersource +type: Opaque +data: + MERCHANT_KEY_ID: base64 encoded accessKey + MERCHANT_SECRET_KEY: base64 encoded secretKey +``` +Then: +``` +kubectl -n ${namespace} apply -f secret.yaml +``` + +### Create secret with sendmail config +``` +kubectl -n ${namespace} apply -f sendmail.yaml ``` +## Run the ChronJob +``` +kubectl -n ${namespace} apply -f cronjob.yaml +``` + +## Run the Debug pod +``` +kubectl -n ${namespace} apply -f debug.yaml +``` + +Give sendmail a couple of minutes to restart before checking the /home/harvester/harvestlog directory. + + +## Switching between the sandbox environment and the production environment +CyberSource maintains a complete sandbox environment for testing and development purposes. This sandbox environment is an exact +duplicate of our production environment with the transaction authorization and settlement process simulated. By default, this sample code is +configured to communicate with the sandbox environment. +To switch to the sandbox environment, set the `CYBS_ENV=sandbox` environment variable. + ## CyberSource API Reference The [API Reference Guide](http://developer.cybersource.com/api/reference) provides examples of what information is needed for a particular request and how that information would be formatted. Using those examples, you can easily determine what methods would be necessary to include that information in a request using this SDK. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..dcc0d15 --- /dev/null +++ b/Rakefile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +begin + require 'rubocop/rake_task' + RuboCop::RakeTask.new +rescue LoadError + puts 'Unable to load RuboCop.' +end + +# Import external rake tasks +Dir.glob('tasks/**/*.rake').each { |r| import r } diff --git a/config/deploy.rb b/config/deploy.rb deleted file mode 100644 index 192062c..0000000 --- a/config/deploy.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -set :application, 'cybersource-rest-ruby' -set :repo_url, 'https://github.com/sul-dlss/cybersource-rest-ruby.git' -set :user, 'sirsi' - -# Default branch is :main -ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp - -# Default value for :log_level is :debug -set :log_level, :info - -# Default deploy_to directory is /var/www/my_app_name -set :deploy_to, "/s/SUL/Bin/CyberSource/#{fetch(:application)}" - -# Default value for linked_dirs is [] -set :linked_dirs, %w[config/settings cybs_log] - -# Default value for keep_releases is 5 -set :keep_releases, 3 - -set :default_env, { path: '/s/sirsi/.rvm/gems/ruby-2.7.1/bin:/usr/local/rvm/gems/ruby-2.7.1/bin:'\ - '/usr/local/rvm/gems/ruby-2.7.1@global/bin:/usr/local/rvm/rubies/ruby-2.7.1/bin:'\ - '/usr/ucb:/bin:/usr/bin:/etc:/usr/sbin:/usr/local/rvm/bin' } diff --git a/config/deploy/dev.rb b/config/deploy/dev.rb deleted file mode 100644 index c8d73c8..0000000 --- a/config/deploy/dev.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -server 'symphony-app-dev-1.stanford.edu', user: fetch(:user).to_s, roles: %w[app db web] - -# allow ssh to host -Capistrano::OneTimeKey.generate_one_time_key! diff --git a/config/deploy/prod.rb b/config/deploy/prod.rb deleted file mode 100644 index e56fc61..0000000 --- a/config/deploy/prod.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -server 'symphony-app-prod-1.stanford.edu', user: fetch(:user).to_s, roles: %w[app db web] - -# allow ssh to host -Capistrano::OneTimeKey.generate_one_time_key! diff --git a/config/settings/test.yml b/config/settings/test.yml deleted file mode 100644 index 7a58763..0000000 --- a/config/settings/test.yml +++ /dev/null @@ -1,11 +0,0 @@ -configurationDictionary: - merchantID: '' - runEnvironment: '' - timeout: 0 # In Milliseconds - authenticationType: '' - enableLog: false - merchantsecretKey: '' - merchantKeyId: '' - logDirectory: '' - -output_file_path: 'spec/fixtures/report_cyberfile' diff --git a/cronjob.yaml b/cronjob.yaml new file mode 100644 index 0000000..b970a1e --- /dev/null +++ b/cronjob.yaml @@ -0,0 +1,54 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: cybersource +spec: + schedule: "30 8 3 * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + imagePullSecrets: + - name: ghcr + containers: + - image: ghcr.io/sul-dlss/cybersource-rest-ruby:main + name: cybersource + envFrom: + - secretRef: + name: db-connect-modules + imagePullPolicy: Always + command: [ "/bin/bash", "-c", "--" ] + args: [ "cd /etc/mail && make && service sendmail restart; cd /home/cybersource && rake download_report >> files/reports.log" 2>&1;" ] + env: + - name: MERCHANT_KEY_ID + valueFrom: + secretKeyRef: + name: cybersource + key: MERCHANT_KEY_ID + - name: MERCHANT_SECRET_KEY + valueFrom: + secretKeyRef: + name: cybersource + key: MERCHANT_SECRET_KEY + volumeMounts: + - mountPath: home/files + name: cybersource-files + - mountPath: /etc/mail/sendmail.mc + name: sendmail + subPath: sendmail.mc + resources: + limits: + cpu: 128m + memory: 512Mi + requests: + cpu: 50m + memory: 400Mi + restartPolicy: Never + volumes: + - name: cybersource-files + persistentVolumeClaim: + claimName: cybersource-files + - name: sendmail + secret: + secretName: sendmail diff --git a/cyber_source/download_payment_batch_detail_report.rb b/cyber_source/download_payment_batch_detail_report.rb new file mode 100644 index 0000000..d0b4193 --- /dev/null +++ b/cyber_source/download_payment_batch_detail_report.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'cybersource_rest_client' +require 'folio_client' +require 'csv' +require 'date' +require 'json' + +# class to download the PaymentbatchDetailReport from CyberSource +class DownloadPaymentBatchDetailReport + attr_accessor :date + + CONFIGURATION_DICTIONARY = { + merchantID: 'wfgsulair', + runEnvironment: "cybersource.environment.#{ENV.fetch('CYBS_ENV', 'production')}", + timeout: 3000, + authenticationType: 'http_signature', + enableLog: true, + merchantKeyId: ENV.fetch('MERCHANT_KEY_ID', nil), + merchantsecretKey: ENV.fetch('MERCHANT_SECRET_KEY', nil), + logDirectory: 'harvestlog', + logFilename: 'cybs' + }.freeze + + def initialize(date) + # Date format: e.g. '2001-02-03' + @date = date ? Date.parse(date) : Date.today + end + + def year_or_prev(date) + date.month == 1 ? date.prev_year.year : date.year + end + + def main + credits = [] + + first_day = Date.new(year_or_prev(@date), @date.prev_month.month, 1) + last_day = Date.new(year_or_prev(@date), @date.prev_month.month, -1) + + (first_day..last_day).each do |date| + report_date = date.strftime('%Y-%m-%d') + begin + data, status_code, headers = download_report(report_date) + puts report_date, status_code, headers, data + rescue StandardError => e + puts report_date + puts e.message + next + end + next unless data + + data.each_line.with_index do |batch_detail, index| + if index > 1 + user_id = batch_detail.split(',')[4] + paid = batch_detail.split(',')[8] + paydate = batch_detail.split(',')[12]&.chomp + + begin + accounts = folio_client.get('/accounts', { query: "userId==#{user_id}" }) + puts accounts + rescue Exception => e + puts e.message + next + end + + if accounts && (accounts['totalRecords']).positive? + accounts['accounts'].each do |account| + account_date = Date.parse(account['metadata']['createdDate']) + payment_date = Date.parse(paydate) + + unless (account_date == payment_date) && (account['amount'].to_f == paid.to_f) && (account['paymentStatus']['name'] == 'Paid fully') + next + end + + payload = { + paydate: paydate, + user_id: user_id, + folio_payment_id: account['id'], + reason: account['feeFineType'], + paid: paid + } + + credits.push(payload) + end + end + end + end + puts credits + sleep(ENV.fetch('SLEEP', 2)&.to_i) + end + credits.any? && CSV.open('files/credits.csv', 'w+') do |csv| + csv << credits.first.keys + credits.each do |credit| + csv << credit.values + end + end + rescue StandardError => e + puts e.message + puts e.backtrace + end + + def download_report(report_date) + CyberSource::ReportDownloadsApi.new( + CyberSource::ApiClient.new, CONFIGURATION_DICTIONARY.transform_keys(&:to_s) + ).download_report( + report_date, + 'PaymentBatchDetailReport_Daily_Classic', organization_id: 'wfgsulair' + ) + end + + def folio_client + FolioClient.configure( + url: ENV.fetch('OKAPI_URL', 'http://okapi:9130'), + login_params: { + 'username' => ENV.fetch('APP_USER', nil), + 'password' => ENV.fetch('APP_PASSWORD', nil) + }, + okapi_headers: { 'X-Okapi-Tenant' => 'sul', 'User-Agent' => 'FolioApiClient' } + ) + end +end diff --git a/debug.yaml b/debug.yaml new file mode 100644 index 0000000..adbaa08 --- /dev/null +++ b/debug.yaml @@ -0,0 +1,50 @@ + +apiVersion: v1 +kind: Pod +metadata: + name: cybersource +spec: + containers: + - image: ghcr.io/sul-dlss/cybersource-rest-ruby:main + name: cybersource + envFrom: + - secretRef: + name: db-connect-modules + imagePullPolicy: Always + # Just spin & wait forever + command: [ "/bin/bash", "-c", "--" ] + args: [ "cd /etc/mail && make && service sendmail restart; while true; do sleep 30; done;" ] + env: + - name: MERCHANT_KEY_ID + valueFrom: + secretKeyRef: + name: cybersource + key: MERCHANT_KEY_ID + - name: MERCHANT_SECRET_KEY + valueFrom: + secretKeyRef: + name: cybersource + key: MERCHANT_SECRET_KEY + volumeMounts: + - mountPath: /home/cyberlog + name: cyberlog + - mountPath: /etc/mail/sendmail.mc + name: sendmail + subPath: sendmail.mc + resources: + limits: + cpu: 128m + memory: 512Mi + requests: + cpu: 50m + memory: 400Mi + restartPolicy: Never + volumes: + - name: sendmail + secret: + secretName: sendmail + - name: cyberlog + persistentVolumeClaim: + claimName: cybersource + imagePullSecrets: + - name: ghcr \ No newline at end of file diff --git a/download_report.rb b/download_report.rb deleted file mode 100755 index 42854d4..0000000 --- a/download_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/ruby -# frozen_string_literal: true - -require_relative 'CyberSource/download_payment_batch_detail_report' - -date = ARGV[0] -result = DownloadPaymentBatchDetailReport.new(date) -result.main diff --git a/config/settings/.keep b/files/.keep similarity index 100% rename from config/settings/.keep rename to files/.keep diff --git a/helpers/write_to_csv.rb b/helpers/write_to_csv.rb deleted file mode 100644 index 25027d8..0000000 --- a/helpers/write_to_csv.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true -require 'config' -Config.load_and_set_settings(Config.setting_files('config', ENV.fetch('STAGE', nil))) - -# module to write a csv file to a directory -module WriteToCsv - def self.file(data, date) - return unless data - - date_stamp = Date.new(date.year, date.month, 1).strftime('%Y%m') - f = File.new("#{Settings.output_file_path}.#{date_stamp}", 'a+') - data.each_line.with_index do |line, index| - f.write(line) if index > 1 - end - - f.close - end -end diff --git a/helpers/year_or_prev_year.rb b/helpers/year_or_prev_year.rb deleted file mode 100644 index a9ca8e2..0000000 --- a/helpers/year_or_prev_year.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -# module to return the current year or the previous year as needed -module YearOrPrev - def self.year(date) - date.month == 1 ? date.prev_year.year : date.year - end -end diff --git a/pv-volume.yaml b/pv-volume.yaml new file mode 100644 index 0000000..af0c0b6 --- /dev/null +++ b/pv-volume.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: cybersource +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/sendmail.yaml b/sendmail.yaml new file mode 100644 index 0000000..16c90bc --- /dev/null +++ b/sendmail.yaml @@ -0,0 +1,124 @@ +apiVersion: v1 +kind: Secret +metadata: + name: sendmail +type: Opaque +stringData: + sendmail.mc: | + divert(-1)dnl + #----------------------------------------------------------------------------- + # $Sendmail: debproto.mc,v 8.15.2 2019-08-25 15:04:16 cowboy Exp $ + # + # Copyright (c) 1998-2010 Richard Nelson. All Rights Reserved. + # + # cf/debian/sendmail.mc. Generated from sendmail.mc.in by configure. + # + # sendmail.mc prototype config file for building Sendmail 8.15.2 + # + # Note: the .in file supports 8.7.6 - 9.0.0, but the generated + # file is customized to the version noted above. + # + # This file is used to configure Sendmail for use with Debian systems. + # + # If you modify this file, you will have to regenerate /etc/mail/sendmail.cf + # by running this file through the m4 preprocessor via one of the following: + # * make (or make -C /etc/mail) + # * sendmailconfig + # * m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf + # The first two options are preferred as they will also update other files + # that depend upon the contents of this file. + # + # The best documentation for this .mc file is: + # /usr/share/doc/sendmail-doc/cf.README.gz + # + #----------------------------------------------------------------------------- + divert(0)dnl + # + # Copyright (c) 1998-2005 Richard Nelson. All Rights Reserved. + # + # This file is used to configure Sendmail for use with Debian systems. + # + define(`_USE_ETC_MAIL_')dnl + include(`/usr/share/sendmail/cf/m4/cf.m4')dnl + VERSIONID(`$Id: sendmail.mc, v 8.15.2-14~deb10u1 2019-08-25 15:04:16 cowboy Exp $') + OSTYPE(`debian')dnl + DOMAIN(`debian-mta')dnl + dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE + undefine(`confHOST_STATUS_DIRECTORY')dnl #DAEMON_HOSTSTATS= + dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE + dnl # + dnl # General defines + dnl # + dnl # SAFE_FILE_ENV: [undefined] If set, sendmail will do a chroot() + dnl # into this directory before writing files. + dnl # If *all* your user accounts are under /home then use that + dnl # instead - it will prevent any writes outside of /home ! + dnl # define(`confSAFE_FILE_ENV', `')dnl + dnl # + dnl # Daemon options - restrict to servicing LOCALHOST ONLY !!! + dnl # Remove `, Addr=' clauses to receive from any interface + dnl # If you want to support IPv6, switch the commented/uncommentd lines + dnl # + FEATURE(`no_default_msa')dnl + dnl DAEMON_OPTIONS(`Family=inet6, Name=MTA-v6, Port=smtp, Addr=::1')dnl + DAEMON_OPTIONS(`Family=inet, Name=MTA-v4, Port=smtp, Addr=127.0.0.1')dnl + dnl DAEMON_OPTIONS(`Family=inet6, Name=MSP-v6, Port=submission, M=Ea, Addr=::1')dnl + DAEMON_OPTIONS(`Family=inet, Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl + dnl # + dnl # Be somewhat anal in what we allow + define(`confPRIVACY_FLAGS',dnl + `needmailhelo,needexpnhelo,needvrfyhelo,restrictqrun,restrictexpand,nobodyreturn,authwarnings')dnl + dnl # + dnl # Define connection throttling and window length + define(`confCONNECTION_RATE_THROTTLE', `15')dnl + define(`confCONNECTION_RATE_WINDOW_SIZE',`10m')dnl + dnl # + dnl # Features + dnl # + dnl # use /etc/mail/local-host-names + FEATURE(`use_cw_file')dnl + dnl # + dnl # The access db is the basis for most of sendmail's checking + FEATURE(`access_db', , `skip')dnl + dnl # + dnl # The greet_pause feature stops some automail bots - but check the + dnl # provided access db for details on excluding localhosts... + FEATURE(`greet_pause', `1000')dnl 1 seconds + dnl # + dnl # Delay_checks allows sender<->recipient checking + FEATURE(`delay_checks', `friend', `n')dnl + dnl # + dnl # If we get too many bad recipients, slow things down... + define(`confBAD_RCPT_THROTTLE',`3')dnl + dnl # + dnl # Stop connections that overflow our concurrent and time connection rates + FEATURE(`conncontrol', `nodelay', `terminate')dnl + FEATURE(`ratecontrol', `nodelay', `terminate')dnl + dnl # + dnl # If you're on a dialup link, you should enable this - so sendmail + dnl # will not bring up the link (it will queue mail for later) + dnl define(`confCON_EXPENSIVE',`True')dnl + dnl # + dnl # SMTP Host + dnl # + define(`SMART_HOST', `mail.folio-test.svc.cluster.local')dnl + define(`RELAY_MAILER',`esmtp')dnl + define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl + dnl # + FEATURE(`authinfo')dnl + dnl # + dnl # Dialup/LAN connection overrides + dnl # + include(`/etc/mail/m4/dialup.m4')dnl + include(`/etc/mail/m4/provider.m4')dnl + dnl # + dnl # Masquerading options + FEATURE(`always_add_domain')dnl + MASQUERADE_AS(`stanford.edu')dnl + FEATURE(`allmasquerade')dnl + FEATURE(`masquerade_envelope')dnl + dnl # + dnl # Default Mailer setup + MAILER_DEFINITIONS + MAILER(`local')dnl + MAILER(`smtp')dnl \ No newline at end of file diff --git a/spec/download_payment_batch_detail_report_spec.rb b/spec/download_payment_batch_detail_report_spec.rb index e09dbac..7d06d07 100644 --- a/spec/download_payment_batch_detail_report_spec.rb +++ b/spec/download_payment_batch_detail_report_spec.rb @@ -1,36 +1,49 @@ # frozen_string_literal: true require 'spec_helper' -require_relative '../helpers/year_or_prev_year' -require_relative '../CyberSource/download_payment_batch_detail_report' +require_relative '../cyber_source/download_payment_batch_detail_report' RSpec.describe DownloadPaymentBatchDetailReport do - let(:result) { described_class } - let(:download_report) { File.read(File.join(Dir.pwd, 'spec', 'fixtures', 'report_file.csv')) } let(:dstring) { Date.today.to_s } - let(:d) { Date.parse(dstring) } - let(:date_stamp) { Date.new(YearOrPrev.year(d), d.prev_month.month, 1).strftime('%Y%m') } - let(:file) { File.join(Dir.pwd, 'spec', 'fixtures', "report_cyberfile.#{date_stamp}") } + let(:report) { described_class.new(dstring) } + let(:file) { File.join(Dir.pwd, 'files/credits.csv') } + let(:download_report) { File.read(File.join(Dir.pwd, 'spec', 'fixtures', 'report_file.csv')) } before do File.open(file, 'a+') - allow_any_instance_of(result).to receive(:download_report).and_return(download_report) - result.new(dstring).main + + stub_request(:post, 'http://example.com/authn/login') + .with(body: { 'username' => 'username', 'password' => 'password' }) + + stub_request(:get, "#{ENV['OKAPI_URL']}/accounts") + .with(query: hash_including) + .to_return(body: '{ + "accounts": [ + { + "amount": 35.0, + "paymentStatus": { + "name": "Paid fully" + }, + "feeFineType": "Lost item fee", + "metadata": { + "createdDate": "2023-10-10T09:33:55.532+00:00" + }, + "userId": "f1cf56ef-5071-477f-b3ee-8779721a7f44", + "id": "cf238f9f-7018-47b7-b815-bb2db798e19f" + } + ], + "totalRecords": 1 + }') + + allow(report).to receive(:download_report).and_return(download_report) + report.main end after do FileUtils.rm_f(file) end - it 'downloads reports for the entire month' do - puts "Opening file: #{file}\n" + it 'creates a file of credits' do expect(File.readlines(file).size).to be >= 28 end - - it 'contains a csv file of details about the transactions' do - puts "Opening file: #{file}\n" - File.foreach(file) do |row| - expect(row.split(',').size).to eq 13 - end - end end diff --git a/spec/fixtures/report_file.csv b/spec/fixtures/report_file.csv index 9ea56a6..3322a13 100644 --- a/spec/fixtures/report_file.csv +++ b/spec/fixtures/report_file.csv @@ -1,3 +1,3 @@ -wfgsulair,PaymentBatchDetailReport_Daily_Classic,PaymentBatchDetail,1.1,2020-06-14T07:00:00Z to 2020-06-15T07:00:00Z -batch_id,merchant_id batch_date,request_id,merchant_ref_number,TransactionReferenceNumber,payment_type,currency,amount,ics_applications,status,transaction_date -12345678901,wfgsulair,2020-01-15T04:13:56Z,5.92183E+21,456789-0-1,87654321,Visa,USD,10,ics_auth,ics_bill,BATCHED,2020-01-15T01:11:36Z +wfgsulair,PaymentBatchDetailReport_Daily_Classic,PaymentBatchDetail,1.10,2023-10-10T07:00:00Z to 2023-10-11T07:00:00Z,,,,,,, +batch_id,merchant_id,batch_date,request_id,merchant_ref_number,TransactionReferenceNumber,payment_type,currency,amount,ics_applications,status,transaction_date +21265991580,wfgsulair,2023-10-11T04:15:21Z,6969304358566698703248,f1cf56ef-5071-477f-b3ee-8779721a7f44,93211601,MasterCard,USD,35.00,"ics_bill,ics_auth",BATCHED,2023-10-10T09:33:55Z diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6f81292..1fb839a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'webmock/rspec' + ENV['STAGE'] = 'test' RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate diff --git a/tasks/download_report.rake b/tasks/download_report.rake new file mode 100644 index 0000000..66b8df3 --- /dev/null +++ b/tasks/download_report.rake @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +desc 'download cybersource monthly report, date format [9999-99-99]' +task :download_report, [:date] do |_, args| + require_relative '../cyber_source/download_payment_batch_detail_report' + result = DownloadPaymentBatchDetailReport.new(args[:date]) + puts result.main +end + +desc 'test mail' +task :mail do + require 'date' + require 'mail' + + options = { host: 'mail.folio-test.svc.cluster.local', + address: 'mail.folio-test.svc.cluster.local', + port: 587 + } + + Mail.defaults do + delivery_method :smtp, options + end + + Mail.deliver do + from 'cardPaymentReporter@libsys.stanford.edu' + to 'jgreben@stanford.edu' + subject "Card Payment Report #{Date.today.strftime('%m-%Y')}" + body File.read('files/credits.csv') + add_file 'files/credits.csv' + end +end