mercadopago-sdk 3.0.1 β 3.1.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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.yml +2 -2
- data/.github/ISSUE_TEMPLATE/feature_request.yml +2 -2
- data/.github/ISSUE_TEMPLATE/question.yml +2 -2
- data/.github/dependabot.yml +44 -0
- data/.github/workflows/ci.yml +22 -20
- data/.github/workflows/release.yml +5 -11
- data/CHANGELOG.md +38 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +7 -10
- data/examples/chargeback/search_chargeback.rb +14 -0
- data/examples/invoice/get_invoice.rb +14 -0
- data/examples/oauth/create_token.rb +22 -0
- data/examples/point/create_payment_intent.rb +26 -0
- data/lib/mercadopago/config/config.rb +1 -1
- data/lib/mercadopago/http/http_client.rb +0 -1
- data/lib/mercadopago/resources/chargeback.rb +36 -0
- data/lib/mercadopago/resources/invoice.rb +40 -0
- data/lib/mercadopago/resources/oauth.rb +83 -0
- data/lib/mercadopago/resources/order.rb +48 -5
- data/lib/mercadopago/resources/order_transaction.rb +0 -2
- data/lib/mercadopago/resources/point.rb +86 -0
- data/lib/mercadopago/sdk.rb +20 -0
- data/lib/mercadopago/webhook/validator.rb +62 -78
- data/lib/mercadopago.rb +4 -0
- data/mercadopago.gemspec +1 -1
- data/tests/test_chargeback.rb +19 -0
- data/tests/test_invoice.rb +19 -0
- data/tests/test_oauth.rb +30 -0
- data/tests/test_point.rb +24 -0
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3f9c30f87f2a16606d96b4b955e9c84b2297608fa13ae849c7c1fa0c5e81e8e
|
|
4
|
+
data.tar.gz: a85e57aab4d0cb2a0916e272b3cba305d0c43bc8b71ac395c5f20436e19b5ba0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb1e14c6daa97a705470095aad0577f4b16691b62e88afde38337a65cb459adc3875611c9c245bab111ed67dece2c52552bf28bddb2f3d190974b562e0197100
|
|
7
|
+
data.tar.gz: 63222ba960570cec0eeeae9a267d4d549ef5862bd9da27918c62acee2aa6689d93e21fb5f91c5ffe0be374d28fcad6bb429bafb34011376cfb18d85c53234731
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
name: π Bug Report
|
|
2
2
|
description: Report a bug or unexpected behavior in the SDK
|
|
3
3
|
title: "[BUG]: "
|
|
4
|
-
labels: ["bug"
|
|
5
|
-
assignees: ["
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
assignees: ["luismeli10","orojaspardo","barajas-d"]
|
|
6
6
|
|
|
7
7
|
body:
|
|
8
8
|
- type: markdown
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
name: β¨ Feature Request
|
|
2
2
|
description: Propose a new feature for the Mercado Pago SDK
|
|
3
3
|
title: "[FEATURE]: "
|
|
4
|
-
labels: ["enhancement"
|
|
5
|
-
assignees: ["
|
|
4
|
+
labels: ["enhancement"]
|
|
5
|
+
assignees: ["luismeli10","orojaspardo","barajas-d"]
|
|
6
6
|
|
|
7
7
|
body:
|
|
8
8
|
- type: markdown
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
name: π¬ Question
|
|
2
2
|
description: Ask a question about the Mercado Pago SDK
|
|
3
3
|
title: "[QUESTION]: "
|
|
4
|
-
labels: ["question"
|
|
5
|
-
assignees: ["
|
|
4
|
+
labels: ["question"]
|
|
5
|
+
assignees: ["luismeli10","orojaspardo","barajas-d"]
|
|
6
6
|
|
|
7
7
|
body:
|
|
8
8
|
- type: markdown
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Dependabot version updates β sdk-ruby
|
|
2
|
+
# UbicaciΓ³n obligatoria: .github/dependabot.yml
|
|
3
|
+
# Ecosistemas: bundler (Ruby) + github-actions
|
|
4
|
+
version: 2
|
|
5
|
+
|
|
6
|
+
updates:
|
|
7
|
+
# ββ Ruby / Bundler βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
8
|
+
- package-ecosystem: "bundler"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
12
|
+
day: "monday"
|
|
13
|
+
time: "09:00"
|
|
14
|
+
timezone: "America/Bogota"
|
|
15
|
+
open-pull-requests-limit: 5
|
|
16
|
+
assignees:
|
|
17
|
+
- "luismeli10"
|
|
18
|
+
- "orojaspardo"
|
|
19
|
+
- "barajas-d"
|
|
20
|
+
labels:
|
|
21
|
+
- "dependencies"
|
|
22
|
+
commit-message:
|
|
23
|
+
prefix: "chore(deps)"
|
|
24
|
+
include: "scope"
|
|
25
|
+
# bundler puede ejecutar cΓ³digo de gemspec durante el update β lo bloqueamos
|
|
26
|
+
insecure-external-code-execution: "deny"
|
|
27
|
+
ignore:
|
|
28
|
+
- dependency-name: "*"
|
|
29
|
+
update-types: ["version-update:semver-major"]
|
|
30
|
+
|
|
31
|
+
# ββ GitHub Actions (CI) ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
32
|
+
- package-ecosystem: "github-actions"
|
|
33
|
+
directory: "/"
|
|
34
|
+
schedule:
|
|
35
|
+
interval: "weekly"
|
|
36
|
+
day: "monday"
|
|
37
|
+
time: "09:00"
|
|
38
|
+
timezone: "America/Bogota"
|
|
39
|
+
open-pull-requests-limit: 1
|
|
40
|
+
labels:
|
|
41
|
+
- "dependencies"
|
|
42
|
+
- "ci"
|
|
43
|
+
commit-message:
|
|
44
|
+
prefix: "chore(ci)"
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
# This workflow uses actions that are not certified by GitHub.
|
|
2
|
-
# They are provided by a third-party and are governed by
|
|
3
|
-
# separate terms of service, privacy policy, and support
|
|
4
|
-
# documentation.
|
|
5
|
-
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
|
6
|
-
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
|
7
|
-
|
|
8
1
|
name: Ruby
|
|
9
2
|
|
|
10
3
|
on:
|
|
@@ -15,27 +8,36 @@ on:
|
|
|
15
8
|
|
|
16
9
|
jobs:
|
|
17
10
|
test:
|
|
18
|
-
|
|
19
11
|
runs-on: ubuntu-latest
|
|
12
|
+
container: ruby:3.4
|
|
20
13
|
|
|
21
14
|
steps:
|
|
22
15
|
- name: Checkout code
|
|
23
|
-
uses: actions/checkout@
|
|
16
|
+
uses: actions/checkout@v6
|
|
24
17
|
with:
|
|
25
18
|
ref: ${{ github.event.pull_request.head.ref }}
|
|
26
19
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
27
20
|
|
|
28
|
-
- name:
|
|
29
|
-
|
|
30
|
-
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
31
|
-
uses: ruby/setup-ruby@v1
|
|
21
|
+
- name: Cache gems
|
|
22
|
+
uses: actions/cache@v4
|
|
32
23
|
with:
|
|
33
|
-
|
|
24
|
+
path: vendor/bundle
|
|
25
|
+
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
|
26
|
+
restore-keys: |
|
|
27
|
+
${{ runner.os }}-gems-
|
|
28
|
+
|
|
34
29
|
- name: Install dependencies
|
|
35
|
-
run:
|
|
30
|
+
run: |
|
|
31
|
+
bundle config set --local path vendor/bundle
|
|
32
|
+
bundle install
|
|
33
|
+
|
|
36
34
|
- name: Rubocop
|
|
37
|
-
run: rubocop lib
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
run: bundle exec rubocop lib
|
|
36
|
+
|
|
37
|
+
# Integration tests disabled: they require a valid MercadoPago ACCESS_TOKEN
|
|
38
|
+
# that is not available in the CI environment. To run them locally:
|
|
39
|
+
# ACCESS_TOKEN=<your_token> bundle exec rake
|
|
40
|
+
# - name: Run tests
|
|
41
|
+
# run: bundle exec rake
|
|
42
|
+
# env:
|
|
43
|
+
# ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN_V2 }}
|
|
@@ -7,27 +7,22 @@ on:
|
|
|
7
7
|
jobs:
|
|
8
8
|
release:
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
|
+
container: ruby:3.4
|
|
10
11
|
|
|
11
12
|
permissions:
|
|
12
13
|
contents: write
|
|
13
14
|
|
|
14
15
|
steps:
|
|
15
16
|
- name: Checkout code
|
|
16
|
-
uses: actions/checkout@
|
|
17
|
-
|
|
18
|
-
- name: Setup Ruby
|
|
19
|
-
uses: ruby/setup-ruby@v1
|
|
20
|
-
with:
|
|
21
|
-
bundler-cache: true
|
|
17
|
+
uses: actions/checkout@v6
|
|
22
18
|
|
|
23
19
|
- name: Install dependencies
|
|
24
20
|
run: bundle install
|
|
25
21
|
|
|
26
|
-
- name:
|
|
27
|
-
run:
|
|
22
|
+
- name: Configure git safe directory
|
|
23
|
+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
28
24
|
|
|
29
25
|
- name: Build gem
|
|
30
|
-
shell: bash
|
|
31
26
|
run: gem build *.gemspec
|
|
32
27
|
|
|
33
28
|
- name: Publish gem to rubygems.org
|
|
@@ -36,5 +31,4 @@ jobs:
|
|
|
36
31
|
run: gem push *.gem
|
|
37
32
|
|
|
38
33
|
- name: Wait for release to propagate
|
|
39
|
-
run:
|
|
40
|
-
gem install rubygems-await
|
|
34
|
+
run: gem install rubygems-await
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [3.1.0] - 2026-05-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **OAuth**: authorization URL builder, token exchange, and token refresh
|
|
13
|
+
(`sdk.oauth.get_authorization_url`, `.create`, `.refresh`).
|
|
14
|
+
Enables marketplace and platform integrations to operate on behalf of
|
|
15
|
+
other sellers via the OAuth 2.0 authorization code flow.
|
|
16
|
+
Endpoints: `POST /oauth/token`.
|
|
17
|
+
|
|
18
|
+
- **Point**: in-person payment intent management for MercadoPago Point
|
|
19
|
+
devices (`sdk.point.get_devices`, `.create`, `.get`, `.cancel`).
|
|
20
|
+
Enables card-present transactions through physical Point card readers.
|
|
21
|
+
Endpoints: `GET /point/integration-api/devices`,
|
|
22
|
+
`POST /point/integration-api/devices/{device_id}/payment-intents`,
|
|
23
|
+
`GET /point/integration-api/payment-intents/{id}`,
|
|
24
|
+
`DELETE /point/integration-api/devices/{device_id}/payment-intents/{id}`.
|
|
25
|
+
Note: `change_operating_mode` (PATCH) is not included; requires HttpClient
|
|
26
|
+
PATCH support.
|
|
27
|
+
|
|
28
|
+
- **Invoice**: retrieval and search of subscription billing invoices
|
|
29
|
+
(`sdk.invoice.get`, `.search`). Enables monitoring of authorized
|
|
30
|
+
payments generated by PreApproval billing cycles.
|
|
31
|
+
Endpoints: `GET /authorized_payments/{id}`,
|
|
32
|
+
`GET /authorized_payments/search`.
|
|
33
|
+
|
|
34
|
+
- **Chargeback**: retrieval and search of payment dispute records
|
|
35
|
+
(`sdk.chargeback.get`, `.search`). Enables monitoring and response to
|
|
36
|
+
cardholder disputes.
|
|
37
|
+
Endpoints: `GET /v1/chargebacks/{id}`,
|
|
38
|
+
`GET /v1/chargebacks/search`.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
mercadopago-sdk (3.0
|
|
4
|
+
mercadopago-sdk (3.1.0)
|
|
5
5
|
faraday (~> 2.0)
|
|
6
6
|
json (~> 2.5)
|
|
7
7
|
|
|
@@ -10,17 +10,17 @@ GEM
|
|
|
10
10
|
specs:
|
|
11
11
|
ast (2.4.2)
|
|
12
12
|
coderay (1.1.3)
|
|
13
|
-
faraday (2.14.
|
|
13
|
+
faraday (2.14.2)
|
|
14
14
|
faraday-net_http (>= 2.0, < 3.5)
|
|
15
15
|
json
|
|
16
16
|
logger
|
|
17
|
-
faraday-net_http (3.4.
|
|
17
|
+
faraday-net_http (3.4.3)
|
|
18
18
|
net-http (~> 0.5)
|
|
19
|
-
json (2.
|
|
19
|
+
json (2.19.5)
|
|
20
20
|
language_server-protocol (3.17.0.3)
|
|
21
21
|
logger (1.7.0)
|
|
22
22
|
method_source (1.0.0)
|
|
23
|
-
minitest (5.
|
|
23
|
+
minitest (5.27.0)
|
|
24
24
|
net-http (0.9.1)
|
|
25
25
|
uri (>= 0.11.1)
|
|
26
26
|
parallel (1.26.3)
|
|
@@ -32,7 +32,7 @@ GEM
|
|
|
32
32
|
method_source (~> 1.0)
|
|
33
33
|
racc (1.8.1)
|
|
34
34
|
rainbow (3.1.1)
|
|
35
|
-
rake (13.
|
|
35
|
+
rake (13.4.2)
|
|
36
36
|
regexp_parser (2.10.0)
|
|
37
37
|
rubocop (1.70.0)
|
|
38
38
|
json (~> 2.3)
|
|
@@ -55,12 +55,9 @@ GEM
|
|
|
55
55
|
PLATFORMS
|
|
56
56
|
ruby
|
|
57
57
|
|
|
58
|
-
RUBY VERSION
|
|
59
|
-
ruby 3.4.3p0
|
|
60
|
-
|
|
61
58
|
DEPENDENCIES
|
|
62
59
|
mercadopago-sdk!
|
|
63
|
-
minitest (~> 5.
|
|
60
|
+
minitest (~> 5.27)
|
|
64
61
|
pry (~> 0.14)
|
|
65
62
|
rake
|
|
66
63
|
rubocop (~> 1.70)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative '../../lib/mercadopago'
|
|
2
|
+
|
|
3
|
+
sdk = Mercadopago::SDK.new('<YOUR_ACCESS_TOKEN>')
|
|
4
|
+
|
|
5
|
+
# Search chargebacks by payment ID
|
|
6
|
+
result = sdk.chargeback.search(filters: { payment_id: '<PAYMENT_ID>' })
|
|
7
|
+
puts "Chargebacks: #{result[:response]}"
|
|
8
|
+
|
|
9
|
+
# Get a specific chargeback by ID
|
|
10
|
+
if result[:response]['results']&.any?
|
|
11
|
+
chargeback_id = result[:response]['results'].first['id']
|
|
12
|
+
chargeback = sdk.chargeback.get(chargeback_id)
|
|
13
|
+
puts "Chargeback details: #{chargeback[:response]}"
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative '../../lib/mercadopago'
|
|
2
|
+
|
|
3
|
+
sdk = Mercadopago::SDK.new('<YOUR_ACCESS_TOKEN>')
|
|
4
|
+
|
|
5
|
+
# Search invoices for a subscription
|
|
6
|
+
result = sdk.invoice.search(filters: { preapproval_id: '<YOUR_PREAPPROVAL_ID>', limit: 10 })
|
|
7
|
+
puts "Invoices: #{result[:response]}"
|
|
8
|
+
|
|
9
|
+
# Get a specific invoice by ID
|
|
10
|
+
if result[:response]['results']&.any?
|
|
11
|
+
invoice_id = result[:response]['results'].first['id']
|
|
12
|
+
invoice = sdk.invoice.get(invoice_id)
|
|
13
|
+
puts "Invoice details: #{invoice[:response]}"
|
|
14
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative '../../lib/mercadopago'
|
|
2
|
+
|
|
3
|
+
sdk = Mercadopago::SDK.new('<YOUR_ACCESS_TOKEN>')
|
|
4
|
+
|
|
5
|
+
# Step 1: Build the authorization URL and redirect the seller to it
|
|
6
|
+
auth_url = sdk.oauth.get_authorization_url(
|
|
7
|
+
'<YOUR_APP_ID>',
|
|
8
|
+
'https://yourapp.com/callback',
|
|
9
|
+
'<UNIQUE_CSRF_STATE>'
|
|
10
|
+
)
|
|
11
|
+
puts "Redirect seller to: #{auth_url}"
|
|
12
|
+
|
|
13
|
+
# Step 2: After the seller authorizes, exchange the code for tokens
|
|
14
|
+
oauth_data = {
|
|
15
|
+
client_secret: '<YOUR_ACCESS_TOKEN>',
|
|
16
|
+
code: '<AUTHORIZATION_CODE_FROM_CALLBACK>',
|
|
17
|
+
redirect_uri: 'https://yourapp.com/callback',
|
|
18
|
+
grant_type: 'authorization_code'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
result = sdk.oauth.create(oauth_data)
|
|
22
|
+
puts "Token created: #{result[:response]}"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require_relative '../../lib/mercadopago'
|
|
2
|
+
|
|
3
|
+
sdk = Mercadopago::SDK.new('<YOUR_ACCESS_TOKEN>')
|
|
4
|
+
|
|
5
|
+
# List available Point devices
|
|
6
|
+
devices = sdk.point.get_devices
|
|
7
|
+
puts "Devices: #{devices[:response]}"
|
|
8
|
+
|
|
9
|
+
# Create a payment intent on a specific device
|
|
10
|
+
device_id = '<YOUR_DEVICE_ID>'
|
|
11
|
+
payment_intent_data = {
|
|
12
|
+
amount: 1500,
|
|
13
|
+
description: 'Product purchase',
|
|
14
|
+
payment: {
|
|
15
|
+
installments: 1,
|
|
16
|
+
type: 'credit_card'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
result = sdk.point.create(device_id, payment_intent_data)
|
|
21
|
+
puts "Payment intent created: #{result[:response]}"
|
|
22
|
+
|
|
23
|
+
# Retrieve the payment intent status
|
|
24
|
+
payment_intent_id = result[:response]['id']
|
|
25
|
+
status = sdk.point.get(payment_intent_id)
|
|
26
|
+
puts "Payment intent status: #{status[:response]}"
|
|
@@ -9,7 +9,7 @@ module Mercadopago
|
|
|
9
9
|
# there is no need to instantiate this class directly.
|
|
10
10
|
class Config
|
|
11
11
|
# Current SDK version following SemVer.
|
|
12
|
-
@@VERSION = '3.0
|
|
12
|
+
@@VERSION = '3.1.0'
|
|
13
13
|
|
|
14
14
|
# User-Agent string sent with every HTTP request for server-side tracking.
|
|
15
15
|
@@USER_AGENT = "MercadoPago Ruby SDK v#{@@VERSION}"
|
|
@@ -16,7 +16,6 @@ module Mercadopago
|
|
|
16
16
|
# replace it via +SDK.new(token, http_client: MyClient.new)+ to
|
|
17
17
|
# inject a custom transport (e.g. for testing or logging).
|
|
18
18
|
class HttpClient
|
|
19
|
-
|
|
20
19
|
RETRYABLE_STATUSES = [429, 500, 502, 503, 504].freeze
|
|
21
20
|
|
|
22
21
|
# Performs an HTTP GET request with automatic retry on transient errors.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mercadopago
|
|
5
|
+
# Provides read access to chargeback disputes.
|
|
6
|
+
#
|
|
7
|
+
# Chargebacks are initiated by cardholders through their issuing bank
|
|
8
|
+
# when they dispute a payment. Use {#get} and {#search} to monitor and
|
|
9
|
+
# respond to disputes.
|
|
10
|
+
#
|
|
11
|
+
# @see https://www.mercadopago.com.br/developers/en/reference/chargebacks/
|
|
12
|
+
class Chargeback < MPBase
|
|
13
|
+
# Retrieves a chargeback by its ID.
|
|
14
|
+
#
|
|
15
|
+
# @param chargeback_id [Integer, String] unique chargeback identifier
|
|
16
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
17
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with the full chargeback object
|
|
18
|
+
# @see https://www.mercadopago.com.br/developers/en/reference/chargebacks/
|
|
19
|
+
def get(chargeback_id, request_options: nil)
|
|
20
|
+
_get(uri: "/v1/chargebacks/#{chargeback_id}", request_options: request_options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Searches chargebacks matching the given filters.
|
|
24
|
+
#
|
|
25
|
+
# @param filters [Hash, nil] query parameters (e.g. +:payment_id+, +:status+)
|
|
26
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
27
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with a paginated list of chargebacks
|
|
28
|
+
# @raise [TypeError] if +filters+ is not a Hash
|
|
29
|
+
# @see https://www.mercadopago.com.br/developers/en/reference/chargebacks/
|
|
30
|
+
def search(filters: nil, request_options: nil)
|
|
31
|
+
raise TypeError, 'Param filters must be a Hash' unless filters.nil? || filters.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
_get(uri: '/v1/chargebacks/search', filters: filters, request_options: request_options)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mercadopago
|
|
5
|
+
# Provides read access to subscription invoices (authorized payments).
|
|
6
|
+
#
|
|
7
|
+
# Each invoice corresponds to a billing cycle of a {Preapproval} and
|
|
8
|
+
# tracks the charge amount, status, retry attempts, and the resulting
|
|
9
|
+
# payment. Use {#get} and {#search} to monitor billing cycles and their
|
|
10
|
+
# outcomes.
|
|
11
|
+
#
|
|
12
|
+
# @see https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/get-authorized-payment/get
|
|
13
|
+
class Invoice < MPBase
|
|
14
|
+
# Retrieves a single invoice (authorized payment) by its ID.
|
|
15
|
+
#
|
|
16
|
+
# @param invoice_id [Integer, String] unique invoice identifier
|
|
17
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
18
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with the full invoice object
|
|
19
|
+
# including +:status+, +:transaction_amount+, +:preapproval_id+, and +:payment+ details
|
|
20
|
+
# @see https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/get-authorized-payment/get
|
|
21
|
+
def get(invoice_id, request_options: nil)
|
|
22
|
+
_get(uri: "/authorized_payments/#{invoice_id}", request_options: request_options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Searches invoices matching the given filters.
|
|
26
|
+
#
|
|
27
|
+
# @param filters [Hash, nil] query parameters such as +:preapproval_id+,
|
|
28
|
+
# +:status+, +:payer_id+, +:offset+, and +:limit+
|
|
29
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
30
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with +:paging+ metadata
|
|
31
|
+
# and a +:results+ list of matching invoices
|
|
32
|
+
# @raise [TypeError] if +filters+ is not a Hash
|
|
33
|
+
# @see https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/authorized-payment-search/get
|
|
34
|
+
def search(filters: nil, request_options: nil)
|
|
35
|
+
raise TypeError, 'Param filters must be a Hash' unless filters.nil? || filters.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
_get(uri: '/authorized_payments/search', filters: filters, request_options: request_options)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module Mercadopago
|
|
7
|
+
# Manages the OAuth 2.0 authorization code flow.
|
|
8
|
+
#
|
|
9
|
+
# Use this resource when your application needs to operate on behalf
|
|
10
|
+
# of other MercadoPago sellers (marketplace or platform scenarios).
|
|
11
|
+
# The flow involves redirecting the seller to the authorization URL,
|
|
12
|
+
# receiving an authorization code, and exchanging it for access and
|
|
13
|
+
# refresh tokens.
|
|
14
|
+
#
|
|
15
|
+
# @see https://www.mercadopago.com/developers/en/docs/security/oauth/creation
|
|
16
|
+
class OAuth < MPBase
|
|
17
|
+
AUTH_URL = 'https://auth.mercadopago.com/authorization'
|
|
18
|
+
private_constant :AUTH_URL
|
|
19
|
+
|
|
20
|
+
# Builds the MercadoPago authorization URL for the OAuth flow.
|
|
21
|
+
#
|
|
22
|
+
# Redirect the seller to this URL to start the authorization process.
|
|
23
|
+
# After granting permission, MercadoPago redirects back to +redirect_uri+
|
|
24
|
+
# with a +code+ query parameter.
|
|
25
|
+
#
|
|
26
|
+
# @param app_id [String] your MercadoPago application's client ID
|
|
27
|
+
# @param redirect_uri [String] URI where MercadoPago sends the seller after authorization
|
|
28
|
+
# @param random_id [String] CSRF-protection state parameter; must be unique per request
|
|
29
|
+
# @return [String] full authorization URL with query parameters
|
|
30
|
+
# @see https://www.mercadopago.com/developers/en/docs/security/oauth/creation
|
|
31
|
+
def get_authorization_url(app_id, redirect_uri, random_id)
|
|
32
|
+
params = URI.encode_www_form(
|
|
33
|
+
client_id: app_id,
|
|
34
|
+
response_type: 'code',
|
|
35
|
+
platform_id: 'mp',
|
|
36
|
+
state: random_id,
|
|
37
|
+
redirect_uri: redirect_uri
|
|
38
|
+
)
|
|
39
|
+
"#{AUTH_URL}?#{params}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Exchanges an authorization code for an access token.
|
|
43
|
+
#
|
|
44
|
+
# Call this after receiving the +code+ parameter in your +redirect_uri+
|
|
45
|
+
# callback. The returned access token can be used to make API requests
|
|
46
|
+
# on behalf of the authorizing seller.
|
|
47
|
+
#
|
|
48
|
+
# @param oauth_data [Hash] authorization request fields:
|
|
49
|
+
# +:client_secret+ (your access token), +:code+ (authorization code),
|
|
50
|
+
# +:redirect_uri+ (must match the one used in {#get_authorization_url}),
|
|
51
|
+
# and +:grant_type+ (++"authorization_code"++).
|
|
52
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
53
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with +access_token+,
|
|
54
|
+
# +refresh_token+, and +expires_in+
|
|
55
|
+
# @raise [TypeError] if +oauth_data+ is not a Hash
|
|
56
|
+
# @see https://www.mercadopago.com/developers/en/reference/authentication/oauth/_oauth_token/post
|
|
57
|
+
def create(oauth_data, request_options: nil)
|
|
58
|
+
raise TypeError, 'Param oauth_data must be a Hash' unless oauth_data.is_a?(Hash)
|
|
59
|
+
|
|
60
|
+
_post(uri: '/oauth/token', data: oauth_data, request_options: request_options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Refreshes an expired access token.
|
|
64
|
+
#
|
|
65
|
+
# Use this to extend the seller's session without requiring them to
|
|
66
|
+
# re-authorize. The +refresh_token+ is obtained from the initial
|
|
67
|
+
# {#create} response.
|
|
68
|
+
#
|
|
69
|
+
# @param oauth_data [Hash] refresh request fields:
|
|
70
|
+
# +:client_secret+ (your access token), +:refresh_token+ (token to refresh),
|
|
71
|
+
# and +:grant_type+ (++"refresh_token"++).
|
|
72
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
73
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with a fresh
|
|
74
|
+
# +access_token+ and +refresh_token+
|
|
75
|
+
# @raise [TypeError] if +oauth_data+ is not a Hash
|
|
76
|
+
# @see https://www.mercadopago.com/developers/en/reference/authentication/oauth/_oauth_token/post
|
|
77
|
+
def refresh(oauth_data, request_options: nil)
|
|
78
|
+
raise TypeError, 'Param oauth_data must be a Hash' unless oauth_data.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
_post(uri: '/oauth/token', data: oauth_data, request_options: request_options)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -9,6 +9,52 @@ module Mercadopago
|
|
|
9
9
|
# Use {OrderTransaction} to manage individual transactions within
|
|
10
10
|
# an order.
|
|
11
11
|
#
|
|
12
|
+
# == Automatic Payments (AP) β supported Hash keys for +stored_credential+
|
|
13
|
+
#
|
|
14
|
+
# {
|
|
15
|
+
# payment_initiator: String, # "cardholder" | "merchant"
|
|
16
|
+
# reason: String, # "recurring" | "installment" | "unscheduled"
|
|
17
|
+
# store_payment_method: Boolean,
|
|
18
|
+
# first_payment: Boolean,
|
|
19
|
+
# prev_transaction_ref: String # ID of the previous transaction in the series.
|
|
20
|
+
# # Required from the second charge onwards.
|
|
21
|
+
# }
|
|
22
|
+
#
|
|
23
|
+
# == Supported Hash keys for +automatic_payments+
|
|
24
|
+
#
|
|
25
|
+
# {
|
|
26
|
+
# payment_profile_id: String, # Stored payment profile ID.
|
|
27
|
+
# retries: Integer, # Max retry attempts on failure.
|
|
28
|
+
# schedule_date: String, # ISO 8601 scheduled charge date.
|
|
29
|
+
# due_date: String # ISO 8601 payment due date.
|
|
30
|
+
# }
|
|
31
|
+
#
|
|
32
|
+
# == Supported Hash keys for +subscription_data+
|
|
33
|
+
#
|
|
34
|
+
# {
|
|
35
|
+
# invoice_id: String, # ID of the invoice being paid.
|
|
36
|
+
# billing_date: String, # ISO 8601 billing date.
|
|
37
|
+
# subscription_sequence: {
|
|
38
|
+
# number: Integer, # Current payment number (1-based).
|
|
39
|
+
# total: Integer # Total payments in the plan.
|
|
40
|
+
# },
|
|
41
|
+
# invoice_period: {
|
|
42
|
+
# type: String, # "monthly" | "daily" | "yearly".
|
|
43
|
+
# period: Integer # Number of period units.
|
|
44
|
+
# }
|
|
45
|
+
# }
|
|
46
|
+
#
|
|
47
|
+
# == Supported Hash keys for +integration_data+ (root of order request)
|
|
48
|
+
#
|
|
49
|
+
# {
|
|
50
|
+
# integrator_id: String, # Certified integrator ID.
|
|
51
|
+
# platform_id: String, # Platform ID assigned by MercadoPago.
|
|
52
|
+
# corporation_id: String, # Corporation ID for multi-account setups.
|
|
53
|
+
# sponsor: {
|
|
54
|
+
# id: String # MercadoPago user ID of the sponsor.
|
|
55
|
+
# }
|
|
56
|
+
# }
|
|
57
|
+
#
|
|
12
58
|
# @see https://www.mercadopago.com/developers/en/docs/checkout-api/landing
|
|
13
59
|
class Order < MPBase
|
|
14
60
|
# Creates a new order.
|
|
@@ -38,7 +84,7 @@ module Mercadopago
|
|
|
38
84
|
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
39
85
|
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with the processed order
|
|
40
86
|
def process(order_id, request_options: nil)
|
|
41
|
-
_post(uri: "/v1/orders/#{order_id}/process", data: nil,request_options: request_options)
|
|
87
|
+
_post(uri: "/v1/orders/#{order_id}/process", data: nil, request_options: request_options)
|
|
42
88
|
end
|
|
43
89
|
|
|
44
90
|
# Refunds an order (full or partial).
|
|
@@ -52,9 +98,7 @@ module Mercadopago
|
|
|
52
98
|
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with the refund result
|
|
53
99
|
# @raise [TypeError] if +refund_data+ is provided but is not a Hash
|
|
54
100
|
def refund(order_id, refund_data: nil, request_options: nil)
|
|
55
|
-
unless refund_data.nil?
|
|
56
|
-
raise TypeError, 'Param refund_data must be a Hash' unless refund_data.is_a?(Hash)
|
|
57
|
-
end
|
|
101
|
+
raise TypeError, 'Param refund_data must be a Hash' unless refund_data.nil? || refund_data.is_a?(Hash)
|
|
58
102
|
|
|
59
103
|
_post(uri: "/v1/orders/#{order_id}/refund", data: refund_data, request_options: request_options)
|
|
60
104
|
end
|
|
@@ -85,6 +129,5 @@ module Mercadopago
|
|
|
85
129
|
def search(filters: nil, request_options: nil)
|
|
86
130
|
_get(uri: '/v1/orders', params: filters, request_options: request_options)
|
|
87
131
|
end
|
|
88
|
-
|
|
89
132
|
end
|
|
90
133
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# typed: true
|
|
3
2
|
# frozen_string_literal: true
|
|
4
3
|
|
|
@@ -47,6 +46,5 @@ module Mercadopago
|
|
|
47
46
|
def delete(order_id, transaction_id, request_options: nil)
|
|
48
47
|
_delete(uri: "/v1/orders/#{order_id}/transactions/#{transaction_id}", request_options: request_options)
|
|
49
48
|
end
|
|
50
|
-
|
|
51
49
|
end
|
|
52
50
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mercadopago
|
|
5
|
+
# Manages payment intents on MercadoPago Point (POS) devices.
|
|
6
|
+
#
|
|
7
|
+
# Enables in-person payment processing by creating payment intents
|
|
8
|
+
# that are sent to a physical Point device for the buyer to complete
|
|
9
|
+
# the transaction by inserting or tapping their card.
|
|
10
|
+
#
|
|
11
|
+
# Note: The +change_operating_mode+ operation (PATCH
|
|
12
|
+
# +/point/integration-api/devices/{device_id}+) is not included because
|
|
13
|
+
# the Ruby SDK HTTP client does not currently expose a PATCH method.
|
|
14
|
+
#
|
|
15
|
+
# @see https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/create-order/post
|
|
16
|
+
class Point < MPBase
|
|
17
|
+
# Lists Point devices linked to the authenticated account.
|
|
18
|
+
#
|
|
19
|
+
# @param filters [Hash, nil] optional query parameters (e.g. +store_id+, +pos_id+)
|
|
20
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
21
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with a list of devices
|
|
22
|
+
# @raise [TypeError] if +filters+ is not a Hash
|
|
23
|
+
# @see https://www.mercadopago.com/developers/en/reference/in-person-payments/point/devices/list-devices/get
|
|
24
|
+
def get_devices(filters: nil, request_options: nil)
|
|
25
|
+
raise TypeError, 'Param filters must be a Hash' unless filters.nil? || filters.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
_get(uri: '/point/integration-api/devices', filters: filters, request_options: request_options)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Creates a payment intent on a specific Point device.
|
|
31
|
+
#
|
|
32
|
+
# The payment intent is sent to the physical device, where the buyer
|
|
33
|
+
# completes the transaction.
|
|
34
|
+
#
|
|
35
|
+
# @param device_id [String] unique identifier of the target Point device
|
|
36
|
+
# @param payment_intent_data [Hash] payment intent attributes (+amount+,
|
|
37
|
+
# +description+, +payment+ method details, etc.)
|
|
38
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
39
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with the created intent
|
|
40
|
+
# @raise [TypeError] if +payment_intent_data+ is not a Hash
|
|
41
|
+
# @see https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/create-order/post
|
|
42
|
+
def create(device_id, payment_intent_data, request_options: nil)
|
|
43
|
+
raise TypeError, 'Param payment_intent_data must be a Hash' unless payment_intent_data.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
_post(
|
|
46
|
+
uri: "/point/integration-api/devices/#{device_id}/payment-intents",
|
|
47
|
+
data: payment_intent_data,
|
|
48
|
+
request_options: request_options
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Retrieves the current state of a payment intent by its ID.
|
|
53
|
+
#
|
|
54
|
+
# Use this to check whether the buyer has completed, cancelled, or is
|
|
55
|
+
# still processing the payment on the device.
|
|
56
|
+
#
|
|
57
|
+
# @param payment_intent_id [String] unique identifier of the payment intent
|
|
58
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
59
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with intent details
|
|
60
|
+
# including +:state+ and, when completed, +:payment_id+
|
|
61
|
+
# @see https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/get-order/get
|
|
62
|
+
def get(payment_intent_id, request_options: nil)
|
|
63
|
+
_get(
|
|
64
|
+
uri: "/point/integration-api/payment-intents/#{payment_intent_id}",
|
|
65
|
+
request_options: request_options
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Cancels a pending payment intent on a specific device.
|
|
70
|
+
#
|
|
71
|
+
# Use this to abort a transaction before the buyer completes the
|
|
72
|
+
# payment on the device.
|
|
73
|
+
#
|
|
74
|
+
# @param device_id [String] unique identifier of the Point device holding the intent
|
|
75
|
+
# @param payment_intent_id [String] unique identifier of the payment intent to cancel
|
|
76
|
+
# @param request_options [RequestOptions, nil] per-call configuration override
|
|
77
|
+
# @return [Hash{Symbol => Object}] +:status+ and +:response+ with cancellation confirmation
|
|
78
|
+
# @see https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/cancel-order/post
|
|
79
|
+
def cancel(device_id, payment_intent_id, request_options: nil)
|
|
80
|
+
_delete(
|
|
81
|
+
uri: "/point/integration-api/devices/#{device_id}/payment-intents/#{payment_intent_id}",
|
|
82
|
+
request_options: request_options
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/mercadopago/sdk.rb
CHANGED
|
@@ -38,6 +38,11 @@ module Mercadopago
|
|
|
38
38
|
self.request_options = request_options.nil? ? RequestOptions.new(access_token: access_token) : request_options
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# @return [Chargeback] resource for retrieving and searching payment disputes
|
|
42
|
+
def chargeback
|
|
43
|
+
Chargeback.new(request_options, http_client)
|
|
44
|
+
end
|
|
45
|
+
|
|
41
46
|
# @return [AdvancedPayment] resource for split-payment operations (marketplace)
|
|
42
47
|
def advanced_payment
|
|
43
48
|
AdvancedPayment.new(request_options, http_client)
|
|
@@ -113,11 +118,26 @@ module Mercadopago
|
|
|
113
118
|
Order.new(request_options, http_client)
|
|
114
119
|
end
|
|
115
120
|
|
|
121
|
+
# @return [Invoice] resource for retrieving subscription billing invoices
|
|
122
|
+
def invoice
|
|
123
|
+
Invoice.new(request_options, http_client)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [OAuth] resource for the OAuth 2.0 authorization code flow
|
|
127
|
+
def oauth
|
|
128
|
+
OAuth.new(request_options, http_client)
|
|
129
|
+
end
|
|
130
|
+
|
|
116
131
|
# @return [OrderTransaction] resource for managing transactions within an order
|
|
117
132
|
def order_transaction
|
|
118
133
|
OrderTransaction.new(request_options, http_client)
|
|
119
134
|
end
|
|
120
135
|
|
|
136
|
+
# @return [Point] resource for in-person payments via Point devices
|
|
137
|
+
def point
|
|
138
|
+
Point.new(request_options, http_client)
|
|
139
|
+
end
|
|
140
|
+
|
|
121
141
|
# @param value [String] OAuth access token
|
|
122
142
|
# @raise [TypeError] if value is not a String
|
|
123
143
|
def access_token=(value)
|
|
@@ -104,78 +104,12 @@ module Mercadopago
|
|
|
104
104
|
x_signature = normalize(x_signature)
|
|
105
105
|
x_request_id = normalize(x_request_id)
|
|
106
106
|
data_id = normalize(data_id)
|
|
107
|
-
versions = supported_versions
|
|
107
|
+
versions = resolve_versions(supported_versions)
|
|
108
108
|
now_proc = now || -> { (Time.now.to_f * 1000).to_i }
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
request_id: x_request_id
|
|
114
|
-
)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
ts, hashes = parse_signature_header(x_signature)
|
|
118
|
-
|
|
119
|
-
if ts.nil? && hashes.empty?
|
|
120
|
-
raise InvalidWebhookSignatureError.new(
|
|
121
|
-
SignatureFailureReason::MALFORMED_SIGNATURE_HEADER,
|
|
122
|
-
request_id: x_request_id
|
|
123
|
-
)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
if ts.nil?
|
|
127
|
-
raise InvalidWebhookSignatureError.new(
|
|
128
|
-
SignatureFailureReason::MISSING_TIMESTAMP,
|
|
129
|
-
request_id: x_request_id
|
|
130
|
-
)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
unless ts.match?(/\A\d+\z/)
|
|
134
|
-
raise InvalidWebhookSignatureError.new(
|
|
135
|
-
SignatureFailureReason::MALFORMED_SIGNATURE_HEADER,
|
|
136
|
-
request_id: x_request_id,
|
|
137
|
-
timestamp: ts
|
|
138
|
-
)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
received_hash = nil
|
|
142
|
-
versions.each do |v|
|
|
143
|
-
if hashes.key?(v)
|
|
144
|
-
received_hash = hashes[v]
|
|
145
|
-
break
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
if received_hash.nil?
|
|
150
|
-
raise InvalidWebhookSignatureError.new(
|
|
151
|
-
SignatureFailureReason::MISSING_HASH,
|
|
152
|
-
request_id: x_request_id,
|
|
153
|
-
timestamp: ts
|
|
154
|
-
)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
manifest = build_manifest(data_id, x_request_id, ts)
|
|
158
|
-
computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest)
|
|
159
|
-
|
|
160
|
-
unless constant_time_equal(computed, received_hash)
|
|
161
|
-
raise InvalidWebhookSignatureError.new(
|
|
162
|
-
SignatureFailureReason::SIGNATURE_MISMATCH,
|
|
163
|
-
request_id: x_request_id,
|
|
164
|
-
timestamp: ts
|
|
165
|
-
)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
unless tolerance_seconds.nil?
|
|
169
|
-
drift_ms = (now_proc.call - ts.to_i).abs
|
|
170
|
-
if drift_ms > tolerance_seconds * 1000
|
|
171
|
-
raise InvalidWebhookSignatureError.new(
|
|
172
|
-
SignatureFailureReason::TIMESTAMP_OUT_OF_TOLERANCE,
|
|
173
|
-
request_id: x_request_id,
|
|
174
|
-
timestamp: ts
|
|
175
|
-
)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
110
|
+
ts, received_hash = parse_and_validate_header(x_signature, x_request_id, versions)
|
|
111
|
+
verify_signature!(data_id, x_request_id, ts, secret, received_hash)
|
|
112
|
+
check_tolerance!(ts, x_request_id, tolerance_seconds, now_proc)
|
|
179
113
|
nil
|
|
180
114
|
end
|
|
181
115
|
|
|
@@ -193,13 +127,10 @@ module Mercadopago
|
|
|
193
127
|
hashes = {}
|
|
194
128
|
ts = nil
|
|
195
129
|
header.split(',').each do |part|
|
|
196
|
-
key, value = part.split('=', 2)
|
|
197
|
-
next if key.
|
|
198
|
-
|
|
199
|
-
key = key.strip.downcase
|
|
200
|
-
value = value.strip
|
|
201
|
-
next if key.empty? || value.empty?
|
|
130
|
+
key, value = part.split('=', 2).map(&:strip)
|
|
131
|
+
next if key.to_s.empty? || value.to_s.empty?
|
|
202
132
|
|
|
133
|
+
key = key.downcase
|
|
203
134
|
if key == 'ts'
|
|
204
135
|
ts = value
|
|
205
136
|
elsif key.match?(VERSION_KEY_REGEX)
|
|
@@ -224,14 +155,67 @@ module Mercadopago
|
|
|
224
155
|
private_class_method :constant_time_equal
|
|
225
156
|
|
|
226
157
|
# Builds the HMAC manifest, omitting empty pairs per the documented rule.
|
|
227
|
-
def self.build_manifest(data_id, request_id,
|
|
158
|
+
def self.build_manifest(data_id, request_id, timestamp)
|
|
228
159
|
parts = []
|
|
229
160
|
parts << "id:#{data_id.downcase}" unless data_id.nil?
|
|
230
161
|
parts << "request-id:#{request_id}" unless request_id.nil?
|
|
231
|
-
parts << "ts:#{
|
|
162
|
+
parts << "ts:#{timestamp}"
|
|
232
163
|
"#{parts.join(';')};"
|
|
233
164
|
end
|
|
234
165
|
private_class_method :build_manifest
|
|
166
|
+
|
|
167
|
+
def self.resolve_versions(supported_versions)
|
|
168
|
+
return DEFAULT_SUPPORTED_VERSIONS if supported_versions.nil? || supported_versions.empty?
|
|
169
|
+
|
|
170
|
+
supported_versions
|
|
171
|
+
end
|
|
172
|
+
private_class_method :resolve_versions
|
|
173
|
+
|
|
174
|
+
def self.parse_and_validate_header(x_signature, x_request_id, versions)
|
|
175
|
+
raise_error!(SignatureFailureReason::MISSING_SIGNATURE_HEADER, x_request_id) if x_signature.nil?
|
|
176
|
+
|
|
177
|
+
ts, hashes = parse_signature_header(x_signature)
|
|
178
|
+
validate_ts_and_hashes!(ts, hashes, x_request_id)
|
|
179
|
+
|
|
180
|
+
received_hash = hashes[versions.find { |v| hashes.key?(v) }]
|
|
181
|
+
raise_error!(SignatureFailureReason::MISSING_HASH, x_request_id, timestamp: ts) if received_hash.nil?
|
|
182
|
+
|
|
183
|
+
[ts, received_hash]
|
|
184
|
+
end
|
|
185
|
+
private_class_method :parse_and_validate_header
|
|
186
|
+
|
|
187
|
+
def self.validate_ts_and_hashes!(timestamp, hashes, x_request_id)
|
|
188
|
+
raise_error!(SignatureFailureReason::MALFORMED_SIGNATURE_HEADER, x_request_id) if timestamp.nil? && hashes.empty?
|
|
189
|
+
raise_error!(SignatureFailureReason::MISSING_TIMESTAMP, x_request_id) if timestamp.nil?
|
|
190
|
+
return if timestamp.match?(/\A\d+\z/)
|
|
191
|
+
|
|
192
|
+
raise_error!(SignatureFailureReason::MALFORMED_SIGNATURE_HEADER, x_request_id, timestamp: timestamp)
|
|
193
|
+
end
|
|
194
|
+
private_class_method :validate_ts_and_hashes!
|
|
195
|
+
|
|
196
|
+
def self.verify_signature!(data_id, x_request_id, timestamp, secret, received_hash)
|
|
197
|
+
manifest = build_manifest(data_id, x_request_id, timestamp)
|
|
198
|
+
computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest)
|
|
199
|
+
return if constant_time_equal(computed, received_hash)
|
|
200
|
+
|
|
201
|
+
raise_error!(SignatureFailureReason::SIGNATURE_MISMATCH, x_request_id, timestamp: timestamp)
|
|
202
|
+
end
|
|
203
|
+
private_class_method :verify_signature!
|
|
204
|
+
|
|
205
|
+
def self.check_tolerance!(timestamp, x_request_id, tolerance_seconds, now_proc)
|
|
206
|
+
return if tolerance_seconds.nil?
|
|
207
|
+
|
|
208
|
+
drift_ms = (now_proc.call - timestamp.to_i).abs
|
|
209
|
+
return unless drift_ms > tolerance_seconds * 1000
|
|
210
|
+
|
|
211
|
+
raise_error!(SignatureFailureReason::TIMESTAMP_OUT_OF_TOLERANCE, x_request_id, timestamp: timestamp)
|
|
212
|
+
end
|
|
213
|
+
private_class_method :check_tolerance!
|
|
214
|
+
|
|
215
|
+
def self.raise_error!(reason, request_id, timestamp: nil)
|
|
216
|
+
raise InvalidWebhookSignatureError.new(reason, request_id: request_id, timestamp: timestamp)
|
|
217
|
+
end
|
|
218
|
+
private_class_method :raise_error!
|
|
235
219
|
end
|
|
236
220
|
end
|
|
237
221
|
end
|
data/lib/mercadopago.rb
CHANGED
|
@@ -28,6 +28,7 @@ require_relative './mercadopago/config/config'
|
|
|
28
28
|
require_relative './mercadopago/config/request_options'
|
|
29
29
|
|
|
30
30
|
# --- API Resources ---
|
|
31
|
+
require_relative './mercadopago/resources/chargeback'
|
|
31
32
|
require_relative './mercadopago/resources/customer'
|
|
32
33
|
require_relative './mercadopago/resources/card'
|
|
33
34
|
require_relative './mercadopago/resources/user'
|
|
@@ -42,8 +43,11 @@ require_relative './mercadopago/resources/advanced_payment'
|
|
|
42
43
|
require_relative './mercadopago/resources/disbursement_refund'
|
|
43
44
|
require_relative './mercadopago/resources/preapproval'
|
|
44
45
|
require_relative './mercadopago/resources/preapproval_plan'
|
|
46
|
+
require_relative './mercadopago/resources/invoice'
|
|
47
|
+
require_relative './mercadopago/resources/oauth'
|
|
45
48
|
require_relative './mercadopago/resources/order'
|
|
46
49
|
require_relative './mercadopago/resources/order_transaction'
|
|
50
|
+
require_relative './mercadopago/resources/point'
|
|
47
51
|
|
|
48
52
|
# --- Entry point ---
|
|
49
53
|
require_relative './mercadopago/sdk'
|
data/mercadopago.gemspec
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/mercadopago'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
|
|
8
|
+
class TestChargeback < Minitest::Test
|
|
9
|
+
def test_search_returns_valid_status
|
|
10
|
+
sdk = Mercadopago::SDK.new(ENV['ACCESS_TOKEN'])
|
|
11
|
+
result = sdk.chargeback.search(filters: { limit: 5 })
|
|
12
|
+
assert_includes [200, 400, 401, 404], result[:status]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_search_raises_on_non_hash_filters
|
|
16
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
17
|
+
assert_raises(TypeError) { sdk.chargeback.search(filters: 'not_a_hash') }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/mercadopago'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
|
|
8
|
+
class TestInvoice < Minitest::Test
|
|
9
|
+
def test_search_returns_valid_status
|
|
10
|
+
sdk = Mercadopago::SDK.new(ENV['ACCESS_TOKEN'])
|
|
11
|
+
result = sdk.invoice.search(filters: { limit: 5 })
|
|
12
|
+
assert_includes [200, 400, 401, 404], result[:status]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_search_raises_on_non_hash_filters
|
|
16
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
17
|
+
assert_raises(TypeError) { sdk.invoice.search(filters: 'not_a_hash') }
|
|
18
|
+
end
|
|
19
|
+
end
|
data/tests/test_oauth.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/mercadopago'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
|
|
8
|
+
class TestOAuth < Minitest::Test
|
|
9
|
+
def test_get_authorization_url_builds_correct_url
|
|
10
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
11
|
+
url = sdk.oauth.get_authorization_url('MY_APP_ID', 'https://example.com/callback', 'csrf_state')
|
|
12
|
+
|
|
13
|
+
assert_includes url, 'https://auth.mercadopago.com/authorization'
|
|
14
|
+
assert_includes url, 'client_id=MY_APP_ID'
|
|
15
|
+
assert_includes url, 'response_type=code'
|
|
16
|
+
assert_includes url, 'platform_id=mp'
|
|
17
|
+
assert_includes url, 'state=csrf_state'
|
|
18
|
+
assert_includes url, 'redirect_uri='
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_create_raises_on_non_hash
|
|
22
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
23
|
+
assert_raises(TypeError) { sdk.oauth.create('not_a_hash') }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_refresh_raises_on_non_hash
|
|
27
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
28
|
+
assert_raises(TypeError) { sdk.oauth.refresh('not_a_hash') }
|
|
29
|
+
end
|
|
30
|
+
end
|
data/tests/test_point.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/mercadopago'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
|
|
8
|
+
class TestPoint < Minitest::Test
|
|
9
|
+
def test_get_devices_returns_valid_status
|
|
10
|
+
sdk = Mercadopago::SDK.new(ENV['ACCESS_TOKEN'])
|
|
11
|
+
result = sdk.point.get_devices
|
|
12
|
+
assert_includes [200, 400, 401, 403, 404], result[:status]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_create_raises_on_non_hash
|
|
16
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
17
|
+
assert_raises(TypeError) { sdk.point.create('device_123', 'not_a_hash') }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_get_devices_raises_on_non_hash_filters
|
|
21
|
+
sdk = Mercadopago::SDK.new('TEST_TOKEN')
|
|
22
|
+
assert_raises(TypeError) { sdk.point.get_devices(filters: 'not_a_hash') }
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mercadopago-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mercado Pago
|
|
@@ -75,6 +75,7 @@ files:
|
|
|
75
75
|
- ".github/ISSUE_TEMPLATE/config.yml"
|
|
76
76
|
- ".github/ISSUE_TEMPLATE/feature_request.yml"
|
|
77
77
|
- ".github/ISSUE_TEMPLATE/question.yml"
|
|
78
|
+
- ".github/dependabot.yml"
|
|
78
79
|
- ".github/workflows/ci.yml"
|
|
79
80
|
- ".github/workflows/release.yml"
|
|
80
81
|
- ".gitignore"
|
|
@@ -82,6 +83,7 @@ files:
|
|
|
82
83
|
- ".rubocop.yml"
|
|
83
84
|
- ".rubocop_todo.yml"
|
|
84
85
|
- ".ruby-version"
|
|
86
|
+
- CHANGELOG.md
|
|
85
87
|
- CODE_OF_CONDUCT.md
|
|
86
88
|
- CODING_GUIDELINES.md
|
|
87
89
|
- CONTRIBUTING.md
|
|
@@ -158,6 +160,9 @@ files:
|
|
|
158
160
|
- docs/js/searcher.js
|
|
159
161
|
- docs/js/searcher.js.gz
|
|
160
162
|
- docs/table_of_contents.html
|
|
163
|
+
- examples/chargeback/search_chargeback.rb
|
|
164
|
+
- examples/invoice/get_invoice.rb
|
|
165
|
+
- examples/oauth/create_token.rb
|
|
161
166
|
- examples/order/cancel.rb
|
|
162
167
|
- examples/order/capture.rb
|
|
163
168
|
- examples/order/create.rb
|
|
@@ -168,6 +173,7 @@ files:
|
|
|
168
173
|
- examples/order/transaction/create.rb
|
|
169
174
|
- examples/order/transaction/delete.rb
|
|
170
175
|
- examples/order/transaction/update.rb
|
|
176
|
+
- examples/point/create_payment_intent.rb
|
|
171
177
|
- examples/preference/create.rb
|
|
172
178
|
- lib/mercadopago.rb
|
|
173
179
|
- lib/mercadopago/config/config.rb
|
|
@@ -177,14 +183,18 @@ files:
|
|
|
177
183
|
- lib/mercadopago/resources/advanced_payment.rb
|
|
178
184
|
- lib/mercadopago/resources/card.rb
|
|
179
185
|
- lib/mercadopago/resources/card_token.rb
|
|
186
|
+
- lib/mercadopago/resources/chargeback.rb
|
|
180
187
|
- lib/mercadopago/resources/customer.rb
|
|
181
188
|
- lib/mercadopago/resources/disbursement_refund.rb
|
|
182
189
|
- lib/mercadopago/resources/identification_type.rb
|
|
190
|
+
- lib/mercadopago/resources/invoice.rb
|
|
183
191
|
- lib/mercadopago/resources/merchant_order.rb
|
|
192
|
+
- lib/mercadopago/resources/oauth.rb
|
|
184
193
|
- lib/mercadopago/resources/order.rb
|
|
185
194
|
- lib/mercadopago/resources/order_transaction.rb
|
|
186
195
|
- lib/mercadopago/resources/payment.rb
|
|
187
196
|
- lib/mercadopago/resources/payment_methods.rb
|
|
197
|
+
- lib/mercadopago/resources/point.rb
|
|
188
198
|
- lib/mercadopago/resources/preapproval.rb
|
|
189
199
|
- lib/mercadopago/resources/preapproval_plan.rb
|
|
190
200
|
- lib/mercadopago/resources/preference.rb
|
|
@@ -195,13 +205,17 @@ files:
|
|
|
195
205
|
- mercadopago.gemspec
|
|
196
206
|
- tests/test_card.rb
|
|
197
207
|
- tests/test_card_token.rb
|
|
208
|
+
- tests/test_chargeback.rb
|
|
198
209
|
- tests/test_customer.rb
|
|
199
210
|
- tests/test_identification_type.rb
|
|
211
|
+
- tests/test_invoice.rb
|
|
200
212
|
- tests/test_merchant_order.rb
|
|
213
|
+
- tests/test_oauth.rb
|
|
201
214
|
- tests/test_order.rb
|
|
202
215
|
- tests/test_order_transaction.rb
|
|
203
216
|
- tests/test_payment.rb
|
|
204
217
|
- tests/test_payment_methods.rb
|
|
218
|
+
- tests/test_point.rb
|
|
205
219
|
- tests/test_preapproval.rb
|
|
206
220
|
- tests/test_preapproval_plan.rb
|
|
207
221
|
- tests/test_preference.rb
|
|
@@ -228,19 +242,23 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
228
242
|
- !ruby/object:Gem::Version
|
|
229
243
|
version: '0'
|
|
230
244
|
requirements: []
|
|
231
|
-
rubygems_version: 3.6.
|
|
245
|
+
rubygems_version: 3.6.9
|
|
232
246
|
specification_version: 4
|
|
233
247
|
summary: Mercado Pago Ruby SDK
|
|
234
248
|
test_files:
|
|
235
249
|
- tests/test_card.rb
|
|
236
250
|
- tests/test_card_token.rb
|
|
251
|
+
- tests/test_chargeback.rb
|
|
237
252
|
- tests/test_customer.rb
|
|
238
253
|
- tests/test_identification_type.rb
|
|
254
|
+
- tests/test_invoice.rb
|
|
239
255
|
- tests/test_merchant_order.rb
|
|
256
|
+
- tests/test_oauth.rb
|
|
240
257
|
- tests/test_order.rb
|
|
241
258
|
- tests/test_order_transaction.rb
|
|
242
259
|
- tests/test_payment.rb
|
|
243
260
|
- tests/test_payment_methods.rb
|
|
261
|
+
- tests/test_point.rb
|
|
244
262
|
- tests/test_preapproval.rb
|
|
245
263
|
- tests/test_preapproval_plan.rb
|
|
246
264
|
- tests/test_preference.rb
|