chartmogul-ruby 4.11.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faa555815d7e07a1c7988a30a4006a2644bacca3c8d082e9b8e7e99233d199b5
4
- data.tar.gz: 9b95f302c336e40149799d347ef43e0ba0286663b9041dabbd1883f5d0c28d43
3
+ metadata.gz: 61fce0fe848312e4a28d7cc9cf5baf8bf62bf7a92da434789bfbffa3099c78ef
4
+ data.tar.gz: 26ab5587c9a0d6c4dc543aca4ff558d4bbe9a89d3e309642c0b933cae2f81433
5
5
  SHA512:
6
- metadata.gz: c1bb1b4874f2fbc7fb164a263bcddedcdb52793fdb640726f07d694652e01d8c403851a2058e88838d402b27d663f9c670dc0848a253c6a55249186462723e02
7
- data.tar.gz: bab85de33f8480fe7eac0065caf5f846875a45a153d46d568cfaa1ef3f548c7e898d747d4cb9d90b518cb5c6c10b8ea2b2330182ce8a15d4b82d9e9eeb0cb4ab
6
+ metadata.gz: b28f66fa79c6ac41497911e0a2811ce4e419c7ec9a143d0acc8adbe85c5ef21ec71c87533855cf5cf119f0a3fdef519ca1717e0f4cc712c258ac35b363f7dd19
7
+ data.tar.gz: b6ce9397538e1d1cad6c799e697c4ee4d0a33b2de8d31d628bc6cfe9d34e1902f2f5257ecf343ca4e316cb828e8c4615e1208dc3bb25ce5265716c7c50131f08
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ cd "$CLAUDE_PROJECT_DIR" || exit 0
3
+
4
+ INPUT=$(cat)
5
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
6
+
7
+ # Only track .rb files, skip vendored/generated paths
8
+ if [[ "$FILE" == *.rb ]] && [[ "$FILE" != */vendor/* ]] && [[ "$FILE" != */coverage/* ]]; then
9
+ TRACKER="${TMPDIR:-/tmp}/claude-edited-rb-files-${CLAUDE_HOOK_SESSION_ID:-default}"
10
+ echo "$FILE" >> "$TRACKER"
11
+ sort -u "$TRACKER" -o "$TRACKER"
12
+ fi
13
+
14
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ cd "$CLAUDE_PROJECT_DIR" || exit 0
3
+
4
+ TRACKER="${TMPDIR:-/tmp}/claude-edited-rb-files-${CLAUDE_HOOK_SESSION_ID:-default}"
5
+
6
+ ctx=""
7
+ had_edits=false
8
+
9
+ # Batch rubocop autocorrect on tracked files (if any were edited)
10
+ if [[ -f "$TRACKER" ]]; then
11
+ files=$(cat "$TRACKER")
12
+ rm -f "$TRACKER"
13
+
14
+ if [[ -n "$files" ]]; then
15
+ had_edits=true
16
+ rubocop_out=$(echo "$files" | xargs bundle exec rubocop -a 2>&1) || true
17
+ offenses=$(echo "$rubocop_out" | grep -E "^.+:[0-9]+:[0-9]+:" | head -20)
18
+ if [[ -n "$offenses" ]]; then
19
+ ctx+="rubocop offenses remaining after autocorrect:\n$offenses\n"
20
+ fi
21
+ fi
22
+ fi
23
+
24
+ # Only run rspec if files were edited (~1s)
25
+ if [[ "$had_edits" == true ]]; then
26
+ rspec_out=$(bundle exec rspec 2>&1)
27
+ rspec_exit=$?
28
+ if [[ $rspec_exit -ne 0 ]]; then
29
+ summary=$(echo "$rspec_out" | grep -E "[0-9]+ examples?, [0-9]+ failures?" | tail -1)
30
+ failed=$(echo "$rspec_out" | grep -E "^rspec .+" | head -10)
31
+ ctx+="rspec failed ($summary):\n$failed\n"
32
+ fi
33
+ fi
34
+
35
+ if [[ -n "$ctx" ]]; then
36
+ jq -n --arg ctx "$ctx" '{
37
+ "hookSpecificOutput": {
38
+ "hookEventName": "Stop",
39
+ "additionalContext": $ctx
40
+ }
41
+ }'
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ cd "$CLAUDE_PROJECT_DIR" || exit 0
3
+
4
+ # Read session_id from hook input and persist via CLAUDE_ENV_FILE
5
+ # so the edit tracker and stop hook share the same file path
6
+ INPUT=$(cat)
7
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
8
+ if [[ -n "$SESSION_ID" ]] && [[ -n "$CLAUDE_ENV_FILE" ]]; then
9
+ echo "export CLAUDE_HOOK_SESSION_ID='$SESSION_ID'" >> "$CLAUDE_ENV_FILE"
10
+ fi
11
+
12
+ ctx=""
13
+
14
+ # Warn if Gemfile.lock is missing or stale
15
+ if [[ ! -f Gemfile.lock ]]; then
16
+ ctx+="Gemfile.lock missing - run bundle install.\n"
17
+ elif [[ Gemfile -nt Gemfile.lock ]]; then
18
+ ctx+="Gemfile.lock is stale - run bundle install before testing.\n"
19
+ fi
20
+
21
+ # Warn about uncommitted changes
22
+ dirty=$(git diff --name-only 2>/dev/null | head -5)
23
+ if [[ -n "$dirty" ]]; then
24
+ ctx+="Uncommitted changes:\n$dirty\n"
25
+ fi
26
+
27
+ # Report current branch
28
+ branch=$(git branch --show-current 2>/dev/null)
29
+ if [[ -n "$branch" ]]; then
30
+ ctx+="Branch: $branch\n"
31
+ fi
32
+
33
+ if [[ -n "$ctx" ]]; then
34
+ jq -n --arg ctx "$ctx" '{
35
+ "hookSpecificOutput": {
36
+ "hookEventName": "SessionStart",
37
+ "additionalContext": $ctx
38
+ }
39
+ }'
40
+ fi
41
+
42
+ exit 0
@@ -0,0 +1,74 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bundle install)",
5
+ "Bash(bundle exec rspec:*)",
6
+ "Bash(bundle exec rubocop:*)",
7
+ "Bash(gem build:*)",
8
+ "Bash(ruby --version)",
9
+ "Bash(git status:*)",
10
+ "Bash(git log:*)",
11
+ "Bash(git diff:*)",
12
+ "Bash(git branch:*)",
13
+ "Bash(git fetch:*)",
14
+ "Bash(git stash:*)",
15
+ "Bash(git checkout:*)",
16
+ "Bash(git add:*)",
17
+ "Bash(git commit:*)",
18
+ "Bash(git push:*)",
19
+ "Bash(git pull:*)",
20
+ "Bash(git cherry-pick:*)",
21
+ "Bash(git reset:*)",
22
+ "Bash(gh pr view:*)",
23
+ "Bash(gh pr list:*)",
24
+ "Bash(gh pr diff:*)",
25
+ "Bash(gh pr checks:*)",
26
+ "Bash(gh pr status:*)",
27
+ "Bash(gh run view:*)",
28
+ "Bash(gh run list:*)",
29
+ "Bash(gh run watch:*)",
30
+ "Bash(gh issue view:*)",
31
+ "Bash(gh issue list:*)",
32
+ "Bash(gh release view:*)",
33
+ "Bash(gh release list:*)",
34
+ "Bash(gh repo view:*)",
35
+ "Bash(gh search:*)"
36
+ ],
37
+ "deny": [
38
+ "Read(./.env*)"
39
+ ]
40
+ },
41
+ "hooks": {
42
+ "SessionStart": [
43
+ {
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "./.claude/hooks/session-start-setup.sh"
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "PostToolUse": [
53
+ {
54
+ "matcher": "Edit|Write|MultiEdit",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "./.claude/hooks/post-edit-lint.sh"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "Stop": [
64
+ {
65
+ "hooks": [
66
+ {
67
+ "type": "command",
68
+ "command": "./.claude/hooks/post-stop-validate.sh"
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ }
74
+ }
data/.gitignore CHANGED
@@ -8,6 +8,25 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  *.swp
11
+ *.gem
11
12
  vendor/bundle
12
13
  /.idea
13
14
  .DS_Store
15
+
16
+ # AI agent local/ephemeral data
17
+ .claude/plans/
18
+ .claude/memory/
19
+ .claude/worktrees/
20
+ .claude/scheduled-tasks/
21
+ .claude/settings.local.json
22
+ .claude/todos/
23
+ .claude/credentials.json
24
+ CLAUDE.local.md
25
+ .cursor/chat/
26
+ .cursor/composer/
27
+ .cursor/mcp.json
28
+ .windsurf/
29
+ .cline/
30
+ .continue/
31
+ .junie/
32
+ .aider*
data/README.md CHANGED
@@ -24,6 +24,8 @@
24
24
  |
25
25
  <b><a href="#contributing">Contributing</a></b>
26
26
  |
27
+ <b><a href="#security">Security</a></b>
28
+ |
27
29
  <b><a href="#license">License</a></b>
28
30
  </p>
29
31
 
@@ -113,12 +115,26 @@ Set it to 0 to disable it.
113
115
 
114
116
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
115
117
 
116
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
118
+ To install this gem onto your local machine, run `bundle exec rake install`.
119
+
120
+ ## Releasing
121
+
122
+ See [RELEASING.md](RELEASING.md) for the full release process.
117
123
 
118
124
  ## Contributing
119
125
 
120
126
  Bug reports and pull requests are welcome on GitHub at https://github.com/chartmogul/chartmogul-ruby.
121
127
 
128
+ ## Security
129
+
130
+ ### Verifying Releases
131
+
132
+ All releases of this library are published as [immutable GitHub Releases](https://github.com/chartmogul/chartmogul-ruby/releases) with protected tags and as a gem on [RubyGems.org](https://rubygems.org/gems/chartmogul-ruby).
133
+
134
+ To maximize supply chain security:
135
+ - **Commit your `Gemfile.lock`** to version control — it records integrity hashes for all dependencies, ensuring reproducible and tamper-evident installs
136
+ - **Verify gem checksums** with `bundle install` (Bundler 2.x+ records checksums automatically)
137
+
122
138
  ## License
123
139
 
124
140
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/changelog.md CHANGED
@@ -1,148 +1,3 @@
1
- # chartmogul-ruby Change Log
1
+ # Changelog
2
2
 
3
- ## Version 4.11.0 - Jan 6, 2026
4
- - Add new API usage for Customer Subscriptions connect and disconnect
5
- - Update retrieve to accept query params
6
-
7
- ## Version 4.10.0 - Dec 22, 2025
8
- - Add fetching additional DataSource fields
9
-
10
- ## Version 4.9.0 - Nov 25, 2025
11
- - Add field subscription-set-external-id to /activities, drop support for eol rubies
12
-
13
- ## Version 4.7.1 - July 25, 2025
14
- - Fix create! and update! methods for Task (customer_uuid was omitted from payload)
15
-
16
- ## Version 4.7.0 - April 25, 2025
17
- - Adds support for Tasks (https://dev.chartmogul.com/reference/tasks)
18
-
19
- ## Version 4.6.0 - March 14, 2025
20
- - Adds support for disconnecting subscriptions
21
- - Adds support for `transaction_fees_in_cents` attribute on transactions
22
-
23
- ## Version 4.5.0 - February 19, 2025
24
- - Adds support for proration type support for LineItems Subscription
25
-
26
- ## Version 4.4.0 - October 24, 2024
27
- - Adds support for unmerging customers
28
-
29
- ## Version 4.3.0 - March 25, 2024
30
- - Adds support for Opportunities (https://dev.chartmogul.com/reference/opportunities)
31
-
32
- ## Version 4.2.0 - February 8, 2024
33
- - Add support for customer's `website_url` attribute
34
- - Add support for customer's subscription `uuid` attribute
35
-
36
- ## Version 4.1.0 - December 20, 2023
37
- - Adds support for Customer Notes
38
-
39
- ## Version 4.0.1 - November 7 2023
40
- - Add upgrade guide from v3.x to v4.x
41
-
42
- ## Version 4.0.0 - November 1 2023
43
- - Remove support for old pagination using `page` parameter.
44
- - Drop support for Ruby versions below 2.7.
45
-
46
- ## Version 3.3.1 - October 27 2023
47
- - Add support for cursor based pagination to `.all` endpoints.
48
- - Add `.next` pagination method for all supported cursor based endpoints.
49
- - Move `fixtures/` folder inside the `spec/` folder.
50
- - Update `.merge!` methods to always return `true` when successful.
51
- - Add new attributes to `ChartMogul::Customer` resource.
52
- - Do not error when the server returns an empty body from `.custom!` methods.
53
-
54
- ## Version 3.3.0 - August 14 2023
55
- - Fix an issue with creating using `SubscriptionEvents.create!(attrs)`.
56
-
57
- ## Version 3.2.0 - June 14 2023
58
- - Adds support for Faraday 2.7
59
- - Removes support for Ruby 2.3, 2.4, and 2.5
60
-
61
- ## Version 3.1.0 - Mar 30 2023
62
- - Adds support for Contacts (https://dev.chartmogul.com/reference/contacts)
63
-
64
- ## Version 3.0.2 - 22 Jun 2022
65
- - Adds percentage change support
66
-
67
- ## Version 3.0.1 - 30 May 2022
68
- - Add Subscription Events support
69
-
70
- ## Version 3.0.0 - 29 October 2021
71
- - Update ChartMogul::Configuration to use `api_key` instead of `account_token`& `secret_key` combo for authentication
72
- - Send library version as part of `User-Agent` header
73
-
74
- ## Version 2.9.0 - 3 Nov 2021
75
- - Adds post install message informing about authentication changes *& deprecation warning.
76
-
77
- ## Version 2.1.0 - 9 July 2021
78
- - Adds ChartMogul::Metrics::ActivitiesExport class to support async activities export endpoint
79
-
80
- ## Version 2.0.0 - 25 June 2021
81
- - Moves customer scoped Metrics::Activities and Metrics::Subscriptions under Metrics::Customers namespace
82
- - Adds unscoped activities API endpoint
83
-
84
- ## Version 1.7.2 - 16 March 2021
85
- - Fix bug preventing instantiating attributes on ChartMogul::Customers objects
86
-
87
- ## Version 1.7.1 - 8 March 2021
88
- - Adds amount_in_cents to payment for partial payments
89
- - Adds account API endpoint
90
-
91
- ## Version 1.6.9 - 10 December 2020
92
- - Fix ChartMogul::Customers class
93
-
94
- ## Version 1.6.8 - 1 November 2020
95
- - Add support for subscription_external_id when listing Activities
96
-
97
- ## Version 1.6.7 - 8 September 2020
98
- - Allow adding Customer custom attributes in camel case
99
-
100
- ## Version 1.5.0 - 20 February 2020
101
- - Add support for plan groups API
102
-
103
- ## Version 1.4.0 - 20 September 2019
104
- - Add support for subscription sets
105
-
106
- ## Version 1.3.1 - 6 May 2019
107
- - Add #last method to Entries
108
-
109
- ## Version 1.3.0 - 5 March 2019
110
- - Method #update_cancellation_dates added to allow users to update the cancellation dates on subscription.
111
-
112
- ## Version 1.2.2 - 21 December 2018
113
- - Added connect subscriptions method
114
-
115
- ## Version 1.2.1 - 11 September 2018
116
- - Update Faraday dependency version
117
-
118
- ## Version 1.2.0 - 15 August 2018
119
- - Rertry requests on certain statuses and allow config
120
-
121
- ## Version 1.1.8 - 22 June 2018
122
- - Fix ChartMogul::ChartMogulError class
123
-
124
- ## Version 1.0.2 - 6 March 2017
125
- - Consolidated Customer, Enrichment/Customer now deprecated
126
-
127
- ## Version 1.0.1 - 21 February 2017
128
- - Fixed all() in Customer, now returns Customers with paging
129
-
130
- ## Version 1.0.0 - 14 February 2017
131
- - Add support for new plan and data source endpoints
132
- - Remove deprecated Import namespace
133
-
134
- ## Version 0.1.4 - 10 November 2016
135
- - Add support for merging customers
136
- - Add support for updating customers
137
-
138
- ## Version 0.1.3 - 8 November 2016
139
- - Add support for lead_created_at and free_trial_started_at fields to work with Leads and Trials charts
140
-
141
- ## Version 0.1.2 - 13 July 2016
142
- - Fix bug preventing cancellation of subscriptions [#22]
143
-
144
- ## Version 0.1.1 - 4 July 2016
145
- - Version bump after 0.1.0 pushed to RubyGems under incorrect account.
146
-
147
- ## Version 0.1.0 - 4 July 2016
148
- - Initial release with support for our Import, Enrichment and Metrics APIs.
3
+ Release notes are auto-generated from merged pull request titles and published on the [GitHub Releases page](https://github.com/chartmogul/chartmogul-ruby/releases).
@@ -16,13 +16,11 @@ Gem::Specification.new do |spec|
16
16
  spec.license = 'MIT'
17
17
  spec.required_ruby_version = '>= 3.2'
18
18
 
19
- spec.post_install_message = %q{
20
- Starting October 29 2021, we are updating our developer libraries to support the enhanced API Access Management. Please use the same API Key for both API Token and Secret Key.
21
- [Deprecation] - account_token/secret_key combo is deprecated. Please use API key for both fields.
22
- Version 3.x will introduce a breaking change in authentication configuration. For more details, please visit: https://dev.chartmogul.com/docs/authentication
23
- }
24
-
25
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features|\.github)/}) ||
21
+ f.match(/\A(AGENTS|CLAUDE|RELEASING)\.md\z/) ||
22
+ f == 'bin/release.sh'
23
+ end
26
24
  spec.bindir = 'exe'
27
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
26
  spec.require_paths = ['lib']
@@ -5,15 +5,28 @@ module ChartMogul
5
5
  set_resource_name 'Account'
6
6
  set_resource_path '/v1/account'
7
7
 
8
+ readonly_attr :id
8
9
  readonly_attr :name
9
10
  readonly_attr :currency
10
11
  readonly_attr :time_zone
11
12
  readonly_attr :week_start_on
12
13
 
14
+ # Optional attributes returned when using the include parameter
15
+ readonly_attr :churn_recognition
16
+ readonly_attr :churn_when_zero_mrr
17
+ readonly_attr :auto_churn_subscription
18
+ readonly_attr :refund_handling
19
+ readonly_attr :proximate_movement_reclassification
20
+
13
21
  include API::Actions::Custom
14
22
 
15
- def self.retrieve
16
- custom!(:get, '/v1/account')
23
+ def self.retrieve(include: nil)
24
+ path = '/v1/account'
25
+ if include
26
+ fields = Array(include).map { |f| CGI.escape(f) }.join(',')
27
+ path += "?include=#{fields}"
28
+ end
29
+ custom!(:get, path)
17
30
  end
18
31
  end
19
32
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+ require "uri"
3
5
  require "forwardable"
4
6
  require "set"
5
7
 
@@ -162,6 +164,39 @@ module ChartMogul
162
164
  custom!(http_method, path, attrs)
163
165
  end
164
166
 
167
+ # Build a URL path with query parameters, handling ? vs & correctly
168
+ def self.build_query_path(base_path, **params)
169
+ filtered = params.compact
170
+ return base_path if filtered.empty?
171
+
172
+ separator = base_path.include?("?") ? "&" : "?"
173
+ "#{base_path}#{separator}#{URI.encode_www_form(filtered)}"
174
+ end
175
+
176
+ # Convenience for JSON PATCH requests
177
+ def self.json_patch(path, body)
178
+ connection.patch(path) do |req|
179
+ req.headers["Content-Type"] = "application/json"
180
+ req.body = JSON.dump(body)
181
+ end
182
+ end
183
+
184
+ # Convenience for JSON PUT requests
185
+ def self.json_put(path, body)
186
+ connection.put(path) do |req|
187
+ req.headers["Content-Type"] = "application/json"
188
+ req.body = JSON.dump(body)
189
+ end
190
+ end
191
+
192
+ # Convenience for JSON POST requests
193
+ def self.json_post(path, body)
194
+ connection.post(path) do |req|
195
+ req.headers["Content-Type"] = "application/json"
196
+ req.body = JSON.dump(body)
197
+ end
198
+ end
199
+
165
200
  def self.build_connection
166
201
  Faraday.new(url: ChartMogul.api_base,
167
202
  headers: { "User-Agent" => "chartmogul-ruby/#{ChartMogul::VERSION}" }) do |faraday|
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChartMogul
4
+ module Concerns
5
+ # Shared class methods for resources that support CRUD by data_source_uuid + external_id.
6
+ # These resources use query-parameter-based lookups (not path-based).
7
+ module ExternalIdOperations
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ # Retrieve a resource by data_source_uuid and external_id
14
+ def retrieve_by_external_id(data_source_uuid:, external_id:)
15
+ path = build_query_path(
16
+ resource_path.path,
17
+ data_source_uuid: data_source_uuid,
18
+ external_id: external_id
19
+ )
20
+ resp = handling_errors { connection.get(path) }
21
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: immutable_keys)
22
+ new_from_json(json)
23
+ end
24
+
25
+ # Update a resource by data_source_uuid and external_id
26
+ # @param handle_as_user_edit [Boolean] If true, the change is treated as a user edit
27
+ def update_by_external_id!(data_source_uuid:, external_id:, handle_as_user_edit: nil, **attributes)
28
+ path = build_query_path(
29
+ resource_path.path,
30
+ data_source_uuid: data_source_uuid,
31
+ external_id: external_id,
32
+ handle_as_user_edit: handle_as_user_edit
33
+ )
34
+ resp = handling_errors { json_patch(path, attributes) }
35
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: immutable_keys)
36
+ new_from_json(json)
37
+ end
38
+
39
+ # Delete a resource by data_source_uuid and external_id
40
+ # @param handle_as_user_edit [Boolean] If true, the change is treated as a user edit
41
+ def destroy_by_external_id!(data_source_uuid:, external_id:, handle_as_user_edit: nil)
42
+ path = build_query_path(
43
+ resource_path.path,
44
+ data_source_uuid: data_source_uuid,
45
+ external_id: external_id,
46
+ handle_as_user_edit: handle_as_user_edit
47
+ )
48
+ handling_errors { connection.delete(path) }
49
+ true
50
+ end
51
+
52
+ # Toggle disabled state of a resource by data_source_uuid and external_id
53
+ # @param handle_as_user_edit [Boolean] If true, the change is treated as a user edit
54
+ def toggle_disabled_by_external_id!(data_source_uuid:, external_id:, disabled:, handle_as_user_edit: nil)
55
+ path = build_query_path(
56
+ "#{resource_path.path}/disabled_state",
57
+ data_source_uuid: data_source_uuid,
58
+ external_id: external_id,
59
+ handle_as_user_edit: handle_as_user_edit
60
+ )
61
+ resp = handling_errors { json_patch(path, { disabled: disabled }) }
62
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: immutable_keys)
63
+ new_from_json(json)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChartMogul
4
+ module Concerns
5
+ # Shared toggle_disabled! instance method for resources that support disabling.
6
+ # Expects the including class to have a `uuid` attribute.
7
+ module ToggleDisabled
8
+ # Toggle the disabled state of the resource
9
+ # @param disabled [Boolean] Whether to disable the resource
10
+ # @param handle_as_user_edit [Boolean] If true, the change is treated as a user edit
11
+ # @return [self] the updated resource
12
+ def toggle_disabled!(disabled:, handle_as_user_edit: nil)
13
+ path = "#{resource_path.path}/#{uuid}/disabled_state"
14
+ path = self.class.build_query_path(path, handle_as_user_edit: handle_as_user_edit)
15
+ resp = handling_errors do
16
+ self.class.json_patch(path, { disabled: disabled })
17
+ end
18
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: self.class.immutable_keys)
19
+ assign_all_attributes(json)
20
+ self
21
+ end
22
+ end
23
+ end
24
+ end
@@ -20,6 +20,7 @@ module ChartMogul
20
20
  writeable_attr :linked_in
21
21
  writeable_attr :twitter
22
22
  writeable_attr :notes
23
+ writeable_attr :external_id
23
24
  writeable_attr :custom
24
25
 
25
26
  include API::Actions::Create
@@ -43,13 +44,15 @@ module ChartMogul
43
44
  if attribute_name == :custom && attribute_value.is_a?(Hash)
44
45
  payload = attribute_value.each_with_object([]) do |custom_value, arr|
45
46
  key, value = custom_value
46
- arr << { key: key, value: value }
47
+ arr << ({ key:, value: })
47
48
  end
48
49
  attributes[:custom] = payload
49
50
  else
50
51
  attributes[attribute_name] = attribute_value
51
52
  end
52
53
  end
54
+ # Include external_id attribute even when nil so callers can explicitly clear it
55
+ attributes[:external_id] = nil if instance_variable_defined?(:@external_id) && external_id.nil?
53
56
  end
54
57
  end
55
58
  end
@@ -57,6 +57,18 @@ module ChartMogul
57
57
  all(external_id: external_id).first
58
58
  end
59
59
 
60
+ def self.retrieve_attributes(customer_uuid)
61
+ custom_without_assign!(:get, "/v1/customers/#{customer_uuid}/attributes")
62
+ end
63
+
64
+ def self.add_tags_by_email!(email, *tags)
65
+ custom_without_assign!(:post, '/v1/customers/attributes/tags', email: email, tags: tags)
66
+ end
67
+
68
+ def self.add_custom_attributes_by_email!(email, *custom_attrs)
69
+ custom_without_assign!(:post, '/v1/customers/attributes/custom', email: email, custom: custom_attrs)
70
+ end
71
+
60
72
  def self.merge!(into_uuid:, from_uuid:)
61
73
  options = {
62
74
  from: { customer_uuid: from_uuid },
@@ -160,6 +172,10 @@ module ChartMogul
160
172
  custom: custom_attrs)
161
173
  end
162
174
 
175
+ def retrieve_attributes
176
+ custom_without_assign!(:get, "/v1/customers/#{uuid}/attributes")
177
+ end
178
+
163
179
  def merge_into!(other_customer)
164
180
  options = {
165
181
  from: { customer_uuid: uuid },
@@ -23,7 +23,17 @@ module ChartMogul
23
23
  writeable_attr :due_date, type: :time
24
24
 
25
25
  include API::Actions::Retrieve
26
+ include API::Actions::Update
26
27
  include API::Actions::Destroy
28
+ include Concerns::ToggleDisabled
29
+ include Concerns::ExternalIdOperations
30
+
31
+ # Update the status of an invoice by external_id
32
+ def self.update_status!(data_source_uuid:, invoice_external_id:, status:)
33
+ path = "/v1/data_sources/#{CGI.escape(data_source_uuid)}/invoices/#{CGI.escape(invoice_external_id)}/status"
34
+ handling_errors { json_put(path, { status: status }) }
35
+ true
36
+ end
27
37
 
28
38
  def serialize_line_items
29
39
  line_items.map(&:serialize_for_write)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChartMogul
4
+ # JsonImport resource for bulk importing data via JSON.
5
+ # Use this to efficiently upload large datasets to a data source.
6
+ class JsonImport < APIResource
7
+ set_resource_name 'JsonImport'
8
+ set_resource_path '/v1/data_sources/:data_source_uuid/json_imports'
9
+
10
+ readonly_attr :id
11
+ readonly_attr :external_id
12
+ readonly_attr :status
13
+ readonly_attr :created_at
14
+ readonly_attr :updated_at
15
+ readonly_attr :error_count
16
+ readonly_attr :processed_count
17
+
18
+ # Create a new JSON import batch for a data source
19
+ # @param data_source_uuid [String] The UUID of the data source
20
+ # @param data [Hash] The import data (customers, plans, invoices, etc.)
21
+ def self.create!(data_source_uuid:, **data)
22
+ path = "/v1/data_sources/#{data_source_uuid}/json_imports"
23
+ resp = handling_errors do
24
+ connection.post(path) do |req|
25
+ req.headers['Content-Type'] = 'application/json'
26
+ req.body = JSON.dump(data)
27
+ end
28
+ end
29
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys:)
30
+ new_from_json(json)
31
+ end
32
+
33
+ # Retrieve the status of a JSON import
34
+ # @param data_source_uuid [String] The UUID of the data source
35
+ # @param id [String, Integer] The ID of the import
36
+ def self.retrieve(data_source_uuid:, id:)
37
+ path = "/v1/data_sources/#{data_source_uuid}/json_imports/#{id}"
38
+ resp = handling_errors do
39
+ connection.get(path)
40
+ end
41
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys:)
42
+ new_from_json(json)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChartMogul
4
+ # Standalone LineItem resource for CRUD operations on existing line items.
5
+ # Note: For creating line items on invoices, use LineItems::Subscription or LineItems::OneTime.
6
+ class LineItem < APIResource
7
+ set_resource_name 'LineItem'
8
+ set_resource_path '/v1/line_items'
9
+
10
+ readonly_attr :uuid
11
+ readonly_attr :disabled
12
+ readonly_attr :disabled_at
13
+ readonly_attr :disabled_by
14
+ readonly_attr :invoice_uuid
15
+ readonly_attr :subscription_uuid
16
+ readonly_attr :edit_history_summary
17
+ readonly_attr :errors
18
+
19
+ writeable_attr :type
20
+ writeable_attr :subscription_external_id
21
+ writeable_attr :subscription_set_external_id
22
+ writeable_attr :plan_uuid
23
+ writeable_attr :service_period_start, type: :time
24
+ writeable_attr :service_period_end, type: :time
25
+ writeable_attr :amount_in_cents
26
+ writeable_attr :quantity
27
+ writeable_attr :discount_amount_in_cents
28
+ writeable_attr :discount_code
29
+ writeable_attr :discount_description
30
+ writeable_attr :tax_amount_in_cents
31
+ writeable_attr :transaction_fees_in_cents
32
+ writeable_attr :transaction_fees_currency
33
+ writeable_attr :external_id
34
+ writeable_attr :data_source_uuid
35
+ writeable_attr :prorated
36
+ writeable_attr :proration_type
37
+ writeable_attr :cancelled_at, type: :time
38
+ writeable_attr :description
39
+ writeable_attr :account_code
40
+ writeable_attr :event_order
41
+
42
+ include API::Actions::Retrieve
43
+ include API::Actions::Update
44
+ include API::Actions::Destroy
45
+ include Concerns::ToggleDisabled
46
+ include Concerns::ExternalIdOperations
47
+
48
+ # Create a line item for an invoice
49
+ # @param handle_as_user_edit [Boolean] If true, the change is treated as a user edit
50
+ def self.create!(invoice_uuid:, handle_as_user_edit: nil, **attributes)
51
+ path = build_query_path(
52
+ "/v1/import/invoices/#{invoice_uuid}/line_items",
53
+ handle_as_user_edit: handle_as_user_edit
54
+ )
55
+ resp = handling_errors { json_post(path, attributes) }
56
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys:)
57
+ new_from_json(json)
58
+ end
59
+ end
60
+ end
@@ -6,6 +6,8 @@ module ChartMogul
6
6
  set_resource_path '/v1/subscription_events'
7
7
 
8
8
  readonly_attr :id
9
+ readonly_attr :disabled
10
+ readonly_attr :disabled_at
9
11
  writeable_attr :data_source_uuid
10
12
  writeable_attr :customer_external_id
11
13
  writeable_attr :subscription_set_external_id
@@ -49,11 +51,71 @@ module ChartMogul
49
51
  :subscription_event)
50
52
  end
51
53
 
54
+ # Instance method: destroys this subscription event
52
55
  def destroy!
53
56
  handling_errors do
54
57
  connection.delete(resource_path.path, subscription_event: { id: instance_attributes[:id] })
55
58
  end
56
59
  end
60
+
61
+ # Class method: accepts both flat and envelope-wrapped params for backwards compatibility
62
+ # flat params. : SubscriptionEvent.destroy!(id: 123)
63
+ # envelope-wrapped params: SubscriptionEvent.destroy!(subscription_event: { id: 123 })
64
+ def self.destroy!(params = {})
65
+ body = params.key?(:subscription_event) ? params : { subscription_event: params }
66
+ handling_errors do
67
+ connection.delete(resource_path.path, body)
68
+ end
69
+ true
70
+ end
71
+
72
+ # Toggle the disabled state of a subscription event.
73
+ # Note: SubscriptionEvent uses envelope-wrapped requests/responses ({ subscription_event: ... })
74
+ # unlike other resources, because the API requires this format for this endpoint.
75
+ def toggle_disabled!(disabled:, handle_as_user_edit: nil)
76
+ path = self.class.build_query_path(
77
+ "#{resource_path.path}/#{instance_attributes[:id]}/disabled_state",
78
+ handle_as_user_edit: handle_as_user_edit
79
+ )
80
+ resp = handling_errors { self.class.json_patch(path, { disabled: disabled }) }
81
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: self.class.immutable_keys)
82
+ assign_all_attributes(json[:subscription_event] || json)
83
+ self
84
+ end
85
+
86
+ # Update a subscription event by data_source_uuid and external_id.
87
+ # Uses envelope-wrapped body ({ subscription_event: ... }) as required by this API endpoint.
88
+ def self.update_by_external_id!(data_source_uuid:, external_id:, handle_as_user_edit: nil, **attributes)
89
+ path = build_query_path(resource_path.path, handle_as_user_edit: handle_as_user_edit)
90
+ resp = handling_errors do
91
+ json_patch(path, { subscription_event: attributes.merge(data_source_uuid: data_source_uuid, external_id: external_id) })
92
+ end
93
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys:)
94
+ new_from_json(json[:subscription_event] || json)
95
+ end
96
+
97
+ # Delete a subscription event by data_source_uuid and external_id
98
+ def self.destroy_by_external_id!(data_source_uuid:, external_id:, handle_as_user_edit: nil)
99
+ path = build_query_path(resource_path.path, handle_as_user_edit: handle_as_user_edit)
100
+ handling_errors do
101
+ connection.delete(path, subscription_event: { data_source_uuid: data_source_uuid, external_id: external_id })
102
+ end
103
+ true
104
+ end
105
+
106
+ # Toggle disabled state of a subscription event by data_source_uuid and external_id.
107
+ # Uses envelope-wrapped body ({ subscription_event: ... }) as required by this API endpoint.
108
+ def self.toggle_disabled_by_external_id!(data_source_uuid:, external_id:, disabled:, handle_as_user_edit: nil)
109
+ path = build_query_path("#{resource_path.path}/disabled_state", handle_as_user_edit: handle_as_user_edit)
110
+ resp = handling_errors do
111
+ json_patch(path, {
112
+ subscription_event: { data_source_uuid: data_source_uuid, external_id: external_id },
113
+ disabled: disabled
114
+ })
115
+ end
116
+ json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys:)
117
+ new_from_json(json[:subscription_event] || json)
118
+ end
57
119
  end
58
120
 
59
121
  class SubscriptionEvents < APIResource
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChartMogul
4
+ # Standalone Transaction resource for CRUD operations on existing transactions.
5
+ # Note: For creating transactions on invoices, use Transactions::Payment or Transactions::Refund.
6
+ class Transaction < APIResource
7
+ set_resource_name 'Transaction'
8
+ set_resource_path '/v1/transactions'
9
+
10
+ readonly_attr :uuid
11
+ readonly_attr :disabled
12
+ readonly_attr :disabled_at
13
+ readonly_attr :disabled_by
14
+ readonly_attr :invoice_uuid
15
+ readonly_attr :edit_history_summary
16
+ readonly_attr :errors
17
+
18
+ writeable_attr :type
19
+ writeable_attr :date, type: :time
20
+ writeable_attr :result
21
+ writeable_attr :external_id
22
+ writeable_attr :data_source_uuid
23
+ writeable_attr :amount_in_cents
24
+ writeable_attr :transaction_fees_in_cents
25
+ writeable_attr :transaction_fees_currency
26
+
27
+ include API::Actions::Retrieve
28
+ include API::Actions::Update
29
+ include API::Actions::Destroy
30
+ include Concerns::ToggleDisabled
31
+ include Concerns::ExternalIdOperations
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChartMogul
4
- VERSION = '4.11.0'
4
+ VERSION = '4.14.0'
5
5
  end
data/lib/chartmogul.rb CHANGED
@@ -52,6 +52,8 @@ require 'chartmogul/concerns/pageable_with_anchor'
52
52
  require 'chartmogul/concerns/pageable_with_cursor'
53
53
  require 'chartmogul/concerns/auto_churn_subscription_setting'
54
54
  require 'chartmogul/concerns/processing_status'
55
+ require 'chartmogul/concerns/toggle_disabled'
56
+ require 'chartmogul/concerns/external_id_operations'
55
57
 
56
58
  require 'chartmogul/subscription'
57
59
  require 'chartmogul/invoice'
@@ -70,6 +72,9 @@ require 'chartmogul/account'
70
72
  require 'chartmogul/subscription_event'
71
73
  require 'chartmogul/opportunity'
72
74
  require 'chartmogul/task'
75
+ require 'chartmogul/transaction'
76
+ require 'chartmogul/line_item'
77
+ require 'chartmogul/json_import'
73
78
 
74
79
  require 'chartmogul/metrics/arpa'
75
80
  require 'chartmogul/metrics/arr'
data/zizmor.yml ADDED
@@ -0,0 +1,7 @@
1
+ rules:
2
+ secrets-outside-env:
3
+ disable: true
4
+ cache-poisoning:
5
+ ignore:
6
+ - release.yml:22
7
+ - release.yml:53
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chartmogul-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.11.0
4
+ version: 4.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Petr Kopac
@@ -184,7 +184,10 @@ executables: []
184
184
  extensions: []
185
185
  extra_rdoc_files: []
186
186
  files:
187
- - ".github/workflows/test.yml"
187
+ - ".claude/hooks/post-edit-lint.sh"
188
+ - ".claude/hooks/post-stop-validate.sh"
189
+ - ".claude/hooks/session-start-setup.sh"
190
+ - ".claude/settings.json"
188
191
  - ".gitignore"
189
192
  - ".rspec"
190
193
  - 2.0-Upgrade.md
@@ -209,6 +212,7 @@ files:
209
212
  - lib/chartmogul/api_resource.rb
210
213
  - lib/chartmogul/concerns/auto_churn_subscription_setting.rb
211
214
  - lib/chartmogul/concerns/entries.rb
215
+ - lib/chartmogul/concerns/external_id_operations.rb
212
216
  - lib/chartmogul/concerns/pageable.rb
213
217
  - lib/chartmogul/concerns/pageable2.rb
214
218
  - lib/chartmogul/concerns/pageable_with_anchor.rb
@@ -216,6 +220,7 @@ files:
216
220
  - lib/chartmogul/concerns/processing_status.rb
217
221
  - lib/chartmogul/concerns/summary.rb
218
222
  - lib/chartmogul/concerns/summary_all.rb
223
+ - lib/chartmogul/concerns/toggle_disabled.rb
219
224
  - lib/chartmogul/config_attributes.rb
220
225
  - lib/chartmogul/configuration.rb
221
226
  - lib/chartmogul/contact.rb
@@ -235,6 +240,8 @@ files:
235
240
  - lib/chartmogul/errors/server_error.rb
236
241
  - lib/chartmogul/errors/unauthorized_error.rb
237
242
  - lib/chartmogul/invoice.rb
243
+ - lib/chartmogul/json_import.rb
244
+ - lib/chartmogul/line_item.rb
238
245
  - lib/chartmogul/line_items/one_time.rb
239
246
  - lib/chartmogul/line_items/subscription.rb
240
247
  - lib/chartmogul/metrics/activities_export.rb
@@ -264,22 +271,18 @@ files:
264
271
  - lib/chartmogul/summary.rb
265
272
  - lib/chartmogul/summary_all.rb
266
273
  - lib/chartmogul/task.rb
274
+ - lib/chartmogul/transaction.rb
267
275
  - lib/chartmogul/transactions/payment.rb
268
276
  - lib/chartmogul/transactions/refund.rb
269
277
  - lib/chartmogul/utils/hash_snake_caser.rb
270
278
  - lib/chartmogul/utils/json_parser.rb
271
279
  - lib/chartmogul/version.rb
272
280
  - pre-commit.example
281
+ - zizmor.yml
273
282
  homepage: https://github.com/chartmogul/chartmogul-ruby
274
283
  licenses:
275
284
  - MIT
276
285
  metadata: {}
277
- post_install_message: "\n Starting October 29 2021, we are updating our developer
278
- libraries to support the enhanced API Access Management. Please use the same API
279
- Key for both API Token and Secret Key.\n [Deprecation] - account_token/secret_key
280
- combo is deprecated. Please use API key for both fields.\n Version 3.x will introduce
281
- a breaking change in authentication configuration. For more details, please visit:
282
- https://dev.chartmogul.com/docs/authentication\n "
283
286
  rdoc_options: []
284
287
  require_paths:
285
288
  - lib
@@ -294,7 +297,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
294
297
  - !ruby/object:Gem::Version
295
298
  version: '0'
296
299
  requirements: []
297
- rubygems_version: 4.0.3
300
+ rubygems_version: 3.6.9
298
301
  specification_version: 4
299
302
  summary: Chartmogul API Ruby Client
300
303
  test_files: []
@@ -1,23 +0,0 @@
1
- name: Run specs
2
- on:
3
- push:
4
- branches: [ main ]
5
- pull_request:
6
- branches: [ main ]
7
- jobs:
8
- test:
9
- runs-on: ubuntu-latest
10
- strategy:
11
- matrix:
12
- ruby-version: [3.2, 3.3, 3.4, head]
13
- steps:
14
- - uses: actions/checkout@v2
15
- - name: Set up Ruby ${{ matrix.ruby-version }}
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby-version }}
19
- bundler-cache: true
20
- - name: Install dependencies
21
- run: bundle install
22
- - name: Run tests
23
- run: bundle exec rake