支付宝支付开票

2024-05-13 19:52
文章标签 支付 支付宝 开票

本文主要是介绍支付宝支付开票,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

完成 接入准备 后,商家/服务商可根据本文内容快速接入 支付开票

1 简介

消费者使用支付宝完成支付后,可在该笔交易的对应的页面直接发起开票请求,用户之后进入开票页面选择抬头,申请开具发票。商家开票平台开具发票,将发票插入到支付宝发票管家,发票管家会将发票交付并通知消费者。

1.1 名词解释

PID:partner_id 的简称,合作伙伴身份,支付宝账号对应的支付宝唯一用户号(支付宝账号的唯一标识),以2088开头的16位纯数字组成。获取步骤参见 查看 PID 。

SMID:针对间连支付的商家,商家支付是委托网商银行完成的(网商银行提供一层接口给商家或服务商,网商银行再和支付宝支付接口对接,这样会导致支付宝端能识别的支付商家是网商银行无法确认实际的商家),所以针对这类的交易商家,在交易中会有一个 SMID 字段来标识实际商家。

1.2 应用示例

支付开票的交互流程如下图所示。

1.3 开票入口

在支付宝端内和交易相关的页面提供开票入口,方便用户快速便捷的发起开票,具体的入口有如下三处,用户点击 开发票 按钮会跳转至支付宝标准开票页(服务商申请表中填入服务器地址即可,每个服务商有唯一地址,和商家无关)。

透出位置选择及信息配置:

  1. 默认三个通道全部开通,如果对投放渠道有需求可以在邮件申请权限时列出具体需求;
  2. 以上三个入口开发者可自由选择,其中 支付结果页 可单独选择是否透出、支付助手页及账单详情页是一体控制的,只能同时选择透出或同时不透出;
  3. logo 和显示名称支持配置,通过《配置申请表》提交由商家决定具体的 logo 和名称,该表会在开发者申请功能成功后邮件发送。申请权限的邮件模板见下文 前置条件 > 申请接入功能

1.4 模式介绍

支付开票根据商家支付方式的不同分为 支付后判断支付前判断 两种开票模式,两种模式不会影响开票入口展示透出。

1.4.1 支付后判断模式

支付后判断 模式下,开票传参数由支付宝的默认参数进行判定,适合大部分对开票信息没有特殊要求的商家。此模式开发票的入口判断放在支付完成后,由支付宝根据不同的维度进行判断:

  • 对于直连支付的交易支持 PID、收款账号 (sellerid)、门店 id(shopid) 三种维度的判断。
  • 对于间连支付的交易支持 SMID 维度的判断。

1.4.2 支付前判断模式

支付前判断 模式适用于商家门店较多,且对开票的后台信息有详细的要求。

  • 此模式开发票的入口判断需放在调用支付宝支付接口前,由商家委托开发支付功能的支付服务商进行接口入参改造。支付宝提供的所有支付接口均支持该模式,支付接口参见 支付 API。
  • 在此模式下,调用支付宝支付接口时将开发票标志、品牌简称、商户门店简称同时传递给支付宝。

具体支付接口中传参字段如下:

