业务示例代码
更新时间:2026.01.221. 目标
通过本教程的学习,你应该可以:
邀请商户开通微信支付电子发票能力
设置商户的开发配置选项
让用户通过该商户的支付凭证开具微信支付电子发票
通过用户主动授权,使商户开具微信支付电子发票
将自建/第三方电子发票平台的发票,通过卡包交付给用户电子发票
开具或冲红数电发票,并下载微信支付电子发票文件
2. 业务处理流程
2.1 邀请商户开通微信支付电子发票
2.1.1 业务描述
通过该业务,可以实现邀请未开通电子发票功能的商户开通微信支付电子发票。也可以邀请已经开通过电子发票功能的商户与原服务商/合作伙伴解除关联关系,并与新的服务商/合作伙伴进行绑定。还可以对已经开通了微信支付电子发票能力——微信电子发票数电模式的商户,追加邀请开通开票能力。
2.1.2 业务示例代码
1package com.java.partner; 2 3import com.java.demo.CheckSubMchStatus; // https://pay.weixin.qq.com/doc/v3/partner/4015792561 4import com.java.demo.CheckSubMchStatus.CheckSubMchStatusRequest; 5import com.java.demo.CheckSubMchStatus.CheckSubMchStatusResponse; 6import com.java.demo.CheckSubMchStatus.DigitalTaxAbilityInfo; 7import com.java.demo.CheckSubMchStatus.DigitalTaxApplyStatus; 8import com.java.demo.CheckSubMchStatus.DigitalTaxBillingPersonInfo; 9import com.java.demo.CheckSubMchStatus.DigitalTaxMode; 10import com.java.demo.CheckSubMchStatus.ThirdMode; 11import com.java.demo.CheckSubMchStatus.ThirdModeStatus; 12import com.java.demo.GetSpInviteUrl; // https://pay.weixin.qq.com/doc/v3/partner/4015941495 13import com.java.demo.GetSpInviteUrl.FapiaoAbilityType; 14import com.java.demo.GetSpInviteUrl.GetSpInviteUrlRequest; 15import com.java.demo.GetSpInviteUrl.GetSpInviteUrlResp; 16import com.java.demo.GetSpInviteUrl.InviteOperationType; 17import com.java.demo.GetSpInviteUrl.InvoiceMode; 18import com.java.demo.ListSpInviteMchInfo; // https://pay.weixin.qq.com/doc/v3/partner/4015941524 19import com.java.demo.ListSpInviteMchInfo.ListSpInviteMchInfoRequest; 20import com.java.demo.ListSpInviteMchInfo.ListSpInviteMchInfoResp; 21import com.java.demo.ListSpInviteMchInfo.MchInviteResultInfo; 22import com.java.demo.ListSpInviteMchInfo.MchInviteStatus; 23import com.java.utils.WXPayUtility; 24import java.time.LocalDateTime; 25import java.time.ZoneId; 26import java.time.format.DateTimeFormatter; 27import java.util.ArrayList; 28import java.util.List; 29import java.util.Objects; 30 31public class InviteMerchantDemo { 32 33 public static void main(String[] args) { 34 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 35 GetSpInviteUrl getInvite = new GetSpInviteUrl( 36 "19xxxxxxxx", 37 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 38 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 39 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 40 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 41 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 42 ); 43 44 ListSpInviteMchInfo listSpInviteMchInfo = new ListSpInviteMchInfo( 45 "19xxxxxxxx", 46 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 47 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 48 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 49 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 50 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 51 ); 52 53 CheckSubMchStatus checkSubMchStatus = new CheckSubMchStatus( 54 "19xxxxxxxx", 55 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 56 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 57 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 58 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 59 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 60 ); 61 62 GetSpInviteUrlRequest inviteRequest = new GetSpInviteUrlRequest(); 63 inviteRequest.operationType = InviteOperationType.AUTH_BINDING; 64 inviteRequest.fapiaoMode = new ArrayList<>(); 65 { 66 inviteRequest.fapiaoMode.add(InvoiceMode.THIRD_OR_SELF_FAPIAO); 67 } 68 inviteRequest.fapiaoAbilityTypeList = new ArrayList<>(); 69 { 70 inviteRequest.fapiaoAbilityTypeList.add(FapiaoAbilityType.BASE_ABILITY); 71 } 72 // 服务商设置标识此次邀请链接生成的渠道 73 inviteRequest.inviteChannel = "miniprogram"; 74 // 服务商设置标识此次邀请链接生成的操作人员 75 inviteRequest.operateUser = "mI7HGEJ4Q2B91IGjHZu/Gthm87Szv0MK2AoC0/3ZMDgltMtdoY6O0qZ4F1iXiwCuqkkBe+9M4ggvdzRVVVB9s+zLEQ8" + 76 "nv74vsgl77MZx14nd5obtCcfAvPfDJob3oG7FqlThmYKJqjiOwBvvQse7p9R8onj/POzSrbM8re8ZYGp4LcehXopTLdk2ZVWRv8bnJgKZeY4vpMmq4xuRTYk6xNXvowBBKLu4oaNFNdBO3fip1a1rFW0vRw=="; 77 inviteRequest.inviteCode = "code_20200101_123"; 78 // 子商户号非必填,如果填入表示这个链接只能邀请这个商户号,如果未填表示商户扫码后,需要手动输入商户号或者微信支付订单号来进行开通 79 inviteRequest.subMchid = "19998278783"; 80 81 ListSpInviteMchInfoRequest listSpInviteMchInfoRequest = new ListSpInviteMchInfoRequest(); 82 final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone( 83 ZoneId.of("Asia/Shanghai")); 84 LocalDateTime now = LocalDateTime.now(); 85 // 定时查询当前时间过去10分钟之内邀请的商户信息 86 listSpInviteMchInfoRequest.queryTimeStart = dateTimeFormatter.format(now.minusMinutes(10).atOffset(ZoneOffset.ofHours(8))); 87 // 查询时间间隔不能查过7天 88 listSpInviteMchInfoRequest.queryTimeEnd = dateTimeFormatter.format(now.atOffset(ZoneOffset.ofHours(8))); 89 // 查询开始的起始位置 90 listSpInviteMchInfoRequest.offset = 0L; 91 listSpInviteMchInfoRequest.limit = 10L; 92 // 与获取邀请链接时传入的inviteCode保持一致 93 listSpInviteMchInfoRequest.inviteCode = "code_20200101_123"; 94 // 只有邀请成功的商户信息,才会同步返回商户号 95 listSpInviteMchInfoRequest.mchInviteStatus = MchInviteStatus.MCH_INVITE_SUCC; 96 97 try { 98 GetSpInviteUrlResp response = getInvite.run(inviteRequest); 99 100 //将inviteUrl转换为二维码,商户通过微信扫码后, 101 //可以跳转到微信支付服务商电子发票能力开通页面,引导用户完成授权开通服务商电子发票能力。 102 System.out.println("inviteUrl: " + response.inviteUrl); 103 //通过指引https://developers.weixin.qq.com/miniprogram/dev/api/navigate/wx.navigateToMiniProgram.html,使用miniprogramAppid,miniprogramPath 104 //引导商户跳转到微信支付服务商电子发票能力开通页面,完成授权开通服务商电子发票能力。 105 System.out.println("miniprogramAppid: " + response.miniprogramAppid); 106 System.out.println("miniprogramPath: " + response.miniprogramPath); 107 108 // 等待商户操作完成开通 109 // 如果服务商不知晓邀请商户的商户号,可以通过定时任务获取邀请确认授权成功的商户的商户号信息 110 ListSpInviteMchInfoResp listSpInviteMchInfoResp = listSpInviteMchInfo.run(listSpInviteMchInfoRequest); 111 List<MchInviteResultInfo> mchInviteResultList = listSpInviteMchInfoResp.mchInviteResultList; 112 for (MchInviteResultInfo mchInviteResultInfo : mchInviteResultList) { 113 if (mchInviteResultInfo.mchInviteStatus == MchInviteStatus.MCH_INVITE_SUCC) { 114 // 如果是邀请成功状态的商户,可以获取到商户的商户号,用于检查商户的开通授权状态 115 String subMchid = mchInviteResultInfo.subMchid; 116 117 CheckSubMchStatusRequest checkSubMchStatusRequest = new CheckSubMchStatusRequest(); 118 checkSubMchStatusRequest.subMchid = subMchid; 119 120 // 服务商获取商户的商户号信息后,请求【检查子商户开票功能状态】(https://pay.weixin.qq.com/doc/v3/partner/4015792561)接口获取商户开通状态 121 CheckSubMchStatusResponse checkSubMchStatusResponse = checkSubMchStatus.run( 122 checkSubMchStatusRequest); 123 if (subMchid.equals(checkSubMchStatusResponse.subMchid)) { 124 ThirdMode thirdMode = checkSubMchStatusResponse.thirdMode; 125 DigitalTaxMode digitalTaxMode = checkSubMchStatusResponse.digitalTaxMode; 126 // 【获取邀请开通的商户信息】(https://pay.weixin.qq.com/doc/v3/partner/4015941524)邀请开通 【THIRD_OR_SELF_FAPIAO:第三方/自建发票】。检查该模式开通状态 127 if (Objects.nonNull(thirdMode)) { 128 ThirdModeStatus status = thirdMode.status; 129 } 130 131 // 获取邀请开通的商户信息】(https://pay.weixin.qq.com/doc/v3/partner/4015941524)邀请开通 【TENCENT_DIGITAL_TAX:腾讯数电发票】。检查该模式开通状态 132 if (Objects.nonNull(digitalTaxMode)) { 133 // 数电发票接入状态,根据【检查子商户开票功能状态】(https://pay.weixin.qq.com/doc/v3/partner/4015792561)状态描述判断用户开通的情况 134 DigitalTaxApplyStatus digitalTaxApplyStatus = digitalTaxMode.status; 135 List<DigitalTaxBillingPersonInfo> billingPersonInfoList = digitalTaxMode.billingPersonInfo; 136 for (DigitalTaxBillingPersonInfo digitalTaxBillingPersonInfo : billingPersonInfoList) { 137 // 商户在乐企平台上设置了开票员信息,才会返回对应开票员的开票员ID,用于开具数电发票接口,请求入参 138 String billingPersonId = digitalTaxBillingPersonInfo.id; 139 } 140 // 商户完成所有操作后,完成接入腾讯数电后的时间 141 String accessTime = digitalTaxMode.accessTime; 142 String expiredTime = digitalTaxMode.expiredTime; 143 String accessFailReason = digitalTaxMode.accessFailReason; 144 List<DigitalTaxAbilityInfo> abilityInfo = digitalTaxMode.abilityInfo; 145 } 146 } 147 } else { 148 // 如果是邀请失败状态的商户,获取商户的商户名称,以及邀请开通失败的原因。 149 // 根据开通小程序页面下的帮助指引,指导商户解决问题重试开通授权流程 150 String epName = mchInviteResultInfo.epName; 151 String inviteFailedReason = mchInviteResultInfo.inviteFailedReason; 152 } 153 } 154 } catch (WXPayUtility.ApiException e) { 155 // 处理异常处理逻辑 156 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 157 // 错误:系统错误 158 // 解决方式:稍后重试 159 // 描述:微信支付系统失败,建议等1分钟后再重试 160 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 161 // 错误:限频报错 162 // 解决方式:稍后重试 163 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 164 } else if (e.getErrorCode().equals("ERROR")) { 165 // 错误:系统错误 166 // 解决方式:系统繁忙,请稍后重试 167 // 描述:微信支付系统失败,建议等1分钟后再重试 168 } else if (e.getErrorCode().equals("NO_AUTH")) { 169 // 错误:服务商无权限 170 // 解决方式:根据具体message,查看指引解决问题 171 // 描述: 172 // - 商户无权限访问,请确认是否已开通相关权限 173 // - 服务商无电子发票权限,请联系支持开通服务商电子发票 174 // - 乐企数电安全校验时间已过期,请登录电子税局重新设置数电安全校验时间 175 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 176 // 错误:签名错误 177 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 178 // 描述:签名报错,需要确认签名材料和签名流程是否正确 179 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 180 // 错误:参数错误 181 // 解决方式:按照报错返回的message,重新输入请求参数 182 // 描述:参数的类型,长度,或者必填选项没有填写等 183 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 184 // 错误:请求非法,请求参数正确,但是不符合业务规则 185 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 186 // 描述:不符合业务规则的场景如: 187 // - 查询起止时间间隔不能大于7天 188 // - 查询起始时间不能大于查询结束时间 189 } else { 190 // 其他类型错误:稍等一会后原单重试 191 } 192 } 193 } 194}
2.1.3 商户操作
商户扫码生成的二维码或者点击按钮跳转到指定的小程序后,商户通过填写对应的微信支付商户号或者在商户交易的微信支付交易订单号后(如邀请填写了商户号,则不需要商户填写),即可按照指引进行微信支付电子发票能力的授权与开通
|
|
|
|---|
|
|
|
| 法人扫脸
| 非法人扫脸 |
|---|
数电发票接入状态状态机:

