From eda8d5ae0d46617399323dc9b076f0dcb915c454 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 16 Jul 2025 14:32:20 +0800 Subject: [PATCH] first commit --- README.md | 3 ++ conf/alipay/private.txt | 3 ++ conf/alipay/public.txt | 3 ++ conf/config.json | 16 ++++++ pf_pay/__init__.py | 1 + pf_pay/ali_pay.py | 66 ++++++++++++++++++++++++ pf_pay/init.py | 9 ++++ pf_pay/paypal_pay.py | 47 +++++++++++++++++ pf_pay/version.py | 1 + pf_pay/weixin_pay.py | 108 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 ++ setup.py | 52 +++++++++++++++++++ 12 files changed, 313 insertions(+) create mode 100644 README.md create mode 100644 conf/alipay/private.txt create mode 100644 conf/alipay/public.txt create mode 100644 conf/config.json create mode 100644 pf_pay/__init__.py create mode 100644 pf_pay/ali_pay.py create mode 100644 pf_pay/init.py create mode 100644 pf_pay/paypal_pay.py create mode 100644 pf_pay/version.py create mode 100644 pf_pay/weixin_pay.py create mode 100644 requirements.txt create mode 100755 setup.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..2658f17 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# pf_pay +a payment module for platform business + diff --git a/conf/alipay/private.txt b/conf/alipay/private.txt new file mode 100644 index 0000000..f220174 --- /dev/null +++ b/conf/alipay/private.txt @@ -0,0 +1,3 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCNEFKfj8jib0cWXyEml7I7cTKyUMAMJmcIfoNNeFcNvp7pNH8cB9QpHvQIOrPTwuyxw591iGWkLCzfKRJc1K594hl558OVrJrB7sM716jyCT1SOqlFcMvuk1Eq3ayMCTR2gyMqdnzaxbSedpPDFQXCXeT5AWDq+IPI1un32Qi35jl0sZu8Ve8KKzaFogig/MkDONShMb593B89p1qRie5HfeHMilcMy4Q1jJ7eo83Q2PfsE0NIuDj6gm38+GcFr3n0h24KeGUANkjU4DhBN2hKwqcpyPFmFio+JWIB8u8dH+8nKS81p8PgqeiKaeJEMWMes1VCeBtICoiyTVgYCpY9AgMBAAECggEALigdIOCnVpAarpNKAZq5UwHjGL2bWV5ncDwVMpAhy/mHfb8TqFRXc20RZG/wz2WElVXxI0ASIfniZNLHk2B0B/SnaWAQezUTHknF0BrsyOWFDxbqtDIISHQjpucJwnhwliaqpwZGLD9srj0WdEq4q7SVa3SsBbZzSJAp1lNJqwJJf7GZUL+5riuSSBBqv+ZExBEwFRlJL8mjOqlISgQQanU5N6ROr5h5vQ3kn2KsXNZdkroEVSA9aeCHn1nDZLE3qCRLhSyOCSmx4YqTO3neFYN50Zo7QoE40LLzSK5SACctp/AWzq12GN9f5iGya6mf+t4pKF/SjZ6ogFZo9QOCcQKBgQDB50aVaBSDsRLXcSFveFPUrOtIzOBmNtuaq96ibqHj+3fGmji6XKIoWN2Yd0Iet6CK7Ph0hKQgljtiaRWSi0cmkqZU4NvZerNWKWxBhnVBkZRqPdyHjEG47qJCaz7wv72S2kQTh3yBap8o/Mtt1M+2QDG0TkHfbjF2mI1q6dnzCwKBgQC6PRwHyArbcqCy48NGTgmxUBJhHIz1zKB/7xX+uPzUyMfvx4bHwwih8bL362zQMlpxwjm28qFQ8/Dh1/baavnrEztaqUChnD6sb2xENP26PNCmxoho71AInVnp0vKEYDw7jYfc5pvl46nlvK8ErbKeohL8gzfew7lit+sUP6lo1wKBgFr9zs+Z0daip7bV7dzDWIN6ycaV7c/JenAwqv8Kb4nunZxjDq/VfHr2iLZdcHe9r+bBoS38eJCaLy/VJDxqg28Ebm1yP3jk7XdHZPeywx+L01uvv+cT2FuSEC6e6SBMugdJyZxoffK1OA8h4cyeiwJ5SVnVR3Az455FpEdBifdVAoGABf5DcaipWMiGjVsxBIksXK1j+gYOLzbHj3ZlMc5ILJzNelTkbHdFRtjdVocX+Fc2e+SxMMb8E/vVq57kjcDVjBARX+iEcO7zQV9Qj51Y8O5WFJfc+euBmtVdeF3WehYSuiPi1GQDblF2PTNmOnNQhTRYAhJC8QNBawDaKsulqv8CgYA1fxxqgYqV3BKh4stzDnczt9bPMrCmWmwo2RkVykJMZv/jtXzefPOE27Q+COp5dxLxRnYnGc55B4COVov0p8y05KABOTvN1IJR5BwJLST+gofZW2X3Zd2swAVO758hbpDukKP1A3BeHFoe40S2udGqgntVsMHQhnWUlnDAH++C5A== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/conf/alipay/public.txt b/conf/alipay/public.txt new file mode 100644 index 0000000..c25151c --- /dev/null +++ b/conf/alipay/public.txt @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgD4YlfnWKd4vEHsim6vxLwustbnBBh9IUJwF5rGJ3b7wjYyzMnQZ36Cgf81A685IQ+Ni9GogNDaUWZx9V+qGxZRwaLbktSLnUNwPMudKlUoPyQtqyygU+Bmwg1B+UBzZsz8eG72qOuvu9xNbT72QZqFxzLlo0vzWldijnaPcqukUhTaeIYe1AObI9v3ySAa72GkGCHaSkQqvBLydCJt2mu3zJYhPMKre1oNmQkGYUxLKCwonbABaugOEl7t1vL8mAMlwFg2ihJbYiogGfr2Imt/Y1jy8rftiW41opX1UQ30rgfRYeuEsKvVwuoyqffGHeBSjs53xZkYStYKj0m+8AQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/conf/config.json b/conf/config.json new file mode 100644 index 0000000..4817f93 --- /dev/null +++ b/conf/config.json @@ -0,0 +1,16 @@ +{ + "pay":{ + "alipay":{ + "public_key_file":"$[workdir]$/alipay/public.txt", + "private_key_file":"$[workdir]$/alipay/private.txt", + "appid":"2021005111636494", + "callback":"/api/callback/alipay" + }, + "paypal":{ + "mode":"sandbox", + "client_id":"myid", + "client_secret":"mysecret", + "return_url":"ret_url", + "cancel_url":"cancel_url" + } +} diff --git a/pf_pay/__init__.py b/pf_pay/__init__.py new file mode 100644 index 0000000..58f3ace --- /dev/null +++ b/pf_pay/__init__.py @@ -0,0 +1 @@ +from .version import __version__ diff --git a/pf_pay/ali_pay.py b/pf_pay/ali_pay.py new file mode 100644 index 0000000..9ef5119 --- /dev/null +++ b/pf_pay/ali_pay.py @@ -0,0 +1,66 @@ +from appPublic.jsonConfig import getConfig +from appPublic.worker import awaitify +from appPublic.log import debug, info, exception, error +from alipay import AliPay + +class Zhifubao_Pay: + """ + config:{ + "pay":{ + "alipay":{ + "public_key_file":, + "private_key_file":, + "appid": + "callback": + } + } + } + """ + def __init__(self): + config = getConfig() + pubfile = config.pay.alipay.public_key_file + prifile = config.pay.alipay.private_key_file + appid = config.pay.alipay.appid + self.callback = config.pay.alipay.callback + """ + 支付宝支付 + 传递参数: + out_trade_no : 订单 + total_amount : 金额 + subject : 产品 + """ + with open(pubfile, 'r') as f: + alipay_public_key_string = f.read() + with open(prifile, 'r') as f: + app_private_key_string = f.read() + #alipay_appid = '2021005111636494' + self.alipay = AliPay( + appid=appid, + app_notify_url=self.callback, # 默认回调url + app_private_key_string=app_private_key_string, + # 支付宝的公钥,验证支付宝回传消息使用,不是自己的公钥, + alipay_public_key_string=alipay_public_key_string, + sign_type="RSA2", # RSA 或者 RSA2 + debug=False, # 默认False + ) + + + async def alipay_payment(self, out_trade_no, total_amount, subject): + order_string = self.alipay.api_alipay_trade_page_pay( + # 订单号 + out_trade_no=out_trade_no, + # 金额 + total_amount=total_amount, + # 产品 + subject=subject, + # 回调地址和默认配置一样即可 + return_url=self.callback, + notify_url=self.callback # 可选, 不填则使用默认notify url + ) + url = f"https://openapi.alipay.com/gateway.do?{order_string}" + return url + + async def alipay_callback_verify(self, data, sign): + f = awaitify(self.alipay.verify) + r = await f(data, sign) + return r diff --git a/pf_pay/init.py b/pf_pay/init.py new file mode 100644 index 0000000..94deee2 --- /dev/null +++ b/pf_pay/init.py @@ -0,0 +1,9 @@ +from ahserver.serverenv import ServerEnv +from pf_pay.ali_pay import Zhifubao_Pay + +def load_pf_pay(): + g = ServerEnv() + zfb = Zhifubao_Pay() + g.alipay_payment = zfb.alipay_payment + g.alipay_callback_verify = zfb.alipay_callback_verify + diff --git a/pf_pay/paypal_pay.py b/pf_pay/paypal_pay.py new file mode 100644 index 0000000..b673d0a --- /dev/null +++ b/pf_pay/paypal_pay.py @@ -0,0 +1,47 @@ +import os +import paypalrestsdk +from paypalrestsdk import Payment + +paypalrestsdk.config({ + "mode":"sandbox", + "client_id":"your id", + "client_secret":"your secret" +}) + +class TransItem: + class __init__(self, itemid, name, price, currency, quantity): + self.id = itemid + self.name = name + self.price = price + self.currency = currency + self.quantity = quantity + + def dic(self): + return { + "name":self.name, + "sku":self.id, + "price":self.price, + "currency":self.currency, + "quantity":self.quantity + } + +class OrderGoods: + +class Paypal_Pay: + def __init__(self): + config = getConfig() + self.client_id = config.paypal.client_id + self.client_secret = config.paypal.client_secret + self.client_mode = "SANDBOX" + + def get_products(self): + credentials = { + 'client_id' : self.client_id, + 'client_secret':self.client_secret, + 'client_mode':self.client_mode + } + result = Products(credentials=credentials).list_product() + payload = result.payload() + return payload + + diff --git a/pf_pay/version.py b/pf_pay/version.py new file mode 100644 index 0000000..b8023d8 --- /dev/null +++ b/pf_pay/version.py @@ -0,0 +1 @@ +__version__ = '0.0.1' diff --git a/pf_pay/weixin_pay.py b/pf_pay/weixin_pay.py new file mode 100644 index 0000000..f661235 --- /dev/null +++ b/pf_pay/weixin_pay.py @@ -0,0 +1,108 @@ +import time +import json +import random +import string +import io + +import requests + +from base64 import b64encode +from urllib.parse import urlparse +from Cryptodome.PublicKey import RSA +from Cryptodome.Signature import pkcs1_15 +from Cryptodome.Hash import SHA256 +import qrcode +from appPublic.jsonConfig import getConfig +from ahserver.globalEnv import save_file +from appPublic.uniqueID import getID + +class WXPay: + """ 微信 Native支付 + """ + + def __init__(self): + self.appid = "wx892972c8fb1005b4" # APPID + self.mchid = "1682646155" # 商户号 + self.payment_url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native' # Native支付下单接口 + self.refund_url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds' # 退款接口 + self.notify_url = "https://www.kaiyuancloud.cn/dev/customer/get_weixpay.dspy" # 通知url + self.serial_no = '7B8C3F6F573F249EDD3ED6AA95EC632691BCB503' # 商户证书序列号 + + # 生成签名 + def get_sign(self, sign_str): + config = getConfig() + # 线上证书路径 + apiclient_key = config.weixinpay.private + rsa_key = RSA.importKey(open(apiclient_key).read()) + signer = pkcs1_15.new(rsa_key) + digest = SHA256.new(sign_str.encode('utf8')) + sign = b64encode(signer.sign(digest)).decode('utf-8') + return sign + + def request(self, url: str, method: str, data: dict = None): + data = json.dumps(data) if data else '' + random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32)) + timestamp = str(int(time.time())) + sign_str = '\n'.join([ + method.upper(), # HTTP请求方法 + url.split(urlparse(url).netloc)[-1], # path+args + timestamp, # 时间戳 + random_str, # 请求随机串 + data, '' # 请求报文主体 + ]) # 结尾空窜仅用于让后面多一个\n + sign = self.get_sign(sign_str) + + headers = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': f'WECHATPAY2-SHA256-RSA2048 mchid="{self.mchid}",nonce_str="{random_str}",signature="{sign}",timestamp="{timestamp}",serial_no="{self.serial_no}"' + } + response = requests.request(url=url, method=method, data=data, headers=headers) + return response + + # # 支付 + async def payment(self, order_no, total, description): + data = { + "mchid": self.mchid, + "out_trade_no": order_no, # 订单号 + "appid": self.appid, + "description": description, # 商品描述 + "notify_url": self.notify_url, + "amount": { + "total": total, # 总金额(分) + "currency": "CNY" + } + } + num = self.request(self.payment_url, 'POST', data) + # 生成二维码 + img = qrcode.make(num.json()['code_url']) + buf = io.BytesIO() + img.save(buf, format='PNG') + buf.seek(0, 0) + byt = buf.read() + imgurl = await save_file(byt, getID()+'.png') + return imgurl + + # 退款 + def refund(self, transaction_id, out_refund_no, refund, reason): + data = { + "transaction_id": transaction_id, # 微信支付订单号(交易单号) + "out_refund_no": out_refund_no, # 商户退款单号(商户单号) + "reason": reason, # 退款原因 + "notify_url": self.notify_url, # 通知Url + "amount": { + "total": refund, # 订单金额 + "refund": refund, # 退款金额(分) + "currency": "CNY" + } + } + return self.request(self.refund_url, 'POST', data) + + def application_bill(self): + url = 'https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2022-02-28&bill_type=ALL' + return self.request(url, 'GET') + + + +async def pay_wx(order_no, total, description): + result = await WXPay().payment(order_no, total, description) + return result diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e53afd5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# python-paypal-api +paypalrestsdk +python-alipay-sdk + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..a53cfe6 --- /dev/null +++ b/setup.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +from pf_pay.version import __version__ +try: + from setuptools import setup +except ImportError: + from distutils.core import setup +required = [] +with open('requirements.txt', 'r') as f: + ls = f.read() + required = ls.split('\n') + +with open('pf_pay/version.py', 'r') as f: + x = f.read() + y = x[x.index("'")+1:] + z = y[:y.index("'")] + version = z +with open("README.md", "r") as fh: + long_description = fh.read() + +name = "pf_pay" +description = "pf_pay" +author = "yumoqing" +email = "yumoqing@gmail.com" + +package_data = {} + +setup( + name="pf_pay", + version=version, + + # uncomment the following lines if you fill them out in release.py + description=description, + author=author, + author_email=email, + platforms='any', + install_requires=required , + packages=[ + "pf_pay" + ], + package_data=package_data, + keywords = [ + ], + url="https://github.com/yumoqing/pf_pay", + long_description=long_description, + long_description_content_type="text/markdown", + classifiers = [ + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'License :: OSI Approved :: MIT License', + ], +)