连续支付(周期扣款)功能开发及注意事项

最近有一个版本需求,需要接入周期扣款做连续会员的功能,没想到这一做就是小半个月,趟了很多坑,所以觉得有必要记录一下

1.周期扣款总体设计

在支付宝和微信中(非苹果支付),周期扣款的流程主要有以下两种,并且各有利弊

  1. 先签约,签约成功后再由商户发起主动扣款 #推荐#
    利端:由于一般来讲连续会员会有额外的折扣优惠,先签约再扣款避免了用户薅羊毛。
    弊端:签约和发起扣款时分开的,要额外做很多工作保障一致性。并且据微信官方文档,主动扣款是要延迟一段时间才能发起的。
  2. 支付并完成签约
    利端:签约和支付是合在一起的,用户完成了签约即完成了第一次支付
    弊端:在支付过程中,用户可以取消签约,只完成普通支付(好反人类,还可以取消),导致用户一直可以享受连续会员的优惠,但是并不进行签约。

由于签约和扣款的一致性问题开发努努力还能保证一下,所以我目前选用的是先签约-再扣款流程,也推荐选用。

苹果连续订阅服务
总结一句话–对服务端来说都是满满的恶意。
首先用户付款连续订阅成功了,服务端收不到苹果的任何通知,只有IOS客户端才能知道用户订阅成功了。
其次,用户订阅期间内每次扣款,服务端也无法进行感知,必须要不断的轮询用户的订单列表才能进行判断(IOS客户端都有办法能知道用户续订了)。

下面是一图流,我目前的系统设计方案
在这里插入图片描述

2.支付宝周期扣款

首先贴上官方文档地址-周期扣款
和支付宝对接还是相对轻松点的,毕竟技术支持还是比较尽心尽力的。
在先签约再扣款流程中,主要有用到以下几个接口

2.1 签约接口-文档

签约接口有几个坑,在这里给大家排一下雷

  1. 签约最小的周期是七天。
  2. 签约接口无法像下单接口一样,透传业务参数,需要开发者生成签约号并保留签约相关的业务信息。
  3. 在签约回调接口中,目前只能收到用户成功签约的通知,收不到用户解约通知,用户解约的通知是回调到应用网关地址(超级坑)详见回调文档说明

