51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

无营业执照如何收款方案

背景: 随着用户量不断增加,服务器成本越来越大。想着实现会员制回点服务器成本。

业务场景分析:

用户在站点上付款 -----> 我监听到付款金额 -----> 给用户开通会员

调研:

  1. 支付宝和微信官方支付接口:基本都需要企业资格才能开通,最起码也要是个体工商户才可以(有营业执照)

  2. 第三方支付平台:例如图灵支付,xpay等,支持个人开发者,但是手续费太高。

  3. 野路子:网上有开源方案是监听支付宝app收款通知,实现收款,例如PaysApi、绿点支付等,本质上依然是采用挂机监听的策略,但针对的是移动端支付宝或微信的收款通知消息,成本高,配置麻烦,需24小时挂台安卓手机,不免费。

  4. 使用第三方卡密平台进行发卡。因为手续费、提现规则等各种原因放弃。

调研结果:

[支付宝当面付] 地址:

https://b.alipay.com/page/product-workspace/all-product?productId=I1011000290000001003

支持个人开通,但是需要门店照片,这个百度就可以

营业执照是可选的,不上传的话,限制单笔收款≤1000,单日收款≤5W,对于个人开发者足够了。

效果图:

二维码支付成功后,执行自己的业务逻辑。例如给用户开通会员。

昵称金色展示。

接入流程:

1.[点击这里进入]