2.2 设置商户开票配置
2.2.1 业务示例代码
商户完成接入微信支付电子发票能力后,需要通过此流程,完成通知回调地址、是否支持开具专用发票等开票配置。
回调地址 by 服务商维度设置,设置一次即可。账单是否展示开发票入口,是否开专票 by 商户号设置。
1package com.java.partner; 2 3import com.java.demo.CreateFapiaoCardTemplate; // https://pay.weixin.qq.com/doc/v3/partner/4015792562 4import com.java.demo.CreateFapiaoCardTemplate.CardTemplateInfo; 5import com.java.demo.CreateFapiaoCardTemplate.CreateFapiaoCardTemplateRequest; 6import com.java.demo.CreateFapiaoCardTemplate.CustomCell; 7import com.java.demo.CreateFapiaoCardTemplate.FapiaoCardTemplateEntity; 8import com.java.demo.UpdateDevelopmentConfig; // https://pay.weixin.qq.com/doc/v3/partner/4015792563 9import com.java.demo.UpdateDevelopmentConfig.DevelopmentConfigEntity; 10import com.java.demo.UpdateDevelopmentConfig.UpdateDevelopmentConfigRequest; 11import com.java.utils.WXPayUtility; 12 13public class SetMerchantInvoiceConfigDemo { 14 15 public static void main(String[] args) { 16 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 17 CreateFapiaoCardTemplate createFapiaoCardTemplate = new CreateFapiaoCardTemplate( 18 "19xxxxxxxx", 19 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 20 "1DDE55AD98Exxxxxxxxxx", 21 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 22 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 23 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 24 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 25 ); 26 27 UpdateDevelopmentConfig updateDevelopmentConfig = new UpdateDevelopmentConfig( 28 "19xxxxxxxx", 29 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 30 "1DDE55AD98Exxxxxxxxxx", 31 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 32 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 33 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 34 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 35 ); 36 37 CreateFapiaoCardTemplateRequest createFapiaoCardTemplateRequest = new CreateFapiaoCardTemplateRequest(); 38 createFapiaoCardTemplateRequest.subMchid = "1900000109"; 39 createFapiaoCardTemplateRequest.cardAppid = "wxb1170446a4c0a5a2"; 40 createFapiaoCardTemplateRequest.cardTemplateInformation = new CardTemplateInfo(); 41 // 具体字段展示对应卡包展示内容在代码后有示例。 42 createFapiaoCardTemplateRequest.cardTemplateInformation.payeeName = "某公司"; 43 createFapiaoCardTemplateRequest.cardTemplateInformation.logoUrl = "http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0"; 44 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell = new CustomCell(); 45 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell.words = "电子发票"; 46 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell.description = "查看发票"; 47 // 点击卡券详情页自定义cell位后跳转的页面链接,用户跳转时会自动在链接后加上参数encrypt_code与card_id,商户需要调用code解码接口解码encrypt_code得到真实的code 48 // https://developers.weixin.qq.com/doc/service/guide/product/card/Redeeming_a_coupon_voucher_or_card.html#5 2.2节 Code解码接口 解码encrypt_code 49 // 通过【查询电子发票】接口,获取card_information.card_code 与 解密的Code进行比对,确认是否为该商户的电子发票。 50 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell.jumpUrl = "http://www.qq.com"; 51 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell.miniprogramUserName = "gh_86a091e50ad4@app"; 52 createFapiaoCardTemplateRequest.cardTemplateInformation.customCell.miniprogramPath = "pages/xxxPage"; 53 54 UpdateDevelopmentConfigRequest updateDevelopmentConfigRequest = new UpdateDevelopmentConfigRequest(); 55 // 设置服务商的可以接受回调请求的地址,回调设置会校验接口是否能够正确返回http code 200,如果无法返回200,会返回错误。 56 updateDevelopmentConfigRequest.callbackUrl = "https://pay.weixin.qq.com/callback"; 57 updateDevelopmentConfigRequest.subMchCode = "1900000109"; 58 updateDevelopmentConfigRequest.showFapiaoCell = false; 59 updateDevelopmentConfigRequest.supportVatFapiao = false; 60 61 try { 62 FapiaoCardTemplateEntity fapiaoCardTemplateEntity = createFapiaoCardTemplate.run( 63 createFapiaoCardTemplateRequest); 64 System.out.println("card_appid:" + fapiaoCardTemplateEntity.cardAppid); 65 // 用于请求申请开具电子发票时,卡券模板ID参数 66 System.out.println("card_id:" + fapiaoCardTemplateEntity.cardId); 67 68 DevelopmentConfigEntity developmentConfigEntity = updateDevelopmentConfig.run( 69 updateDevelopmentConfigRequest); 70 // 服务商申请接受回调请求的地址 71 System.out.println("callback_url:" + developmentConfigEntity.callbackUrl); 72 // 属于商户的微信支付账单凭证,是否展示开发票入口 73 System.out.println("show_fapiao_cell:" + developmentConfigEntity.showFapiaoCell); 74 // 商户是否支持开具增值税专用发票。如果设置为ture,针对商户所有的开票申请,填写抬头页面是否展示选择电子发票类型选项 75 System.out.println("support_vat_fapiao:" + developmentConfigEntity.supportVatFapiao); 76 } catch (WXPayUtility.ApiException e) { 77 // 处理异常处理逻辑 78 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 79 // 错误:系统错误 80 // 解决方式:稍后重试 81 // 描述:微信支付系统失败,建议等1分钟后再重试 82 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 83 // 错误:限频报错 84 // 解决方式:稍后重试 85 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 86 } else if (e.getErrorCode().equals("ERROR")) { 87 // 错误:系统错误 88 // 解决方式:系统繁忙,请稍后重试 89 // 描述:微信支付系统失败,建议等1分钟后再重试 90 } else if (e.getErrorCode().equals("NO_AUTH")) { 91 // 错误:服务商无权限 92 // 解决方式:开通微信支付电子发票服务商权限,并且检查商户是否接受邀请开通授权微信支付电子发票能力,等1分钟后重试。 93 // 描述:商户无权限访问,请确认是否已开通相关权限 94 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 95 // 错误:签名错误 96 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 97 // 描述:签名报错,需要确认签名材料和签名流程是否正确 98 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 99 // 错误:参数错误 100 // 解决方式:按照报错返回的message,重新输入请求参数 101 // 描述:参数的类型,长度,或者必填选项没有填写等 102 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 103 // 错误:请求非法,请求参数正确,但是不符合业务规则 104 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 105 // 描述:不符合业务规则的场景如: 106 // - 创建发票卡券模板失败,请稍候重试 107 // - 传入的参数“点击cell位跳转的地址”以及“点击cell位跳转的小程序的用户名/页面路径”均为空(位于/body/card_template_info/custom_cell/jump_url|miniprogram_user_name|miniprogram_page_path), 108 // 请检查参数格式是否符合要求 109 } else { 110 // 其他类型错误:稍等一会后原单重试 111 } 112 } 113 } 114}

设置商户支持开具专用发票后,用户填写抬头信息页面会支持选择开具的发票类型,服务商可以通过【获取用户填写抬头信息】获取到用户提交的申请开具发票的类型。

2.3 电子发票状态状态机

2.4 通过微信支付交易凭证开票
2.4.1 业务描述
通过微信支付交易凭证,实现用户主动申请开具微信支付电子发票并插卡,以及用户未授权商户主动申请开具微信支付电子发票并插卡。
2.4.2 用户操作