以下是签约接口调用的sdk示例

	/*** @Description: 生成客户端唤起签约页面的参数* @param notifyUrl 签约成功回调通知地址* @param isH5 是否为h5* @return: xxx.Result* @Author: lvqiushi* @Date: 2021-04-21*/public static Result<String> userAgreement(AlipayUserAgreementPageSignModel model, String notifyUrl, boolean isH5) {AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", AlipayAppId, AlipayPriveteKey, "json",H5AlipayConfig.CHARSET, AlipayPublicKey, "RSA2");AlipayUserAgreementPageSignRequest request = new AlipayUserAgreementPageSignRequest();request.setBizModel(model);request.setNotifyUrl(notifyUrl);// 周期扣款场景使用小程序/h5 接口跳转至签约页面时请使用 alipayClient.sdkExecute 方法; 若想获取跳转链接转换二维码可使用 alipayClient.pageExecute(request,"get")AlipayUserAgreementPageSignResponse response = null;try {if (isH5) {response = alipayClient.pageExecute(request,"get");} else {response = alipayClient.sdkExecute(request);}} catch (AlipayApiException e) {log.error("生成支付宝签约参数时,发生错误", e);return Result.fail("失败");}return Result.ok(response.getBody());}public static void main(String[] args) {LocalDateTime now = LocalDateTime.now();AlipayUserAgreementPageSignModel model = new AlipayUserAgreementPageSignModel();model.setSignValidityPeriod("7d");model.setProductCode("CYCLE_PAY_AUTH");model.setPersonalProductCode("CYCLE_PAY_AUTH_P");model.setSignScene("INDUSTRY|SOCIALIZATION");// 自定义订单号model.setExternalAgreementNo("XIUCAI2013548132543612315");model.setAgreementEffectType("DIRECT");AccessParams accessParams = new AccessParams();accessParams.setChannel("ALIPAYAPP");model.setAccessParams(accessParams);PeriodRuleParams periodRuleParams = new PeriodRuleParams();// 周期天数periodRuleParams.setPeriodType("DAY");periodRuleParams.setPeriod(7L);periodRuleParams.setExecuteTime(now.format(CommonConstant.SIMPLE_DAY_FORMATTER_OTHER));// 单个周期价格int singlePrice = 100;periodRuleParams.setSingleAmount(Money.ofCent(singlePrice).getYuanString());periodRuleParams.setTotalAmount(Money.ofCent(singlePrice * 36L).getYuanString());periodRuleParams.setTotalPayments(36L);model.setPeriodRuleParams(periodRuleParams);Result<String> signResult = AlipayUtils.userAgreement(model, "http://192.168.0.1:222222/callback/sign/aliNotify", true);}

2.2 主动扣款接口-文档

主动扣款接口其实使用的普通的收单交易接口
注意点有以下几个

  1. SDK方法默认是同步返回扣款结果的,如果需异步通知,需要设置is_async_pay参数。
  2. 支持提前5天发起扣款。
  3. 在一个扣款周期内,只能发起一次扣款,后续扣款会返回40004和无法再次扣款的响应。
  4. 同步返回结果中,只有total_amount字段有值,在周期扣款中,表示实际扣款金额。

2.3 解约接口-文档

解约接口的话,没什么好讲的,就调用就好了。。。。

3.微信周期扣款

截止目前仍没申请下来。。。等申请下来了再补上

4.苹果连续订阅

苹果本身对于内购和连续订阅的购买逻辑是统一的
在用户完成付款后,会立即向IOS客户端返回苹果交易号transaction_id和用户订单凭证receipt。
这时需要客户端将这两个参数加上订单号通知服务端,完成后续付款操作。
但是对于连续订阅的receipt验证与普通订单有些区别,需要额外的苹果专用共享密钥。
以下这个验证方法也是我从别的地方看过来的,再分享一下吧。
这个是苹果官方的订单信息文档

	private static class TrustAnyTrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}}/** 苹果沙盒环境  */private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";/** 正式环境  */private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt";/** 专用共享密钥 */private static final String IOS_SHARED_SECRET_PASSWORD = "xxxxxx";/*** @Description: 连续订阅验证回执* @param receipt* @param online* @return: java.lang.String* @Author: lvqiushi* @Date: 2021-04-25*/public static String buyAppVerifyContinuesSub(String receipt, boolean online) {String url = online ? url_verify : url_sandbox;InputStream is = null;try {SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());URL console = new URL(url);HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();conn.setSSLSocketFactory(sc.getSocketFactory());conn.setHostnameVerifier(new TrustAnyHostnameVerifier());conn.setRequestMethod("POST");conn.setRequestProperty("content-type", "text/json");conn.setRequestProperty("Proxy-Connection", "Keep-Alive");conn.setDoInput(true);conn.setDoOutput(true);BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream());String str = String.format(Locale.CHINA,"{\"receipt-data\":\"" + receipt + "\",\"password\":\"" + IOS_SHARED_SECRET_PASSWORD + "\"}");hurlBufOus.write(str.getBytes());hurlBufOus.flush();is = conn.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is));String line = null;StringBuffer sb = new StringBuffer();while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();} catch (Exception ex) {log.error("调用苹果服务器,进行验证订单回执异常", ex);} finally {try {is.close();} catch (IOException e) {e.printStackTrace();}}return null;}