(https://b.alipay.com/page/product-workspace/all-product?productId=I1011000290000001003),登陆支付宝账户选择立即接入。

2.经营内容选择百货零售-超市-超市(非平台类)

3.营业执照可不上传

4.店铺招牌 百度即可

5.提交申请后十多分钟就可收到通过通知。


可参考这个同学的文章:应用申请开通和配置

https://blog.csdn.net/qq_40881680/article/details/128406119


开发流程:

成功接入以后,可以在

[蚂蚁金服开放平台]

https://openhome.alipay.com/platform/appManage.htm#/apps

网页&移动应用中,看到我的应用列表中多了一个"应用2.0签约******"的应用: 或者是你自己起名字的应用

现在我们可以开发接入了,总体分为以下几个步骤

参考[当面付文档]

https://docs.open.alipay.com/194/

当面付[开发流程]

https://fw.alipay.com/alipaymarket/ability/SM010000000000001000/detail.htm#anchor-accessSchedule


  • 配置当面付公钥私钥

找到 你的 应用,点击右侧查看详情

  • 在应用信息中设置公钥

支付宝官方提供了密钥生成工具,很简单,使用工具生成应用公钥和私钥,应用公钥设置到支付宝,应用私钥保存到本地,应用公钥设置到支付宝后,支付宝会生成一个支付宝公钥,保存到本地。

[具体参见这里]

https://docs.open.alipay.com/291/105971


  • 回调地址配置


总结:

借鉴三个同学的文章 + 并结合GPT4调试代码 + 返回base64码方便前端展示。 改造优化:

【自己个人拥有一个可以支付功能的网站?当然可以了!保姆级演示!】

https://blog.csdn.net/qq_40881680/article/details/128406119

【个人支付方案(免签约)-支付宝当面付】

https://blog.csdn.net/rankun1/article/details/92401295

【zxing生成二维码】

https://blog.csdn.net/gdgztt/article/details/134756196

结合GPT4,代码报错改造优化

java.lang.UnsatisfiedLinkError: /usr/local/java/jdk1.8.0_152/jre/lib/amd64/libawt_xawt.so: libXrender.so.1: cannot open shared object file: No such file or directory

oro.sprinofrananork.neb.util.mestedserletEexception: Hanmer dispatch faled; nested exception is famna.amt.Eropr: can't comet to xll window sener usin0 "locahost:10.0 as the vawe of the DIspl variabl

简单示例代码:

示例主要流程代码。后续优化,可自行调整,比如,金额配置到数据库或者配置中心,金额校验。 为简化,只贴出主要代码。请自行继续优化。

1.Maven引入需要的jar包 * * * * * * * * * * * *

<!--alipay SDK--><dependency>  <groupId>com.alipay.sdk</groupId>  <artifactId>alipay-sdk-java</artifactId>  <version>4.35.9.ALL</version></dependency><!-- zxing --><dependency>  <groupId>com.google.zxing</groupId>  <artifactId>core</artifactId>  <version>3.5.1</version></dependency>

2.Controller代码


package com.kaihang.my.money.web.admin.web.controller;
import com.alipay.api.AlipayApiException;import com.alipay.api.internal.util.AlipaySignature;import com.kaihang.my.money.dao.entity.TbUser;import com.kaihang.my.money.dao.entity.TbUserOrder;import com.kaihang.my.money.dao.entity.TbUserOrderExample;import com.kaihang.my.money.dao.mapper.TbUserOrderMapper;import com.kaihang.my.money.web.admin.util.AliPayUtil;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.util.CollectionUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;import java.util.Base64;import java.util.Date;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;
/** * 支付回调接口 */@Slf4j@Controllerpublic class AliPayController {
    @Autowired    private TbUserOrderMapper tbUserOrderMapper;
    @RequestMapping(value = "/alipay/queryCode",method = RequestMethod.POST)    @ResponseBody    public HashMap<String,String> queryCode(HttpServletRequest request,TbUser tbUser){        HashMap<String,String> resultMap = new HashMap<>();
        String onemonthVal = createQcode("八爪鱼1个月会员","onemonth","5",tbUser);        resultMap.put("onemonth",onemonthVal);
        String threemonthVal = createQcode("八爪鱼3个月会员","threemonth","15",tbUser);        resultMap.put("threemonth",threemonthVal);        // 55元        String oneyearVal = createQcode("八爪鱼1年会员","oneyear","55",tbUser);        resultMap.put("oneyear",oneyearVal);
        return resultMap;
    }
    public String createQcode(String productName,String productPrefix,String totalPrice,TbUser tbUser){
        //自己生成一个订单号,我这里直接用时间戳演示,正常情况下创建完订单需要存储到自己的业务数据库,做记录和支付完成后校验        //  前缀pay    +  1个月的会员onemonth   +userId + 加时间戳        String orderNo = "pay"+productPrefix+tbUser.getId() + System.currentTimeMillis();
        Date now = new Date();        // 保存到自己设计的订单表 order ,可自己设计。        saveUserOrder(productName,orderNo,totalPrice,tbUser,now);
        // 获取到静态资源的绝对路径        // String logoPath = servletContext.getRealPath("/static/assets/img/logo1.jpg");        String logoPath = ""; //传递空就行,没必要加logo
        byte[] qRcode = AliPayUtil.getQRcode(productName, orderNo, totalPrice, logoPath);        return Base64.getEncoder().encodeToString(qRcode);    }
    public boolean saveUserOrder(String productName,String orderNo,String totalPrice,TbUser tbUser,Date now){        TbUserOrder userOrder = new TbUserOrder();        userOrder.setUserId(tbUser.getId()+"");        userOrder.setUserEmail(tbUser.getEmail());        userOrder.setTotalPrice(totalPrice);        userOrder.setOrderNo(orderNo);        userOrder.setProductName(productName);        userOrder.setBuyTime(now);        userOrder.setCreateTime(now);        userOrder.setOrderStatus("新建未支付");        userOrder.setValidInd("1");        int insert = tbUserOrderMapper.insert(userOrder);        // 如果大于0,保存成功,返回true        return insert>0;    }

    /**     * 支付成功回调接口     * @return     */    @RequestMapping(value = "/alipay/bazhuayu/callback",method = RequestMethod.POST)    public Object callback(HttpServletRequest request){        log.info("【===支付宝回调开始===】");
        Map<String, String> params = new HashMap<>();
        Map requestParams = request.getParameterMap();        for(Iterator iter = requestParams.keySet().iterator(); iter.hasNext();){            String name = (String)iter.next();            String[] values = (String[]) requestParams.get(name);            String valueStr = "";            for(int i=0; i<values.length;i++){                valueStr = (i == values.length-1) ? valueStr + values[i] : valueStr + values[i] + ",";            }            params.put(name,valueStr);        }        log.info("支付宝回调: sign:{}, trade_status:{}, 参数:{}",params.get("sign"),params.get("trade_status"),params.toString());
        //验证回调的正确性:是不是支付宝发的        String alipayPublicKey = "xxxxxxxxxxxx"; // 你的支付宝公钥。TODO 替换为你自己的支付宝公钥!!!!        String signType = "RSA2";        params.remove("sign_type");        try {            //这里使用的是支付宝提供的验签方式            boolean alipayRSACheckedV2 = AlipaySignature.rsaCheckV2(params, alipayPublicKey,"utf-8",signType);            if(!alipayRSACheckedV2) {                // return ServerResponse.createByErrorMessage("非法请求,验证不通过!");                // throw new RuntimeException("非法请求,验证不通过!");                log.info("非法请求,验证不通过!");                return "failed";            }        } catch (AlipayApiException e) {            log.error("支付宝回调异常",e);        }
        //订单支付后修改订单状态        // 订单金额,订单号 out_trade_no=payoneyear81714208576353 订单状态修改。同时给开通对应的会员天数
        String outTradeNo = params.get("out_trade_no");        String totalAmount = params.get("total_amount");
        TbUserOrderExample example = new TbUserOrderExample();        example.createCriteria().andValidIndEqualTo("1").andOrderNoEqualTo(outTradeNo);        List<TbUserOrder> tbUserOrders = tbUserOrderMapper.selectByExample(example);
        if(!CollectionUtils.isEmpty(tbUserOrders)){            TbUserOrder tbUserOrder = tbUserOrders.get(0);            if(null != tbUserOrder){                String totalPrice = tbUserOrder.getTotalPrice();                // 校验金额                if(totalPrice.equals(totalAmount)){                    // 旧的支付状态                    String oldOrderStatus = tbUserOrder.getOrderStatus();
                    tbUserOrder.setOrderStatus("支付成功");                    tbUserOrderMapper.updateByPrimaryKey(tbUserOrder);

                    // 成功之后,会员的,添加会员时间                    TbUser tbUser = addHuiYuan(tbUserOrder,oldOrderStatus);
                    //返回支付状态给支付宝,避免支付宝重复通知                    return "TRADE_SUCCESS";                }
            }        }
        return "failed";    }
    public TbUser addHuiYuan(TbUserOrder tbUserOrder,String oldOrderStatus){        // 增加会员天数        // TODO 这里写你自己的业务处理逻辑。回调之后执行会到这里。        return null;
    }
}

3.AliPayUtil工具类 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

package com.kaihang.my.money.web.admin.util;
/** * @Description: 支付宝-面对面支付 * * @Author: * @Date: 2024-04-23 16:09:33 */import com.alipay.easysdk.factory.Factory;import com.alipay.easysdk.kernel.BaseClient;import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;
public class AliPayUtil {
    private static Logger logger = LoggerFactory.getLogger(AliPayUtil.class);
    public static byte[] getQRcode(String subject, String orderNo, String totalAmount,String logoPath) {        // 1. 设置参数(全局只需设置一次)        Factory.setOptions(getOptions());        try {            // 2. 发起API调用(使用面对面支付中的预下单)            AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace().                    preCreate(subject,orderNo, totalAmount);            // 3. 处理响应或异常            if ("10000".equals(response.code)) {                logger.info("调用成功:{}",response.qrCode);
                //获取生成的二维码,这里是一个String字符串,即二维码的内容;                //然后用二维码生成SDK生成一下二维码,弄成图片返回给前端就行,我这里使用Zxing生成                //其实也可以直接把这个字符串信息返回,让前端去生成,一样的道理,只需要关心这个二维码的内容就行                String qrCode = response.qrCode;
                //生成支付二维码图片                BufferedImage image = QRCodeUtil.createImage(qrCode,logoPath,true);
                ByteArrayOutputStream out = new ByteArrayOutputStream();                ImageIO.write(image, "jpeg", out);                byte[] b = out.toByteArray();                out.write(b);                out.close();                //最终返回图片                return b;            } else {                logger.error("调用失败,原因:{},{}",response.msg,response.subMsg);            }        } catch (Exception e) {            logger.error("调用遭遇异常,原因:{}",e.getMessage());            throw new RuntimeException(e.getMessage(), e);        }        return null;    }


    private static BaseClient.Config getOptions() {        BaseClient.Config config = new BaseClient.Config();        config.protocol = "https";        config.gatewayHost = "openapi.alipay.com";        config.signType = "RSA2";
        // 请更换为您的AppId        config.appId = "xxxxxxxxxx"; // 请替换为您的AppId
        // 请更换为您的PKCS8格式的应用私钥        config.merchantPrivateKey = "应用私钥RSA2048-敏感数据,请妥善保管xxxxxxxxxx"; // TODO 替换为你的支付宝私钥
        // 支付宝公钥        config.alipayPublicKey = "xxxxxxxx"; // TODO 替换为你的支付宝公钥
        config.notifyUrl = "https://你的域名/alipay/bazhuayu/callback";//这里是支付宝接口回调地址  成功后会调用AliPayController的callback方法
        return config;    }}

4.QRCodeUtil工具类 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

package com.kaihang.my.money.web.admin.util;
import com.google.zxing.BinaryBitmap;import com.google.zxing.DecodeHintType;import com.google.zxing.MultiFormatReader;import com.google.zxing.Result;import com.google.zxing.client.j2se.BufferedImageLuminanceSource;import com.google.zxing.common.HybridBinarizer;
import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.File;import java.io.OutputStream;import java.util.Hashtable;import java.util.Random;
/** * @Description: 生成二维码 * @Author: 29489 * @Date: 2024-04-23 17:41:42 */public class QRCodeUtil {    private static final String CHARSET = "utf-8";    private static final String FORMAT = "JPG";    // 二维码尺寸    private static final int QRCODE_SIZE = 300;    // LOGO宽度    private static final int LOGO_WIDTH = 60;    // LOGO高度    private static final int LOGO_HEIGHT = 60;
    public static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {        BufferedImage result = QRCodeGenerator.generateQRCodeImage(content);        return result;    }
    /**     * 生成二维码(内嵌LOGO)     * 二维码文件名随机,文件名可能会有重复     *     * @param content     *            内容     * @param logoPath     *            LOGO地址     * @param destPath     *            存放目录     * @param needCompress     *            是否压缩LOGO     * @throws Exception     */    public static String encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {        BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);        mkdirs(destPath);        String fileName = new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));        return fileName;    }
    /**     * 生成二维码(内嵌LOGO)     * 调用者指定二维码文件名     *     * @param content     *            内容     * @param logoPath     *            LOGO地址     * @param destPath     *            存放目录     * @param fileName     *            二维码文件名     * @param needCompress     *            是否压缩LOGO     * @throws Exception     */    public static String encode(String content, String logoPath, String destPath, String fileName, boolean needCompress) throws Exception {        BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);        mkdirs(destPath);        fileName = fileName.substring(0, fileName.indexOf(".")>0?fileName.indexOf("."):fileName.length())                + "." + FORMAT.toLowerCase();        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));        return fileName;    }
    /**     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.     * (mkdir如果父目录不存在则会抛出异常)     * @param destPath     *            存放目录     */    public static void mkdirs(String destPath) {        File file = new File(destPath);        if (!file.exists() && !file.isDirectory()) {            file.mkdirs();        }    }
    /**     * 生成二维码(内嵌LOGO)     *     * @param content     *            内容     * @param logoPath     *            LOGO地址     * @param destPath     *            存储地址     * @throws Exception     */    public static String encode(String content, String logoPath, String destPath) throws Exception {        return QRCodeUtil.encode(content, logoPath, destPath, false);    }
    /**     * 生成二维码     *     * @param content     *            内容     * @param destPath     *            存储地址     * @param needCompress     *            是否压缩LOGO     * @throws Exception     */    public static String encode(String content, String destPath, boolean needCompress) throws Exception {        return QRCodeUtil.encode(content, null, destPath, needCompress);    }
    /**     * 生成二维码     *     * @param content     *            内容     * @param destPath     *            存储地址     * @throws Exception     */    public static String encode(String content, String destPath) throws Exception {        return QRCodeUtil.encode(content, null, destPath, false);    }
    /**     * 生成二维码(内嵌LOGO)     *     * @param content     *            内容     * @param logoPath     *            LOGO地址     * @param output     *            输出流     * @param needCompress     *            是否压缩LOGO     * @throws Exception     */    public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)            throws Exception {        BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);        ImageIO.write(image, FORMAT, output);    }
    /**     * 生成二维码     *     * @param content     *            内容     * @param output     *            输出流     * @throws Exception     */    public static void encode(String content, OutputStream output) throws Exception {        QRCodeUtil.encode(content, null, output, false);    }
    /**     * 解析二维码     *     * @param file     *            二维码图片     * @return     * @throws Exception     */    public static String decode(File file) throws Exception {        BufferedImage image;        image = ImageIO.read(file);        if (image == null) {            return null;        }        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));        Result result;        Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);        result = new MultiFormatReader().decode(bitmap, hints);        String resultStr = result.getText();        return resultStr;    }
    /**     * 解析二维码     *     * @param path     *            二维码图片地址     * @return     * @throws Exception     */    public static String decode(String path) throws Exception {        return QRCodeUtil.decode(new File(path));    }

}

