mercadopago-sdk 3.2.0 → 3.3.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/workflows/ci.yml +2 -2
- data/.github/workflows/release.yml +1 -1
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +5 -5
- data/examples/order/create_checkout_pro.rb +51 -4
- data/examples/payment/automatic_payment_credential_on_file.rb +194 -0
- data/examples/preference/create_with_industry_fields.rb +137 -0
- data/lib/mercadopago/config/request_options.rb +30 -2
- data/lib/mercadopago/core/mp_base.rb +29 -7
- data/lib/mercadopago/errors/exceptions.rb +108 -0
- data/lib/mercadopago/errors/response.rb +58 -0
- data/lib/mercadopago/http/http_client.rb +45 -57
- data/lib/mercadopago/pagination/iterator.rb +75 -0
- data/lib/mercadopago/resources/advanced_payment.rb +5 -5
- data/lib/mercadopago/resources/card.rb +20 -4
- data/lib/mercadopago/resources/card_token.rb +1 -1
- data/lib/mercadopago/resources/chargeback.rb +1 -1
- data/lib/mercadopago/resources/customer.rb +8 -3
- data/lib/mercadopago/resources/disbursement_refund.rb +3 -3
- data/lib/mercadopago/resources/invoice.rb +1 -1
- data/lib/mercadopago/resources/merchant_order.rb +2 -2
- data/lib/mercadopago/resources/order.rb +5 -5
- data/lib/mercadopago/resources/order_transaction.rb +4 -3
- data/lib/mercadopago/resources/payment.rb +30 -2
- data/lib/mercadopago/resources/point.rb +3 -3
- data/lib/mercadopago/resources/preapproval.rb +7 -2
- data/lib/mercadopago/resources/preapproval_plan.rb +2 -2
- data/lib/mercadopago/resources/preference.rb +15 -2
- data/lib/mercadopago/resources/refund.rb +16 -2
- data/lib/mercadopago/resources/subscription.rb +63 -0
- data/lib/mercadopago/sdk.rb +5 -0
- data/lib/mercadopago/webhook/validator.rb +2 -1
- data/lib/mercadopago.rb +12 -4
- data/mercadopago.gemspec +1 -1
- data/tests/test_card.rb +8 -0
- data/tests/test_ergonomia.rb +123 -0
- data/tests/test_path_param.rb +14 -0
- data/tests/test_payment.rb +51 -3
- data/tests/test_preference.rb +7 -0
- data/tests/test_refund.rb +4 -0
- data/tests/test_subscription.rb +70 -0
- data/tests/test_webhook_signature_validator.rb +10 -2
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 518cbc2c53d00de1452bf210afff62915ba381a970d084ca42a848e5604fa582
|
|
4
|
+
data.tar.gz: 85468b9a03305eb33e4b053a4aae7149cbacd1ceb33df494e25bf63391b2bfea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59839cc98cb80e2718b5694f05559b0a73fe23f04b953c0d172bb80b0609c220f673bd88952326eab551bcd52edd0beec0cb2173c188b9ea9cace45c5513b75b
|
|
7
|
+
data.tar.gz: 4e2e68a2fef69a84eafba9c8fa6b877d214f953e764d26525f48dca5cb812686195fe7fea80085704c8827de1d023fe385c366dede92156a98e680d51ae8dbce
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -13,13 +13,13 @@ jobs:
|
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
15
|
- name: Checkout code
|
|
16
|
-
uses: actions/checkout@v7
|
|
16
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
17
17
|
with:
|
|
18
18
|
ref: ${{ github.event.pull_request.head.ref }}
|
|
19
19
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
20
20
|
|
|
21
21
|
- name: Cache gems
|
|
22
|
-
uses: actions/cache@v6
|
|
22
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
23
23
|
with:
|
|
24
24
|
path: vendor/bundle
|
|
25
25
|
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
data/.rubocop.yml
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
Metrics/AbcSize: {Max: 32}
|
|
4
|
+
Metrics/CyclomaticComplexity: {Max: 18}
|
|
5
|
+
Metrics/ParameterLists: {Max: 12}
|
|
6
|
+
Metrics/PerceivedComplexity: {Max: 18}
|
|
7
|
+
Style/ExplicitBlockArgument: {Enabled: false}
|
|
8
|
+
Style/NumericPredicate: {Enabled: false}
|
|
9
|
+
Style/TrivialAccessors: {Enabled: false}
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.3.0] - 2026-08-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **SDK ergonomics**: typed exceptions, configurable retry, and auto-pagination ([#162](https://github.com/mercadopago/sdk-ruby/pull/162))
|
|
13
|
+
- `MercadoPago::ApiError` now has 12 specific subtypes per HTTP status code
|
|
14
|
+
- Request options gain optional `max_retries`, `retry_on`, `initial_delay_ms`, `max_delay_ms` and `on_retry` callback
|
|
15
|
+
- New auto-pagination support on search endpoints
|
|
16
|
+
- **Missing API methods** — `disbursement_refund.list`, `advanced_payment.update`, `customer_card.update`, `payment.update` ([#161](https://github.com/mercadopago/sdk-ruby/pull/161))
|
|
17
|
+
- **CREDENTIAL_ON_FILE messaging fields** on Payment types ([#158](https://github.com/mercadopago/sdk-ruby/pull/158)): `first_transaction`, `storage`, `transaction_initiator`, `reference`
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Webhook `tolerance_seconds` unit mismatch — `ts` header value compared in seconds against a millisecond clock ([#163](https://github.com/mercadopago/sdk-ruby/pull/163))
|
|
22
|
+
- `constant_time_equals` error on multibyte v1 hash ([#163](https://github.com/mercadopago/sdk-ruby/pull/163))
|
|
23
|
+
|
|
24
|
+
### Dependencies
|
|
25
|
+
|
|
26
|
+
- Bump `json` gem ([#159](https://github.com/mercadopago/sdk-ruby/pull/159))
|
|
27
|
+
- Bump `actions/cache` to `v6.1.0` ([#160](https://github.com/mercadopago/sdk-ruby/pull/160))
|
|
28
|
+
- Bump `actions/checkout` to `v7.0.1` ([#157](https://github.com/mercadopago/sdk-ruby/pull/157))
|
|
29
|
+
|
|
8
30
|
## [3.2.0] - 2026-06-30
|
|
9
31
|
|
|
10
32
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
mercadopago-sdk (3.
|
|
4
|
+
mercadopago-sdk (3.3.0)
|
|
5
5
|
faraday (~> 2.0)
|
|
6
6
|
json (~> 2.5)
|
|
7
7
|
|
|
@@ -17,8 +17,8 @@ GEM
|
|
|
17
17
|
faraday-net_http (3.4.4)
|
|
18
18
|
net-http (~> 0.5)
|
|
19
19
|
io-console (0.8.2)
|
|
20
|
-
json (2.
|
|
21
|
-
language_server-protocol (3.17.0.
|
|
20
|
+
json (2.21.2)
|
|
21
|
+
language_server-protocol (3.17.0.6)
|
|
22
22
|
lint_roller (1.1.0)
|
|
23
23
|
logger (1.7.0)
|
|
24
24
|
method_source (1.1.0)
|
|
@@ -40,7 +40,7 @@ GEM
|
|
|
40
40
|
regexp_parser (2.12.0)
|
|
41
41
|
reline (0.6.3)
|
|
42
42
|
io-console (~> 0.5)
|
|
43
|
-
rubocop (1.88.
|
|
43
|
+
rubocop (1.88.2)
|
|
44
44
|
json (~> 2.3)
|
|
45
45
|
language_server-protocol (~> 3.17.0.2)
|
|
46
46
|
lint_roller (~> 1.1.0)
|
|
@@ -51,7 +51,7 @@ GEM
|
|
|
51
51
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
52
52
|
ruby-progressbar (~> 1.7)
|
|
53
53
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
54
|
-
rubocop-ast (1.
|
|
54
|
+
rubocop-ast (1.50.0)
|
|
55
55
|
parser (>= 3.3.7.2)
|
|
56
56
|
prism (~> 1.7)
|
|
57
57
|
ruby-progressbar (1.13.0)
|
|
@@ -22,11 +22,18 @@ def create_checkout_pro_order(sdk)
|
|
|
22
22
|
identification: {
|
|
23
23
|
type: 'CPF',
|
|
24
24
|
number: '12345678909'
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
|
+
date_created: '2024-01-01T00:00:00Z',
|
|
27
|
+
authentication_type: 'Gmail',
|
|
28
|
+
is_prime_user: false,
|
|
29
|
+
is_first_purchase_online: false,
|
|
30
|
+
last_purchase: '2024-01-01T00:00:00Z',
|
|
31
|
+
registration_date: '2023-01-01T00:00:00Z'
|
|
26
32
|
},
|
|
27
33
|
shipment: {
|
|
28
34
|
mode: 'custom',
|
|
29
35
|
local_pickup: false,
|
|
36
|
+
express_shipment: false,
|
|
30
37
|
cost: '15.00',
|
|
31
38
|
free_shipping: false,
|
|
32
39
|
address: {
|
|
@@ -34,7 +41,11 @@ def create_checkout_pro_order(sdk)
|
|
|
34
41
|
street_name: 'Av. Paulista',
|
|
35
42
|
street_number: '1000',
|
|
36
43
|
neighborhood: 'Bela Vista',
|
|
37
|
-
city: 'Sao Paulo'
|
|
44
|
+
city: 'Sao Paulo',
|
|
45
|
+
state_name: 'Sao Paulo',
|
|
46
|
+
city_name: 'Sao Paulo',
|
|
47
|
+
floor: '2',
|
|
48
|
+
apartment: 'A'
|
|
38
49
|
}
|
|
39
50
|
},
|
|
40
51
|
config: {
|
|
@@ -85,7 +96,25 @@ def create_checkout_pro_order(sdk)
|
|
|
85
96
|
quantity: 1,
|
|
86
97
|
unit_price: '450.00',
|
|
87
98
|
type: 'travel',
|
|
88
|
-
|
|
99
|
+
warranty: false,
|
|
100
|
+
event_date: '2027-01-15T00:00:00.000-03:00',
|
|
101
|
+
category_descriptor: {
|
|
102
|
+
passenger: {
|
|
103
|
+
first_name: 'John',
|
|
104
|
+
last_name: 'Smith',
|
|
105
|
+
identification: {
|
|
106
|
+
type: 'CPF',
|
|
107
|
+
number: '12345678909'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
route: {
|
|
111
|
+
departure: 'SAO',
|
|
112
|
+
destination: 'RIO',
|
|
113
|
+
departure_date_time: '2027-01-15T08:00:00.000-03:00',
|
|
114
|
+
arrival_date_time: '2027-01-15T09:30:00.000-03:00',
|
|
115
|
+
company: 'LATAM'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
89
118
|
},
|
|
90
119
|
{
|
|
91
120
|
external_code: 'ITEM-002',
|
|
@@ -96,7 +125,25 @@ def create_checkout_pro_order(sdk)
|
|
|
96
125
|
quantity: 1,
|
|
97
126
|
unit_price: '50.00',
|
|
98
127
|
type: 'travel',
|
|
99
|
-
|
|
128
|
+
warranty: true,
|
|
129
|
+
event_date: '2027-01-15T00:00:00.000-03:00',
|
|
130
|
+
category_descriptor: {
|
|
131
|
+
passenger: {
|
|
132
|
+
first_name: 'John',
|
|
133
|
+
last_name: 'Smith',
|
|
134
|
+
identification: {
|
|
135
|
+
type: 'CPF',
|
|
136
|
+
number: '12345678909'
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
route: {
|
|
140
|
+
departure: 'SAO',
|
|
141
|
+
destination: 'RIO',
|
|
142
|
+
departure_date_time: '2027-01-15T08:00:00.000-03:00',
|
|
143
|
+
arrival_date_time: '2027-01-15T09:30:00.000-03:00',
|
|
144
|
+
company: 'LATAM'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
100
147
|
}
|
|
101
148
|
]
|
|
102
149
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Example: Automatic Payments with CREDENTIAL_ON_FILE (COF).
|
|
2
|
+
#
|
|
3
|
+
# CREDENTIAL_ON_FILE replaces the deprecated SUBSCRIPTIONS type for recurring
|
|
4
|
+
# and unscheduled automatic payments. It covers three main scenarios:
|
|
5
|
+
#
|
|
6
|
+
# 1. CIT – Customer-Initiated Transaction (enrollment / first charge):
|
|
7
|
+
# The customer actively authorizes the initial payment and consents to
|
|
8
|
+
# storing their credentials for future merchant-initiated charges.
|
|
9
|
+
#
|
|
10
|
+
# 2. MIT – Merchant-Initiated Transaction (recurring charge):
|
|
11
|
+
# Subsequent charges triggered by the merchant without customer
|
|
12
|
+
# interaction (e.g. monthly subscription billing).
|
|
13
|
+
#
|
|
14
|
+
# 3. UCOF-CIT – Unscheduled COF Customer-Initiated Transaction:
|
|
15
|
+
# A one-off purchase where the customer uses previously stored
|
|
16
|
+
# credentials, but the amount / date was not pre-agreed.
|
|
17
|
+
#
|
|
18
|
+
# Reference: https://www.mercadopago.com/developers/en/reference
|
|
19
|
+
|
|
20
|
+
require_relative '../../lib/mercadopago'
|
|
21
|
+
|
|
22
|
+
sdk = Mercadopago::SDK.new('<ACCESS_TOKEN>')
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# 1. CIT — Customer-Initiated Transaction (enrollment)
|
|
26
|
+
#
|
|
27
|
+
# The customer completes the first payment and grants permission to store
|
|
28
|
+
# their card credentials for future recurring charges.
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
def create_cit_payment(sdk)
|
|
31
|
+
request = {
|
|
32
|
+
transaction_amount: 100.00,
|
|
33
|
+
token: '<CARD_TOKEN>',
|
|
34
|
+
description: 'Monthly subscription — enrollment',
|
|
35
|
+
installments: 1,
|
|
36
|
+
payment_method_id: '<PAYMENT_METHOD_ID>',
|
|
37
|
+
payer: {
|
|
38
|
+
email: '<PAYER_EMAIL>',
|
|
39
|
+
identification: {
|
|
40
|
+
type: '<ID_TYPE>',
|
|
41
|
+
number: '<ID_NUMBER>'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
# CREDENTIAL_ON_FILE block for the enrollment (first) transaction.
|
|
45
|
+
point_of_interaction: {
|
|
46
|
+
linked_to: 'subscription',
|
|
47
|
+
transaction_data: {
|
|
48
|
+
# type: "CREDENTIAL_ON_FILE" — new credential-on-file payment type.
|
|
49
|
+
type: 'CREDENTIAL_ON_FILE',
|
|
50
|
+
# sub_type: "recurring" — charge is part of a recurring series.
|
|
51
|
+
sub_type: 'recurring',
|
|
52
|
+
# storage: "store" — instructs the processor to persist the credentials.
|
|
53
|
+
storage: 'store',
|
|
54
|
+
# transaction_initiator: "customer" — the customer triggered this charge.
|
|
55
|
+
transaction_initiator: 'customer',
|
|
56
|
+
# first_transaction: true — marks this as the enrollment / anchor payment.
|
|
57
|
+
first_transaction: true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
custom_headers = {
|
|
63
|
+
'X-Idempotency-Key': '<SOME_UNIQUE_VALUE>'
|
|
64
|
+
}
|
|
65
|
+
custom_request_options = Mercadopago::RequestOptions.new(custom_headers: custom_headers)
|
|
66
|
+
|
|
67
|
+
result = sdk.payment.create(request, request_options: custom_request_options)
|
|
68
|
+
payment = result[:response]
|
|
69
|
+
puts "CIT payment created. ID: #{payment['id']}, status: #{payment['status']}"
|
|
70
|
+
payment['id'] # return payment ID to use as reference in subsequent MITs
|
|
71
|
+
rescue MercadoPago::MPApiException => e
|
|
72
|
+
puts "Status code: #{e.api_response.status_code}"
|
|
73
|
+
puts "Content: #{e.api_response.content}"
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
puts e.message
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# 2. MIT — Merchant-Initiated Transaction (recurring monthly charge)
|
|
80
|
+
#
|
|
81
|
+
# The merchant charges the customer without customer interaction, using the
|
|
82
|
+
# credentials stored during the CIT enrollment. The CIT payment ID is
|
|
83
|
+
# provided as a reference to the original agreement.
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
def create_mit_payment(sdk, cit_payment_id)
|
|
86
|
+
request = {
|
|
87
|
+
transaction_amount: 100.00,
|
|
88
|
+
token: '<CARD_TOKEN>',
|
|
89
|
+
description: 'Monthly subscription — recurring charge',
|
|
90
|
+
installments: 1,
|
|
91
|
+
payment_method_id: '<PAYMENT_METHOD_ID>',
|
|
92
|
+
payer: {
|
|
93
|
+
email: '<PAYER_EMAIL>',
|
|
94
|
+
identification: {
|
|
95
|
+
type: '<ID_TYPE>',
|
|
96
|
+
number: '<ID_NUMBER>'
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
# CREDENTIAL_ON_FILE block for merchant-initiated recurring charges.
|
|
100
|
+
point_of_interaction: {
|
|
101
|
+
linked_to: 'subscription',
|
|
102
|
+
transaction_data: {
|
|
103
|
+
# type: "CREDENTIAL_ON_FILE" — same type as the enrollment.
|
|
104
|
+
type: 'CREDENTIAL_ON_FILE',
|
|
105
|
+
# sub_type: "recurring" — charge belongs to a recurring series.
|
|
106
|
+
sub_type: 'recurring',
|
|
107
|
+
# storage: "stored" — credentials were already stored in a prior CIT.
|
|
108
|
+
storage: 'stored',
|
|
109
|
+
# transaction_initiator: "merchant" — the merchant triggered this charge.
|
|
110
|
+
transaction_initiator: 'merchant',
|
|
111
|
+
# first_transaction: false — not the enrollment; uses stored credentials.
|
|
112
|
+
first_transaction: false,
|
|
113
|
+
# reference.id — ID of the CIT payment that anchors this agreement.
|
|
114
|
+
reference: {
|
|
115
|
+
id: cit_payment_id.to_s
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
custom_headers = {
|
|
122
|
+
'X-Idempotency-Key': '<SOME_UNIQUE_VALUE>'
|
|
123
|
+
}
|
|
124
|
+
custom_request_options = Mercadopago::RequestOptions.new(custom_headers: custom_headers)
|
|
125
|
+
|
|
126
|
+
result = sdk.payment.create(request, request_options: custom_request_options)
|
|
127
|
+
payment = result[:response]
|
|
128
|
+
puts "MIT payment created. ID: #{payment['id']}, status: #{payment['status']}"
|
|
129
|
+
rescue MercadoPago::MPApiException => e
|
|
130
|
+
puts "Status code: #{e.api_response.status_code}"
|
|
131
|
+
puts "Content: #{e.api_response.content}"
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
puts e.message
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# 3. UCOF-CIT — Unscheduled COF Customer-Initiated Transaction
|
|
138
|
+
#
|
|
139
|
+
# The customer actively initiates a one-off purchase using previously stored
|
|
140
|
+
# credentials. The amount and date were not pre-agreed (unscheduled), but the
|
|
141
|
+
# customer is present and initiates the transaction themselves.
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
def create_ucof_cit_payment(sdk)
|
|
144
|
+
request = {
|
|
145
|
+
transaction_amount: 250.00,
|
|
146
|
+
token: '<CARD_TOKEN>',
|
|
147
|
+
description: 'One-off purchase with stored credentials',
|
|
148
|
+
installments: 1,
|
|
149
|
+
payment_method_id: '<PAYMENT_METHOD_ID>',
|
|
150
|
+
payer: {
|
|
151
|
+
email: '<PAYER_EMAIL>',
|
|
152
|
+
identification: {
|
|
153
|
+
type: '<ID_TYPE>',
|
|
154
|
+
number: '<ID_NUMBER>'
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
# CREDENTIAL_ON_FILE block for an unscheduled customer-initiated purchase.
|
|
158
|
+
point_of_interaction: {
|
|
159
|
+
linked_to: 'subscription',
|
|
160
|
+
transaction_data: {
|
|
161
|
+
# type: "CREDENTIAL_ON_FILE" — credential-on-file payment type.
|
|
162
|
+
type: 'CREDENTIAL_ON_FILE',
|
|
163
|
+
# sub_type: "unscheduled" — amount/date not pre-agreed; ad-hoc purchase.
|
|
164
|
+
sub_type: 'unscheduled',
|
|
165
|
+
# storage: "stored" — credentials already stored from a prior enrollment.
|
|
166
|
+
storage: 'stored',
|
|
167
|
+
# transaction_initiator: "customer" — the customer triggered this charge.
|
|
168
|
+
transaction_initiator: 'customer',
|
|
169
|
+
# first_transaction: false — uses previously stored credentials.
|
|
170
|
+
first_transaction: false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
custom_headers = {
|
|
176
|
+
'X-Idempotency-Key': '<SOME_UNIQUE_VALUE>'
|
|
177
|
+
}
|
|
178
|
+
custom_request_options = Mercadopago::RequestOptions.new(custom_headers: custom_headers)
|
|
179
|
+
|
|
180
|
+
result = sdk.payment.create(request, request_options: custom_request_options)
|
|
181
|
+
payment = result[:response]
|
|
182
|
+
puts "UCOF-CIT payment created. ID: #{payment['id']}, status: #{payment['status']}"
|
|
183
|
+
rescue MercadoPago::MPApiException => e
|
|
184
|
+
puts "Status code: #{e.api_response.status_code}"
|
|
185
|
+
puts "Content: #{e.api_response.content}"
|
|
186
|
+
rescue StandardError => e
|
|
187
|
+
puts e.message
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Run all three scenarios sequentially.
|
|
191
|
+
# In production, CIT runs once at enrollment; MIT runs on each billing cycle.
|
|
192
|
+
cit_payment_id = create_cit_payment(sdk)
|
|
193
|
+
create_mit_payment(sdk, cit_payment_id) if cit_payment_id
|
|
194
|
+
create_ucof_cit_payment(sdk)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Example: Create a Checkout Preference with industry-specific fields.
|
|
2
|
+
# Demonstrates payer authentication info, item category descriptors,
|
|
3
|
+
# passenger/route details, and shipment address enrichment.
|
|
4
|
+
|
|
5
|
+
require_relative '../../lib/mercadopago'
|
|
6
|
+
|
|
7
|
+
sdk = Mercadopago::SDK.new('<ACCESS_TOKEN>')
|
|
8
|
+
|
|
9
|
+
def create_preference_with_industry_fields(sdk)
|
|
10
|
+
request = {
|
|
11
|
+
payer: {
|
|
12
|
+
name: 'John',
|
|
13
|
+
surname: 'Smith',
|
|
14
|
+
email: '<PAYER_EMAIL>',
|
|
15
|
+
phone: {
|
|
16
|
+
area_code: '11',
|
|
17
|
+
number: '999998888'
|
|
18
|
+
},
|
|
19
|
+
identification: {
|
|
20
|
+
type: 'CPF',
|
|
21
|
+
number: '12345678909'
|
|
22
|
+
},
|
|
23
|
+
address: {
|
|
24
|
+
zip_code: '01310-100',
|
|
25
|
+
street_name: 'Av. Paulista',
|
|
26
|
+
street_number: 1000
|
|
27
|
+
},
|
|
28
|
+
date_created: '2024-01-01T00:00:00Z',
|
|
29
|
+
authentication_type: 'Gmail',
|
|
30
|
+
is_prime_user: false,
|
|
31
|
+
is_first_purchase_online: false,
|
|
32
|
+
last_purchase: '2024-01-01T00:00:00Z',
|
|
33
|
+
registration_date: '2023-01-01T00:00:00Z'
|
|
34
|
+
},
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
id: 'ITEM-001',
|
|
38
|
+
title: 'Flight SAO-RIO',
|
|
39
|
+
description: 'Round trip, economy class',
|
|
40
|
+
category_id: 'travels',
|
|
41
|
+
picture_url: 'https://example.com/img.jpg',
|
|
42
|
+
quantity: 1,
|
|
43
|
+
currency_id: 'BRL',
|
|
44
|
+
unit_price: 450.00,
|
|
45
|
+
warranty: false,
|
|
46
|
+
event_date: '2027-01-15T00:00:00.000-03:00',
|
|
47
|
+
category_descriptor: {
|
|
48
|
+
passenger: {
|
|
49
|
+
first_name: 'John',
|
|
50
|
+
last_name: 'Smith',
|
|
51
|
+
identification: {
|
|
52
|
+
type: 'CPF',
|
|
53
|
+
number: '12345678909'
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
route: {
|
|
57
|
+
departure: 'SAO',
|
|
58
|
+
destination: 'RIO',
|
|
59
|
+
departure_date_time: '2027-01-15T08:00:00.000-03:00',
|
|
60
|
+
arrival_date_time: '2027-01-15T09:30:00.000-03:00',
|
|
61
|
+
company: 'LATAM'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'ITEM-002',
|
|
67
|
+
title: 'Travel insurance',
|
|
68
|
+
description: 'Basic coverage during trip',
|
|
69
|
+
category_id: 'travels',
|
|
70
|
+
picture_url: 'https://example.com/insurance.jpg',
|
|
71
|
+
quantity: 1,
|
|
72
|
+
currency_id: 'BRL',
|
|
73
|
+
unit_price: 50.00,
|
|
74
|
+
warranty: true,
|
|
75
|
+
event_date: '2027-01-15T00:00:00.000-03:00',
|
|
76
|
+
category_descriptor: {
|
|
77
|
+
passenger: {
|
|
78
|
+
first_name: 'John',
|
|
79
|
+
last_name: 'Smith',
|
|
80
|
+
identification: {
|
|
81
|
+
type: 'CPF',
|
|
82
|
+
number: '12345678909'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
route: {
|
|
86
|
+
departure: 'SAO',
|
|
87
|
+
destination: 'RIO',
|
|
88
|
+
departure_date_time: '2027-01-15T08:00:00.000-03:00',
|
|
89
|
+
arrival_date_time: '2027-01-15T09:30:00.000-03:00',
|
|
90
|
+
company: 'LATAM'
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
shipments: {
|
|
96
|
+
mode: 'custom',
|
|
97
|
+
local_pickup: false,
|
|
98
|
+
express_shipment: false,
|
|
99
|
+
cost: 15.0,
|
|
100
|
+
free_shipping: false,
|
|
101
|
+
receiver_address: {
|
|
102
|
+
zip_code: '01310-100',
|
|
103
|
+
street_name: 'Av. Paulista',
|
|
104
|
+
street_number: 1000,
|
|
105
|
+
state_name: 'Sao Paulo',
|
|
106
|
+
city_name: 'Sao Paulo',
|
|
107
|
+
floor: '2',
|
|
108
|
+
apartment: 'A'
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
back_urls: {
|
|
112
|
+
success: 'https://example.com/success',
|
|
113
|
+
failure: 'https://example.com/failure',
|
|
114
|
+
pending: 'https://example.com/pending'
|
|
115
|
+
},
|
|
116
|
+
auto_return: 'approved',
|
|
117
|
+
external_reference: 'MP0001',
|
|
118
|
+
notification_url: 'https://example.com/notifications',
|
|
119
|
+
statement_descriptor: 'MYSTORE'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
custom_headers = {
|
|
123
|
+
'X-Idempotency-Key': '<SOME_UNIQUE_VALUE>'
|
|
124
|
+
}
|
|
125
|
+
custom_request_options = Mercadopago::RequestOptions.new(custom_headers: custom_headers)
|
|
126
|
+
|
|
127
|
+
result = sdk.preference.create(request, request_options: custom_request_options)
|
|
128
|
+
preference = result[:response]
|
|
129
|
+
|
|
130
|
+
puts "Preference id: #{preference['id']}"
|
|
131
|
+
puts "Init point: #{preference['init_point']}"
|
|
132
|
+
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
puts e.message
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
create_preference_with_industry_fields(sdk)
|
|
@@ -13,6 +13,9 @@ module Mercadopago
|
|
|
13
13
|
# opts = Mercadopago::RequestOptions.new(access_token: token, connection_timeout: 120.0)
|
|
14
14
|
# sdk.payment.get(123, request_options: opts)
|
|
15
15
|
class RequestOptions
|
|
16
|
+
DEFAULT_RETRY_ON = [429, 500, 502, 503, 504].freeze
|
|
17
|
+
DEFAULT_MAX_DELAY = 30_000
|
|
18
|
+
|
|
16
19
|
# @!attribute [r] access_token
|
|
17
20
|
# @return [String, nil] OAuth access token used for Bearer authentication
|
|
18
21
|
# @!attribute [r] connection_timeout
|
|
@@ -27,8 +30,18 @@ module Mercadopago
|
|
|
27
30
|
# @return [String, nil] MercadoPago platform identifier (x-platform-id header)
|
|
28
31
|
# @!attribute [r] max_retries
|
|
29
32
|
# @return [Integer] maximum automatic retries on transient HTTP errors (default: 3)
|
|
33
|
+
# @!attribute [r] initial_delay_ms
|
|
34
|
+
# @return [Integer, nil] initial backoff delay in ms (nil = SDK default)
|
|
35
|
+
# @!attribute [r] max_delay_ms
|
|
36
|
+
# @return [Integer] maximum backoff delay cap in ms (default: 30_000)
|
|
37
|
+
# @!attribute [r] jitter
|
|
38
|
+
# @return [Boolean] add random jitter to retry delay using SecureRandom
|
|
39
|
+
# @!attribute [r] retry_on
|
|
40
|
+
# @return [Array<Integer>, nil] HTTP status codes to retry (nil = DEFAULT_RETRY_ON)
|
|
41
|
+
# @!attribute [r] on_retry
|
|
42
|
+
# @return [Proc, nil] callback(attempt, error) invoked before each retry
|
|
30
43
|
attr_reader :access_token, :connection_timeout, :custom_headers, :corporation_id, :integrator_id,
|
|
31
|
-
:platform_id, :max_retries
|
|
44
|
+
:platform_id, :max_retries, :initial_delay_ms, :max_delay_ms, :jitter, :retry_on, :on_retry
|
|
32
45
|
|
|
33
46
|
# Builds a new request configuration.
|
|
34
47
|
#
|
|
@@ -39,6 +52,11 @@ module Mercadopago
|
|
|
39
52
|
# @param integrator_id [String, nil] integrator identifier for certified partners
|
|
40
53
|
# @param platform_id [String, nil] platform identifier for marketplace integrations
|
|
41
54
|
# @param max_retries [Integer] retry limit for transient failures (429, 5xx)
|
|
55
|
+
# @param initial_delay_ms [Integer, nil] initial backoff delay in ms
|
|
56
|
+
# @param max_delay_ms [Integer] maximum backoff delay cap in ms
|
|
57
|
+
# @param jitter [Boolean] add SecureRandom jitter to retry delay
|
|
58
|
+
# @param retry_on [Array<Integer>, nil] HTTP status codes to retry
|
|
59
|
+
# @param on_retry [Proc, nil] callback invoked before each retry
|
|
42
60
|
# @raise [TypeError] if any parameter is not the expected type
|
|
43
61
|
def initialize(access_token: nil,
|
|
44
62
|
connection_timeout: 60.0,
|
|
@@ -46,7 +64,12 @@ module Mercadopago
|
|
|
46
64
|
corporation_id: nil,
|
|
47
65
|
integrator_id: nil,
|
|
48
66
|
platform_id: nil,
|
|
49
|
-
max_retries: 3
|
|
67
|
+
max_retries: 3,
|
|
68
|
+
initial_delay_ms: nil,
|
|
69
|
+
max_delay_ms: DEFAULT_MAX_DELAY,
|
|
70
|
+
jitter: false,
|
|
71
|
+
retry_on: nil,
|
|
72
|
+
on_retry: nil)
|
|
50
73
|
self.access_token = access_token
|
|
51
74
|
self.connection_timeout = connection_timeout
|
|
52
75
|
self.custom_headers = custom_headers
|
|
@@ -54,6 +77,11 @@ module Mercadopago
|
|
|
54
77
|
self.integrator_id = integrator_id
|
|
55
78
|
self.platform_id = platform_id
|
|
56
79
|
self.max_retries = max_retries
|
|
80
|
+
@initial_delay_ms = initial_delay_ms
|
|
81
|
+
@max_delay_ms = max_delay_ms
|
|
82
|
+
@jitter = jitter
|
|
83
|
+
@retry_on = retry_on
|
|
84
|
+
@on_retry = on_retry
|
|
57
85
|
|
|
58
86
|
@config = Config.new
|
|
59
87
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: false
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'cgi'
|
|
5
|
+
|
|
4
6
|
module Mercadopago
|
|
5
7
|
# Abstract base class for every API resource (Payment, Customer, Order, etc.).
|
|
6
8
|
#
|
|
@@ -61,6 +63,11 @@ module Mercadopago
|
|
|
61
63
|
headers
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
# Encodes a dynamic URL path segment before interpolation.
|
|
67
|
+
def _path_param(value)
|
|
68
|
+
CGI.escape(value.to_s).gsub('+', '%20')
|
|
69
|
+
end
|
|
70
|
+
|
|
64
71
|
# Performs a GET request against the MercadoPago API.
|
|
65
72
|
#
|
|
66
73
|
# @param uri [String] API path (e.g. "/v1/payments/123")
|
|
@@ -74,8 +81,16 @@ module Mercadopago
|
|
|
74
81
|
request_options = _check_request_options(request_options)
|
|
75
82
|
headers = _check_headers(request_options)
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
MPResponse.new(
|
|
85
|
+
@http_client.get(url: @config.api_base_url + uri, headers: headers, params: filters,
|
|
86
|
+
timeout: request_options.connection_timeout,
|
|
87
|
+
maxretries: request_options.max_retries,
|
|
88
|
+
retry_on: request_options.retry_on,
|
|
89
|
+
initial_delay_ms: request_options.initial_delay_ms,
|
|
90
|
+
max_delay_ms: request_options.max_delay_ms,
|
|
91
|
+
jitter: request_options.jitter,
|
|
92
|
+
on_retry: request_options.on_retry)
|
|
93
|
+
)
|
|
79
94
|
end
|
|
80
95
|
|
|
81
96
|
# Performs a POST request against the MercadoPago API.
|
|
@@ -94,7 +109,10 @@ module Mercadopago
|
|
|
94
109
|
headers = _check_headers(request_options, { 'Content-Type': @config.mime_json })
|
|
95
110
|
payload = data&.to_json
|
|
96
111
|
|
|
97
|
-
|
|
112
|
+
MPResponse.new(
|
|
113
|
+
@http_client.post(url: @config.api_base_url + uri, data: payload, headers: headers,
|
|
114
|
+
timeout: request_options.connection_timeout)
|
|
115
|
+
)
|
|
98
116
|
end
|
|
99
117
|
|
|
100
118
|
# Performs a PUT request against the MercadoPago API.
|
|
@@ -112,8 +130,10 @@ module Mercadopago
|
|
|
112
130
|
request_options = _check_request_options(request_options)
|
|
113
131
|
headers = _check_headers(request_options, { 'Content-Type': @config.mime_json })
|
|
114
132
|
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
MPResponse.new(
|
|
134
|
+
@http_client.put(url: @config.api_base_url + uri, data: data.to_json, headers: headers,
|
|
135
|
+
timeout: request_options.connection_timeout)
|
|
136
|
+
)
|
|
117
137
|
end
|
|
118
138
|
|
|
119
139
|
# Performs a DELETE request against the MercadoPago API.
|
|
@@ -125,8 +145,10 @@ module Mercadopago
|
|
|
125
145
|
request_options = _check_request_options(request_options)
|
|
126
146
|
headers = _check_headers(request_options)
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
MPResponse.new(
|
|
149
|
+
@http_client.delete(url: @config.api_base_url + uri, headers: headers,
|
|
150
|
+
timeout: request_options.connection_timeout)
|
|
151
|
+
)
|
|
130
152
|
end
|
|
131
153
|
end
|
|
132
154
|
end
|