拿到苹果的订单信息后,还需要进行订单验证,和续订状态的判断,相关的封装类和方法都已经贴在下面了,可以直接用来使用的

	/*** https://developer.apple.com/documentation/appstorereceipts/responsebody* @description: 苹果服务器 根据回执返回的订单信息* @author: xiucai* @create: 2021-04-29***/@Datapublic static class AppleReceiptOrder {private AppleReceipt receipt;/** latest_receipt_info 包含订阅的所有交易,其中包括初次购买和后续续期,但不包括任何恢复购买  */private List<AppleTradeInfo> latest_receipt_info;/** 最新的Base64编码的应用收据。仅针对包含自动续订的收据返回。  */private String latest_receipt;/*** 0 正常** 21000 未使用HTTP POST请求方法向App Store发送请求。** 21001 此状态代码不再由App Store发送。** 21002 receipt-data属性中的数据格式错误,或者服务遇到了临时问题。再试一次。** 21003 收据无法认证。** 21004 您提供的共享密钥与您帐户的文件共享密钥不匹配。** 21005 收据服务器暂时无法提供收据。再试一次。** 21006 该收据有效,但订阅已过期。当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回。仅针对自动续订的iOS 6样式的交易收据返回。** 21007 该收据来自测试环境,但是已发送到生产环境以进行验证。** 21008 该收据来自生产环境,但是已发送到测试环境以进行验证。** 21009 内部数据访问错误。稍后再试。** 21010 找不到或删除了该用户帐户。*/private Integer status;}@Datapublic static class AppleReceipt {private String receipt_type;private String bundle_id;private String application_version;private String receipt_creation_date;private String receipt_creation_date_ms;private String original_purchase_date;private String original_purchase_date_ms;/** in_app 数组包含非消耗型、非续期订阅,以及用户之前购买的自动续期订阅。根据需要,检查响应中这些 App 内购买项目类型对应的值来验证交易。  */private List<AppleTradeInfo> in_app;}@Datapublic static class AppleTradeInfo {/** 购买的消费品数量  */private String quantity;/** 购买产品的唯一标识符。您可以在App Store Connect中创建产品时提供此值,该值与存储在交易的付款属性中的对象的属性相对应  */private String product_id;/** 交易的唯一标识符,例如购买,还原或续订  */private String transaction_id;/** 原始购买的交易标识符  */private String original_transaction_id;private String purchase_date;/** 对于自动续订订阅,指经过一段时间后,App Store向用户的帐户收取订阅购买或续订费用的时间  */private Long purchase_date_ms;private String original_purchase_date;/** 原始应用购买时间(以UNIX纪元时间格式),以毫秒为单位。使用此时间格式来处理日期。对于自动续订的订阅,此值指示订阅的首次购买日期  */private String original_purchase_date_ms;private String expires_date;/** 订阅到期的时间或续订的时间(以UNIX纪元时间格式),以毫秒为单位  */private Long expires_date_ms;/** 订阅所属的订阅组的标识符  */private String subscription_group_identifier;/** 订阅是否在免费试用期内的指标  */private Boolean is_trial_period;/** 自动续订订阅是否在介绍性价格期内的指标。请参阅以获取更多信息  */private Boolean is_in_intro_offer_period;}/*** @Description: 在苹果订单中,验证是否包含所给订单,如果验证成功,返回苹果订单信息* @param in_app* @param appleProductId* @param transaction_id* @return: boolean* @Author: lvqiushi* @Date: 2021-05-19*/public static AppleTradeInfo judgeTradeSuccess(List<AppleTradeInfo> in_app, String appleProductId, String transaction_id) {if (CollectionUtils.isEmpty(in_app)) {return null;}for (AppleTradeInfo tradeInfo : in_app) {if (tradeInfo.getTransaction_id().equals(transaction_id) && tradeInfo.getProduct_id().equals(appleProductId)) {return tradeInfo;}}return null;}/*** @Description: 判断苹果用户是否有进行过最新一次的订阅* @param latest_receipt_info* @param original_transaction_id* @param lastPayTime* @return: boolean* @Author: lvqiushi* @Date: 2021-04-29*/public static boolean judgeSubscriptSuccess(List<AppleTradeInfo> latest_receipt_info, String original_transaction_id, LocalDateTime lastPayTime) {if (CollectionUtils.isEmpty(latest_receipt_info)) {return false;}long expireTimeMs = lastPayTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();for (AppleTradeInfo tradeInfo : latest_receipt_info) {if (tradeInfo.getOriginal_transaction_id().equals(original_transaction_id)) {// 购买时间大于上期购买时间 则认定为有续订if (tradeInfo.getPurchase_date_ms() > expireTimeMs) {return true;}}}return false;}

唯一需要注意的是,当IOS客户端向苹果发送了确认订单后,订单会从苹果的订单列表消失

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/27289.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

证监会计算机类笔试上岸经验,公务员考试笔试166分上岸经验(全干货)

原标题&#xff1a;公务员考试笔试166分上岸经验(全干货) 一、考前自审 1、具备公务员考试需要的相关知识结构与基本素质(70%)。包括政治素质(理解和掌握国家大政方针与政策)&#xff0c;基本科学素质&#xff0c;常识&#xff0c;阅读理解能力&#xff0c;语言功底&#xff0c…

行政职业能力测试软件,公务员行政职业能力测试考试宝典

公务员行政职业能力考试宝典是一款通用的考试练习软件&#xff0c;丰富的题库&#xff0c;包含历年真题和模拟试题&#xff0c;有时间就可以开启刷题模式&#xff0c;对于错误的题目可以进行错题重做、解题思路介绍、统计分析等&#xff0c;达到熟练的程度&#xff0c;大大提高…

一政网是真实助公考上岸的吗?

在当下的经济时代&#xff0c;需要不断的学习&#xff0c;不断的提升自我。报考公务员考试成为了不二选择。报考公务员考试成为了毕业生、在职员工、宝妈等等众多不同类型人的最佳选择。但是公务员考试的难度大&#xff0c;想要公考一次上岸&#xff0c;那么就必需选择报班学习…

2021四川紧急选调/国考备考策略----申论/行测(2020.8.22号开始)

文章目录 1 申论1.1 申论题型1.2 申论备考策略1.3 申论做题顺序及复习阶段 2 行测备考2.1 行测题型2.2 行测备考策略 3 参考资料 1 申论 1.1 申论题型 申论部分一共有五大题型(归纳概括、提出措施、综合分析、公文写作、大作文) 1.2 申论备考策略 这五个题型在国考中是一样…

公务员考试要求及流程梳理

背景 虽然我不考, 但考公这么热门, 在信息层面不能落伍. 本文以 浙江2023省考 (考试时间在2022年12月) 为例, 主要梳理 怎么报名, 选职位, 考试科目与题型, 录用流程等. 为什么考公/考编这么热 因为当下的社会环境对员工太不友好了: 工作强度大, 精神压力大, 还有硬性比例淘…

基于JavaWeb的事业单位公务员招考信息发布平台-考务考试报名系统

本文介绍了使用Java技术开发公务员招考信息发布平台的设计与实现过程&#xff0c;首先对实现该系统的技术进行分析&#xff0c;说明选择Java和MySQL数据库的必要性&#xff0c;然后对公务员招考信息发布平台的需求进行分析。并接着对系统进行设计&#xff0c;包括架构设计、功能…

OpenAI发布了GPT的最新一代版本ChatGPT-4

今日凌晨&#xff0c;OpenAI发布了GPT的最新一代版本ChatGPT-4&#xff0c;相比上一代&#xff0c;其AI能力再度提升&#xff0c;同时支持输入的内容不再仅限于文字&#xff0c;而且支持图像内容的输入&#xff0c;成为一个能够理解照片的人工智能。 其AI能力的恐怖之处体现在哪…

chatgpt赋能python:Python循环暂停和继续的方法

Python循环暂停和继续的方法 Python是一种高级编程语言&#xff0c;在编程中使用循环结构非常常见。很多情况下&#xff0c;我们需要在循环中暂停或者继续执行。在本文中&#xff0c;我们将介绍如何在Python中实现循环暂停和继续的方法。 循环暂停和继续的意义 在Python编程…

Chat GPT5如果问世会对世界产生什么影响?以及未来chat gpt 5会取代什么类型的工作。

Chat GPT-5是一种基于人工智能技术的自然语言处理系统&#xff0c;可以自动回复和生成各种文本随着其技术的不断发展和改进&#xff0c;Chat GPT-5对未来世界将会产生以下几方面的影响&#xff1a; 1. 提升人类语言交流的效率和质量 Chat GPT-5可以高效地处理自然语言&#xf…

chatgpt 正面案例 2018-11-13T20:20:39+00:00 后面的 +00:00 代表什么意思

chatgpt 百度 2018-11-13T20:20:3900:00 后面的 00:00 代表什么意思? &#xff01;&#xff01;&#xff01;有任何问题请让我知道&#xff0c;十分感激&#xff01;谢谢&#xff01; 2021-07-29T21:35:5408:00 2021-07-29T21:35:54Z 中间的T&#xff08;当日期与时间组合…

chatpdf+mindshow由论文pdf直接生成报告PPT,拯救研究牲

chatpdfmindshow由论文pdf直接生成报告PPT&#xff0c;拯救研究牲 感谢AIGC技术&#xff0c;现在可以从论文的pdf直接生成展示的PPT。 (STEP1STEP2)最终效果预览 步骤 STEP1、论文PDF到PPT的markdown格式 chatpdf 把论文pdf提交给chatpdf并善用以下prompts 你会说中文吗 接下…

chatgpt赋能python:Python打包整个项目:打包与分享

Python打包整个项目&#xff1a;打包与分享 如果您是一名Python开发者&#xff0c;您可能已经经历过在多个环境中为您的项目安装依赖项的困惑。解决方案是将您的应用程序打包为一个易于安装的软件包。 在本文中&#xff0c;我们将学习如何使用pipenv和pyinstaller创建、打包和…

tiktoken (a fast BPE tokeniser for gpt4、chatgpt)

OpenAI在其官方GitHub上公开了一个最新的开源Python库&#xff1a;tiktoken&#xff0c;这个库主要是用做字节对编码(BPE)的。相比较HuggingFace的tokenizer&#xff0c;其速度提升了好几倍。 chatgpt 按token 数量收费&#xff0c;1000个token大约700个单词&#xff0c;可以用…

chatgpt赋能python:Python下载安装教程

Python下载安装教程 Python是一种高级编程语言&#xff0c;具有简单易学、强大多样的特点&#xff0c;不仅可以用来开发网站、应用程序、游戏&#xff0c;还可以用于数据科学、机器学习、人工智能等领域&#xff0c;被广泛应用于各行各业。 本文将为您提供详细的Python下载安…

LLaMA模型系统解读

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

3月份读文+学习思考记录

基于GPT3.5搭建定制化知识库 https://mp.weixin.qq.com/s?__bizMzIyNDAzMzYxNQ&mid2652028778&idx1&sn985a386f915dea0d4dc97186af7c50b6&srcid0316LqkslRQXM1UyluqQFTxe 当然&#xff0c;还有一个问题&#xff1a;程序员怎么办&#xff1f;程序员会被替代吗…

大神李沐被曝离职亚马逊,投身大模型创业!网友:“AI 已成创业致富新思路?”...

整理 | 朱珂欣 出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 横空出世的 ChatGPT 在火爆出圈之后&#xff0c;硅谷巨头、各大互联网大厂可谓是上演了一场群雄逐“ChatGPT”……在 AIGC 新风口之下&#xff0c;人才无疑成为这场“硬仗”的主力军。 近日…

别只关心315打老虎,来看看全年无休的AI打假

一年一度的“315”消费者权益日如期而至&#xff0c;这一天&#xff0c;全民最期待、各家公关最紧张的环节&#xff0c;应该是“打老虎”的高光时刻。 某些高高在上的巨头或大牌的“套路”被曝光&#xff0c;次日忙不迭道歉整改&#xff0c;让广受其害但维权困难的消费者们拍手…

如何成为某一领域的顶尖专家?—— 看看 ChatGPT、文心一眼、GPT-4 都怎么说的……

如何成为某一领域的顶尖专家? 目录 如何成为某一领域的顶尖专家? GPT-4

Midjourney, Dall-E, Stable Diffusion-人工智能艺术大师班

欢迎来到 Midjourney、Dall-E、Stable Diffusion&#xff1a;AI 摄影与艺术课程&#xff01;该在线课程专门旨在为您提供使用尖端人工智能艺术工具所需的技能和知识&#xff0c;并将您的创作过程提升到一个新的水平。无论您是数字艺术家、摄影师、设计师&#xff0c;还是仅仅是…