{"invoice_info":{"key_info":{"is_support_invoice":"true",//是否支持开票  "invoice_merchant_name":"BEST_WONDER|BEST_WONDER",//品牌简称(m_short_name)|商户门店简称(sub_m_short_name)  不能带空格,必须要英文竖线中文无法校验,需与支付宝商户入驻接口入参一致"tax_num":"44010068329136"//税号  }}
}

说明:

  1. invoice_merchant_name 参数中品牌简称和商户门店简称为调用 alipay.ebpp.invoice.merchantlist.enter.apply(商户批量入驻申请接口)填写的品牌简称(m_short_name)和商户门店简称(sub_m_short_name);
  2. 优先推荐 支付后判断 模式,如果商家业务上类似有不同开票项目、不同税率的、部分商品才能开票的诉求请选择 支付前判断 模式;
  3. 支付前判断 模式,请提前协调好支付服务商进行改造处理。

2 前置条件

2.1 接入支付产品

如需使用支付开票功能,商家需接入支付宝收单支付功能(目前支付开票支持的支付功能包括:当面付、手机网站支付、生活缴费、小程序支付 等)。

2.2 申请开通功能

正式接入支付开票功能前,商家/服务商需按照下方邮件模板要求发送邮件至 antinvoice@service.alipay.com 申请功能。

邮件格式如下:

  • 接入模式:【支付开票】
  • 主体:【公司名称】
  • 申请人:
  • 申请人手机号:
  • 公司支付宝账号:
  • 创建的APPID:
  • 公司简介:

说明:

  • 申请成功后支付宝将向您提供《配置申请表》,您需要如实填写以下信息:
    • 品牌及报销平台等简称只支持数字、英文大小写、下划线且不能存在敏感词汇。
    • logo 大小为 120 * 120 px。
  • 支付宝将在 2 个工作日内进行审核并通知您审核结果,审批通过后,您向支付宝提供的服务商简称 (s_short_name)、品牌简称(m_short_name)及品牌门店简称(sub_m_short_name)即可正式使用。
  • 后续接口的调用会需要使用到《配置申请表》中填写的信息,请妥善保管。

2.png

提交信息展示

说明:logo 及显示名称支持配置,由商家决定具体的 logo 和名称(通过【配置申请表】提交)。

image

3 功能接入

系统流程图

image

3.1 第一步:支付宝推送开票所需信息

  • 服务商需按照以下规范提供申请开票接口。当消费者在支付宝标准开票页提交开票申请之后,支付宝服务端会推送开票所需信息给服务商服务器的接口,由服务商检验开票参数,处理用户的开票申请。
  • 服务商需要自行验证参数的有效性,并且做幂等控制,接收到支付宝推送的开票信息之后,服务商需要实时返回受理申请的结果。
  • 支付宝端会对调用失败的申请进行重试,如有不可重试的错误需与支付宝进行约定。
  • 服务商接口仅受理开票申请,需马上返回结果是否申请成功,注意这里仅为提交开票申请的操作,而不是执行开票的操作,服务商需保证开票操作异步化,不要在该接口上执行同步开票操作。

说明:支付宝只调用开票申请接口,不调用开票接口,开票信息组装和接口调用由服务商自行设计。

3.1.1 服务商接口入参设计说明

参数名

参数类型

描述

applyIdString支付宝开票申请 id,该 id 为支付宝内的开票申请唯一标识,与订单号绑定。
invoiceAmountString开票金额
orderNoString订单号
mShortNameString商家的品牌名称简称,与发票回传接口的商户名称保持一致。
subShortNameString支付宝为商家分配的商户门店简称,与发票回传接口的商户名称保持一致。
payerNameString抬头名称
payerRegisterNoString纳税人识别号
payerAddressString地址
payerTelPhoneString电话
payerBankNameString开户行
payerBankAccountString账号
userEmailString用户邮箱
userMobileNoString用户手机号
signString签名,防篡改

注意:

支付开票场景中服务商需要生成 RSA2 方式及 RSA 各一套加签密钥,作用如下:

  • RSA2 方式加签密钥用于配置在开放平台控制台,在调用支付宝开票平台的接口使用,如:发票回传接口。
  • RSA 方式验签公钥需服务商在申请接入支付开票功能时提供给支付宝,服务商接收到开票申请推送后可用发票管家的公钥(由发票管家技术人员提供)进行验签操作,同步返回的申请结果使用服务商自己的私钥进行加签,支付宝端将使用服务商提供的公钥对申请结果进行验签,验签通过后会展示 商家开票中 状态。如下图所示:

3.1.2 服务商接口响应参数设计说明

参数名

参数类型

描述

resultMsgString业务结果说明
resultCodeString业务结果码
resultUrlString

用户提交申请之后,支付宝侧会提供入口让用户访问服务商提供的结果页面(可选,链接需进行 urlencode)。

signString签名

3.1.3 服务商接口结果码设计说明

如果支付宝推送的开票信息已经申请开票成功,服务商需明确返回申请开票成功,如果申请开票异常也需要返回明确的异常结果。

下面是结果码示例,服务商可以定义自己的错误码,但需要自定义的错误码需要跟支付宝端确认映射关系,错误码至少需要包含以下几种业务结果含义:

resultCoderesultMsg说明
APPLY_SUCCESS申请成功开票申请的信息校验无误,已提交开票。
INVOICE_PARAM_ILLEGAL开票参数非法加签验证不通过,金额不准确等开票参数异常。

3.2 第二步:服务商开票结果响应

3.2.1 开票成功

由于调用服务商开票申请接口是异步执行开票操作,服务商在开票成功时需调用 alipay.ebpp.invoice.info.send(发票信息回传接口(新版))并将支付宝开票申请 id(applyId)一并带回,用于将发票与用户的开票申请关联起来。

之后消费者便可以在支付宝端查看该发票信息或者下载发票 pdf 或 ofd 文件,接口的系统交互流程如下图所示:

1.png

调用代码
package com.java.sdk.demo;import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.domain.InvoiceItemOpenModel;
import com.alipay.api.request.AlipayEbppInvoiceInfoSendRequest;
import com.alipay.api.domain.InvoiceSendOpenModel;
import com.alipay.api.response.AlipayEbppInvoiceInfoSendResponse;
import com.alipay.api.domain.AlipayEbppInvoiceInfoSendModel;
import com.alipay.api.domain.EinvTrade;
import com.alipay.api.domain.InvoiceTitleOpenModel;import com.alipay.api.FileItem;
import java.util.Base64;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import java.text.ParseException;
import java.text.SimpleDateFormat;public class AlipayEbppInvoiceInfoSend {public static void main(String[] args) throws AlipayApiException, ParseException {// 初始化SDKAlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));// 构造请求参数以调用接口AlipayEbppInvoiceInfoSendRequest request = new AlipayEbppInvoiceInfoSendRequest();AlipayEbppInvoiceInfoSendModel model = new AlipayEbppInvoiceInfoSendModel();// 设置开票商户品牌简称model.setMShortName("XSD");// 设置开票商户门店简称model.setSubMShortName("XSD_HL");// 设置发票信息列表List<InvoiceSendOpenModel> invoiceInfoList = new ArrayList<InvoiceSendOpenModel>();InvoiceSendOpenModel invoiceInfoList0 = new InvoiceSendOpenModel();invoiceInfoList0.setTaxAmount("1.00");invoiceInfoList0.setSumAmount("101.00");invoiceInfoList0.setOpenId("074a1CcTG1LelxKe4xQC0zgNdId0nxi95b5lsNpazWYoCo5");InvoiceTitleOpenModel invoiceTitle = new InvoiceTitleOpenModel();invoiceTitle.setPayerBankNameAccount("中国建设银行11111111");invoiceTitle.setPayerAddressTel("杭州市西湖区天目山路黄龙时代广场0571-11111111");invoiceTitle.setPayerRegisterNo("9133010060913454XP");invoiceTitle.setTitleName("支付宝(中国)网络技术有限公司");invoiceInfoList0.setInvoiceTitle(invoiceTitle);invoiceInfoList0.setFileDownloadUrl("http://img.hadalo.com/aa/kq/ddhrtdefgxKVXXXXa6apXXXXXXXXXX.pdf");invoiceInfoList0.setChecker("李四");invoiceInfoList0.setPayeeAddressTel("杭州市西湖区某某办公楼 0571-237405862");invoiceInfoList0.setPayeeBankNameAccount("西湖区建行11111111111");invoiceInfoList0.setInvoiceDate("2017-10-10");invoiceInfoList0.setPayee("张三");List<EinvTrade> tradeList = new ArrayList<EinvTrade>();EinvTrade tradeList0 = new EinvTrade();tradeList0.setOutJson("透传字段");tradeList0.setPaymentTime(sdf.parse("2019-12-24 00:11:12"));tradeList0.setPaymentAmount("20.22");tradeList0.setMerchantName("肯德基");tradeList0.setBillNo("20200615102930XXXXX00");tradeList0.setBillTime(sdf.parse("2020-06-15 22:00:13"));tradeList0.setCityName("北京市");tradeList0.setSouce("itinerary");tradeList0.setDownloadUrl("http://img.hadalo.com/aa/kq/ddhrtdefgxKVXXXXa6apXXXXXXXXXX.pdf");tradeList0.setPayeeName("蚂蚁金服(杭州)网络技术有限公司");tradeList0.setTradeType("hotel");tradeList0.setExtendMap("k=v\nk2=v2");tradeList0.setDetailJson("账单明细信息,酒店水单信息,行程单信息,餐饮小票信息");tradeList.add(tradeList0);invoiceInfoList0.setTradeList(tradeList);invoiceInfoList0.setOriBlueInvCode("4112740002");invoiceInfoList0.setFileDownloadType("PDF");List<InvoiceItemOpenModel> invoiceContent = new ArrayList<InvoiceItemOpenModel>();InvoiceItemOpenModel invoiceContent0 = new InvoiceItemOpenModel();invoiceContent0.setItemUnit("台");invoiceContent0.setRowType("0");invoiceContent0.setItemTaxAmount("1.00");invoiceContent0.setItemSpec("G39");invoiceContent0.setItemSumAmount("101.00");invoiceContent0.setItemUnitPrice("100.00");invoiceContent0.setItemName("餐饮费");invoiceContent0.setItemExTaxAmount("100.00");invoiceContent0.setItemTaxRate("0.01");invoiceContent0.setItemQuantity(1L);invoiceContent0.setItemNo("1010101990000000000");invoiceContent.add(invoiceContent0);invoiceInfoList0.setInvoiceContent(invoiceContent);invoiceInfoList0.setCheckCode("15170246985745164986");invoiceInfoList0.setApplyId("2016112800152005000000000239");invoiceInfoList0.setInvoiceType("BLUE");invoiceInfoList0.setExtendFields("m_invoice_detail_url=http://196.021.871.011:8080/invoice/detail.action?fpdm= 4112740003&fphm=41791003");invoiceInfoList0.setPayeeRegisterName("支付宝(杭州)信息技术有限公司");invoiceInfoList0.setFinancialElectronicType("01");invoiceInfoList0.setInvoiceMemo("订单号:2017120800001");invoiceInfoList0.setOutInvoiceId("201710283459661232435535");invoiceInfoList0.setOriBlueInvNo("41791002");invoiceInfoList0.setPayeeRegisterNo("310101000000090");invoiceInfoList0.setInvoiceNo("41791003");invoiceInfoList0.setInvoiceCode("4112740003");invoiceInfoList0.setClerk("赵吴");invoiceInfoList0.setOutTradeNo("20171023293456785924325");invoiceInfoList0.setInvoiceKind("PLAIN");// uid参数未来计划废弃,存量商户可继续使用,新商户请使用openid。请根据应用-开发配置-openid配置选择支持的字段。// invoiceInfoList0.setUserId("2088399922382233");invoiceInfoList0.setExTaxAmount("100.00");invoiceInfoList.add(invoiceInfoList0);model.setInvoiceInfoList(invoiceInfoList);request.setBizModel(model);AlipayEbppInvoiceInfoSendResponse response = alipayClient.execute(request);System.out.println(response.getBody());if (response.isSuccess()) {System.out.println("调用成功");} else {System.out.println("调用失败");// sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接// String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);// System.out.println(diagnosisUrl);}}private static AlipayConfig getAlipayConfig() {String privateKey  = "<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->";String alipayPublicKey = "<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->";AlipayConfig alipayConfig = new AlipayConfig();alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");alipayConfig.setAppId("<-- 请填写您的AppId,例如:2019091767145019 -->");alipayConfig.setPrivateKey(privateKey);alipayConfig.setFormat("json");alipayConfig.setAlipayPublicKey(alipayPublicKey);alipayConfig.setCharset("UTF-8");alipayConfig.setSignType("RSA2");return alipayConfig;}
}

注意:

  • 有了 applyId 就无需填写 userId,因为开票申请 id 可以关联到用户。
  • out_trade_no 必须与支付宝推送的 orderNo 一致。

更多参数及响应示例详情可查看 alipay.ebpp.invoice.info.send(发票信息回传接口(新版))文档。

3.2.2 开票失败

如果开票失败服务商需调用 alipay.ebpp.invoice.apply.result.sync(ISV向支付宝同步发票申请结果接口),返回开票结果。

示例代码
package com.java.sdk.demo;import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.request.AlipayEbppInvoiceApplyResultSyncRequest;
import com.alipay.api.domain.AlipayEbppInvoiceApplyResultSyncModel;
import com.alipay.api.response.AlipayEbppInvoiceApplyResultSyncResponse;import com.alipay.api.FileItem;
import java.util.Base64;
import java.util.ArrayList;
import java.util.List;public class AlipayEbppInvoiceApplyResultSync {public static void main(String[] args) throws AlipayApiException {// 初始化SDKAlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());// 构造请求参数以调用接口AlipayEbppInvoiceApplyResultSyncRequest request = new AlipayEbppInvoiceApplyResultSyncRequest();AlipayEbppInvoiceApplyResultSyncModel model = new AlipayEbppInvoiceApplyResultSyncModel();// 设置支付宝向税控商或ISV发起发票申请后model.setResult("SUCCESS");// 设置结果描述model.setResultMsg("成功");// 设置支付宝发起开票申请的idmodel.setApplyId("2023000000000000000000000001");// 设置结果码model.setResultCode("SUCCESS");// 设置该字段是税控商或ISV收到支付宝开票请求后生成的申请idmodel.setTaxApplyId("20160707399929991001");request.setBizModel(model);AlipayEbppInvoiceApplyResultSyncResponse response = alipayClient.execute(request);System.out.println(response.getBody());if (response.isSuccess()) {System.out.println("调用成功");} else {System.out.println("调用失败");// sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接// String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);// System.out.println(diagnosisUrl);}}private static AlipayConfig getAlipayConfig() {String privateKey  = "<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->";String alipayPublicKey = "<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->";AlipayConfig alipayConfig = new AlipayConfig();alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");alipayConfig.setAppId("<-- 请填写您的AppId,例如:2019091767145019 -->");alipayConfig.setPrivateKey(privateKey);alipayConfig.setFormat("json");alipayConfig.setAlipayPublicKey(alipayPublicKey);alipayConfig.setCharset("UTF-8");alipayConfig.setSignType("RSA2");return alipayConfig;}
}

重要入参说明:

  • apply_id:必填,支付宝发起开票申请的 id,该id具有唯一性。具体值由支付宝向税控发起开票申请时传递,作为支付宝向税控开票申请的唯一标志。详情可查看 第一步:支付宝推送开票所需信息
  • tax_apply_id:税控商或服务商收到支付宝开票请求后自定义生成的申请 id,需保证该 id 唯一。 若服务商接入时是按照 tax_apply_id 来查询发票信息,则该字段必填。
  • result:必填,支付宝向税控商或服务商发起发票申请后,对应这笔申请的发票开具结果。取值如下:
    • SUCCESS:成功。
    • FAIL:失败。
  • result_code:结果码,支付宝向税控商或服务商发起发票申请后,对应这笔申请的发票开具结果进行详细说明的结果码。取值如下:
    • SUCCESS:成功。
    • PARAMETER_ILLEGAL:参数不合法。
    • MERCHANT_TAX_DEVICE_ERROR:商户税控设备异常。

3.3 加签验签说明

3.3.1 加签

  1. 排序:将筛选的参数按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推;
  2. 拼接:将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串;
  3. 生成 sign:用 SHA1 算法对待签名字符串计算摘要,然后使用 RSA 私钥对摘要进行加密,得到 sign;
  4. 生成最终参数:sign 的值放到参数 map 中一起生成 queryString 格式的字符串。
待加签字符串
customParam2=自定义参数2&invoiceAmount=10.75&mShortName=xiaokai&orderNo=123456&productCode=PAYMENT_OPEN&resultUrl=http%3A%2F%2Fdummy.com&subShortName=xiaokai
加签后最终参数
customParam2=自定义参数2&invoiceAmount=10.75&mShortName=xiaokai&orderNo=123456&productCode=PAYMENT_OPEN&resultUrl=http%3A%2F%2Fdummy.com&sign=NMd%2Fm%2B2TlaQoMPhj%2BmYHOrgWgOM02PNKqeX0xxKj3DmvrXbCdvgfXmyzzyOOAAxyftYTcFzLfV0CNhwsuZZcoJHAzSLAZAi%2BoyonRhKGL2pGpYJMsJy8qXdkkzj9tIXlBa2n6jJg%2BdWLdZjUGRjPCrYGbGzvwcHd51ikMn0wB10%3D&subShortName=xiaokai

3.3.2 验签

  1. 获取参数中的 sign:从参数的最后面截取 sign;
  2. 解密sign:用公钥解密 sign 得到摘要1;
  3. 对比摘要:用 SHA1 算法对参数(排除sign)计算摘要2,如果摘要1与摘要2相等,说明参数未被篡改,否则说明参数被篡改。

3.3.3 示例代码

/*** Alipay.com Inc.* Copyright (c) 2004-2017 All Rights Reserved.*/
package com.alipay.antinvoice.common.util;
import org.apache.commons.codec.binary.Base64;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/*** RSA加密算法工具类* @author xingqian.xq* @version $Id: RSAUtil.java, v 0.1 2017-01-13 下午3:16 xingqian.xq Exp $$*/
public class RSAUtil {/*** 加签* @param data  待加签数据* @param privateKey  私钥* @return  签名* @throws Exception  异常*/public static String sign(String data, PrivateKey privateKey) throws Exception {byte[] keyBytes = privateKey.getEncoded();PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);Signature signature = Signature.getInstance("SHA1withRSA");signature.initSign(privateK);signature.update(data.getBytes("GBK"));return new String(Base64.encodeBase64(signature.sign()));}/*** 验签* @param publicKey  公钥* @param srcData  原始字符串* @param sign  签名* @return  是否验签通过* @throws Exception  异常*/public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {byte[] keyBytes = publicKey.getEncoded();X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");PublicKey publicK = keyFactory.generatePublic(keySpec);Signature signature = Signature.getInstance("SHA1withRSA");signature.initVerify(publicK);signature.update(srcData.getBytes("GBK"));return signature.verify(Base64.decodeBase64(sign.getBytes()));}/*** 获取密钥对* @return  密钥对* @throws Exception  异常*/public static KeyPair getKeyPair() throws Exception {KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");generator.initialize(1024);KeyPair pair = generator.generateKeyPair();return pair;}/*** 获取私钥** @param privateKey  私钥字符串* @return 私钥对象*/public static PrivateKey getPrivateKey(String privateKey) throws Exception {KeyFactory keyFactory = KeyFactory.getInstance("RSA");byte[] encodedKey = privateKey.getBytes();encodedKey = Base64.decodeBase64(encodedKey);EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);return keyFactory.generatePrivate(keySpec);}/*** 获取公钥* @param publicKey   公钥字符串* @return 公钥对象* @throws Exception 异常*/public static PublicKey getPublickey(String publicKey) throws Exception {KeyFactory keyFactory = KeyFactory.getInstance("RSA");X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey.getBytes()));return keyFactory.generatePublic(bobPubKeySpec);}
}
/*** Alipay.com Inc.* Copyright (c) 2004-2018 All Rights Reserved.*/
package com.alipay.antinvoice.core.service.stripper;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alipay.antinvoice.common.util.RSAUtil;
/*** @author hansong.xhs* @version $Id: SignTest.java, v 0.1 2018年05月03日 下午8:36 hansong.xhs Exp $*/
public class SignTest {public static String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIgp2pq98axr1IcLrsW5MPUqeggdeGPAyh8+2aHObn4ER6KmpTgo+pi2SScFoNgesWx72kY2PEEGsUwcTXKkSYaeZVs7z7Z/ujQvP0kbPGouG7Q6MN5+jud0sM2Gkmu60fGJXQ8IfeFi6oQCKZpMviXg00KV7ct6JmHlgUpP0jR5AgMBAAECgYBlxAo6/t1iBVFZATVFV4ysn2uHJyd0PoGR6rJTSWqxSleTy8LN/2qTuiFgRceZ3w6xyrsvIJfV7b+S59BGb1z3ZdO09gOeviP5W3pC23ClItBlsyNf4njXylQus9Nl4ZnKcV/UEDvjChGIma8ZZChkxHNpLls9WGWFkQkFk88TkQJBALuOBCgCVyI1Lm2r0mPlC7RbuPiKzmT5kOs0+1696WulbRMODPGXsz8ivMJSBQy+ehU6xV//nd5GOAfjt5PqRw0CQQC52rJZdqPGxHhESw8eI5ZBe9Mz7b6T2UKuPJch6xwpiMPlQB7p0hk5Nhnyic01fppG3gpP6MEtHwiTOTMwXsgdAkAqzpkoQJB+oEC+i07zud1YBu9K2vOMnGF1LZyJ3TKffRxOExDlO0iQCm+msm2woPDgU4+k/4SarNAxDMpjmj8pAkA6/1WGWMb8nfmflEQkSR+1gd01qs7ImDs2nD1Noxi5hpTI/WXSy8L+ClKKT3w48wt+W5Xib/yCmktakNnTDQNxAkBbXimHdGVY3GrZszuAN1n3cyafBSTqpFUdqhfRZ/QLj7wJcmJ+PrLpyB6KMZVsnjzOCS9/tWtIZi14ynhOvM2l";public static String publicKey  = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIKdqavfGsa9SHC67FuTD1KnoIHXhjwMofPtmhzm5+BEeipqU4KPqYtkknBaDYHrFse9pGNjxBBrFMHE1ypEmGnmVbO8+2f7o0Lz9JGzxqLhu0OjDefo7ndLDNhpJrutHxiV0PCH3hYuqEAimaTL4l4NNCle3LeiZh5YFKT9I0eQIDAQAB";public static void main(String[] args) throws Exception {String params = sign(RSAUtil.getPrivateKey(privateKey));System.out.println(params);verify(params, RSAUtil.getPublickey(publicKey));}public static void verify(String params, PublicKey publicKey) throws Exception {//解析参数Map<String, String> map = restoreMap(params, "&");//urldecode 签名String sign = URLDecoder.decode(map.get("sign"), "utf-8");//去除签名参数map.remove("sign");//生成待验签字符串String signatureContent = getSignatureContent(map);System.out.println(signatureContent);System.out.println(RSAUtil.verify(signatureContent, publicKey, sign));}/*** 加签demo方法*/public static String sign(PrivateKey privateKey) {try {//获取待验签原始数据Map<String, String> params = new HashMap<String, String>();params.put("invoiceAmount", "开票金额");params.put("orderNo", "订单号");params.put("mShortName", "商户的品牌名称简称");params.put("subShortName", "商户门店简称");params.put("customParam1", "自定义参数1");params.put("customParam2", "自定义参数2");//获取原始加签内容String signatureContent = getSignatureContent(params);//获取签名,得到签名之后进行urlencodeString sign = URLEncoder.encode(RSAUtil.sign(signatureContent, privateKey), "utf-8");params.put("sign", sign);return getSignatureContent(params);} catch (Exception e) {e.printStackTrace();}return "";}/*** 获得需要签名的数据,按照参数名字母升序的顺序将所有参数用&连接起来* @param params 待签名参数集* @return 排好序的待签名字符串*/public static String getSignatureContent(Map<String, String> params) {if (params == null) {return null;}StringBuffer content = new StringBuffer();List<String> keys = new ArrayList<String>(params.keySet());Collections.sort(keys);for (int i = 0; i < keys.size(); i++) {String key = keys.get(i);String value = params.get(key);content.append((i == 0 ? "" : "&") + key + "=" + value);}return content.toString();}/*** 把数据库里边String型的扩展字段转换成map型* @param extension 需要转换的String* @param spiltorKey 指定的字符串* @return      转换后的扩展MAP*/public static Map<String, String> restoreMap(String extension, String spiltorKey) {Map<String, String> extensionMap = new HashMap<String, String>();String[] fieldList = extension.split(spiltorKey);for (String field : fieldList) {String[] entry = field.split("=");if (entry.length >= 2) {if (entry.length > 2) {//如果包含多个“=”取第一个后边的为value,如URL后边的for (int i = 2; i < entry.length; i++) {entry[1] += ("=" + entry[i]);}}extensionMap.put(entry[0], entry[1]);}}return extensionMap;}
}