2.4.3 处理用户填写抬头通知
用户提交开票申请后,如果通过【配置开发选项】接口,成功设置了回调地址,会接受到此次用户提交对应的填写抬头通知。
1package com.tencent.wxpay.fapiao; 2 3import com.google.gson.annotations.SerializedName; 4import com.tencent.wxpay.utils.WXPayUtility; 5import com.tencent.wxpay.utils.WXPayUtility.Notification; 6import java.security.PublicKey; 7import okhttp3.Headers; 8import okhttp3.Response; 9 10public class UserTitleNotifyDemo { 11 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 12 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 13 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 14 private static String mchid = "19xxxxxxxx"; 15 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 16 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 17 // 商户API证书私钥文件路径,本地文件路径 18 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 19 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 20 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 21 // 微信支付公钥文件路径,本地文件路径 22 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 23 // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 24 private static String apiv3Key = "your apiv3 key"; 25 26 public static class UserTitle { 27 @SerializedName("mchid") 28 public String mchid; 29 30 @SerializedName("sub_mchid") 31 public String subMchId; 32 33 @SerializedName("fapiao_apply_id") 34 public String fapiaoApplyId; 35 36 @SerializedName("apply_time") 37 public String applyTime; 38 } 39 40 public void userTitleNotify(Response httpResponse) { 41 String refundNotifyStr = WXPayUtility.extractBody(httpResponse); // 用户填写抬头通知的原始字符串 42 Headers headers = httpResponse.headers(); // 用户填写抬头通知的Headers 43 PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 44 45 Notification notification = WXPayUtility.parseNotification( 46 apiv3Key, 47 wechatPayPublicKeyId, wechatPayPublicKey, headers, 48 refundNotifyStr); 49 50 // 解析用户填写抬头信息 51 UserTitle userTitle = WXPayUtility.fromJson(notification.getResource().getCiphertext(), UserTitle.class); 52 53 String notifyMchId = userTitle.mchid; 54 if (mchid.equals(notifyMchId)) { 55 // 用于服务商获取用户填写抬头的具体信息 56 String subMchId = userTitle.subMchId; 57 String fapiaoApplyId = userTitle.fapiaoApplyId; 58 } 59 } 60}
2.4.4 业务示例代码
1package com.java.partner; 2 3import com.java.demo.GetFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792567 4import com.java.demo.GetFapiaoApplications.FapiaoApplicationsEntity; 5import com.java.demo.GetFapiaoApplications.FapiaoEntity; 6import com.java.demo.GetFapiaoApplications.FapiaoInfo; 7import com.java.demo.GetFapiaoApplications.GetFapiaoApplicationsRequest; 8import com.java.demo.GetFapiaoFileDownloadInfo; // https://pay.weixin.qq.com/doc/v3/partner/4015792576 9import com.java.demo.GetFapiaoFileDownloadInfo.FapiaoDownloadInfo; 10import com.java.demo.GetFapiaoFileDownloadInfo.GetFapiaoFileDownloadInfoRequest; 11import com.java.demo.GetFapiaoFileDownloadInfo.GetFapiaoFileDownloadInfoResponse; 12import com.java.demo.GetUserTitle; // https://pay.weixin.qq.com/doc/v3/partner/4015784260 13import com.java.demo.GetUserTitle.BuyerType; 14import com.java.demo.GetUserTitle.GetUserTitleRequest; 15import com.java.demo.GetUserTitle.Scene; 16import com.java.demo.GetUserTitle.UserTitleEntity; 17import com.java.demo.IssueGeneralIndustryFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792574 18import com.java.demo.IssueGeneralIndustryFapiaoApplications.FapiaoBillType; 19import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoInfo; 20import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoItem; 21import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoRequest; 22import com.java.demo.IssueGeneralIndustryFapiaoApplications.PayChannel; 23import com.java.demo.IssueGeneralIndustryFapiaoApplications.TransactionInfo; 24import com.java.utils.WXPayUtility.ApiException; 25import java.util.ArrayList; 26import java.util.Objects; 27 28public class IssueByTransactionVoucherDemo { 29 30 public static void main(String[] args) { 31 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 32 GetUserTitle getUserTitle = new GetUserTitle( 33 "19xxxxxxxx", 34 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 35 "1DDE55AD98Exxxxxxxxxx", 36 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 37 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 38 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 39 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 40 ); 41 42 IssueGeneralIndustryFapiaoApplications issueGeneralIndustryFapiaoApplications = new IssueGeneralIndustryFapiaoApplications( 43 "19xxxxxxxx", 44 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 45 "1DDE55AD98Exxxxxxxxxx", 46 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 47 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 48 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 49 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 50 ); 51 52 GetFapiaoApplications getFapiaoApplications = new GetFapiaoApplications( 53 "19xxxxxxxx", 54 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 55 "1DDE55AD98Exxxxxxxxxx", 56 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 57 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 58 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 59 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 60 ); 61 62 GetFapiaoFileDownloadInfo client = new GetFapiaoFileDownloadInfo( 63 "19xxxxxxxx", 64 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 65 "1DDE55AD98Exxxxxxxxxx", 66 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 67 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 68 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 69 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 70 ); 71 72 GetUserTitleRequest getUserTitleRequest = new GetUserTitleRequest(); 73 getUserTitleRequest.subMchid = "1900000109"; 74 getUserTitleRequest.scene = Scene.WITH_WECHATPAY; 75 // 通过处理用户抬头填写完成回调通知,获取此次申请的申请单号,交易凭证开票默认申请单号为此次交易的交易单号 76 getUserTitleRequest.fapiaoApplyId = "4200000444201910177461284488"; 77 78 IssueGeneralFapiaoRequest issueGeneralFapiaoRequest = new IssueGeneralFapiaoRequest(); 79 issueGeneralFapiaoRequest.subMchid = "1900000109"; 80 issueGeneralFapiaoRequest.fapiaoApplyId = getUserTitleRequest.fapiaoApplyId; 81 issueGeneralFapiaoRequest.buyerInformation = new IssueGeneralIndustryFapiaoApplications.UserTitleEntity(); 82 83 issueGeneralFapiaoRequest.fapiaoInformation = new IssueGeneralFapiaoInfo(); 84 // 此次申请开票下,申请开具的一张发票的唯一标识,如果需要红冲后换开发票、开票失败后重开发票,使用相同的fapiao_apply_id,更换不同的fapiao_id即可 85 issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId = "20200701123456"; 86 // 申请开票的金额,支持小于本次申请开票对应的所有交易信息的交易金额之和 87 issueGeneralFapiaoRequest.fapiaoInformation.totalAmount = 1123L; 88 issueGeneralFapiaoRequest.fapiaoInformation.items = new ArrayList<>(); 89 { 90 IssueGeneralFapiaoItem itemsItem = new IssueGeneralFapiaoItem(); 91 itemsItem.taxCode = "3010101020203000000"; 92 itemsItem.goodsName = "*xxx*服务"; 93 itemsItem.specification = "米"; 94 itemsItem.unit = "米"; 95 itemsItem.quantity = 100000000L; 96 itemsItem.totalAmount = 429900L; 97 itemsItem.taxRate = 1300L; 98 itemsItem.discount = false; 99 itemsItem.preferentialPolicyCode = 1L; 100 issueGeneralFapiaoRequest.fapiaoInformation.items.add(itemsItem); 101 } 102 ; 103 issueGeneralFapiaoRequest.fapiaoInformation.exportBusinessPolicyCode = 1L; 104 issueGeneralFapiaoRequest.fapiaoInformation.vatRefundLevyCode = 1L; 105 issueGeneralFapiaoRequest.fapiaoInformation.billingPersonId = "128279891283"; 106 issueGeneralFapiaoRequest.fapiaoInformation.billingPerson = "example_billing_person"; 107 issueGeneralFapiaoRequest.fapiaoInformation.fapiaoBillType = FapiaoBillType.COMM_FAPIAO; 108 109 issueGeneralFapiaoRequest.fapiaoInformation.remark = "备注"; 110 111 GetFapiaoApplicationsRequest getFapiaoApplicationsRequest = new GetFapiaoApplicationsRequest(); 112 getFapiaoApplicationsRequest.fapiaoApplyId = getUserTitleRequest.fapiaoApplyId; 113 getFapiaoApplicationsRequest.subMchid = "1900000109"; 114 getFapiaoApplicationsRequest.fapiaoId = issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId; 115 116 GetFapiaoFileDownloadInfoRequest getFapiaoFileDownloadInfoRequest = new GetFapiaoFileDownloadInfoRequest(); 117 getFapiaoFileDownloadInfoRequest.fapiaoApplyId = getUserTitleRequest.fapiaoApplyId; 118 getFapiaoFileDownloadInfoRequest.subMchid = "1900000109"; 119 getFapiaoFileDownloadInfoRequest.fapiaoId = issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId; 120 121 try { 122 // 获取用户填写的抬头信息,用于申请开票接口。如果返回的字段内容为空,则开票接口不用传 123 UserTitleEntity userTitleEntity = getUserTitle.run(getUserTitleRequest); 124 // 申请开票的用户,提交的留言信息 125 System.out.println("userApplyMessage:" + userTitleEntity.userApplyMessage); 126 if (userTitleEntity.type.equals(BuyerType.INDIVIDUAL)) { 127 issueGeneralFapiaoRequest.buyerInformation.type = IssueGeneralIndustryFapiaoApplications.BuyerType.INDIVIDUAL; 128 } else { 129 issueGeneralFapiaoRequest.buyerInformation.type = IssueGeneralIndustryFapiaoApplications.BuyerType.ORGANIZATION; 130 } 131 issueGeneralFapiaoRequest.buyerInformation.name = userTitleEntity.name; 132 issueGeneralFapiaoRequest.buyerInformation.taxpayerId = userTitleEntity.taxpayerId; 133 issueGeneralFapiaoRequest.buyerInformation.address = userTitleEntity.address; 134 issueGeneralFapiaoRequest.buyerInformation.telephone = userTitleEntity.telephone; 135 issueGeneralFapiaoRequest.buyerInformation.bankName = userTitleEntity.bankName; 136 issueGeneralFapiaoRequest.buyerInformation.bankAccount = userTitleEntity.bankAccount; 137 issueGeneralFapiaoRequest.buyerInformation.phone = issueGeneralIndustryFapiaoApplications.encrypt( 138 userTitleEntity.phone); 139 issueGeneralFapiaoRequest.buyerInformation.email = issueGeneralIndustryFapiaoApplications.encrypt( 140 userTitleEntity.email); 141 // 返回当前交易信息对应的交易金额 142 issueGeneralFapiaoRequest.buyerInformation.amount = userTitleEntity.amount; 143 if (userTitleEntity.fapiaoBillType.equals(GetUserTitle.FapiaoBillType.COMM_FAPIAO)) { 144 issueGeneralFapiaoRequest.buyerInformation.fapiaoBillType = FapiaoBillType.COMM_FAPIAO; 145 } else { 146 issueGeneralFapiaoRequest.buyerInformation.fapiaoBillType = FapiaoBillType.VAT_FAPIAO; 147 } 148 149 issueGeneralFapiaoRequest.fapiaoInformation.transactionInformation = new ArrayList<>(); 150 { 151 TransactionInfo transactionInformationItem = new TransactionInfo(); 152 transactionInformationItem.payChannel = PayChannel.WECHAT_PAY; 153 transactionInformationItem.transactionId = getUserTitleRequest.fapiaoApplyId; 154 transactionInformationItem.amount = userTitleEntity.amount; 155 issueGeneralFapiaoRequest.fapiaoInformation.transactionInformation.add(transactionInformationItem); 156 } 157 ; 158 159 // 服务商通过匹配交易的商品、服务内容等信息,根据税局的要求,自行选择需要调用的行业开票接口。本示例以通用行业接口为例 160 // 无返回内容 http code202即为请求成功 161 issueGeneralIndustryFapiaoApplications.run(issueGeneralFapiaoRequest); 162 163 // 申请开具通用行业数电发票 164 FapiaoApplicationsEntity fapiaoApplicationsEntity = getFapiaoApplications.run(getFapiaoApplicationsRequest); 165 String fapiaoErrorCode = ""; 166 String fapiaoErrorMessage = ""; 167 for (FapiaoEntity fapiaoEntity : fapiaoApplicationsEntity.fapiaoInformation) { 168 switch (fapiaoEntity.status) { 169 case ISSUED: 170 // 发票开具成功,可以获取蓝票信息 171 FapiaoInfo fapiaoInfo = fapiaoEntity.blueFapiao; 172 break; 173 case REVERSED: 174 // 发票已红冲,可以获取红票信息 175 FapiaoInfo redFapiaoInfo = fapiaoEntity.redFapiao; 176 break; 177 case ISSUE_ACCEPTED: 178 // 发票开票中,等待一段时间后,重试查询电子发票 179 break; 180 case REVERSE_ACCEPTED: 181 // 发票红冲中,等待一段时间后,重试查询电子发票 182 break; 183 case ISSUE_FAILED: 184 // 蓝票开具失败,根据错误原因,解决问题后,换fapiao_id 进行重试开票 185 // 常见的蓝票开具失败原因如: 186 // - 开票参数不符合。税收编码[ 1030201000000000000 ]为汇总项,不可开具,请修改后再试。 187 // - 开票失败乐企发票开具失败:他用或联用企业开票人未授权开票,请修改后再试。或联系主管税务机关。 188 // - 开票预扣授信额度失败,授信额度不足本次开票金额:9.71;剩余可用额度:0.00 —— 在税局重新申请额度后,换fapiao_id 重试 189 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 190 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 191 break; 192 case REVERSE_FAILED: 193 // 蓝票开具失败,根据错误原因,解决问题后,直接进行重试红冲 194 // 常见的红票开具失败原因如: 195 // - 该发票存在进行中的红字确认单 —— 电子税局已存在处理中的红字确认单,商户登录电子税局继续红冲,或者撤销红字确认单后,再重试请求红茶 196 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 197 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 198 break; 199 default: 200 break; 201 } 202 } 203 System.out.println("fapiaoErrorCode:" + fapiaoErrorCode); 204 System.out.println("fapiaoErrorMessage:" + fapiaoErrorMessage); 205 206 // 获取电子发票下载信息 207 GetFapiaoFileDownloadInfoResponse getFapiaoFileDownloadInfoResponse = client.run( 208 getFapiaoFileDownloadInfoRequest); 209 for (FapiaoDownloadInfo fapiaoDownloadInfo : getFapiaoFileDownloadInfoResponse.fapiaoDownloadInfoList) { 210 if (Objects.equals(getFapiaoFileDownloadInfoRequest.fapiaoId, fapiaoDownloadInfo.fapiaoId)) { 211 // 根据【下载发票文件】(https://pay.weixin.qq.com/doc/v3/partner/4015792569)指引,添加参数到downloadUrl,通过GET请求downloadUrl即可获取发票文件流 212 String downloadUrl = fapiaoDownloadInfo.downloadUrl; 213 } 214 } 215 } catch (ApiException e) { 216 // 异常处理逻辑 217 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 218 // 错误:系统错误 219 // 解决方式:稍后重试 220 // 描述:微信支付系统失败,建议等1分钟后再重试 221 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 222 // 错误:限频报错 223 // 解决方式:稍后重试 224 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 225 } else if (e.getErrorCode().equals("ERROR")) { 226 // 错误:系统错误 227 // 解决方式:系统繁忙,请稍后重试 228 // 描述:微信支付系统失败,建议等1分钟后再重试 229 } else if (e.getErrorCode().equals("NO_AUTH")) { 230 // 错误:服务商无权限 231 // 解决方式:根据具体message查看具体没有什么权限 232 // 描述:无权限的场景如: 233 // - 检查服务商商户号是否开通微信支付电子发票服务商权限,检查商户是否接受邀请开通授权微信支付电子发票能力,等待1分钟后重试 234 // - 开票申请单不是当前服务商创建,无权限进行当前操作 235 // - 商户乐企发票能力未授权,请授权相关发票能力 —— 确认通过【获取邀请商户开通服务商电子发票能力】(https://pay.weixin.qq.com/doc/v3/partner/4015941495)邀请追加能力开通,并且商户在电子税局乐企系统上完成授权 236 // - 商户未开通数电发票能力,请开通数电发票 —— 确认通过【获取邀请商户开通服务商电子发票能力】(https://pay.weixin.qq.com/doc/v3/partner/4015941495)邀请能力开通,并且商户在电子税局乐企系统上完成授权 237 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 238 // 错误:签名错误 239 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 240 // 描述:签名报错,需要确认签名材料和签名流程是否正确 241 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 242 // 错误:参数错误 243 // 解决方式:按照报错返回的message,重新输入请求参数 244 // 描述:参数的类型,长度,或者必填选项没有填写等 245 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 246 // 错误:资源错误 247 // 解决方式:请检查发票申请单号是否正确,或在确认用户已提交抬头后重新调用 248 // 描述:根据发票申请单查询不到用户填写的抬头 249 } else if (e.getErrorCode().equals("RULE_LIMIT")) { 250 // 错误:条件限制 251 // 解决方式:按照报错返回的message接引操作后,重新调用 252 // 描述:需要按照指引重新处理的情况如: 253 // - 当前开票场景与发票申请单来源不匹配,请确认参数中的“开票场景”与“发票申请单号”是否符合要求。确认参数中的“开票场景”与“发票申请单号”是否对应 254 // - 商户尚未配置电子发票卡券模版信息,请先调用【创建电子发票卡券模板】(https://pay.weixin.qq.com/doc/v3/partner/4015792562)接口成功后再重新调用 255 // - 商户提交的购买方信息与用户填写的发票抬头不一致,请检查参数中的购买方信息与【获取用户填写抬头信息】(https://pay.weixin.qq.com/doc/v3/partner/4015784260)接口返回的发票抬头信息是否一致 256 // - 开票总金额不能超过发票申请单的总金额,请检查参数中各张发票的“总价税合计”,若是微信支付场景,不能超过微信支付订单的金额,若是非微信支付场景, 257 // 不能超过【获取用户抬头填写链接】(https://pay.weixin.qq.com/doc/v3/partner/4015770776)接口中指定的总金额 258 } else if (e.getErrorCode().equals("ORDER_CLOSED")) { 259 // 错误:订单错误 260 // 解决方式:联系商户,重新申请开具发票 261 // 描述:开票申请已被商户拒绝,无法进行当前操作 262 } else if (e.getErrorCode().equals("RESOURCE_ALREADY_EXISTS")) { 263 // 错误:资源已存在 264 // 解决方式:调用【查询电子发票】接口获取开票结果 265 // 描述:开票请求已被受理,请稍后调用【查询电子发票】接口获取开票结果 266 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 267 // 错误:请求非法,请求参数正确,但是不符合业务规则 268 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 269 // 描述:不符合业务规则的场景如: 270 // - 交易信息中,交易金额必须大于0 271 // - 交易渠道非微信支付时,交易商户订单号必填 272 // - 微信支付交易信息金额错误 273 // - 多个微信支付渠道交易合并开票时,必须是同一个用户的所属交易账单 274 // - 交易单号在支付宝渠道下非法 275 // - 发票申请单申请开票的总金额,不能超过交易信息的总金额 276 // - 非折扣行金额必须为正数,请使用正确的参数重新调用 277 // - 总价税合计与所有发票行单行金额的累加和不相等,请使用正确的参数重新调用 278 // - 折扣行不能作为首行 279 // - 第{}行是折扣行,数电模式下,“单位”,“规格”必须为空,请使用正确的参数重新调用 280 // - 第{}行是折扣行,数电模式下,“货物或应税劳务、服务名称”,“税率”,“税收分类编码”,“优惠政策标识” 必须和被折扣行相同 281 // - 折扣行必须在被折扣行的下一行,请使用正确的参数重新调用 282 // - 折扣行金额必须为负数,请使用正确的参数重新调用 283 // - 折扣行金额绝对值不能超过被折扣行金额,请使用正确的参数重新调用 284 // - 折扣行的数量值非法,请使用正确的参数重新调用 285 // - 当前发票开具中,不可申请重开发票 286 // - 当前发票红冲中,不可申请重开发票 287 // - 申请开票次数,超过最大限制 288 // - 开票请求已被受理,请调用【查询电子发票】(https://pay.weixin.qq.com/doc/v3/partner/4015792567)接口获取开票结果 289 // - 交易单已申请开票,两次申请的交易金额不相同,修改交易金额重试 290 // - 交易可剩余开票金额不足,修改交易订单或者开票金额重试 291 // - 仅在微信支付侧开具的电子发票才允许下载发票文件 292 } else { 293 // 其他类型错误:联系获取支持 294 } 295 } 296 } 297} 298
2.4.5 处理开票成功通知
1package com.tencent.wxpay.fapiao; 2 3import com.google.gson.annotations.SerializedName; 4import com.tencent.wxpay.utils.WXPayUtility; 5import com.tencent.wxpay.utils.WXPayUtility.Notification; 6import java.security.PublicKey; 7import java.util.List; 8import okhttp3.Headers; 9import okhttp3.Response; 10 11public class FapiaoIssuedNotifyDemo { 12 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 13 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 14 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 15 private static String mchid = "19xxxxxxxx"; 16 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 17 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 18 // 商户API证书私钥文件路径,本地文件路径 19 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 20 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 21 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 22 // 微信支付公钥文件路径,本地文件路径 23 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 24 // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 25 private static String apiv3Key = "your apiv3 key"; 26 27 public static class FapiaoIssued { 28 @SerializedName("mchid") 29 public String mchid; 30 31 @SerializedName("sub_mchid") 32 public String subMchId; 33 34 @SerializedName("fapiao_apply_id") 35 public String fapiaoApplyId; 36 37 @SerializedName("fapiao_information") 38 public List<FapiaoInfo> fapiaoInformation; 39 } 40 41 public static class FapiaoInfo { 42 @SerializedName("fapiao_id") 43 public String fapiaoId; 44 45 @SerializedName("fapiao_status") 46 public Status fapiaoStatus; 47 48 @SerializedName("card_status") 49 public CardStatus cardStatus; 50 } 51 52 public enum Status { 53 @SerializedName("ISSUE_ACCEPTED") 54 ISSUE_ACCEPTED, 55 @SerializedName("ISSUED") 56 ISSUED, 57 @SerializedName("REVERSE_ACCEPTED") 58 REVERSE_ACCEPTED, 59 @SerializedName("REVERSED") 60 REVERSED, 61 } 62 63 public enum CardStatus { 64 @SerializedName("INSERT_ACCEPTED") 65 INSERT_ACCEPTED, 66 @SerializedName("INSERTED") 67 INSERTED, 68 @SerializedName("DISCARD_ACCEPTED") 69 DISCARD_ACCEPTED, 70 @SerializedName("DISCARDED") 71 DISCARDED 72 } 73 74 public void FapiaoIssuedNotify(Response httpResponse) { 75 String refundNotifyStr = WXPayUtility.extractBody(httpResponse); // 用户填写抬头通知的原始字符串 76 Headers headers = httpResponse.headers(); // 用户填写抬头通知的Headers 77 PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 78 79 Notification notification = WXPayUtility.parseNotification( 80 apiv3Key, 81 wechatPayPublicKeyId, wechatPayPublicKey, headers, 82 refundNotifyStr); 83 84 // 解析用户填写抬头信息 85 FapiaoIssued fapiaoIssued = WXPayUtility.fromJson(notification.getResource().getCiphertext(), FapiaoIssued.class); 86 87 String notifyMchId = fapiaoIssued.mchid; 88 if (mchid.equals(notifyMchId)) { 89 // 用于服务商获取电子发票信息 90 String subMchId = fapiaoIssued.subMchId; 91 String fapiaoApplyId = fapiaoIssued.fapiaoApplyId; 92 for (FapiaoInfo fapiaoInfo : fapiaoIssued.fapiaoInformation) { 93 Status fapiaoStatus = fapiaoInfo.fapiaoStatus; 94 switch (fapiaoStatus) { 95 case ISSUED: 96 // 使用回调的fapiao_apply_id,调用【查询电子发票】接口,获取发票详情 97 break; 98 case ISSUE_ACCEPTED: 99 // 等待开票成功回调 100 break; 101 default: 102 break; 103 } 104 } 105 } 106 } 107}
2.5 扫码/小程序开票(用户主动授权开票)
2.5.1 业务描述
用户通过商户系统,提供的二维码,或者商户小程序,使用微信支付订单或者非微信支付订单,进行开票的流程。
2.5.2 业务示例代码
1package com.java.partner; 2 3import com.java.demo.AcquireFapiaoTitleUrl; // https://pay.weixin.qq.com/doc/v3/partner/4015770776 4import com.java.demo.AcquireFapiaoTitleUrl.AcquireFapiaoTitleUrlRequest; 5import com.java.demo.AcquireFapiaoTitleUrl.AcquireFapiaoTitleUrlResponse; 6import com.java.demo.AcquireFapiaoTitleUrl.Source; 7import com.java.demo.GetFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792567 8import com.java.demo.GetFapiaoApplications.FapiaoApplicationsEntity; 9import com.java.demo.GetFapiaoApplications.FapiaoEntity; 10import com.java.demo.GetFapiaoApplications.FapiaoInfo; 11import com.java.demo.GetFapiaoApplications.GetFapiaoApplicationsRequest; 12import com.java.demo.GetFapiaoFileDownloadInfo; // https://pay.weixin.qq.com/doc/v3/partner/4015792576 13import com.java.demo.GetFapiaoFileDownloadInfo.FapiaoDownloadInfo; 14import com.java.demo.GetFapiaoFileDownloadInfo.GetFapiaoFileDownloadInfoRequest; 15import com.java.demo.GetFapiaoFileDownloadInfo.GetFapiaoFileDownloadInfoResponse; 16import com.java.demo.GetUserTitle; // https://pay.weixin.qq.com/doc/v3/partner/4015784260 17import com.java.demo.GetUserTitle.BuyerType; 18import com.java.demo.GetUserTitle.GetUserTitleRequest; 19import com.java.demo.GetUserTitle.Scene; 20import com.java.demo.GetUserTitle.UserTitleEntity; 21import com.java.demo.IssueGeneralIndustryFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792574 22import com.java.demo.IssueGeneralIndustryFapiaoApplications.FapiaoBillType; 23import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoInfo; 24import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoItem; 25import com.java.demo.IssueGeneralIndustryFapiaoApplications.IssueGeneralFapiaoRequest; 26import com.java.demo.IssueGeneralIndustryFapiaoApplications.PayChannel; 27import com.java.demo.IssueGeneralIndustryFapiaoApplications.TransactionInfo; 28import com.java.utils.WXPayUtility; 29import java.util.ArrayList; 30import java.util.Objects; 31 32public class IssueByMerchantQrCodeDemo { 33 34 public static void main(String[] args) { 35 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 36 AcquireFapiaoTitleUrl acquireFapiaoTitleUrl = new AcquireFapiaoTitleUrl( 37 "19xxxxxxxx", 38 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 39 "1DDE55AD98Exxxxxxxxxx", 40 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 41 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 42 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 43 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 44 ); 45 46 GetUserTitle getUserTitle = new GetUserTitle( 47 "19xxxxxxxx", 48 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 49 "1DDE55AD98Exxxxxxxxxx", 50 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 51 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 52 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 53 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 54 ); 55 56 IssueGeneralIndustryFapiaoApplications issueGeneralIndustryFapiaoApplications = new IssueGeneralIndustryFapiaoApplications( 57 "19xxxxxxxx", 58 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 59 "1DDE55AD98Exxxxxxxxxx", 60 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 61 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 62 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 63 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 64 ); 65 66 GetFapiaoApplications getFapiaoApplications = new GetFapiaoApplications( 67 "19xxxxxxxx", 68 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 69 "1DDE55AD98Exxxxxxxxxx", 70 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 71 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 72 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 73 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 74 ); 75 76 GetFapiaoFileDownloadInfo getFapiaoFileDownloadInfo = new GetFapiaoFileDownloadInfo( 77 "19xxxxxxxx", 78 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 79 "1DDE55AD98Exxxxxxxxxx", 80 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 81 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 82 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 83 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 84 ); 85 86 AcquireFapiaoTitleUrlRequest acquireFapiaoTitleUrlRequest = new AcquireFapiaoTitleUrlRequest(); 87 acquireFapiaoTitleUrlRequest.subMchid = "1900000109"; 88 // 服务商每次申请开票的唯一标识 89 acquireFapiaoTitleUrlRequest.fapiaoApplyId = "fapiao_20200701_123456"; 90 acquireFapiaoTitleUrlRequest.source = Source.WEB; 91 acquireFapiaoTitleUrlRequest.appid = "wxb1170446a4c0a5a2"; 92 acquireFapiaoTitleUrlRequest.openid = "plN5twRbHym_j-QcqCzstl0HmwEs"; 93 acquireFapiaoTitleUrlRequest.totalAmount = 382895L; 94 acquireFapiaoTitleUrlRequest.sellerName = "深圳市南山区测试商户"; 95 acquireFapiaoTitleUrlRequest.showPhoneCell = true; 96 acquireFapiaoTitleUrlRequest.mustInputPhone = true; 97 acquireFapiaoTitleUrlRequest.showEmailCell = true; 98 acquireFapiaoTitleUrlRequest.mustInputEmail = true; 99 100 GetUserTitleRequest getUserTitleRequest = new GetUserTitleRequest(); 101 getUserTitleRequest.subMchid = "1900000109"; 102 getUserTitleRequest.scene = Scene.WITHOUT_WECHATPAY; 103 getUserTitleRequest.fapiaoApplyId = acquireFapiaoTitleUrlRequest.fapiaoApplyId; 104 105 IssueGeneralFapiaoRequest issueGeneralFapiaoRequest = new IssueGeneralFapiaoRequest(); 106 issueGeneralFapiaoRequest.subMchid = "1900000109"; 107 issueGeneralFapiaoRequest.fapiaoApplyId = acquireFapiaoTitleUrlRequest.fapiaoApplyId; 108 issueGeneralFapiaoRequest.buyerInformation = new IssueGeneralIndustryFapiaoApplications.UserTitleEntity(); 109 110 issueGeneralFapiaoRequest.fapiaoInformation = new IssueGeneralFapiaoInfo(); 111 // 此次申请开票下,申请开具的一张发票的唯一标识,如果需要红冲后换开发票、开票失败后重开发票,使用相同的fapiao_apply_id,更换不同的fapiao_id即可 112 issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId = "20200701123456"; 113 // 申请开票的金额,支持小于本次申请开票对应的所有交易信息的交易金额之和 114 issueGeneralFapiaoRequest.fapiaoInformation.totalAmount = 1123L; 115 issueGeneralFapiaoRequest.fapiaoInformation.items = new ArrayList<>(); 116 { 117 IssueGeneralFapiaoItem itemsItem = new IssueGeneralFapiaoItem(); 118 itemsItem.taxCode = "3010101020203000000"; 119 itemsItem.goodsName = "*xxx*服务"; 120 itemsItem.specification = "米"; 121 itemsItem.unit = "米"; 122 itemsItem.quantity = 100000000L; 123 itemsItem.totalAmount = 429900L; 124 itemsItem.taxRate = 1300L; 125 itemsItem.discount = false; 126 itemsItem.preferentialPolicyCode = 1L; 127 issueGeneralFapiaoRequest.fapiaoInformation.items.add(itemsItem); 128 } 129 ; 130 131 issueGeneralFapiaoRequest.fapiaoInformation.exportBusinessPolicyCode = 1L; 132 issueGeneralFapiaoRequest.fapiaoInformation.vatRefundLevyCode = 1L; 133 issueGeneralFapiaoRequest.fapiaoInformation.billingPersonId = "128279891283"; 134 issueGeneralFapiaoRequest.fapiaoInformation.billingPerson = "example_billing_person"; 135 issueGeneralFapiaoRequest.fapiaoInformation.fapiaoBillType = FapiaoBillType.COMM_FAPIAO; 136 issueGeneralFapiaoRequest.fapiaoInformation.remark = "备注"; 137 138 GetFapiaoApplicationsRequest getFapiaoApplicationsRequest = new GetFapiaoApplicationsRequest(); 139 getFapiaoApplicationsRequest.fapiaoApplyId = acquireFapiaoTitleUrlRequest.fapiaoApplyId; 140 getFapiaoApplicationsRequest.subMchid = "1900000109"; 141 getFapiaoApplicationsRequest.fapiaoId = issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId; 142 143 GetFapiaoFileDownloadInfoRequest getFapiaoFileDownloadInfoRequest = new GetFapiaoFileDownloadInfoRequest(); 144 getFapiaoFileDownloadInfoRequest.fapiaoApplyId = acquireFapiaoTitleUrlRequest.fapiaoApplyId; 145 getFapiaoFileDownloadInfoRequest.subMchid = "1900000109"; 146 getFapiaoFileDownloadInfoRequest.fapiaoId = issueGeneralFapiaoRequest.fapiaoInformation.fapiaoId; 147 148 try { 149 AcquireFapiaoTitleUrlResponse acquireFapiaoTitleUrlResponse = acquireFapiaoTitleUrl.run( 150 acquireFapiaoTitleUrlRequest); 151 // 通过指引https://developers.weixin.qq.com/miniprogram/dev/api/navigate/wx.navigateToMiniProgram.html,使用返回的参数,跳转到指定的小程序,展示给用户 152 String miniprogramAppid = acquireFapiaoTitleUrlResponse.miniprogramAppid; 153 String miniprogramPath = acquireFapiaoTitleUrlResponse.miniprogramPath; 154 String miniprogramUserName = acquireFapiaoTitleUrlResponse.miniprogramUserName; 155 156 // 用户填写抬头信息完成后,会发送回调请求,然后就可以获取用户填写的抬头信息 157 UserTitleEntity userTitleEntity = getUserTitle.run(getUserTitleRequest); 158 // 申请开票的用户,提交的留言信息 159 System.out.println("userApplyMessage:" + userTitleEntity.userApplyMessage); 160 if (userTitleEntity.type.equals(BuyerType.INDIVIDUAL)) { 161 issueGeneralFapiaoRequest.buyerInformation.type = IssueGeneralIndustryFapiaoApplications.BuyerType.INDIVIDUAL; 162 } else { 163 issueGeneralFapiaoRequest.buyerInformation.type = IssueGeneralIndustryFapiaoApplications.BuyerType.ORGANIZATION; 164 } 165 issueGeneralFapiaoRequest.buyerInformation.name = userTitleEntity.name; 166 issueGeneralFapiaoRequest.buyerInformation.taxpayerId = userTitleEntity.taxpayerId; 167 issueGeneralFapiaoRequest.buyerInformation.address = userTitleEntity.address; 168 issueGeneralFapiaoRequest.buyerInformation.telephone = userTitleEntity.telephone; 169 issueGeneralFapiaoRequest.buyerInformation.bankName = userTitleEntity.bankName; 170 issueGeneralFapiaoRequest.buyerInformation.bankAccount = userTitleEntity.bankAccount; 171 issueGeneralFapiaoRequest.buyerInformation.phone = issueGeneralIndustryFapiaoApplications.encrypt( 172 userTitleEntity.phone); 173 issueGeneralFapiaoRequest.buyerInformation.email = issueGeneralIndustryFapiaoApplications.encrypt( 174 userTitleEntity.email); 175 // 返回当前交易信息对应的交易金额 176 issueGeneralFapiaoRequest.buyerInformation.amount = userTitleEntity.amount; 177 if (userTitleEntity.fapiaoBillType.equals(GetUserTitle.FapiaoBillType.COMM_FAPIAO)) { 178 issueGeneralFapiaoRequest.buyerInformation.fapiaoBillType = FapiaoBillType.COMM_FAPIAO; 179 } else { 180 issueGeneralFapiaoRequest.buyerInformation.fapiaoBillType = FapiaoBillType.VAT_FAPIAO; 181 } 182 183 issueGeneralFapiaoRequest.fapiaoInformation.transactionInformation = new ArrayList<>(); 184 { 185 TransactionInfo transactionInformationItem = new TransactionInfo(); 186 transactionInformationItem.payChannel = PayChannel.WECHAT_PAY; 187 // 服务商系统自行收集该笔发票申请对应交易信息的交易单号以及交易金额 188 transactionInformationItem.transactionId = "x23189jojfiujei1o08"; 189 transactionInformationItem.amount = 1231243L; 190 issueGeneralFapiaoRequest.fapiaoInformation.transactionInformation.add(transactionInformationItem); 191 } 192 ; 193 194 // 服务商通过匹配交易的商品、服务内容等信息,根据税局的要求,自行选择需要调用的行业开票接口。本示例以通用行业接口为例 195 // 无返回内容 http code202即为请求成功 196 issueGeneralIndustryFapiaoApplications.run(issueGeneralFapiaoRequest); 197 198 FapiaoApplicationsEntity fapiaoApplicationsEntity = getFapiaoApplications.run(getFapiaoApplicationsRequest); 199 String fapiaoErrorCode = ""; 200 String fapiaoErrorMessage = ""; 201 for (FapiaoEntity fapiaoEntity : fapiaoApplicationsEntity.fapiaoInformation) { 202 switch (fapiaoEntity.status) { 203 case ISSUED: 204 // 发票开具成功,可以获取蓝票信息 205 FapiaoInfo fapiaoInfo = fapiaoEntity.blueFapiao; 206 break; 207 case REVERSED: 208 // 发票已红冲,可以获取红票信息 209 FapiaoInfo redFapiaoInfo = fapiaoEntity.redFapiao; 210 break; 211 case ISSUE_ACCEPTED: 212 // 发票开票中,等待一段时间后,重试查询电子发票 213 break; 214 case REVERSE_ACCEPTED: 215 // 发票红冲中,等待一段时间后,重试查询电子发票 216 break; 217 case ISSUE_FAILED: 218 // 蓝票开具失败,根据错误原因,解决问题后,换fapiao_id 进行重试开票 219 // 常见的蓝票开具失败原因如: 220 // - 开票参数不符合。税收编码[ 1030201000000000000 ]为汇总项,不可开具,请修改后再试。 221 // - 开票失败乐企发票开具失败:他用或联用企业开票人未授权开票,请修改后再试。或联系主管税务机关。 222 // - 开票预扣授信额度失败,授信额度不足本次开票金额:9.71;剩余可用额度:0.00 —— 在税局重新申请额度后,换fapiao_id 重试 223 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 224 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 225 break; 226 case REVERSE_FAILED: 227 // 蓝票开具失败,根据错误原因,解决问题后,直接进行重试红冲 228 // 常见的红票开具失败原因如: 229 // - 该发票存在进行中的红字确认单 —— 电子税局已存在处理中的红字确认单,商户登录电子税局继续红冲,或者撤销红字确认单后,再重试请求红茶 230 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 231 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 232 break; 233 default: 234 break; 235 } 236 } 237 System.out.println("fapiaoErrorCode:" + fapiaoErrorCode); 238 System.out.println("fapiaoErrorMessage:" + fapiaoErrorMessage); 239 240 // 获取电子发票下载信息 241 GetFapiaoFileDownloadInfoResponse getFapiaoFileDownloadInfoResponse = getFapiaoFileDownloadInfo.run( 242 getFapiaoFileDownloadInfoRequest); 243 for (FapiaoDownloadInfo fapiaoDownloadInfo : getFapiaoFileDownloadInfoResponse.fapiaoDownloadInfoList) { 244 if (Objects.equals(getFapiaoFileDownloadInfoRequest.fapiaoId, fapiaoDownloadInfo.fapiaoId)) { 245 // 根据【下载发票文件】(https://pay.weixin.qq.com/doc/v3/partner/4015792569)指引,添加参数到downloadUrl,通过GET请求downloadUrl即可获取发票文件流 246 String downloadUrl = fapiaoDownloadInfo.downloadUrl; 247 } 248 } 249 } catch (WXPayUtility.ApiException e) { 250 // 处理异常处理逻辑 251 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 252 // 错误:系统错误 253 // 解决方式:稍后重试 254 // 描述:微信支付系统失败,建议等1分钟后再重试 255 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 256 // 错误:限频报错 257 // 解决方式:稍后重试 258 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 259 } else if (e.getErrorCode().equals("ERROR")) { 260 // 错误:系统错误 261 // 解决方式:系统繁忙,请稍后重试 262 // 描述:微信支付系统失败,建议等1分钟后再重试 263 } else if (e.getErrorCode().equals("NO_AUTH")) { 264 // 错误:商户无权限 265 // 解决方式:按照报错返回的message,参照指引解决问题 266 // 描述:无权限的场景如下: 267 // - 检查服务商商户号是否开通微信支付电子发票服务商权限,检查商户是否接受邀请开通授权微信支付电子发票能力,等待1分钟后重试 268 // - 开票申请单不是当前服务商创建,无权限进行当前操作 269 // - 商户乐企发票能力未授权,请授权相关发票能力 —— 确认通过【获取邀请商户开通服务商电子发票能力】邀请能力开通,并且商户在电子税局乐企系统上完成授权 270 // - 商户未开通数电发票能力,请开通数电发票 —— 确认通过【获取邀请商户开通服务商电子发票能力】邀请能力开通,并且商户在电子税局乐企系统上完成授权 271 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 272 // 错误:签名错误 273 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 274 // 描述:签名报错,需要确认签名材料和签名流程是否正确 275 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 276 // 错误:参数错误 277 // 解决方式:按照报错返回的message,重新输入请求参数 278 // 描述:参数的类型,长度,或者必填选项没有填写等 279 } else if (e.getErrorCode().equals("RULE_LIMIT")) { 280 // 错误:条件限制 281 // 解决方式:按照报错返回的message接引操作后,重新调用 282 // 描述:需要按照指引重新处理的情况如: 283 // - 商户获取抬头链接指定的用户与已提交抬头的用户不一致,请确认发票申请单号对应的用户是否正确 284 // - 当前开票场景与发票申请单来源不匹配,请确认参数中的“开票场景”与“发票申请单号”是否符合要求。确认参数中的“开票场景”与“发票申请单号”是否对应 285 // - 商户尚未配置电子发票卡券模版信息,请先调用【创建电子发票卡券模板】接口成功后再重新调用 286 // - 商户提交的购买方信息与用户填写的发票抬头不一致,请检查参数中的购买方信息与【获取用户填写的抬头】接口返回的发票抬头信息是否一致 287 // - 开票总金额不能超过发票申请单的总金额,请检查参数中各张发票的“总价税合计”,若是微信支付场景,不能超过微信支付订单的金额,若是非微信支付场景,不能超过【获取抬头填写链接】接口中指定的总金额 288 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 289 // 错误:请求非法,请求参数正确,但是不符合业务规则 290 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 291 // 描述:不符合业务规则的场景如: 292 // - 传入的参数“用户是否必须填写手机号”在“是否需要展示手机号填写栏”为false时不能为true(位于/url/must_input_phone),请检查参数格式是否符合要求 293 // - 传入的参数“用户是否必须填写邮箱地址”在“是否需要展示邮箱地址填写栏”为false时不能为true(位于/url/must_input_email),请检查参数格式是否符合要求 294 // - 交易信息中,交易金额必须大于0 295 // - 交易渠道非微信支付时,交易商户订单号必填 296 // - 微信支付交易信息金额错误 297 // - 多个微信支付渠道交易合并开票时,必须是同一个用户的所属交易账单 298 // - 交易单号在支付宝渠道下非法 299 // - 发票申请单申请开票的总金额,不能超过交易信息的总金额 300 // - 非折扣行金额必须为正数,请使用正确的参数重新调用 301 // - 总价税合计与所有发票行单行金额的累加和不相等,请使用正确的参数重新调用 302 // - 折扣行不能作为首行 303 // - 第{}行是折扣行,数电模式下,“单位”,“规格”必须为空,请使用正确的参数重新调用 304 // - 第{}行是折扣行,数电模式下,“货物或应税劳务、服务名称”,“税率”,“税收分类编码”,“优惠政策标识” 必须和被折扣行相同 305 // - 折扣行必须在被折扣行的下一行,请使用正确的参数重新调用 306 // - 折扣行金额必须为负数,请使用正确的参数重新调用 307 // - 折扣行金额绝对值不能超过被折扣行金额,请使用正确的参数重新调用 308 // - 折扣行的数量值非法,请使用正确的参数重新调用 309 // - 当前发票开具中,不可申请重开发票 310 // - 当前发票红冲中,不可申请重开发票 311 // - 申请开票次数,超过最大限制 312 // - 开票请求已被受理,请调用【查询电子发票】接口获取开票结果 313 // - 交易单已申请开票,两次申请的交易金额不相同,修改交易金额重试 314 // - 交易可剩余开票金额不足,修改交易订单或者开票金额重试 315 } else { 316 // 其他类型错误:联系支持 317 } 318 } 319 } 320}
2.5.3 用户操作

用户完成操作后,即可通过2.3.3处理用户填写抬头完成通知,然后获取到用户填写的对应的抬头信息。
2.6 第三方/自建模式插卡
2.6.1 业务描述
商户通过【获取用户填写抬头链接】的方式或者通过自建发票系统收集用户抬头信息,在第三方、自建的发票平台上,成功开具发票。可以通过微信支付,将该发票交付到用户的微信卡包中。如果是微信支付的交易订单开票场景,无需用户授权,即可插卡。
2.6.2 业务示例代码
1package com.java.partner; 2 3import com.java.demo.InsertCards; // https://pay.weixin.qq.com/doc/v3/partner/4015792579 4import com.java.demo.InsertCards.BuyerType; 5import com.java.demo.InsertCards.ExtraInfo; 6import com.java.demo.InsertCards.FapiaoCardInfo; 7import com.java.demo.InsertCards.FapiaoItem; 8import com.java.demo.InsertCards.InsertCardsRequest; 9import com.java.demo.InsertCards.Scene; 10import com.java.demo.InsertCards.SellerInfo; 11import com.java.demo.InsertCards.TaxPreferMark; 12import com.java.demo.InsertCards.UserTitleEntity; 13import com.java.demo.UploadFapiaoFile; // https://pay.weixin.qq.com/doc/v3/partner/4015792580 14import com.java.demo.UploadFapiaoFile.DigestType; 15import com.java.demo.UploadFapiaoFile.FapiaoFileType; 16import com.java.demo.UploadFapiaoFile.FileMeta; 17import com.java.demo.UploadFapiaoFile.UploadFapiaoFileRequest; 18import com.java.demo.UploadFapiaoFile.UploadFapiaoFileResponse; 19import com.java.utils.WXPayUtility; 20import java.util.ArrayList; 21 22public class InsertInvoiceCardDemo { 23 24 public static void main(String[] args) { 25 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 26 UploadFapiaoFile uploadFapiaoFile = new UploadFapiaoFile( 27 "19xxxxxxxx", 28 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 29 "1DDE55AD98Exxxxxxxxxx", 30 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 31 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 32 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 33 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 34 ); 35 36 InsertCards insertCards = new InsertCards( 37 "19xxxxxxxx", 38 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 39 "1DDE55AD98Exxxxxxxxxx", 40 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 41 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 42 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 43 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 44 ); 45 46 UploadFapiaoFileRequest uploadFapiaoFileRequest = new UploadFapiaoFileRequest(); 47 uploadFapiaoFileRequest.file = "file_bytes".getBytes(); 48 uploadFapiaoFileRequest.meta = new FileMeta(); 49 uploadFapiaoFileRequest.meta.subMchid = "1900000109"; 50 uploadFapiaoFileRequest.meta.fileType = FapiaoFileType.PDF; 51 uploadFapiaoFileRequest.meta.digestAlogrithm = DigestType.SM3; 52 uploadFapiaoFileRequest.meta.digest = "addca90a6a290b9642dbaccffdf01e8c22aa348940b2e96754169ba08c19e5db"; 53 54 InsertCardsRequest insertCardsRequest = new InsertCardsRequest(); 55 // 申请插卡的唯一标识 56 insertCardsRequest.fapiaoApplyId = "4200000444201910177461284488"; 57 insertCardsRequest.subMchid = "1900000109"; 58 insertCardsRequest.scene = Scene.WITH_WECHATPAY; 59 insertCardsRequest.buyerInformation = new UserTitleEntity(); 60 insertCardsRequest.buyerInformation.type = BuyerType.INDIVIDUAL; 61 insertCardsRequest.buyerInformation.name = "深圳市南山区测试企业"; 62 insertCardsRequest.buyerInformation.taxpayerId = "202003261233701778"; 63 insertCardsRequest.buyerInformation.address = "深圳市南山区深南大道10000号"; 64 insertCardsRequest.buyerInformation.telephone = "075512345678"; 65 insertCardsRequest.buyerInformation.bankName = "测试银行"; 66 insertCardsRequest.buyerInformation.bankAccount = "62001234567890"; 67 insertCardsRequest.buyerInformation.phone = insertCards.encrypt("phone"); 68 insertCardsRequest.buyerInformation.email = insertCards.encrypt("email"); 69 insertCardsRequest.buyerInformation.amount = 1000L; 70 71 try { 72 UploadFapiaoFileResponse uploadFapiaoFileResponse = uploadFapiaoFile.run(uploadFapiaoFileRequest); 73 74 insertCardsRequest.fapiaoCardInformation = new ArrayList<>(); 75 { 76 FapiaoCardInfo fapiaoCardInformationItem = new FapiaoCardInfo(); 77 // 【上传电子发票文件】调用成功后,会立即返回对应文件的fapiaoMediaId 78 fapiaoCardInformationItem.fapiaoMediaId = uploadFapiaoFileResponse.fapiaoMediaId; 79 // 申请插卡请求的发票信息,要和上传的发票文件票面内容保持一致 80 fapiaoCardInformationItem.fapiaoNumber = "12897794"; 81 fapiaoCardInformationItem.fapiaoCode = "044001911211"; 82 fapiaoCardInformationItem.fapiaoTime = "2020-07-01T12:00:00+08:00"; 83 fapiaoCardInformationItem.checkCode = "69001808340631374774"; 84 fapiaoCardInformationItem.password = "006>299-375/326>2+7/_*0-+<351059<80<4*_/5>+<11631+*3030/5*37+/-243159658+013>3409*044>4-/1+/9->*>69501*6++1997--21"; 85 fapiaoCardInformationItem.totalAmount = 382895L; 86 fapiaoCardInformationItem.taxAmount = 44050L; 87 fapiaoCardInformationItem.amount = 338845L; 88 fapiaoCardInformationItem.sellerInformation = new SellerInfo(); 89 fapiaoCardInformationItem.sellerInformation.name = "深圳市南山区测试公司"; 90 fapiaoCardInformationItem.sellerInformation.taxpayerId = "202003261233701778"; 91 fapiaoCardInformationItem.sellerInformation.address = "深圳市南山区深南大道10000号"; 92 fapiaoCardInformationItem.sellerInformation.telephone = "075512345678"; 93 fapiaoCardInformationItem.sellerInformation.bankName = "测试银行"; 94 fapiaoCardInformationItem.sellerInformation.bankAccount = "62001234567890"; 95 fapiaoCardInformationItem.extraInformation = new ExtraInfo(); 96 fapiaoCardInformationItem.extraInformation.drawer = "张三"; 97 fapiaoCardInformationItem.items = new ArrayList<>(); 98 { 99 FapiaoItem itemsItem = new FapiaoItem(); 100 itemsItem.taxCode = "3010101020203000000"; 101 itemsItem.goodsName = "出租汽车客运服务"; 102 itemsItem.specification = "A4"; 103 itemsItem.unit = "次"; 104 itemsItem.quantity = 100000000L; 105 itemsItem.unitPrice = 380442000000L; 106 itemsItem.amount = 380442L; 107 itemsItem.taxAmount = 49458L; 108 itemsItem.totalAmount = 429900L; 109 itemsItem.taxRate = 1300L; 110 itemsItem.taxPreferMark = TaxPreferMark.NO_FAVORABLE; 111 itemsItem.discount = false; 112 fapiaoCardInformationItem.items.add(itemsItem); 113 } 114 fapiaoCardInformationItem.remark = "备注"; 115 insertCardsRequest.fapiaoCardInformation.add(fapiaoCardInformationItem); 116 } 117 insertCards.run(insertCardsRequest); 118 } catch (WXPayUtility.ApiException e) { 119 // 处理异常处理逻辑 120 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 121 // 错误:系统错误 122 // 解决方式:稍后重试 123 // 描述:微信支付系统失败,建议等1分钟后再重试 124 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 125 // 错误:限频报错 126 // 解决方式:稍后重试 127 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 128 } else if (e.getErrorCode().equals("ERROR")) { 129 // 错误:系统错误 130 // 解决方式:系统繁忙,请稍后重试 131 // 描述:微信支付系统失败,建议等1分钟后再重试 132 } else if (e.getErrorCode().equals("NO_AUTH")) { 133 // 错误:商户无权限 134 // 解决方式:按照报错返回的message,参照指引解决问题 135 // 描述:商户无权限访问,请确认是否已开通相关权限 —— 开通微信支付电子发票服务商权限,并且检查商户是否接受邀请开通授权微信支付电子发票能力,等1分钟后重试。 136 // - 根据发票申请单查询不到用户提交的授权,请检查发票申请单号是否正确,或在确认用户已提交授权后重新调用 137 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 138 // 错误:签名错误 139 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 140 // 描述:签名报错,需要确认签名材料和签名流程是否正确 141 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 142 // 错误:参数错误 143 // 解决方式:按照报错返回的message,重新输入请求参数 144 // 描述:参数的类型,长度,或者必填选项没有填写等 145 // - 传入的参数“电子发票文件”长度超过限制(位于/body/file),请检查参数格式是否符合要求 146 // - 传入的参数“文件摘要”与上传的电子发票文件不匹配(位于/body/meta/digest),请检查参数格式是否符合要求 147 } else if (e.getErrorCode().equals("RULE_LIMIT")) { 148 // 错误:条件限制 149 // 解决方式:按照报错返回的message接引操作后,重新调用 150 // 描述:需要按照指引重新处理的情况如: 151 // - 商户尚未配置电子发票卡券模版信息,请先调用【创建电子发票卡券模板】(https://pay.weixin.qq.com/doc/v3/partner/4015792562)接口成功后再重新调用 152 // - 当前开票场景与发票申请单来源不匹配,请确认参数中的“开票场景”与“发票申请单号”是否符合要求 153 // - 商户提交的购买方信息与用户填写的发票抬头不一致,请检查参数中的购买方信息与【获取用户填写抬头信息】(https://pay.weixin.qq.com/doc/v3/partner/4015784260)接口返回的发票抬头信息是否一致 154 // - 微信支付订单状态不满足开票要求,请确认微信支付订单已完成支付 155 // - 该间连商户未绑定可开票的直连商户 156 // - 微信支付订单所属商户与当前开票商户不一致,请检查微信支付订单号是否属于商户,若是服务商模式则检查是否属于子商户 157 // - 开票总金额不能超过微信支付订单的总金额,请检查参数中各张发票的“总价税合计” 158 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 159 // 错误:请求非法,请求参数正确,但是不符合业务规则 160 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 161 // 描述:不符合业务规则的场景如: 162 // - 电子发票文件内容无法解析,请检查文件格式是否正确 163 // - 请求中的发票代码、发票号码或校验码与电子发票文件不匹配,请使用正确的参数重新调用 164 } else { 165 // 其他类型错误:联系获取支持 166 } 167 } 168 } 169} 170
插入成功后,用户会在微信上收到卡包通知

2.6.3 处理发票插入用户卡包成功通知
1package com.tencent.wxpay.fapiao; 2 3import com.google.gson.annotations.SerializedName; 4import com.tencent.wxpay.fapiao.FapiaoIssuedNotifyDemo.CardStatus; 5import com.tencent.wxpay.fapiao.FapiaoIssuedNotifyDemo.FapiaoInfo; 6import com.tencent.wxpay.fapiao.FapiaoIssuedNotifyDemo.Status; 7import com.tencent.wxpay.fapiao.UserTitleNotifyDemo.UserTitle; 8import com.tencent.wxpay.utils.WXPayUtility; 9import com.tencent.wxpay.utils.WXPayUtility.Notification; 10import java.security.PublicKey; 11import java.util.List; 12import okhttp3.Headers; 13import okhttp3.Response; 14 15public class CardInsertedNotifyDemo { 16 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 17 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 18 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 19 private static String mchid = "19xxxxxxxx"; 20 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 21 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 22 // 商户API证书私钥文件路径,本地文件路径 23 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 24 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 25 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 26 // 微信支付公钥文件路径,本地文件路径 27 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 28 // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 29 private static String apiv3Key = "your apiv3 key"; 30 31 public static class CardInserted { 32 @SerializedName("mchid") 33 public String mchid; 34 35 @SerializedName("sub_mchid") 36 public String subMchId; 37 38 @SerializedName("fapiao_apply_id") 39 public String fapiaoApplyId; 40 41 @SerializedName("fapiao_information") 42 public List<FapiaoInfo> fapiaoInformation; 43 } 44 45 public static class FapiaoInfo { 46 @SerializedName("fapiao_id") 47 public String fapiaoId; 48 49 @SerializedName("fapiao_status") 50 public Status fapiaoStatus; 51 52 @SerializedName("card_status") 53 public CardStatus cardStatus; 54 } 55 56 public enum Status { 57 @SerializedName("ISSUE_ACCEPTED") 58 ISSUE_ACCEPTED, 59 @SerializedName("ISSUED") 60 ISSUED, 61 @SerializedName("REVERSE_ACCEPTED") 62 REVERSE_ACCEPTED, 63 @SerializedName("REVERSED") 64 REVERSED, 65 } 66 67 public enum CardStatus { 68 @SerializedName("INSERT_ACCEPTED") 69 INSERT_ACCEPTED, 70 @SerializedName("INSERTED") 71 INSERTED, 72 @SerializedName("DISCARD_ACCEPTED") 73 DISCARD_ACCEPTED, 74 @SerializedName("DISCARDED") 75 DISCARDED 76 } 77 78 public void cardInsertedNotify(Response httpResponse) { 79 String refundNotifyStr = WXPayUtility.extractBody(httpResponse); // 用户填写抬头通知的原始字符串 80 Headers headers = httpResponse.headers(); // 用户填写抬头通知的Headers 81 PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 82 83 Notification notification = WXPayUtility.parseNotification( 84 apiv3Key, 85 wechatPayPublicKeyId, wechatPayPublicKey, headers, 86 refundNotifyStr); 87 88 // 解析用户填写抬头信息 89 CardInserted cardInserted = WXPayUtility.fromJson(notification.getResource().getCiphertext(), CardInserted.class); 90 91 String notifyMchId = cardInserted.mchid; 92 if (mchid.equals(notifyMchId)) { 93 for (FapiaoInfo fapiaoInfo : cardInserted.fapiaoInformation) { 94 CardStatus cardStatus = fapiaoInfo.cardStatus; 95 switch (cardStatus) { 96 case INSERTED: 97 // 插卡成功 98 break; 99 case DISCARDED: 100 // 发票卡劵已作废,已从用户卡包移除 101 break; 102 case INSERT_ACCEPTED: 103 // 插卡已受理,等待插卡成功回调 104 break; 105 case DISCARD_ACCEPTED: 106 // 作废申请已受理 107 break; 108 default: 109 break; 110 } 111 } 112 } 113 } 114}
2.7 开具/冲红电子发票
2.7.1 业务描述
申请对商户在微信支付电子发票系统申请开具并且已经开具成功的电子发票进行冲红,然后自动作废已经插入用户卡包的发票。
2.7.2 业务示例代码
1package com.java.partner; 2 3import com.java.demo.GetFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792576 4import com.java.demo.GetFapiaoApplications.FapiaoApplicationsEntity; 5import com.java.demo.GetFapiaoApplications.FapiaoEntity; 6import com.java.demo.GetFapiaoApplications.FapiaoInfo; 7import com.java.demo.GetFapiaoApplications.GetFapiaoApplicationsRequest; 8import com.java.demo.ReverseFapiaoApplications; // https://pay.weixin.qq.com/doc/v3/partner/4015792575 9import com.java.demo.ReverseFapiaoApplications.ReverseFapiaoApplicationsRequest; 10import com.java.demo.ReverseFapiaoApplications.ReverseFapiaoInfo; 11import com.java.utils.WXPayUtility; 12import java.util.ArrayList; 13 14public class ReverseFapiaoDemo { 15 16 public static void main(String[] args) { 17 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 18 ReverseFapiaoApplications reverseFapiaoApplications = new ReverseFapiaoApplications( 19 "19xxxxxxxx", 20 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 21 "1DDE55AD98Exxxxxxxxxx", 22 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 23 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 24 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 25 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 26 ); 27 28 GetFapiaoApplications getFapiaoApplications = new GetFapiaoApplications( 29 "19xxxxxxxx", 30 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 31 "1DDE55AD98Exxxxxxxxxx", 32 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 33 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 34 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 35 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 36 ); 37 38 // 现阶段红冲只支持全额红冲,不支持部分红冲 39 ReverseFapiaoApplicationsRequest reverseFapiaoApplicationsRequest = new ReverseFapiaoApplicationsRequest(); 40 // 申请红冲发票,对应蓝票开票时申请开票的唯一标识 41 reverseFapiaoApplicationsRequest.fapiaoApplyId = "4200000444201910177461284488"; 42 reverseFapiaoApplicationsRequest.subMchid = "1900000109"; 43 // ISSUE_ERROR: 开票有误 44 // SALES_RETURN: 销货退回 45 // SERVICE_SUSPENTION: 服务中止 46 // SALES_DISCOUNT: 销售折让 47 reverseFapiaoApplicationsRequest.reverseReason = "ISSUE_ERROR"; 48 reverseFapiaoApplicationsRequest.fapiaoInformation = new ArrayList<>(); 49 { 50 ReverseFapiaoInfo fapiaoInformationItem = new ReverseFapiaoInfo(); 51 // 申请红冲的发票对应蓝票的fapiaoId 52 fapiaoInformationItem.fapiaoId = "20200701123456"; 53 fapiaoInformationItem.fapiaoNumber = "12897794"; 54 reverseFapiaoApplicationsRequest.fapiaoInformation.add(fapiaoInformationItem); 55 }; 56 57 GetFapiaoApplicationsRequest getFapiaoApplicationsRequest = new GetFapiaoApplicationsRequest(); 58 getFapiaoApplicationsRequest.fapiaoApplyId = reverseFapiaoApplicationsRequest.fapiaoApplyId; 59 getFapiaoApplicationsRequest.subMchid = "1900000109"; 60 try { 61 // 无返回内容, http code 202即为申请红冲成功 62 reverseFapiaoApplications.run(reverseFapiaoApplicationsRequest); 63 64 // 申请开具通用行业数电发票 65 FapiaoApplicationsEntity fapiaoApplicationsEntity = getFapiaoApplications.run(getFapiaoApplicationsRequest); 66 String fapiaoErrorCode = ""; 67 String fapiaoErrorMessage = ""; 68 for (FapiaoEntity fapiaoEntity : fapiaoApplicationsEntity.fapiaoInformation) { 69 switch (fapiaoEntity.status) { 70 case ISSUED: 71 // 发票开具成功,可以获取蓝票信息 72 FapiaoInfo fapiaoInfo = fapiaoEntity.blueFapiao; 73 break; 74 case REVERSED: 75 // 发票已红冲,可以获取红票信息 76 FapiaoInfo redFapiaoInfo = fapiaoEntity.redFapiao; 77 break; 78 case ISSUE_ACCEPTED: 79 // 发票开票中,等待一段时间后,重试查询电子发票 80 break; 81 case REVERSE_ACCEPTED: 82 // 发票红冲中,等待一段时间后,重试查询电子发票 83 break; 84 case ISSUE_FAILED: 85 // 蓝票开具失败,根据错误原因,解决问题后,换fapiao_id 进行重试开票 86 // 常见的蓝票开具失败原因如: 87 // - 开票参数不符合。税收编码[ 1030201000000000000 ]为汇总项,不可开具,请修改后再试。 88 // - 开票失败乐企发票开具失败:他用或联用企业开票人未授权开票,请修改后再试。或联系主管税务机关。 89 // - 开票预扣授信额度失败,授信额度不足本次开票金额:9.71;剩余可用额度:0.00 —— 在税局重新申请额度后,换fapiao_id 重试 90 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 91 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 92 break; 93 case REVERSE_FAILED: 94 // 蓝票开具失败,根据错误原因,解决问题后,直接进行重试红冲 95 // 常见的红票开具失败原因如: 96 // - 该发票存在进行中的红字确认单 —— 电子税局已存在处理中的红字确认单,商户登录电子税局继续红冲,或者撤销红字确认单后,再重试请求冲红 97 fapiaoErrorCode = fapiaoEntity.fapiaoErrorCode; 98 fapiaoErrorMessage = fapiaoEntity.fapiaoErrorMessage; 99 break; 100 default: 101 break; 102 } 103 } 104 System.out.println("fapiaoErrorCode:" + fapiaoErrorCode); 105 System.out.println("fapiaoErrorMessage:" + fapiaoErrorMessage); 106 } catch (WXPayUtility.ApiException e) { 107 // 异常处理逻辑 108 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 109 // 错误:系统错误 110 // 解决方式:稍后重试 111 // 描述:微信支付系统失败,建议等1分钟后再重试 112 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 113 // 错误:限频报错 114 // 解决方式:稍后重试 115 // 描述: 接口频率限制,一定要原单重试,建议等1分钟后再重试 116 } else if (e.getErrorCode().equals("ERROR")) { 117 // 错误:系统错误 118 // 解决方式:系统繁忙,请稍后重试 119 // 描述:微信支付系统失败,建议等1分钟后再重试 120 } else if (e.getErrorCode().equals("NO_AUTH")) { 121 // 错误:无权限 122 // 解决方式:按照报错返回的message,查看对应的解决指引 123 // 描述:无权限的情况如: 124 // - 商户无权限访问,请确认是否已开通相关权限。—— 开通微信支付电子发票服务商权限,并且检查商户是否接受邀请开通授权微信支付电子发票能力,等1分钟后重试。 125 // - 开票申请单不是当前服务商创建,无权限进行当前操作 126 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 127 // 错误:签名错误 128 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 129 // 描述:签名报错,需要确认签名材料和签名流程是否正确 130 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 131 // 错误:参数错误 132 // 解决方式:按照报错返回的message,重新输入请求参数 133 // 描述:参数的类型,长度,或者必填选项没有填写, 134 // - 传入的参数“需要冲红的发票信息”为空(位于/body/fapiao_information),请检查参数格式是否符合要求 135 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 136 // 错误:资源错误 137 // 解决方式:请检查发票申请单号是否正确,或在确认用户已提交抬头后重新调用 138 // 描述:根据发票申请单查询不到用户填写的抬头 139 } else if (e.getErrorCode().equals("ORDER_CLOSED")) { 140 // 错误:订单错误 141 // 解决方式:联系商户,重新申请开具发票 142 // 描述:开票申请已被商户拒绝,无法进行当前操作 143 } else if (e.getErrorCode().equals("RESOURCE_ALREADY_EXISTS")) { 144 // 错误:资源已存在 145 // 解决方式:调用【查询电子发票】接口获取开票结果 146 // 描述:开票请求已被受理,请稍后调用【查询电子发票】接口获取开票结果 147 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 148 // 错误:请求非法,请求参数正确,但是不符合业务规则 149 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后重试 150 // 描述:不符合业务规则的场景如: 151 // - 仅在微信支付侧开具的电子发票才允许发起冲红 152 // - 传入的参数“商户发票单号”出现重复(位于/body/fapiao_information/fapiao_id),请使用正确的参数重新调用 153 // - 传入的参数“发票代码”及“发票号码”出现重复(位于/body/fapiao_information/fapiao_code|fapiao_number),请使用正确的参数重新调用 154 } else { 155 // 其他类型错误:联系获取支持 156 } 157 } 158 } 159 160} 161
2.7.3 处理发票冲红成功通知
1package com.tencent.wxpay.fapiao; 2 3import com.google.gson.annotations.SerializedName; 4import com.tencent.wxpay.utils.WXPayUtility; 5import com.tencent.wxpay.utils.WXPayUtility.Notification; 6import java.security.PublicKey; 7import java.util.List; 8import okhttp3.Headers; 9import okhttp3.Response; 10 11public class ReversedNotifyDemo { 12 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 13 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 14 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 15 private static String mchid = "19xxxxxxxx"; 16 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 17 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 18 // 商户API证书私钥文件路径,本地文件路径 19 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 20 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 21 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 22 // 微信支付公钥文件路径,本地文件路径 23 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 24 // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 25 private static String apiv3Key = "your apiv3 key"; 26 27 public static class Reversed { 28 @SerializedName("mchid") 29 public String mchid; 30 31 @SerializedName("sub_mchid") 32 public String subMchId; 33 34 @SerializedName("fapiao_apply_id") 35 public String fapiaoApplyId; 36 37 @SerializedName("fapiao_information") 38 public List<FapiaoInfo> fapiaoInformation; 39 } 40 41 public static class FapiaoInfo { 42 @SerializedName("fapiao_id") 43 public String fapiaoId; 44 45 @SerializedName("fapiao_status") 46 public Status fapiaoStatus; 47 48 @SerializedName("card_status") 49 public CardStatus cardStatus; 50 } 51 52 public enum Status { 53 @SerializedName("ISSUE_ACCEPTED") 54 ISSUE_ACCEPTED, 55 @SerializedName("ISSUED") 56 ISSUED, 57 @SerializedName("REVERSE_ACCEPTED") 58 REVERSE_ACCEPTED, 59 @SerializedName("REVERSED") 60 REVERSED, 61 } 62 63 public enum CardStatus { 64 @SerializedName("INSERT_ACCEPTED") 65 INSERT_ACCEPTED, 66 @SerializedName("INSERTED") 67 INSERTED, 68 @SerializedName("DISCARD_ACCEPTED") 69 DISCARD_ACCEPTED, 70 @SerializedName("DISCARDED") 71 DISCARDED 72 } 73 74 public void cardInsertedNotify(Response httpResponse) { 75 String refundNotifyStr = WXPayUtility.extractBody(httpResponse); // 用户填写抬头通知的原始字符串 76 Headers headers = httpResponse.headers(); // 用户填写抬头通知的Headers 77 PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 78 79 Notification notification = WXPayUtility.parseNotification( 80 apiv3Key, 81 wechatPayPublicKeyId, wechatPayPublicKey, headers, 82 refundNotifyStr); 83 84 // 解析用户填写抬头信息 85 Reversed reversed = WXPayUtility.fromJson(notification.getResource().getCiphertext(), Reversed.class); 86 87 String notifyMchId = reversed.mchid; 88 if (mchid.equals(notifyMchId)) { 89 String fapiaoApplyId = reversed.fapiaoApplyId; 90 for (FapiaoInfo fapiaoInfo : reversed.fapiaoInformation) { 91 Status status = fapiaoInfo.fapiaoStatus; 92 switch (status) { 93 case REVERSED: 94 // 使用回调的fapiao_apply_id,调用【查询电子发票】接口,获取发票详情 95 break; 96 case REVERSE_ACCEPTED: 97 // 发票红冲中,继续等待发票红冲成功通知 98 break; 99 default: 100 break; 101 } 102 } 103 } 104 } 105}
2.7.4 处理发票卡券作废通知
1package com.tencent.wxpay.fapiao; 2 3import com.google.gson.annotations.SerializedName; 4import com.tencent.wxpay.fapiao.ReversedNotifyDemo.CardStatus; 5import com.tencent.wxpay.fapiao.ReversedNotifyDemo.FapiaoInfo; 6import com.tencent.wxpay.fapiao.ReversedNotifyDemo.Reversed; 7import com.tencent.wxpay.fapiao.ReversedNotifyDemo.Status; 8import com.tencent.wxpay.utils.WXPayUtility; 9import com.tencent.wxpay.utils.WXPayUtility.Notification; 10import java.security.PublicKey; 11import java.util.List; 12import okhttp3.Headers; 13import okhttp3.Response; 14 15public class CardDiscarded { 16 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 17 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 18 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 19 private static String mchid = "19xxxxxxxx"; 20 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 21 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 22 // 商户API证书私钥文件路径,本地文件路径 23 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 24 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 25 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 26 // 微信支付公钥文件路径,本地文件路径 27 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 28 // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 29 private static String apiv3Key = "your apiv3 key"; 30 31 public static class Reversed { 32 @SerializedName("mchid") 33 public String mchid; 34 35 @SerializedName("sub_mchid") 36 public String subMchId; 37 38 @SerializedName("fapiao_apply_id") 39 public String fapiaoApplyId; 40 41 @SerializedName("fapiao_information") 42 public List<FapiaoInfo> fapiaoInformation; 43 } 44 45 public static class FapiaoInfo { 46 @SerializedName("fapiao_id") 47 public String fapiaoId; 48 49 @SerializedName("fapiao_status") 50 public Status fapiaoStatus; 51 52 @SerializedName("card_status") 53 public CardStatus cardStatus; 54 } 55 56 public enum Status { 57 @SerializedName("ISSUE_ACCEPTED") 58 ISSUE_ACCEPTED, 59 @SerializedName("ISSUED") 60 ISSUED, 61 @SerializedName("REVERSE_ACCEPTED") 62 REVERSE_ACCEPTED, 63 @SerializedName("REVERSED") 64 REVERSED, 65 } 66 67 public enum CardStatus { 68 @SerializedName("INSERT_ACCEPTED") 69 INSERT_ACCEPTED, 70 @SerializedName("INSERTED") 71 INSERTED, 72 @SerializedName("DISCARD_ACCEPTED") 73 DISCARD_ACCEPTED, 74 @SerializedName("DISCARDED") 75 DISCARDED 76 } 77 78 public void cardDiscardedNotify(Response httpResponse) { 79 String refundNotifyStr = WXPayUtility.extractBody(httpResponse); // 用户填写抬头通知的原始字符串 80 Headers headers = httpResponse.headers(); // 用户填写抬头通知的Headers 81 PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 82 83 Notification notification = WXPayUtility.parseNotification( 84 apiv3Key, 85 wechatPayPublicKeyId, wechatPayPublicKey, headers, 86 refundNotifyStr); 87 88 // 解析用户填写抬头信息 89 CardDiscarded cardDiscarded = WXPayUtility.fromJson(notification.getResource().getCiphertext(), CardDiscarded.class); 90 91 String notifyMchId = cardDiscarded.mchid; 92 if (mchid.equals(notifyMchId)) { 93 for (FapiaoInfo fapiaoInfo : cardDiscarded.fapiaoInformation) { 94 CardStatus cardStatus = fapiaoInfo.cardStatus; 95 switch (cardStatus) { 96 case DISCARD_ACCEPTED: 97 // 发票作废已申请,等待发票作废成功通知 98 break; 99 case DISCARDED: 100 // 发票已作废 101 break; 102 default: 103 break; 104 } 105 } 106 } 107 } 108} 109










