chub-dev 0.1.0 → 0.1.2-beta.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.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gateway
|
|
3
|
+
description: "Braintree Python SDK for payment gateway, PayPal, and subscriptions"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "python"
|
|
6
|
+
versions: "4.40.0"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "braintree,gateway,payments,paypal,subscriptions"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Braintree Python SDK Context
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**ALWAYS use the official Braintree Python SDK package: `braintree`**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install braintree
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**DO NOT use:**
|
|
23
|
+
- Deprecated or unofficial packages
|
|
24
|
+
- Direct API calls without the SDK
|
|
25
|
+
- Outdated versions (version 3.40.0+ required for SSL certificate support)
|
|
26
|
+
|
|
27
|
+
The official package is `braintree` and is maintained by Braintree (a PayPal service).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install braintree
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The SDK requires Python 3.8 or higher. The SDK also depends on the `requests` library.
|
|
36
|
+
|
|
37
|
+
## Environment Configuration
|
|
38
|
+
|
|
39
|
+
Create a `.env` file with your Braintree credentials:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
BRAINTREE_ENVIRONMENT=Sandbox
|
|
43
|
+
BRAINTREE_MERCHANT_ID=your_merchant_id
|
|
44
|
+
BRAINTREE_PUBLIC_KEY=your_public_key
|
|
45
|
+
BRAINTREE_PRIVATE_KEY=your_private_key
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Load environment variables using `python-dotenv`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install python-dotenv
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from dotenv import load_dotenv
|
|
56
|
+
import os
|
|
57
|
+
|
|
58
|
+
load_dotenv()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Initialization
|
|
62
|
+
|
|
63
|
+
### Basic Gateway Setup
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import braintree
|
|
67
|
+
|
|
68
|
+
gateway = braintree.BraintreeGateway(
|
|
69
|
+
braintree.Configuration(
|
|
70
|
+
braintree.Environment.Sandbox,
|
|
71
|
+
merchant_id="your_merchant_id",
|
|
72
|
+
public_key="your_public_key",
|
|
73
|
+
private_key="your_private_key"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Using Environment Variables
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import braintree
|
|
82
|
+
import os
|
|
83
|
+
|
|
84
|
+
gateway = braintree.BraintreeGateway(
|
|
85
|
+
braintree.Configuration(
|
|
86
|
+
braintree.Environment.Production if os.getenv('BRAINTREE_ENVIRONMENT') == 'Production'
|
|
87
|
+
else braintree.Environment.Sandbox,
|
|
88
|
+
merchant_id=os.getenv('BRAINTREE_MERCHANT_ID'),
|
|
89
|
+
public_key=os.getenv('BRAINTREE_PUBLIC_KEY'),
|
|
90
|
+
private_key=os.getenv('BRAINTREE_PRIVATE_KEY')
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Environment Options
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Sandbox environment (for testing)
|
|
99
|
+
braintree.Environment.Sandbox
|
|
100
|
+
|
|
101
|
+
# Production environment (for live transactions)
|
|
102
|
+
braintree.Environment.Production
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Client Token Generation
|
|
106
|
+
|
|
107
|
+
Client tokens contain authorization and configuration information for the client SDK.
|
|
108
|
+
|
|
109
|
+
### Basic Client Token
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
client_token = gateway.client_token.generate()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Client Token with Customer ID
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
client_token = gateway.client_token.generate({
|
|
119
|
+
"customer_id": "customer_123"
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Flask Endpoint for Client Token
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from flask import Flask, jsonify
|
|
127
|
+
|
|
128
|
+
app = Flask(__name__)
|
|
129
|
+
|
|
130
|
+
@app.route("/client_token", methods=["GET"])
|
|
131
|
+
def get_client_token():
|
|
132
|
+
try:
|
|
133
|
+
token = gateway.client_token.generate()
|
|
134
|
+
return jsonify({"client_token": token})
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return jsonify({"error": str(e)}), 500
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Django View for Client Token
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from django.http import JsonResponse
|
|
143
|
+
|
|
144
|
+
def client_token(request):
|
|
145
|
+
try:
|
|
146
|
+
token = gateway.client_token.generate()
|
|
147
|
+
return JsonResponse({"client_token": token})
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return JsonResponse({"error": str(e)}, status=500)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Transactions
|
|
153
|
+
|
|
154
|
+
### Creating a Transaction
|
|
155
|
+
|
|
156
|
+
**Basic Sale:**
|
|
157
|
+
```python
|
|
158
|
+
result = gateway.transaction.sale({
|
|
159
|
+
"amount": "10.00",
|
|
160
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
161
|
+
"options": {
|
|
162
|
+
"submit_for_settlement": True
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if result.is_success:
|
|
167
|
+
print("Transaction ID: " + result.transaction.id)
|
|
168
|
+
else:
|
|
169
|
+
print("Error: " + result.message)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Transaction with Device Data
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
result = gateway.transaction.sale({
|
|
176
|
+
"amount": "10.00",
|
|
177
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
178
|
+
"device_data": device_data_from_the_client,
|
|
179
|
+
"options": {
|
|
180
|
+
"submit_for_settlement": True
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Transaction with Customer Information
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
result = gateway.transaction.sale({
|
|
189
|
+
"amount": "100.00",
|
|
190
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
191
|
+
"customer": {
|
|
192
|
+
"first_name": "John",
|
|
193
|
+
"last_name": "Doe",
|
|
194
|
+
"email": "john.doe@example.com",
|
|
195
|
+
"phone": "312-555-1234"
|
|
196
|
+
},
|
|
197
|
+
"billing": {
|
|
198
|
+
"first_name": "John",
|
|
199
|
+
"last_name": "Doe",
|
|
200
|
+
"street_address": "123 Main St",
|
|
201
|
+
"locality": "Chicago",
|
|
202
|
+
"region": "IL",
|
|
203
|
+
"postal_code": "60614",
|
|
204
|
+
"country_code_alpha2": "US"
|
|
205
|
+
},
|
|
206
|
+
"shipping": {
|
|
207
|
+
"first_name": "John",
|
|
208
|
+
"last_name": "Doe",
|
|
209
|
+
"street_address": "123 Main St",
|
|
210
|
+
"locality": "Chicago",
|
|
211
|
+
"region": "IL",
|
|
212
|
+
"postal_code": "60614",
|
|
213
|
+
"country_code_alpha2": "US"
|
|
214
|
+
},
|
|
215
|
+
"options": {
|
|
216
|
+
"submit_for_settlement": True
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Transaction with Stored Payment Method
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
result = gateway.transaction.sale({
|
|
225
|
+
"amount": "10.00",
|
|
226
|
+
"payment_method_token": "the_token",
|
|
227
|
+
"options": {
|
|
228
|
+
"submit_for_settlement": True
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Transaction with Customer ID
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
result = gateway.transaction.sale({
|
|
237
|
+
"amount": "10.00",
|
|
238
|
+
"customer_id": "customer_123",
|
|
239
|
+
"options": {
|
|
240
|
+
"submit_for_settlement": True
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Flask Checkout Endpoint
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from flask import Flask, request, jsonify
|
|
249
|
+
|
|
250
|
+
app = Flask(__name__)
|
|
251
|
+
|
|
252
|
+
@app.route("/checkout", methods=["POST"])
|
|
253
|
+
def checkout():
|
|
254
|
+
nonce_from_the_client = request.form.get("payment_method_nonce")
|
|
255
|
+
amount = request.form.get("amount")
|
|
256
|
+
|
|
257
|
+
result = gateway.transaction.sale({
|
|
258
|
+
"amount": amount,
|
|
259
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
260
|
+
"options": {
|
|
261
|
+
"submit_for_settlement": True
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
if result.is_success:
|
|
266
|
+
return jsonify({
|
|
267
|
+
"success": True,
|
|
268
|
+
"transaction_id": result.transaction.id
|
|
269
|
+
})
|
|
270
|
+
else:
|
|
271
|
+
return jsonify({
|
|
272
|
+
"success": False,
|
|
273
|
+
"message": result.message
|
|
274
|
+
}), 500
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Finding a Transaction
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
transaction = gateway.transaction.find("the_transaction_id")
|
|
281
|
+
print(transaction.amount)
|
|
282
|
+
print(transaction.status)
|
|
283
|
+
print(transaction.credit_card_details.last_4)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Refunding a Transaction
|
|
287
|
+
|
|
288
|
+
**Full Refund:**
|
|
289
|
+
```python
|
|
290
|
+
result = gateway.transaction.refund("the_transaction_id")
|
|
291
|
+
|
|
292
|
+
if result.is_success:
|
|
293
|
+
print("Refund successful")
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Partial Refund:**
|
|
297
|
+
```python
|
|
298
|
+
result = gateway.transaction.refund("the_transaction_id", "10.00")
|
|
299
|
+
|
|
300
|
+
if result.is_success:
|
|
301
|
+
print("Partial refund successful")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Voiding a Transaction
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
result = gateway.transaction.void("the_transaction_id")
|
|
308
|
+
|
|
309
|
+
if result.is_success:
|
|
310
|
+
print("Transaction voided")
|
|
311
|
+
else:
|
|
312
|
+
print("Error: " + result.message)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Submitting for Settlement
|
|
316
|
+
|
|
317
|
+
**Full Amount:**
|
|
318
|
+
```python
|
|
319
|
+
result = gateway.transaction.submit_for_settlement("transaction_id")
|
|
320
|
+
|
|
321
|
+
if result.is_success:
|
|
322
|
+
print("Submitted for settlement")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Partial Amount:**
|
|
326
|
+
```python
|
|
327
|
+
result = gateway.transaction.submit_for_settlement("transaction_id", "50.00")
|
|
328
|
+
|
|
329
|
+
if result.is_success:
|
|
330
|
+
print("Submitted for partial settlement")
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Customer Management
|
|
334
|
+
|
|
335
|
+
### Creating a Customer
|
|
336
|
+
|
|
337
|
+
**Basic Customer:**
|
|
338
|
+
```python
|
|
339
|
+
result = gateway.customer.create({
|
|
340
|
+
"first_name": "John",
|
|
341
|
+
"last_name": "Doe",
|
|
342
|
+
"email": "john.doe@example.com",
|
|
343
|
+
"phone": "312-555-1234"
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
if result.is_success:
|
|
347
|
+
print("Customer ID: " + result.customer.id)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Creating Customer with Company Information
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
result = gateway.customer.create({
|
|
354
|
+
"first_name": "John",
|
|
355
|
+
"last_name": "Doe",
|
|
356
|
+
"company": "Acme Corp",
|
|
357
|
+
"email": "john.doe@example.com",
|
|
358
|
+
"phone": "312-555-1234",
|
|
359
|
+
"fax": "614-555-5678",
|
|
360
|
+
"website": "www.example.com"
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Creating Customer with Payment Method
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
result = gateway.customer.create({
|
|
368
|
+
"first_name": "John",
|
|
369
|
+
"last_name": "Doe",
|
|
370
|
+
"email": "john.doe@example.com",
|
|
371
|
+
"payment_method_nonce": nonce_from_the_client
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
if result.is_success:
|
|
375
|
+
customer_id = result.customer.id
|
|
376
|
+
payment_token = result.customer.payment_methods[0].token
|
|
377
|
+
print(f"Customer ID: {customer_id}")
|
|
378
|
+
print(f"Payment Method Token: {payment_token}")
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Creating Customer with Credit Card
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
result = gateway.customer.create({
|
|
385
|
+
"first_name": "John",
|
|
386
|
+
"last_name": "Doe",
|
|
387
|
+
"email": "john.doe@example.com",
|
|
388
|
+
"credit_card": {
|
|
389
|
+
"number": "4111111111111111",
|
|
390
|
+
"expiration_date": "05/2025",
|
|
391
|
+
"cvv": "123",
|
|
392
|
+
"billing_address": {
|
|
393
|
+
"postal_code": "60614"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
if result.is_success:
|
|
399
|
+
token = result.customer.payment_methods[0].token
|
|
400
|
+
print("Payment method token: " + token)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Finding a Customer
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
customer = gateway.customer.find("the_customer_id")
|
|
407
|
+
print(customer.first_name)
|
|
408
|
+
print(customer.last_name)
|
|
409
|
+
print(customer.email)
|
|
410
|
+
print(customer.payment_methods)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Updating a Customer
|
|
414
|
+
|
|
415
|
+
```python
|
|
416
|
+
result = gateway.customer.update("the_customer_id", {
|
|
417
|
+
"first_name": "Jane",
|
|
418
|
+
"last_name": "Smith",
|
|
419
|
+
"email": "jane.smith@example.com"
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
if result.is_success:
|
|
423
|
+
print("Customer updated")
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Deleting a Customer
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
result = gateway.customer.delete("the_customer_id")
|
|
430
|
+
|
|
431
|
+
if result.is_success:
|
|
432
|
+
print("Customer deleted")
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Searching for Customers
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
collection = gateway.customer.search(
|
|
439
|
+
braintree.CustomerSearch.first_name == "John",
|
|
440
|
+
braintree.CustomerSearch.last_name == "Doe"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
for customer in collection.items:
|
|
444
|
+
print(customer.id)
|
|
445
|
+
print(customer.email)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Payment Method Management
|
|
449
|
+
|
|
450
|
+
### Creating a Payment Method
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
result = gateway.payment_method.create({
|
|
454
|
+
"customer_id": "customer_123",
|
|
455
|
+
"payment_method_nonce": nonce_from_the_client
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
if result.is_success:
|
|
459
|
+
print("Payment Method Token: " + result.payment_method.token)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Creating Payment Method with Billing Address
|
|
463
|
+
|
|
464
|
+
```python
|
|
465
|
+
result = gateway.payment_method.create({
|
|
466
|
+
"customer_id": "customer_123",
|
|
467
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
468
|
+
"billing_address": {
|
|
469
|
+
"street_address": "123 Main St",
|
|
470
|
+
"locality": "Chicago",
|
|
471
|
+
"region": "IL",
|
|
472
|
+
"postal_code": "60614"
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Creating Payment Method with Verification
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
result = gateway.payment_method.create({
|
|
481
|
+
"customer_id": "customer_123",
|
|
482
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
483
|
+
"options": {
|
|
484
|
+
"verify_card": True
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
if result.is_success:
|
|
489
|
+
print("Payment method verified and created")
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Finding a Payment Method
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
payment_method = gateway.payment_method.find("the_token")
|
|
496
|
+
print(payment_method.token)
|
|
497
|
+
print(payment_method.card_type)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Updating a Payment Method
|
|
501
|
+
|
|
502
|
+
**Set as Default:**
|
|
503
|
+
```python
|
|
504
|
+
result = gateway.payment_method.update("the_token", {
|
|
505
|
+
"options": {
|
|
506
|
+
"make_default": True
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
if result.is_success:
|
|
511
|
+
print("Payment method is now default")
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Update Billing Address:**
|
|
515
|
+
```python
|
|
516
|
+
result = gateway.payment_method.update("the_token", {
|
|
517
|
+
"billing_address": {
|
|
518
|
+
"street_address": "456 Oak Ave",
|
|
519
|
+
"locality": "Chicago",
|
|
520
|
+
"region": "IL",
|
|
521
|
+
"postal_code": "60614",
|
|
522
|
+
"options": {
|
|
523
|
+
"update_existing": True
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Deleting a Payment Method
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
result = gateway.payment_method.delete("the_token")
|
|
533
|
+
|
|
534
|
+
if result.is_success:
|
|
535
|
+
print("Payment method deleted")
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Subscription Billing
|
|
539
|
+
|
|
540
|
+
### Creating a Subscription
|
|
541
|
+
|
|
542
|
+
**Basic Subscription:**
|
|
543
|
+
```python
|
|
544
|
+
result = gateway.subscription.create({
|
|
545
|
+
"payment_method_token": "the_token",
|
|
546
|
+
"plan_id": "plan_id"
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
if result.is_success:
|
|
550
|
+
print("Subscription ID: " + result.subscription.id)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Creating Subscription with Price Override
|
|
554
|
+
|
|
555
|
+
```python
|
|
556
|
+
result = gateway.subscription.create({
|
|
557
|
+
"payment_method_token": "the_token",
|
|
558
|
+
"plan_id": "plan_id",
|
|
559
|
+
"price": "15.00"
|
|
560
|
+
})
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Creating Subscription with Add-ons and Discounts
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
result = gateway.subscription.create({
|
|
567
|
+
"payment_method_token": "the_token",
|
|
568
|
+
"plan_id": "plan_id",
|
|
569
|
+
"add_ons": {
|
|
570
|
+
"add": [
|
|
571
|
+
{
|
|
572
|
+
"inherited_from_id": "add_on_id_1",
|
|
573
|
+
"amount": "10.00"
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
},
|
|
577
|
+
"discounts": {
|
|
578
|
+
"add": [
|
|
579
|
+
{
|
|
580
|
+
"inherited_from_id": "discount_id_1",
|
|
581
|
+
"amount": "5.00"
|
|
582
|
+
}
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Creating Subscription with Trial Period
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
result = gateway.subscription.create({
|
|
592
|
+
"payment_method_token": "the_token",
|
|
593
|
+
"plan_id": "plan_id",
|
|
594
|
+
"trial_duration": 14,
|
|
595
|
+
"trial_duration_unit": braintree.Subscription.TrialDurationUnit.Day,
|
|
596
|
+
"trial_period": True
|
|
597
|
+
})
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Finding a Subscription
|
|
601
|
+
|
|
602
|
+
```python
|
|
603
|
+
subscription = gateway.subscription.find("subscription_id")
|
|
604
|
+
print(subscription.status)
|
|
605
|
+
print(subscription.price)
|
|
606
|
+
print(subscription.plan_id)
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Updating a Subscription
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
result = gateway.subscription.update("subscription_id", {
|
|
613
|
+
"price": "15.00"
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
if result.is_success:
|
|
617
|
+
print("Subscription updated")
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Updating Subscription Plan
|
|
621
|
+
|
|
622
|
+
```python
|
|
623
|
+
result = gateway.subscription.update("subscription_id", {
|
|
624
|
+
"plan_id": "new_plan_id",
|
|
625
|
+
"price": "20.00"
|
|
626
|
+
})
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Canceling a Subscription
|
|
630
|
+
|
|
631
|
+
```python
|
|
632
|
+
result = gateway.subscription.cancel("subscription_id")
|
|
633
|
+
|
|
634
|
+
if result.is_success:
|
|
635
|
+
print("Subscription canceled")
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Retrieving All Plans
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
plans = gateway.plan.all()
|
|
642
|
+
|
|
643
|
+
for plan in plans:
|
|
644
|
+
print(plan.id)
|
|
645
|
+
print(plan.name)
|
|
646
|
+
print(plan.price)
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
## Webhooks
|
|
650
|
+
|
|
651
|
+
### Parsing Webhook Notifications
|
|
652
|
+
|
|
653
|
+
```python
|
|
654
|
+
webhook_notification = gateway.webhook_notification.parse(
|
|
655
|
+
request.form["bt_signature"],
|
|
656
|
+
request.form["bt_payload"]
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
print(webhook_notification.kind)
|
|
660
|
+
print(webhook_notification.timestamp)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Flask Webhook Endpoint
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
from flask import Flask, request
|
|
667
|
+
|
|
668
|
+
app = Flask(__name__)
|
|
669
|
+
|
|
670
|
+
@app.route('/webhooks', methods=['POST'])
|
|
671
|
+
def webhooks():
|
|
672
|
+
try:
|
|
673
|
+
webhook_notification = gateway.webhook_notification.parse(
|
|
674
|
+
request.form["bt_signature"],
|
|
675
|
+
request.form["bt_payload"]
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
# Handle different webhook types
|
|
679
|
+
if webhook_notification.kind == braintree.WebhookNotification.Kind.SubscriptionCanceled:
|
|
680
|
+
print(f"Subscription {webhook_notification.subscription.id} canceled")
|
|
681
|
+
elif webhook_notification.kind == braintree.WebhookNotification.Kind.SubscriptionChargedSuccessfully:
|
|
682
|
+
print(f"Subscription {webhook_notification.subscription.id} charged")
|
|
683
|
+
elif webhook_notification.kind == braintree.WebhookNotification.Kind.DisputeOpened:
|
|
684
|
+
print(f"Dispute opened for transaction {webhook_notification.dispute.transaction.id}")
|
|
685
|
+
|
|
686
|
+
return "", 200
|
|
687
|
+
except Exception as e:
|
|
688
|
+
print(f"Error: {e}")
|
|
689
|
+
return "", 400
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Django Webhook View
|
|
693
|
+
|
|
694
|
+
```python
|
|
695
|
+
from django.http import HttpResponse
|
|
696
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
697
|
+
|
|
698
|
+
@csrf_exempt
|
|
699
|
+
def webhook(request):
|
|
700
|
+
try:
|
|
701
|
+
webhook_notification = gateway.webhook_notification.parse(
|
|
702
|
+
request.POST["bt_signature"],
|
|
703
|
+
request.POST["bt_payload"]
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# Handle webhook
|
|
707
|
+
if webhook_notification.kind == braintree.WebhookNotification.Kind.SubscriptionWentPastDue:
|
|
708
|
+
subscription_id = webhook_notification.subscription.id
|
|
709
|
+
# Handle past due subscription
|
|
710
|
+
|
|
711
|
+
return HttpResponse(status=200)
|
|
712
|
+
except Exception as e:
|
|
713
|
+
return HttpResponse(status=400)
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Handling Subscription Webhooks
|
|
717
|
+
|
|
718
|
+
```python
|
|
719
|
+
webhook_notification = gateway.webhook_notification.parse(
|
|
720
|
+
signature,
|
|
721
|
+
payload
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
if webhook_notification.kind == braintree.WebhookNotification.Kind.SubscriptionWentPastDue:
|
|
725
|
+
subscription_id = webhook_notification.subscription.id
|
|
726
|
+
# Handle past due subscription
|
|
727
|
+
elif webhook_notification.kind == braintree.WebhookNotification.Kind.SubscriptionChargedSuccessfully:
|
|
728
|
+
subscription_id = webhook_notification.subscription.id
|
|
729
|
+
transaction_id = webhook_notification.subscription.transactions[0].id
|
|
730
|
+
# Handle successful charge
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Verifying Webhook Signatures
|
|
734
|
+
|
|
735
|
+
```python
|
|
736
|
+
verification_string = gateway.webhook_notification.verify("challenge_string")
|
|
737
|
+
print(verification_string)
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Testing Webhooks
|
|
741
|
+
|
|
742
|
+
```python
|
|
743
|
+
sample_notification = gateway.webhook_testing.sample_notification(
|
|
744
|
+
braintree.WebhookNotification.Kind.SubscriptionWentPastDue,
|
|
745
|
+
"subscription_id"
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
webhook_notification = gateway.webhook_notification.parse(
|
|
749
|
+
sample_notification['bt_signature'],
|
|
750
|
+
sample_notification['bt_payload']
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
print(webhook_notification.kind)
|
|
754
|
+
print(webhook_notification.subscription.id)
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
## Advanced Features
|
|
758
|
+
|
|
759
|
+
### Searching Transactions
|
|
760
|
+
|
|
761
|
+
```python
|
|
762
|
+
collection = gateway.transaction.search(
|
|
763
|
+
braintree.TransactionSearch.amount >= "10.00",
|
|
764
|
+
braintree.TransactionSearch.amount <= "100.00",
|
|
765
|
+
braintree.TransactionSearch.status == braintree.Transaction.Status.Settled,
|
|
766
|
+
braintree.TransactionSearch.created_at >= "2024-01-01"
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
for transaction in collection.items:
|
|
770
|
+
print(transaction.id)
|
|
771
|
+
print(transaction.amount)
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Transaction with Custom Fields
|
|
775
|
+
|
|
776
|
+
```python
|
|
777
|
+
result = gateway.transaction.sale({
|
|
778
|
+
"amount": "10.00",
|
|
779
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
780
|
+
"custom_fields": {
|
|
781
|
+
"order_id": "12345",
|
|
782
|
+
"user_type": "premium"
|
|
783
|
+
},
|
|
784
|
+
"options": {
|
|
785
|
+
"submit_for_settlement": True
|
|
786
|
+
}
|
|
787
|
+
})
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Transaction with Order ID
|
|
791
|
+
|
|
792
|
+
```python
|
|
793
|
+
result = gateway.transaction.sale({
|
|
794
|
+
"amount": "10.00",
|
|
795
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
796
|
+
"order_id": "order_123",
|
|
797
|
+
"options": {
|
|
798
|
+
"submit_for_settlement": True
|
|
799
|
+
}
|
|
800
|
+
})
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Holding in Escrow
|
|
804
|
+
|
|
805
|
+
```python
|
|
806
|
+
result = gateway.transaction.sale({
|
|
807
|
+
"amount": "100.00",
|
|
808
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
809
|
+
"service_fee_amount": "10.00",
|
|
810
|
+
"merchant_account_id": "submerchant_account_id",
|
|
811
|
+
"options": {
|
|
812
|
+
"submit_for_settlement": True,
|
|
813
|
+
"hold_in_escrow": True
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### Transaction with Descriptor
|
|
819
|
+
|
|
820
|
+
```python
|
|
821
|
+
result = gateway.transaction.sale({
|
|
822
|
+
"amount": "10.00",
|
|
823
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
824
|
+
"descriptor": {
|
|
825
|
+
"name": "Company*Product",
|
|
826
|
+
"phone": "8004567890",
|
|
827
|
+
"url": "example.com"
|
|
828
|
+
},
|
|
829
|
+
"options": {
|
|
830
|
+
"submit_for_settlement": True
|
|
831
|
+
}
|
|
832
|
+
})
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Storing Payment Method on Success
|
|
836
|
+
|
|
837
|
+
```python
|
|
838
|
+
result = gateway.transaction.sale({
|
|
839
|
+
"amount": "10.00",
|
|
840
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
841
|
+
"customer_id": "customer_123",
|
|
842
|
+
"options": {
|
|
843
|
+
"submit_for_settlement": True,
|
|
844
|
+
"store_in_vault_on_success": True
|
|
845
|
+
}
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
if result.is_success:
|
|
849
|
+
payment_token = result.transaction.credit_card_details.token
|
|
850
|
+
print(f"Payment method stored with token: {payment_token}")
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
### Cloning a Transaction
|
|
854
|
+
|
|
855
|
+
```python
|
|
856
|
+
result = gateway.transaction.clone_transaction(
|
|
857
|
+
"original_transaction_id",
|
|
858
|
+
{
|
|
859
|
+
"amount": "20.00",
|
|
860
|
+
"options": {
|
|
861
|
+
"submit_for_settlement": True
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
if result.is_success:
|
|
867
|
+
print("Cloned transaction ID: " + result.transaction.id)
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
### Dispute Management
|
|
871
|
+
|
|
872
|
+
**Finding a Dispute:**
|
|
873
|
+
```python
|
|
874
|
+
dispute = gateway.dispute.find("dispute_id")
|
|
875
|
+
print(dispute.status)
|
|
876
|
+
print(dispute.amount)
|
|
877
|
+
print(dispute.reason)
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
**Accept Dispute:**
|
|
881
|
+
```python
|
|
882
|
+
result = gateway.dispute.accept("dispute_id")
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
**Add Text Evidence:**
|
|
886
|
+
```python
|
|
887
|
+
result = gateway.dispute.add_text_evidence("dispute_id", "evidence_text")
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
**Finalize Dispute:**
|
|
891
|
+
```python
|
|
892
|
+
result = gateway.dispute.finalize("dispute_id")
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Merchant Account Creation
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
result = gateway.merchant_account.create({
|
|
899
|
+
"individual": {
|
|
900
|
+
"first_name": "John",
|
|
901
|
+
"last_name": "Doe",
|
|
902
|
+
"email": "john.doe@example.com",
|
|
903
|
+
"date_of_birth": "1980-01-01",
|
|
904
|
+
"ssn": "123-45-6789",
|
|
905
|
+
"address": {
|
|
906
|
+
"street_address": "123 Main St",
|
|
907
|
+
"locality": "Chicago",
|
|
908
|
+
"region": "IL",
|
|
909
|
+
"postal_code": "60614"
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
"business": {
|
|
913
|
+
"legal_name": "John Doe Inc",
|
|
914
|
+
"dba_name": "JD Services",
|
|
915
|
+
"tax_id": "12-3456789",
|
|
916
|
+
"address": {
|
|
917
|
+
"street_address": "123 Main St",
|
|
918
|
+
"locality": "Chicago",
|
|
919
|
+
"region": "IL",
|
|
920
|
+
"postal_code": "60614"
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
"funding": {
|
|
924
|
+
"destination": braintree.MerchantAccount.FundingDestination.Bank,
|
|
925
|
+
"routing_number": "021000021",
|
|
926
|
+
"account_number": "1234567890"
|
|
927
|
+
},
|
|
928
|
+
"tos_accepted": True,
|
|
929
|
+
"master_merchant_account_id": "master_merchant_account_id"
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
if result.is_success:
|
|
933
|
+
print("Merchant Account ID: " + result.merchant_account.id)
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
## Error Handling
|
|
937
|
+
|
|
938
|
+
### Checking Result Success
|
|
939
|
+
|
|
940
|
+
```python
|
|
941
|
+
result = gateway.transaction.sale({
|
|
942
|
+
"amount": "10.00",
|
|
943
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
944
|
+
"options": {
|
|
945
|
+
"submit_for_settlement": True
|
|
946
|
+
}
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
if result.is_success:
|
|
950
|
+
print("Transaction ID:", result.transaction.id)
|
|
951
|
+
else:
|
|
952
|
+
print("Error:", result.message)
|
|
953
|
+
|
|
954
|
+
# Check validation errors
|
|
955
|
+
for error in result.errors.deep_errors:
|
|
956
|
+
print("Error:", error.code)
|
|
957
|
+
print("Message:", error.message)
|
|
958
|
+
print("Attribute:", error.attribute)
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### Handling Validation Errors
|
|
962
|
+
|
|
963
|
+
```python
|
|
964
|
+
result = gateway.customer.create({
|
|
965
|
+
"email": "invalid-email"
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
if not result.is_success:
|
|
969
|
+
for error in result.errors.deep_errors:
|
|
970
|
+
print(f"Error Code: {error.code}")
|
|
971
|
+
print(f"Message: {error.message}")
|
|
972
|
+
print(f"Attribute: {error.attribute}")
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
### Handling Not Found Errors
|
|
976
|
+
|
|
977
|
+
```python
|
|
978
|
+
try:
|
|
979
|
+
customer = gateway.customer.find("nonexistent_id")
|
|
980
|
+
except braintree.exceptions.NotFoundError:
|
|
981
|
+
print("Customer not found")
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### Handling Authentication Errors
|
|
985
|
+
|
|
986
|
+
```python
|
|
987
|
+
try:
|
|
988
|
+
result = gateway.transaction.sale({
|
|
989
|
+
"amount": "10.00",
|
|
990
|
+
"payment_method_nonce": nonce_from_the_client
|
|
991
|
+
})
|
|
992
|
+
except braintree.exceptions.AuthenticationError:
|
|
993
|
+
print("Invalid API credentials")
|
|
994
|
+
except braintree.exceptions.AuthorizationError:
|
|
995
|
+
print("Not authorized to perform this action")
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
## Common Patterns
|
|
999
|
+
|
|
1000
|
+
### Complete Flask Application Example
|
|
1001
|
+
|
|
1002
|
+
```python
|
|
1003
|
+
from flask import Flask, request, jsonify, render_template
|
|
1004
|
+
import braintree
|
|
1005
|
+
import os
|
|
1006
|
+
|
|
1007
|
+
app = Flask(__name__)
|
|
1008
|
+
|
|
1009
|
+
gateway = braintree.BraintreeGateway(
|
|
1010
|
+
braintree.Configuration(
|
|
1011
|
+
braintree.Environment.Sandbox,
|
|
1012
|
+
merchant_id=os.getenv('BRAINTREE_MERCHANT_ID'),
|
|
1013
|
+
public_key=os.getenv('BRAINTREE_PUBLIC_KEY'),
|
|
1014
|
+
private_key=os.getenv('BRAINTREE_PRIVATE_KEY')
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
@app.route('/')
|
|
1019
|
+
def index():
|
|
1020
|
+
return render_template('index.html')
|
|
1021
|
+
|
|
1022
|
+
@app.route('/client_token', methods=['GET'])
|
|
1023
|
+
def get_client_token():
|
|
1024
|
+
try:
|
|
1025
|
+
token = gateway.client_token.generate()
|
|
1026
|
+
return jsonify({"client_token": token})
|
|
1027
|
+
except Exception as e:
|
|
1028
|
+
return jsonify({"error": str(e)}), 500
|
|
1029
|
+
|
|
1030
|
+
@app.route('/checkout', methods=['POST'])
|
|
1031
|
+
def checkout():
|
|
1032
|
+
nonce_from_the_client = request.form.get("payment_method_nonce")
|
|
1033
|
+
amount = request.form.get("amount")
|
|
1034
|
+
|
|
1035
|
+
result = gateway.transaction.sale({
|
|
1036
|
+
"amount": amount,
|
|
1037
|
+
"payment_method_nonce": nonce_from_the_client,
|
|
1038
|
+
"options": {
|
|
1039
|
+
"submit_for_settlement": True
|
|
1040
|
+
}
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
if result.is_success:
|
|
1044
|
+
return jsonify({
|
|
1045
|
+
"success": True,
|
|
1046
|
+
"transaction_id": result.transaction.id
|
|
1047
|
+
})
|
|
1048
|
+
else:
|
|
1049
|
+
return jsonify({
|
|
1050
|
+
"success": False,
|
|
1051
|
+
"message": result.message
|
|
1052
|
+
}), 500
|
|
1053
|
+
|
|
1054
|
+
if __name__ == '__main__':
|
|
1055
|
+
app.run(debug=True, port=3000)
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### Customer Creation with Payment Method Workflow
|
|
1059
|
+
|
|
1060
|
+
```python
|
|
1061
|
+
# Step 1: Create customer
|
|
1062
|
+
result = gateway.customer.create({
|
|
1063
|
+
"first_name": "John",
|
|
1064
|
+
"last_name": "Doe",
|
|
1065
|
+
"email": "john.doe@example.com",
|
|
1066
|
+
"payment_method_nonce": nonce_from_the_client
|
|
1067
|
+
})
|
|
1068
|
+
|
|
1069
|
+
if result.is_success:
|
|
1070
|
+
customer_id = result.customer.id
|
|
1071
|
+
payment_token = result.customer.payment_methods[0].token
|
|
1072
|
+
|
|
1073
|
+
# Step 2: Create subscription
|
|
1074
|
+
sub_result = gateway.subscription.create({
|
|
1075
|
+
"payment_method_token": payment_token,
|
|
1076
|
+
"plan_id": "monthly_plan"
|
|
1077
|
+
})
|
|
1078
|
+
|
|
1079
|
+
if sub_result.is_success:
|
|
1080
|
+
print("Subscription ID:", sub_result.subscription.id)
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
### Stored Payment Method Workflow
|
|
1084
|
+
|
|
1085
|
+
```python
|
|
1086
|
+
# Step 1: Get customer
|
|
1087
|
+
customer = gateway.customer.find("customer_id")
|
|
1088
|
+
|
|
1089
|
+
# Step 2: Find default payment method
|
|
1090
|
+
default_payment_method = None
|
|
1091
|
+
for pm in customer.payment_methods:
|
|
1092
|
+
if pm.default:
|
|
1093
|
+
default_payment_method = pm
|
|
1094
|
+
break
|
|
1095
|
+
|
|
1096
|
+
# Step 3: Use default payment method for transaction
|
|
1097
|
+
if default_payment_method:
|
|
1098
|
+
result = gateway.transaction.sale({
|
|
1099
|
+
"amount": "25.00",
|
|
1100
|
+
"payment_method_token": default_payment_method.token,
|
|
1101
|
+
"options": {
|
|
1102
|
+
"submit_for_settlement": True
|
|
1103
|
+
}
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
if result.is_success:
|
|
1107
|
+
print("Transaction ID:", result.transaction.id)
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
### Django Integration Example
|
|
1111
|
+
|
|
1112
|
+
```python
|
|
1113
|
+
# views.py
|
|
1114
|
+
from django.http import JsonResponse
|
|
1115
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
1116
|
+
import braintree
|
|
1117
|
+
import os
|
|
1118
|
+
|
|
1119
|
+
gateway = braintree.BraintreeGateway(
|
|
1120
|
+
braintree.Configuration(
|
|
1121
|
+
braintree.Environment.Sandbox,
|
|
1122
|
+
merchant_id=os.getenv('BRAINTREE_MERCHANT_ID'),
|
|
1123
|
+
public_key=os.getenv('BRAINTREE_PUBLIC_KEY'),
|
|
1124
|
+
private_key=os.getenv('BRAINTREE_PRIVATE_KEY')
|
|
1125
|
+
)
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
def client_token(request):
|
|
1129
|
+
try:
|
|
1130
|
+
token = gateway.client_token.generate()
|
|
1131
|
+
return JsonResponse({"client_token": token})
|
|
1132
|
+
except Exception as e:
|
|
1133
|
+
return JsonResponse({"error": str(e)}, status=500)
|
|
1134
|
+
|
|
1135
|
+
@csrf_exempt
|
|
1136
|
+
def checkout(request):
|
|
1137
|
+
if request.method == 'POST':
|
|
1138
|
+
nonce = request.POST.get('payment_method_nonce')
|
|
1139
|
+
amount = request.POST.get('amount')
|
|
1140
|
+
|
|
1141
|
+
result = gateway.transaction.sale({
|
|
1142
|
+
"amount": amount,
|
|
1143
|
+
"payment_method_nonce": nonce,
|
|
1144
|
+
"options": {
|
|
1145
|
+
"submit_for_settlement": True
|
|
1146
|
+
}
|
|
1147
|
+
})
|
|
1148
|
+
|
|
1149
|
+
if result.is_success:
|
|
1150
|
+
return JsonResponse({
|
|
1151
|
+
"success": True,
|
|
1152
|
+
"transaction_id": result.transaction.id
|
|
1153
|
+
})
|
|
1154
|
+
else:
|
|
1155
|
+
return JsonResponse({
|
|
1156
|
+
"success": False,
|
|
1157
|
+
"message": result.message
|
|
1158
|
+
}, status=500)
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
### Subscription Renewal Handler
|
|
1162
|
+
|
|
1163
|
+
```python
|
|
1164
|
+
def handle_subscription_renewal(subscription_id):
|
|
1165
|
+
try:
|
|
1166
|
+
subscription = gateway.subscription.find(subscription_id)
|
|
1167
|
+
|
|
1168
|
+
if subscription.status == braintree.Subscription.Status.PastDue:
|
|
1169
|
+
# Attempt to retry billing
|
|
1170
|
+
result = gateway.subscription.retry_charge(subscription_id)
|
|
1171
|
+
|
|
1172
|
+
if result.is_success:
|
|
1173
|
+
print(f"Successfully charged subscription {subscription_id}")
|
|
1174
|
+
else:
|
|
1175
|
+
print(f"Failed to charge subscription: {result.message}")
|
|
1176
|
+
|
|
1177
|
+
except braintree.exceptions.NotFoundError:
|
|
1178
|
+
print(f"Subscription {subscription_id} not found")
|
|
1179
|
+
```
|