参考文档:

小程序文档 - 支付宝文档中心

这篇关于支付宝支付开票的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/986672

相关文章

jsapi 支付缺少appid ¬ify_url

$.ajax({url: 'url',type: 'get',dataType: "json",//改成jsonsuccess: function (data) {//$('#xx').val(data)WeixinJSBridge.invoke('getBrandWCPayRequest', $.parseJSON(data),function(res){if(res.err_msg == "

“设计模式双剑合璧:工厂模式与策略模式在支付系统中的完美结合”

工厂模式(Factory Pattern)和策略模式(Strategy Pattern)都是常见的设计模式,但它们解决的问题和应用场景不同。下面是它们的区别: 1. 目的不同: 工厂模式(Factory Pattern): 工厂模式的主要目的是创建对象。它通过定义一个创建对象的接口,让子类决定实例化哪一个具体类,从而将对象创建的逻辑与使用的代码分离。 工厂模式可以分为简单工厂、工厂方法和抽象

一步步教你如何为你的app接入支付宝

官方接口文档步骤链接: https://doc.open.alipay.com/doc2/detail?treeId=59&articleId=103563&docType=1 1首先,你的要有一个企业的账户,并且已经和支付宝平台签约了, (具体操作查看https://doc.open.alipay.com/doc2/detail.htm?treeId=58&articleId=1035

社群空间站付费入群系统易支付版全套搭建教程

社群空间站9.9付费入群系统易支付版全套搭建教程 1.创建站点 2.搭建环境 php7.2 3.上传源码包 数据库批量修改sq9.dongge1.icu s10.dongge1.icu 改为你的域名 4.上传数据库 修改数据库文件/data/config/ 5.访问域名 6.账户密码 admin 123456 7.易支付修改地址是在/data/tpl/app/default/yy_

店匠科技携手Stripe共谋电商支付新篇章

在全球电商行业蓬勃发展的背景下,支付环节作为交易闭环的核心,其重要性日益凸显。随着消费者对支付体验要求的不断提高,以及跨境电商的迅猛发展,支付市场正经历着前所未有的变革与挑战。在这一充满机遇与竞争的领域,店匠科技(Shoplazza)凭借其创新的嵌入式支付解决方案—— Shoplazza Payments,成功在市场中占据了一席之地。 近日,在新加坡举办的 Stripe Tour 新加坡站 20

支付宝直付通与微信收付通分账产品:功能差异与适用场景

引言: 在电商和移动支付蓬勃发展的今天,支付宝直付通与微信收付通作为两大主流分账产品,各自拥有独特的功能和适用场景。本文将从功能差异和适用场景两个方面对这两款产品进行比较。 一、功能差异 支付宝直付通:支付宝直付通主要面向平台资金清算,提供支付、结算、营销、退款、分账等一站式服务。其优势在于资金100%可控,且支持合并支付和灵活的结算时间。此外,支付宝直付通还具备数字化营销能力,支持平台前置

支付宝开放平台-开发者社区——「外滩大会-AI能为理财做什么」正在直播

《1000天后的AI金融服务—2024蚂蚁财富论坛》 主办机构:蚂蚁财富 论坛简介: AIGC技术加速落地,为金融服务打开了哪些想象空间?本次财富论坛将围绕这一主题,探讨下一代理财服务的新范式。 论坛议程: 1、思想碰撞:用户需求趋势探讨 2、重磅发布:AIGC焕新理财服务 3、深度展望:1000天后AI金融服务 直播链接: 钉钉直播: 直播链接:直播 二维码: 支付宝开发

浪子易支付8.29版本PHP网站源码

源码下载 浪子易支付8.29版本PHP网站源码 更新记录 2024/08/29: 1.付款记录管理支持批量操作 2.优化数据清理功能 3.修复了一些已知问题 2024/07/21: 1.更新全新的V2版API接口,使用RSA公私钥验证 2.支持通过接口发起代付转账、退款、查询等 3.支持通过接口发起付款码支付、JSAPI支付、APP支付 4.订单退款支持多次部分金额退款 5.针对插件开

微信支付商家转账到零钱:快速开通攻略及功能全解

一、申请资格与条件 哪些商家可以申请商家转账到零钱功能? 仅公司性质的商户可以申请,个体工商户当前不支持此功能。商户账号应无正在进行的处罚,且历史无风险行为。微信支付账户没有历史违规记录。商家系统已经上线并可以访问。是否需要满足开通满90天和连续交易30天的要求? 目前商家转账到零钱及现金红包功能已取消该限制,新注册公司可直接申请,无需等待。 二、申请流程 如何申请开通商家转账到零钱功能