first commit

This commit is contained in:
yumoqing 2025-07-16 14:32:20 +08:00
commit eda8d5ae0d
12 changed files with 313 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# pf_pay
a payment module for platform business

3
conf/alipay/private.txt Normal file
View File

@ -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-----

3
conf/alipay/public.txt Normal file
View File

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgD4YlfnWKd4vEHsim6vxLwustbnBBh9IUJwF5rGJ3b7wjYyzMnQZ36Cgf81A685IQ+Ni9GogNDaUWZx9V+qGxZRwaLbktSLnUNwPMudKlUoPyQtqyygU+Bmwg1B+UBzZsz8eG72qOuvu9xNbT72QZqFxzLlo0vzWldijnaPcqukUhTaeIYe1AObI9v3ySAa72GkGCHaSkQqvBLydCJt2mu3zJYhPMKre1oNmQkGYUxLKCwonbABaugOEl7t1vL8mAMlwFg2ihJbYiogGfr2Imt/Y1jy8rftiW41opX1UQ30rgfRYeuEsKvVwuoyqffGHeBSjs53xZkYStYKj0m+8AQIDAQAB
-----END PUBLIC KEY-----

16
conf/config.json Normal file
View File

@ -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"
}
}

1
pf_pay/__init__.py Normal file
View File

@ -0,0 +1 @@
from .version import __version__

66
pf_pay/ali_pay.py Normal file
View File

@ -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

9
pf_pay/init.py Normal file
View File

@ -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

47
pf_pay/paypal_pay.py Normal file
View File

@ -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

1
pf_pay/version.py Normal file
View File

@ -0,0 +1 @@
__version__ = '0.0.1'

108
pf_pay/weixin_pay.py Normal file
View File

@ -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

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
# python-paypal-api
paypalrestsdk
python-alipay-sdk

52
setup.py Executable file
View File

@ -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',
],
)