Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ jobs:
startsWith(github.ref, 'refs/tags/v')
needs: [build, test]
runs-on: ubuntu-24.04
environment: release
permissions:
contents: read
id-token: write
steps:
- name: Download all workflow run artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -112,10 +114,10 @@ jobs:
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: 3.1
- name: Configure RubyGems credentials
uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
- name: Publish gems to Rubygems
run: gem push gems/*.gem
env:
GEM_HOST_API_KEY: ${{secrets.GEM_HOST_API_KEY}}
- uses: stripe/openapi/actions/notify-release@master
if: always()
with:
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/publish-historical.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Publish historical release

on:
workflow_dispatch:
inputs:
tag:
description: Stable historical tag to build and publish, such as v7.1.1
required: true
type: string

permissions: {}

jobs:
build:
name: Build
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Validate release request
env:
TAG: ${{ inputs.tag }}
run: |
if [[ "$GITHUB_REF" != "refs/heads/master" ]]; then
echo "Historical releases must be dispatched from master, not $GITHUB_REF" >&2
exit 1
fi
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag must be a stable tag in the form vX.Y.Z" >&2
exit 1
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ inputs.tag }}
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: 3.1
- name: Validate version
env:
TAG: ${{ inputs.tag }}
run: |
version="$(tr -d '[:space:]' < VERSION)"
if [[ "v$version" != "$TAG" ]]; then
echo "Tag $TAG does not match VERSION ($version)" >&2
exit 1
fi
echo "GEM_FILE=stripe-$version.gem" >> "$GITHUB_ENV"
- name: Build gem
run: gem build stripe.gemspec --output "$GEM_FILE"
- name: Upload gem
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: historical-gem
path: stripe-*.gem
if-no-files-found: error
retention-days: 1

publish:
name: Publish
needs: build
runs-on: ubuntu-24.04
environment: historical-publish
permissions:
contents: read
id-token: write
steps:
- name: Download gem
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: historical-gem
path: gems
- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: 3.1
- name: Configure RubyGems credentials
uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
- name: Publish gem to RubyGems
run: gem push gems/*.gem
8 changes: 8 additions & 0 deletions .hark/changes/2026-10-26_stripe-openapi_79c776e1.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Update generated code
pr_url: https://github.com/stripe/stripe-ruby/pull/1987
semver_level: minor
is_stripe_api_change: true
---

* Release specs are identical.
2 changes: 1 addition & 1 deletion CODEGEN_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
588801ef34f7b5f7d6a784d3354f5f93d2a26ffb
7df6c334d365375514a978f37f3f8a2ab38f98f8
2 changes: 1 addition & 1 deletion OPENAPI_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2526
v2591
1 change: 1 addition & 0 deletions lib/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
require "stripe/stripe_response"
require "stripe/list_object"
require "stripe/v2_list_object"
require "stripe/v2_search_result_object"
require "stripe/search_result_object"
require "stripe/error_object"
require "stripe/api_resource"
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/object_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def self.object_names_to_classes
def self.v2_object_names_to_classes
{
V2::ListObject.object_name => V2::ListObject,
V2::SearchResultObject.object_name => V2::SearchResultObject,

# v2 object classes: The beginning of the section generated from our OpenAPI spec
V2::Billing::BillSetting.object_name => V2::Billing::BillSetting,
Expand Down
6 changes: 6 additions & 0 deletions lib/stripe/singleton_api_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module Stripe
class SingletonAPIResource < APIResource
def self.resource_url
if name.include?("Stripe::V2")
raise NotImplementedError,
"V2 singleton resources do not have a defined URL. Please use the StripeClient " \
"to make V2 requests"
end

if self == SingletonAPIResource
raise NotImplementedError,
"SingletonAPIResource is an abstract class. You should " \
Expand Down
2 changes: 1 addition & 1 deletion lib/stripe/stripe_event_notification_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ class StripeEventNotificationHandler < StripeEventNotificationHandlerBase
def initialize(client, webhook_secret, &fallback_callback)
super(client, &fallback_callback)

raise ArgumentError, "webhook_secret must be a non-empty string" if webhook_secret.nil? || webhook_secret.empty?
raise ArgumentError, "webhook_secret must be a non-empty string" if Util.webhook_secret_blank?(webhook_secret)

@webhook_secret = webhook_secret
end
Expand Down
10 changes: 10 additions & 0 deletions lib/stripe/stripe_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ def initialize(requestor)
end

def request(method:, path:, base_address:, params: {}, opts: {})
params = params.to_h if params.is_a?(RequestParams)
if method == :post &&
path.split("?", 2).first.start_with?("/v2/") &&
path.split("?", 2).first.end_with?("/search") &&
(params.key?(:limit) || params.key?("limit"))
params = params.dup
limit = params.delete(:limit) || params.delete("limit")
separator = path.include?("?") ? "&" : "?"
path = "#{path}#{separator}limit=#{URI.encode_www_form_component(limit)}"
end
@requestor.execute_request(
method,
path,
Expand Down
11 changes: 9 additions & 2 deletions lib/stripe/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ def self.convert_to_stripe_object_with_params(
when Hash
# TODO: This is a terrible hack.
# Waiting on https://go/j/API_SERVICES-3167 to add an object in v2 lists
if api_mode == :v2 && data.include?(:data) && data.include?(:next_page_url)
if api_mode == :v2 && data.include?(:data) && data.include?(:next_page_url) &&
data[:object] != V2::SearchResultObject::OBJECT_NAME
return V2::ListObject.construct_from(data, opts, last_response, api_mode, requestor)
end

Expand Down Expand Up @@ -173,7 +174,9 @@ def self.convert_to_stripe_object_with_params(

# set filters so that we can fetch the same limit, expansions, and
# predicates when accessing the next and previous pages
obj.filters = params.dup if obj && (obj.is_a?(SearchResultObject) || obj.is_a?(ListObject))
if obj && (obj.is_a?(SearchResultObject) || obj.is_a?(ListObject) || obj.is_a?(V2::SearchResultObject))
obj.filters = StripeObject.send(:deep_copy, params, api_mode: api_mode)
end

obj
else
Expand Down Expand Up @@ -381,6 +384,10 @@ def self.secure_compare(str_a, str_b)
res.zero?
end

def self.webhook_secret_blank?(secret)
secret.nil? || secret.empty? || secret.match?(/\A[ \t\r\n\f\v]*\z/)
end

# Asserts that a request path is origin-relative: that it begins with a
# single "/" and carries no scheme, authority or userinfo.
#
Expand Down
62 changes: 62 additions & 0 deletions lib/stripe/v2_search_result_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module Stripe
module V2
class SearchResultObject < StripeObject
include Enumerable
include Stripe::APIOperations::Request

OBJECT_NAME = "v2.search_result"
attr_accessor :filters

def self.object_name
OBJECT_NAME
end

def initialize(*args)
super
self.filters = {}
end

def each(&blk)
data.each(&blk)
end

def empty?
data.empty?
end

def auto_paging_each(&blk)
return enum_for(:auto_paging_each) unless block_given?

page = self
loop do
page.each(&blk)
break if page.next_page_url.nil?

page = page.fetch_next_page
rescue LocalJumpError => e
raise unless e.reason == :break

break
end
end

def fetch_next_page(opts = {})
return self.class.construct_from({ data: [] }, opts, nil, :v2) if next_page_url.nil?

params = filters.dup
params.delete(:limit)
params.delete("limit")

_request(
method: :post,
path: next_page_url,
params: params,
opts: opts,
base_address: :api
).tap { |page| page.filters = filters }
end
end
end
end
2 changes: 1 addition & 1 deletion lib/stripe/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def self.verify_header(payload, header, secret, tolerance: nil)
)
end

if secret.nil? || secret.empty?
if Util.webhook_secret_blank?(secret)
raise SignatureVerificationError.new(
"No webhook secret value was provided. It should start with " \
"`whsec_`",
Expand Down
18 changes: 18 additions & 0 deletions test/stripe/api_resource_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
require File.expand_path("../test_helper", __dir__)

module Stripe
module V2
class TestV2SingletonResource < SingletonAPIResource
OBJECT_NAME = "v2.test.singleton"

def self.object_name
"v2.test.singleton"
end
end
end

class ApiResourceTest < Test::Unit::TestCase
class CustomMethodAPIResource < APIResource
OBJECT_NAME = "custom_method"
Expand Down Expand Up @@ -949,6 +959,14 @@ def self.object_name
end
end

context "v2 singleton resource" do
should "raise an NotImplementedError on resource_url" do
assert_raises NotImplementedError do
Stripe::V2::TestV2SingletonResource.resource_url
end
end
end

class CustomStripeObject < APIResource
def self.resource_url
"/v1/custom_stripe_object"
Expand Down
23 changes: 23 additions & 0 deletions test/stripe/stripe_event_notification_handler_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,29 @@ class StripeEventNotificationHandlerTest < Test::Unit::TestCase
assert_match(/webhook_secret must be a non-empty string/, e.message)
end

should "raise ArgumentError when initialized with an ASCII-whitespace-only webhook_secret" do
[" ", "\t", "\r", "\n", "\f", "\v", " \t\r\n\f\v"].each do |webhook_secret|
e = assert_raises(ArgumentError) do
StripeEventNotificationHandler.new(@client, webhook_secret, &@on_unhandled_handler)
end
assert_equal "webhook_secret must be a non-empty string", e.message
end
end

should "preserve surrounding ASCII whitespace in a nonblank webhook_secret" do
webhook_secret = " \t#{Test::WebhookHelpers::SECRET}\r\n"
handler = StripeEventNotificationHandler.new(@client, webhook_secret, &@on_unhandled_handler)

assert_equal webhook_secret, handler.instance_variable_get(:@webhook_secret)
end

should "allow a non-breaking-space-only webhook_secret" do
webhook_secret = "\u00A0"
handler = StripeEventNotificationHandler.new(@client, webhook_secret, &@on_unhandled_handler)

assert_equal webhook_secret, handler.instance_variable_get(:@webhook_secret)
end

should "handler uses event stripe_context" do
client_with_context = StripeClient.new(
"sk_test_123",
Expand Down
26 changes: 26 additions & 0 deletions test/stripe/stripe_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,32 @@ def respond_to_missing?(method, include_private = false)
end
end

class TestV2SearchParams < RequestParams
attr_accessor :limit, :query
end

context "v2 search parameters" do
should "convert typed request parameters before lifting limit into the query" do
requestor = APIRequestor.new("sk_test_123")
service = StripeService.new(requestor)
params = TestV2SearchParams.new
params.limit = 2
params.query = "widgets"

requestor.expects(:execute_request)
.with(:post, "/v2/widgets/search?limit=2", :api,
has_entries(params: { query: "widgets" }))
.returns([{ object: "v2.search_result", data: [] }, nil])

service.request(
method: :post,
path: "/v2/widgets/search",
base_address: :api,
params: params
)
end
end

context "usage parameter" do
should "send usage stripe_client with request" do
stub_request(:get, "#{Stripe::DEFAULT_API_BASE}/v1/test")
Expand Down
Loading
Loading