5.前端代码 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

<%@ page contentType="text/html;charset=UTF-8" language="java" %><!DOCTYPE html><html><head>  <title>八爪鱼官网-会员购买页面</title>  <meta charset="utf-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <!-- SEO 关键字 -->  <meta name="keywords" content="八爪鱼,财务自由,财务,财务自由之路,什么叫财务自由,财务自由需要多少资产,什么叫被动收入,打造被动收入,增加被动收入,怎样获得被动收入,价值投资,个人资产管理,理财,躺着赚钱,让钱为我打工,市场风云">  <meta name="description" content="个人资产管理平台,帮助您打造被动收入,发现投资机会,助力实现财务自由。财务自由、被动收入、个人资产管理、价值投资平台">  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
  <style>    * {      box-sizing: border-box;    }    body {      font-family: 'Arial', sans-serif;      background-color: #1a1a1a; /* 页面的背景色 */      color: #ccc; /* 文字颜色 */      margin: 0;      padding: 0;      display: flex;      flex-direction: column; /* 使导航栏在顶部 */    }    .navbar {      position: relative; /* 如果之前没有设置,现在需要设置为相对定位 */      background-color: #202020; /* 导航栏背景色 */      padding: 10px 20px;      display: flex;      justify-content: space-between; /* 企业名称和导航项分开 */      align-items: center;    }    .navbar .logo {      color: #fff;      font-weight: bold; /* 企业名称字体加粗 */      font-size: 24px;    }
    /* 确保主内容区域有足够的下边距,以免被固定位置的footer遮挡 */    .main-content {      margin-top: 50px; /* 导航栏的高度 */      padding-bottom: 40px; /* 根据新的footer高度调整,确保内容可见 */    }
    /* 其他样式保持不变 */    .main-content {      display: flex;      justify-content: center;      align-items: center;      flex-direction: column;      height: 100vh; /* 确保 .main-content 高度充满视口,以便居中 */      text-align: center; /* 文本居中 */      color: #FFFFFF; /* 设置文字颜色为亮白色 */    }
    /* 保持原有的logo样式,现在应用于a标签 */    .logo {      color: #fff;      font-weight: bold; /* 企业名称字体加粗 */      font-size: 24px;      text-decoration: none; /* 去除链接下划线 */      display: inline-block; /* 或其他适合的显示方式,确保布局正确 */    }
    /* 可选:指定鼠标悬停在logo上时的样式,例如改变颜色 */    .logo:hover {      color: #e0e0e0; /* 鼠标悬停时的颜色,可自定义 */    }
    .qr-codes-container {      display: flex;      justify-content: center; /* 子元素水平居中 */      flex-wrap: wrap; /* 允许子元素在容器满时换行 */      gap: 20px; /* 子元素之间的间隔 */      width: 100%; /* 充满父容器宽度 */      max-width: 1200px; /* 最大宽度,根据需要调整 */      margin: 137px 20px auto; /* 上下保持20px,左右auto使得容器居中 */    }
    .qr-code {      text-align: center;      /* Add additional styling as needed */    }
    /* 二维码图片的样式,根据需要增加尺寸 */    .qr-code img {      width: 250px; /* 图片宽度,根据需要调整 */      height: auto; /* 高度自动,保持图片比例 */    }
    /* 二维码描述的样式 */    .qr-code p {      color: #ffffff; /* 保持文字颜色为白色 */      font-size: 1rem; /* 调整字体大小为1rem,根据需要调整 */    }</style>
</head><body>
<div class="navbar">  <a href="/moneyTotal" class="logo">八爪鱼</a></div>
<div class="main-content">  <p>注:此页面暂时不自动跳转,购买支付成功后,请您重新登录网站!!!</p>  <p style="font-size: 14px">如有其他问题请邮件联系我们:[email protected]</p>  <div class="qr-codes-container">    <div class="qr-code">      <img src="data:image/png;base64,${onemonth}" alt="八爪鱼1个月会员(5元)"/>      <p>1个月会员(5元)</p>    </div>    <div class="qr-code">      <img src="data:image/png;base64,${threemonth}" alt="八爪鱼3个月会员(15元)"/>      <p>3个月会员(15元)</p>    </div>    <div class="qr-code">      <img src="data:image/png;base64,${oneyear}" alt="八爪鱼1年会员(55元)"/>      <p>1年会员(55元)</p>    </div>
    <p style="font-size: 14px; color: #f39c12">会员权益:尊享金色会员标识。月报、资产包、负债包、机会卡额度限制放开。</p>
  </div>

</div>
</body></html>

网站示例:

八爪鱼现金流 https://www.incom.top


代码详情查看 csdn博客地址:

https://blog.csdn.net/u011055858/article/details/139558558

赞(1)
未经允许不得转载:工具盒子 » 无营业执照如何收款方案