-
Notifications
You must be signed in to change notification settings - Fork 233
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(integration): add payment solution integration through stripe ec…
…osystem (#511)
- Loading branch information
1 parent
cc9c768
commit 5a89350
Showing
9 changed files
with
698 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Stripe Payment Solution | ||
|
||
Ever had an idea to use Stripe as a payment partner for your business, That's why I've harnessed the power of Fetch.AI | ||
Agent and Stripe payment ecosystem to develop a **Stripe Payment Solution** integration, designed to transform all your | ||
payment requirements, into a simple and convenient manner. | ||
|
||
## Table of Contents | ||
|
||
- [Introduction](#introduction) | ||
- [Features](#features) | ||
- [How It Works](#how-it-works) | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Contributing](#contributing) | ||
- [License](#license) | ||
|
||
## Introduction | ||
|
||
**Stripe Payment Solution** is an innovative tool that leverages Fetch.AI's Agent technology to help anyone with a requirement to accept payment over the Internet. This System integrates Stripe as the Payment regulator for all the payment related services. | ||
|
||
## Features | ||
|
||
- Real Time Payment Link Generator. | ||
- Real Time Payment Confirmation mechanism. | ||
- Custom Multi Cart Item Payments. | ||
- Simple UI. | ||
- Automated Invoicing & Receipts | ||
- One-Click Payments for Customers | ||
|
||
## Working Diagram | ||
|
||
![Working Diagram](./images/payment_system_overview.png "Working Diagram") | ||
|
||
This Agent can work like a template and can be used in different organizations with their API keys from stripe A/c. | ||
|
||
|
||
## Demo Video | ||
|
||
https://github.com/user-attachments/assets/3de86092-e2aa-43f1-b59d-73b725698600 | ||
|
||
|
||
## Detail Document | ||
|
||
[Detail Document](https://github.com/user-attachments/files/16706323/Stripe_Agent_Integration.pdf) | ||
|
||
|
||
## How It Works | ||
|
||
Stripe Payment Solution few items to choose from, for testing purposes. | ||
|
||
1. Choose one of the items to initiate the payment. | ||
2. Then a payment link from Stripe is generated for payment, Click it. | ||
3. Fill it the Payment Details and click **Place Order**, wait for Payment Confirmation. | ||
|
||
## Installation | ||
|
||
To get started with **Stripe Payment Solution**, follow these steps: | ||
|
||
1. **Clone the repository**: | ||
```bash | ||
git clone https://github.com/fetchai/uAgents.git | ||
cd stripe-agent | ||
|
||
2. **Open a shell**: | ||
```bash | ||
poetry shell | ||
3. **Install dependencies using Poetry**: | ||
```bash | ||
poetry install --no-root | ||
|
||
4. Get API Key and Endpoint Secret from [Stripe.com](https://dashboard.stripe.com/login). | ||
|
||
5. **Run the webhook**: | ||
```bash | ||
cd src/stripe_webhook | ||
poetry run python webhook.py | ||
6. Using Ngrok or any Tunneling Software, Create secure https endpoint and set it up as a webhook in Stripe dashboard under the webhook section. | ||
7. **Set up .env file**: | ||
To run the demo, you need to add these API keys and URLs in .env file: | ||
```bash | ||
STRIPE_API_KEY='YOUR_STRIPE_KEY' | ||
STRIPE_ENDPOINT_SECRET='YOUR_STRIPE_ENDPOINT_SECRET' | ||
STRIPE_WEBHOOK_URL='YOUR_STRIPE_WEBHOOK_URL' | ||
|
||
8. **Run the agent**: | ||
```bash | ||
cd src/stripe_agent | ||
poetry run python agent.py | ||
9. **Run the demo_server**: | ||
```bash | ||
cd src/stripe_demo_server | ||
poetry run python app.py |
Binary file added
BIN
+47 KB
integrations/stripe_payment_solution/images/payment_system_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"categories": [ | ||
"Stripe", | ||
"Payment Solution" | ||
], | ||
"deltav": false, | ||
"description": "Helps User for there Payment Requirements", | ||
"title": "Stripe Payment Solution" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[tool.poetry] | ||
name = "general-use-payment-stripe-solution" | ||
version = "0.1.0" | ||
description = "General Use Payment Solution with Stripe and Fetch.AI Agents" | ||
authors = ["Neeraj Bansal <[email protected]>"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.10" | ||
uagents = { version = "^0.15.2", python = ">=3.10,<3.13" } | ||
uagents-ai-engine = { version = "^0.5.0", python = ">=3.10,<3.12" } | ||
stripe = "^10.7.0" | ||
Flask = { extras = ["async"], version = "^3.0.3" } | ||
python-dotenv = "^1.0.1" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
157 changes: 157 additions & 0 deletions
157
integrations/stripe_payment_solution/src/stripe_agent/agent.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import os | ||
import uuid | ||
|
||
import requests | ||
from ai_engine import UAgentResponse, UAgentResponseType | ||
from dotenv import load_dotenv | ||
from uagents import Agent, Protocol, Context, Model | ||
from uagents.setup import fund_agent_if_low | ||
|
||
class KeyValue(Model): | ||
currency: str | ||
product_name: str | ||
unit_price: int | ||
units: int | ||
|
||
class StripePaymentRequest(Model): | ||
item_in_cart: list[KeyValue] | ||
customer_email: str | ||
order_id: str | ||
|
||
class QueryPaymentStatus(Model): | ||
client_reference_id: str | ||
|
||
|
||
|
||
class PaymentStatus(Model): | ||
client_reference_id: str | ||
data: dict | ||
|
||
load_dotenv() | ||
|
||
agent = Agent( | ||
name="Stripe Agent", | ||
seed="Stripe Agent secret seed phrase", | ||
log_level="DEBUG", | ||
endpoint="http://localhost:8001/submit", | ||
port=8001 | ||
) | ||
fund_agent_if_low(str(agent.wallet.address())) | ||
|
||
print("Agent Address:", agent.address) | ||
|
||
|
||
stripe_payment_protocol = Protocol("Stripe Payment Protocol", "1.00") | ||
|
||
API_KEY = os.getenv('STRIPE_API_KEY', "") | ||
ENDPOINT_SECRET = os.getenv('STRIPE_ENDPOINT_SECRET', "") | ||
WEBHOOK_URL = os.getenv('STRIPE_WEBHOOK_URL', "") | ||
STRIPE_API_URL = os.getenv('STRIPE_API_URL', "") | ||
|
||
|
||
@stripe_payment_protocol.on_message(model=StripePaymentRequest, replies={UAgentResponse}) | ||
async def create_checkout_session(ctx: Context, sender: str, msg: StripePaymentRequest): | ||
ctx.logger.info( | ||
f"Received Request from {msg.customer_email} for Payment Link.") | ||
try: | ||
print(agent.address) | ||
payload = { | ||
'customer_creation': 'always', | ||
'customer_email': msg.customer_email, | ||
'mode': 'payment', | ||
'payment_intent_data[metadata][order_id]': msg.order_id, | ||
'success_url': f"{WEBHOOK_URL}/order/success?session_id={{CHECKOUT_SESSION_ID}}", | ||
'cancel_url': f"{WEBHOOK_URL}/cancel" | ||
} | ||
|
||
for index, item in enumerate(msg.item_in_cart): | ||
payload[f'line_items[{index}][price_data][currency]'] = item.currency | ||
payload[f'line_items[{index}][price_data][product_data][name]'] = item.product_name | ||
payload[f'line_items[{index}][price_data][unit_amount]'] = item.unit_price | ||
payload[f'line_items[{index}][quantity]'] = item.units | ||
|
||
headers = { | ||
'Authorization': f'Bearer {API_KEY}', | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
} | ||
|
||
# encoded_payload = urllib.parse.urlencode(payload, doseq=True) | ||
response = requests.post(f"{STRIPE_API_URL}/checkout/sessions", headers=headers, data=payload) | ||
session = response.json() | ||
request_id = str(uuid.uuid4()) | ||
if response.status_code == 200 and 'url' in session: | ||
resp = await ctx.send( | ||
sender, | ||
UAgentResponse( | ||
message=str(session['url']), | ||
type=UAgentResponseType.FINAL, | ||
request_id=request_id | ||
), | ||
) | ||
|
||
ctx.storage.set(msg.order_id, {'address': sender}) | ||
else: | ||
await ctx.send( | ||
sender, | ||
UAgentResponse( | ||
message="Can't generate payment link, please try again after some time.!", | ||
type=UAgentResponseType.FINAL, | ||
request_id=request_id | ||
), | ||
) | ||
except Exception as exc: | ||
ctx.logger.error(exc) | ||
await ctx.send( | ||
sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR) | ||
) | ||
|
||
|
||
@stripe_payment_protocol.on_message(model=QueryPaymentStatus, replies={UAgentResponse}) | ||
async def payment_status(ctx: Context, sender: str, msg: QueryPaymentStatus): | ||
ctx.logger.info( | ||
f"Received query request from {msg.client_reference_id}.") | ||
try: | ||
payment_details = ctx.storage.get(msg.client_reference_id) or None | ||
if 'payment_data' in payment_details: | ||
await ctx.send( | ||
sender, UAgentResponse(message="Payment Succeeded", type=UAgentResponseType.FINAL) | ||
) | ||
else: | ||
await ctx.send( | ||
sender, UAgentResponse(message="Payment confirmation awaited", type=UAgentResponseType.FINAL) | ||
) | ||
except Exception as exc: | ||
ctx.logger.error(exc) | ||
await ctx.send( | ||
sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR) | ||
) | ||
|
||
|
||
@stripe_payment_protocol.on_message(model=PaymentStatus, replies={UAgentResponse}) | ||
async def payment_status(ctx: Context, sender: str, msg: PaymentStatus): | ||
ctx.logger.info( | ||
f"Received Payment Update of Customer {msg.client_reference_id}.") | ||
try: | ||
payment_details = ctx.storage.get(msg.client_reference_id) or None | ||
if payment_details: | ||
payment_details['payment_data'] = msg.data | ||
ctx.storage.set(msg.client_reference_id, payment_details) | ||
|
||
await ctx.send( | ||
payment_details['address'], UAgentResponse(message="Payment Succeeded", type=UAgentResponseType.FINAL) | ||
) | ||
print(f"Notified {payment_details['address']} of payment confirmation") | ||
else: | ||
print('Weird...') | ||
|
||
except Exception as exc: | ||
ctx.logger.error(exc) | ||
await ctx.send( | ||
sender, UAgentResponse(message=str(exc), type=UAgentResponseType.ERROR) | ||
) | ||
|
||
|
||
agent.include(stripe_payment_protocol, publish_manifest=True) | ||
|
||
if __name__ == "__main__": | ||
agent.run() |
79 changes: 79 additions & 0 deletions
79
integrations/stripe_payment_solution/src/stripe_demo_server/app.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import asyncio | ||
from typing import List | ||
|
||
from flask import Flask, render_template, request, jsonify | ||
from pydantic import Field | ||
from uagents import Model | ||
from uagents.communication import send_sync_message | ||
|
||
|
||
app = Flask(__name__) | ||
|
||
class KeyValue(Model): | ||
currency: str | ||
product_name: str | ||
unit_price: int | ||
units: int | ||
|
||
class QueryPaymentStatus(Model): | ||
client_reference_id: str | ||
|
||
class StripePaymentRequest(Model): | ||
item_in_cart: list[KeyValue] | ||
customer_email: str | ||
order_id: str | ||
|
||
|
||
@app.route('/', methods=['GET']) | ||
def index(): | ||
return render_template('index.html') | ||
|
||
|
||
@app.route('/test', methods=['GET']) | ||
def test(): | ||
return jsonify({"message": "Flask server is running"}), 200 | ||
|
||
|
||
def run_async(coro): | ||
loop = asyncio.new_event_loop() | ||
asyncio.set_event_loop(loop) | ||
return loop.run_until_complete(coro) | ||
|
||
|
||
@app.route('/query', methods=['POST']) | ||
async def query_agent(): | ||
payment_status = QueryPaymentStatus( | ||
client_reference_id='123456' | ||
) | ||
response = await send_sync_message( | ||
'agent1qtehcv05d2yfdxgyhk8wnk9pavl7yxyzg2m05gvqwgggwtxas99nugry6xk', | ||
payment_status | ||
) | ||
print(response) | ||
|
||
return jsonify(response) | ||
|
||
|
||
@app.route('/process', methods=['POST']) | ||
async def process(): | ||
data = request.json | ||
|
||
# Create a StripePaymentRequest object | ||
payment_request = StripePaymentRequest( | ||
item_in_cart=data['item_in_cart'], | ||
customer_email=data['customer_email'], | ||
order_id=data['order_id'] | ||
) | ||
print("sending the payment request to Stripe agent") | ||
|
||
response = await send_sync_message( | ||
'agent1qtehcv05d2yfdxgyhk8wnk9pavl7yxyzg2m05gvqwgggwtxas99nugry6xk', | ||
payment_request | ||
) | ||
print(response) | ||
|
||
return jsonify(response) | ||
|
||
|
||
if __name__ == '__main__': | ||
app.run(debug=True, port=9000, host='0.0.0.0') |
Oops, something went wrong.