對接蘋果支付退款退單接口

前言

????????一般而言,我們其實很少對接退款接口,因為退款基本都是商家自己決定后進行操作的,但是蘋果比較特殊,用戶可以直接向蘋果發起退款請求,蘋果覺得合理會退給用戶,但是目前公司業務還是需要對接這個接口,可能是以后為了對賬之類使用的吧

? ? ? ? 本來對接api也沒啥好說的,但是由于蘋果官方是英文的,考慮到大部分人可能還是懶得找英文文檔,所以進行了整理歸檔(我自己也是百度整理的...)

? ? ? ? 以下為參考的一些地址,2023-11-22記錄,目前是有限的,以后不確定..請知悉

參考對接地址:???????蘋果(apple)支付退款通知、api_蘋果支付api_Arhhhhhhh的博客-CSDN博客

官網地址:

官網對接地址

主動通知地址:Get Refund History | Apple Developer Documentation

被動通知地址:Handling refund notifications | Apple Developer Documentation

必知

? ? ? ? 這里主要介紹被動接收的(連接需要支持https),因為這種不是很好性能,主要是由于主動查詢沒有條件可以終止,所以選擇用被動的,但是也會把相應工具類放上來,方便使用

對接步驟

配置通知URL

在?App Store Connect 進行配置,地址為:https://appstoreconnect.apple.com/login,由于我沒有賬號,所以是別人幫忙配的,如果不知道在哪配置可以參考這篇文章

蘋果iOS內購三步曲:App內退款、歷史訂單查詢、綁定用戶防掉單!--- WWDC21 - 掘金

? ? ? ? ? ? ?我這里使用的是V2版本的,V1是明文的,不太安全,所以我這里采用了V2版本

引入依賴

? ? ? ? 加解密需要引入工具包進行處理,以下是maven的坐標

<!-- jwt -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.8.1</version>
</dependency>

編寫工具類

? ? ? ? 這一步最重要,這里直接放代碼,到時你們可以直接復制使用

主動調用工具類

public class AppStoreReturnUtil {//退款api正式環境private static final String APP_STORE_RETURN = "https://api.storekit.itunes.apple.com/inApps/v2/refund/lookup/{originalTransactionId}";//退款api沙箱環境private static final String APP_STORE_SANDBOX_RETURN = "https://api.storekit-sandbox.itunes.apple.com/inApps/v2/refund/lookup/{originalTransactionId}";/*** 生成token* @return* @throws Exception*/private static String generateJwtToken() throws Exception {Map<String, Object> headers = new HashMap<>();// apple指定ES256算法headers.put("alg", "ES256");// 密鑰IDheaders.put("kid", "你的kid");// jwt格式headers.put("typ", "JWT");return JWT.create().withHeader(headers)// issId:見apple connect后臺右上角.withIssuer("你的issId")// 簽名日期.withIssuedAt(new Date())// 失效日期:最晚一個小時,否則報錯401.withExpiresAt(DateUtils.addHours(new Date(), 1))// 目標接收者,固定值.withAudience("appstoreconnect-v1")// 包名,bundleId.withClaim("bid", "你的bundleId")// 簽名密鑰,需要用到apple connect下載p8文件.sign(Algorithm.ECDSA256(null, (ECPrivateKey) getPrivateKey("p8文件路徑")));}/*** 獲取私鑰* @param fileName apple connect下載的p8文件路徑* @return* @throws Exception*/private static PrivateKey getPrivateKey(String fileName) throws Exception {String content = new String(Files.readAllBytes(Paths.get(fileName)), StandardCharsets.UTF_8);try {String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf = KeyFactory.getInstance("EC");return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));} catch (InvalidKeySpecException e) {throw new RuntimeException("Invalid key format");}}//任何http請求工具類都可以  private static RefundHistResponseVO getRefundHist() throws Exception {String token = generateToken();HttpHeaders header = new HttpHeaders();header.set("Authorization", "Bearer "+ token);RequestEntity<Map<String, String>> requestEntity = new RequestEntity<>(header, HttpMethod.GET, URI.create("https://api.storekit-sandbox.itunes.apple.com/inApps/v2/refund/lookup/2000000308586738"));ResponseEntity<RefundHistResponseVO> exchange = restTemplate.exchange(requestEntity, RefundHistResponseVO.class);return exchange.getBody();}

這里有幾個注意的點,如下

1.?getRefundHist 需要基于http工具去發送請求,你可以自己找你們項目中的,或者自己寫一個

2. kid、issId、bundleId、p8文件都是你自己賬號的,如果你不知道可以問ios或者產品經理要

3.?originalTransactionId就是你之前下單時蘋果返回的,所以這個數據你們之前必須要有

到此為止,剩下的就是你自己寫代碼去請求就行了

被動接收

蘋果返回數據格式

格式如下(真實的很長,這里是為了你能看懂才故意弄短)

{"signedPayload":"BaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBh"}

如果你是用Java SpringBoot開發的話,可以直接這樣接收(也就是用@RequestBody即可)

@RestController
@RequestMapping("app/store")
@Slf4j
public class AppStoreMsgController {@PostMapping("/notify")public String appStoreMsgNotify(@RequestBody AppStoreNotifyPayLoadDto appStoreNotifyPayLoadDto) {log.info("appStoreNotifyPayLoadDto{}", JsonUtils.Object2Json(appStoreNotifyPayLoadDto));return MSG.SUCCESS(result);}
}
@Data
public class AppStoreNotifyPayLoadDto implements Serializable {private static final long serialVersionUID = 1L;private String signedPayload;
}
?被動接收工具類
@Slf4j
public class AppStoreReturnUtil {/*** 驗證簽名并返回解析數據* @param jws* @return* @throws CertificateException*/public static AppStoreNotifyDto verifyAndGet(String jws) throws CertificateException {DecodedJWT decodedJWT = JWT.decode(jws);// 拿到 header 中 x5c 數組中第一個String header = new String(java.util.Base64.getDecoder().decode(decodedJWT.getHeader()));String x5c = JSONObject.parseObject(header).getJSONArray("x5c").getString(0);// 獲取公鑰PublicKey publicKey = getPublicKeyByX5c(x5c);// 驗證 tokenAlgorithm algorithm = Algorithm.ECDSA256((ECPublicKey) publicKey, null);try {algorithm.verify(decodedJWT);} catch (SignatureVerificationException e) {log.error("解密蘋果數據失敗", e);throw  new AppException("解密蘋果數據失敗");}// 解析數據String decodeString = new String(java.util.Base64.getDecoder().decode(decodedJWT.getPayload()));return JSON.parseObject(decodeString, AppStoreNotifyDto.class);}/*** 解析事務數據* @param appStoreNotifyDto* @return*/public static AppStoreDecodedPayloadDto parseTransactionInfo(AppStoreNotifyDto appStoreNotifyDto) {DecodedJWT decode = JWT.decode(appStoreNotifyDto.getData().getSignedTransactionInfo());String decodeString = new String(Base64.getDecoder().decode(decode.getPayload()));return JSON.parseObject(decodeString, AppStoreDecodedPayloadDto.class);}/*** 獲取公鑰* @param x5c* @return* @throws CertificateException*/private static PublicKey getPublicKeyByX5c(String x5c) throws CertificateException {byte[] x5c0Bytes = java.util.Base64.getDecoder().decode(x5c);CertificateFactory fact = CertificateFactory.getInstance("X.509");X509Certificate cer = (X509Certificate) fact.generateCertificate(new ByteArrayInputStream(x5c0Bytes));return cer.getPublicKey();}
}

這些都是固定寫法,放上去就行了,沒啥好說的,相關的java Bean也貼出來吧,放在下面

/*** zxc_user* time: 2023-11-17 15:34:47* @description:  解密核心數據** 參考地址: https://developer.apple.com/documentation/appstoreservernotifications/jwstransactiondecodedpayload?language=objc*/
@Data
public class AppStoreDecodedPayloadDto implements Serializable {private static final long serialVersionUID = 1L;///退款訂單必存的字段/*** 應用的bundle標識符*/private String bundleId;/*** 與price參數相關聯的三個字母的ISO 4217貨幣代碼。此值僅在存在price時才存在*/private String currency;/*** 服務器環境,沙箱或生產環境。   sandbox or production*/private String environment;/*** 包含優惠代碼或促銷優惠標識符的標識符。*/private String offerIdentifier;/*** 表示促銷優惠類型的值*/private String offerType;/*** UNIX時間,以毫秒為單位,表示原始事務標識符的購買日期。*/private String originalPurchaseDate;/*** 原始購買的交易標識符。*/private String originalTransactionId;/*** 一個整數值,表示您在App Store Connect中配置的應用內購買或訂閱報價的價格乘以1000,并在購買時系統記錄。有關更多信息,請參閱價格。currency參數表示此價格的貨幣。*/private String price;/*** 應用內購買的產品標識符。*/private String productId;/*** 用戶購買的消耗品數量。*/private String quantity;/*** UNIX時間,以毫秒為單位,App Store在過期后向用戶帳戶收取購買、恢復產品、訂閱或續訂費用。*/private String purchaseDate;/*** UNIX時間,以毫秒為單位,應用商店將交易退款或從家庭共享中撤銷交易*/private String revocationDate;/*** App Store退還交易或從家庭共享中撤銷交易的原因。*/private String revocationReason;/*** 事務的唯一標識符。*/private String transactionId;/***  購買事務的原因,這表明它是客戶購買還是系統啟動的自動續訂訂閱的續訂。*/private String transactionReason;/*** 應用內購買的類型。*/private String type;///跟訂閱相關//*** 訂閱到期或更新的UNIX時間,以毫秒為單位。   跟訂閱相關*/private String expiresDate;/*** 一個布爾值,指示客戶是否升級到另一個訂閱。  跟訂閱相關*/private boolean isUpgraded;/*** 訂閱服務使用的付費模式,如免費試用、按需付費或預先付費 ,跟訂閱相關*/private String offerDiscountType;/*** 訂閱所屬的訂閱組的標識符。 跟訂閱相關*/private String subscriptionGroupIdentifier;///其他相關//*** 您在購買時創建的UUID,它將交易與您自己服務上的客戶關聯起來。如果你的應用沒有提供appAccountToken,這個字符串是空的。更多信息請參見appAccountToken(_:)。*/private String appAccountToken;/*** 一個字符串,描述該事務是由客戶購買的,還是通過家庭共享提供給客戶*/private String inAppOwnershipType;/*** UNIX時間,以毫秒為單位,應用商店簽署JSON Web簽名(JWS)數據的時間。*/private String signedDate;/*** 三個字母的代碼,表示與購買的App Store店面相關的國家或地區。*/private String storefront;/*** 一個apple定義的值,唯一標識與購買相關的App Store店面。*/private String storefrontId;/*** 跨設備訂閱購買事件的唯一標識符,包括訂閱續訂。*/private String webOrderLineItemId;
}
/*** zxc_user* time: 2023-11-17 15:22:08* @description:  蘋果V2版本回調通知返回數據*** 參考官方地址:    https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2decodedpayload?language=objc*/
@Data
public class AppStoreNotifyDto implements Serializable {private static final long serialVersionUID = 1L;/*** 回調類型, 最主要的,等于REFUND是眼用戶退款事件** 參考地址:https://developer.apple.com/documentation/appstoreservernotifications/notificationtype?language=objc*/private String notificationType;/*** 通知的唯一標識符。使用此值來標識重復的通知。*/private String notificationUUID;/*** 標識通知事件的其他信息。子類型字段僅用于特定的版本2通知。*/private String subtype;/*** 核心數據,退款信息之類的都在里面*/private AppStoreNotifyDataDto data;private String summary;/*** 通知版本號,V2*/private String version;/*** UNIX時間,以毫秒為單位*/private String signedDate;
}

操作步驟:

? ? ? ? 就是把AppStoreNotifyPayLoadDto對象里面的signedPayload傳到AppStoreReturnUtil工具類的verifyAndGet即可,便可以獲得基礎數據

? ? ? ? 如果獲取退款數據再調用一下AppStoreReturnUtil的parseTransactionInfo即可,

? ? ? ? 記得如果只是處理退款的需要注意一下AppStoreNotifyDto對象的notificationType類型當等于REFUND才是退款,其他的業務請參考官方文檔,notificationType | Apple Developer Documentation

到這里就行了,剩下的就是你要處理的業務邏輯,每個人的可能不太一樣,這里就不贅述了

兩者對比

? ? ? ? 主動查詢需要消耗你的性能,而且你不知道終止條件是啥,因為用戶是隨時可以向蘋果發起退款申請的,雖然網上有人說下單后90天就不能,但是是不是也不確定....

? ? ? ? 其次主動查詢需要那些kid,k8文件等數據記錄(這里可以理解為私鑰),所以還是比較麻煩的

? ? ? ? 被動接收相對就非常方便了,只需要配置url,然后提供控制器接收數據即可,這里是不需要kid,k8文件那些的(這里我理解是公鑰在jar包里面提供的)而且可以節省你服務器性能

? ? ? ? 所以我目前是選擇了被動接收處理

設計模式使用

? ? ? ? 這里我是用了command進行設計的,目前還沒整理文檔,后續整理了可以放出來大家討論討論

結語

? ? ? ? 這里再次感謝開頭放置的那些文章地址,說的挺詳細了,因為我英文也不是很好,如果沒有這些文章可能還挺麻煩

? ? ? ? 整個流程其實并不難,就是以前沒接過蘋果的,所以剛開始有點懵逼,不過真正搞懂了其實也就那樣

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/160932.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/160932.shtml
英文地址,請注明出處:http://en.pswp.cn/news/160932.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

試試MyBatis-Plus可視化代碼生成器,太香了,你一定會感謝我

前言 在基于Mybatis的開發模式中&#xff0c;很多開發者還會選擇Mybatis-Plus來輔助功能開發&#xff0c;以此提高開發的效率。雖然Mybatis也有代碼生成的工具&#xff0c;但Mybatis-Plus由于在Mybatis基礎上做了一些調整&#xff0c;因此&#xff0c;常規的生成工具生成的代碼…

PC端使子組件的彈框關閉

子組件 <template><el-dialog title"新增部門" :visible"showDialog" close"close"> </el-dialog> </template> <script> export default {props: {showDialog: {type: Boolean,default: false,},},data() {retu…

【JavaSE】-5-嵌套循環

回顧 一、java語言特點 二、配置java環境 path 三、記事本 javac -d . java 包名.類名 四、eclipse 五、變量 定義變量 數據類型 變量名值; 六、相關的數據類型 ? 基本&#xff08;四類 、8種&#xff09;、引用 ? 類型轉換&#xff08;自動、強制&#xff09; ? 運…

Java面向對象(高級)-- 類中屬性賦值的位置及過程

文章目錄 一、賦值順序&#xff08;1&#xff09;賦值的位置及順序&#xff08;2&#xff09;舉例&#xff08;3&#xff09;字節碼文件&#xff08;4&#xff09;進一步探索&#xff08;5&#xff09;最終賦值順序&#xff08;6&#xff09;實際開發如何選 二、(超綱)關于字節…

1992-2021年省市縣經過矯正的夜間燈光數據(GNLD、VIIRS)

1992-2021年省市縣經過矯正的夜間燈光數據&#xff08;GNLD、VIIRS&#xff09; 1、時間&#xff1a;1992-2021年3月&#xff0c;其中1992-2013年為年度數據&#xff0c;2013-2021年3月為月度數據 2、來源&#xff1a;DMSP、VIIRS 3、范圍&#xff1a;分區域匯總&#xff1a…

SpringBoot : ch05 整合Mybatis

前言 隨著Java Web應用程序的快速發展&#xff0c;開發人員需要越來越多地關注如何高效地構建可靠的應用程序。Spring Boot作為一種快速開發框架&#xff0c;旨在簡化基于Spring的應用程序的初始搭建和開發過程。而MyBatis作為一種優秀的持久層框架&#xff0c;提供了對數據庫…

【Linux】-進程間通信-共享內存(SystemV),詳解接口函數以及原理(使用管道處理同步互斥機制)

&#x1f496;作者&#xff1a;小樹苗渴望變成參天大樹&#x1f388; &#x1f389;作者宣言&#xff1a;認真寫好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee? &#x1f49e;作者專欄&#xff1a;C語言,數據結構初階,Linux,C 動態規劃算法&#x1f384; 如 果 你 …

中低壓MOSFET 2N7002T 60V 300mA 雙N通道 采用SOT-523封裝形式

2N7002KW小電流雙N通道MOSFET&#xff0c;電壓60V電流300mA&#xff0c;采用SOT-523封裝形式。低Ros (on)的高密度單元設計&#xff0c;堅固可靠&#xff0c;具有高飽和電流能力&#xff0c;ESD防護門HBM2KV。可應用于直流/直流轉換器&#xff0c;電池開關等產品應用上。

Redis JDBC

1、導入依賴&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version></dependency> 2、連接JDBC public class JedisDemo05 {public static void main(String[]…

成為AI產品經理——AI產品經理工作全流程

一、業務背景 背景&#xff1a;日常排球訓練&#xff0c;中考排球項目和排球體測項目耗費大量人力成本和時間成本。 目標&#xff1a;開發一套用于實時檢測排球運動并進行排球墊球計數和姿勢分析的軟件。 二、產品工作流程 我們這里對于產品工作流程的關鍵部分進行講解&…

「Docker」如何在蘋果電腦上構建簡單的Go云原生程序「MacOS」

介紹 使用Docker開發Golang云原生應用程序&#xff0c;使用Golang服務和Redis服務 注&#xff1a;寫得很詳細 為方便我的朋友可以看懂 環境部署 確保已經安裝Go、docker等基礎配置 官網下載鏈接直達&#xff1a;Docker官網下載 Go官網下載 操作步驟 第一步 創建一個…

Java 多線程之 DCL(Double-Checked Locking)

文章目錄 一、概述二、使用方法 一、概述 DCL&#xff08;Double-Checked Locking&#xff09;是一種用于在多線程環境下實現延遲初始化的技術。它結合了懶加載&#xff08;Lazy Initialization&#xff09;和線程安全性&#xff0c;用于在需要時創建單例對象或共享資源。它的…

什么是SEO?(初學者建議收藏)

前言 在這個充滿機遇和挑戰的時代&#xff0c;人們不斷追求著更好的生活和更高的成就。無論是個人還是企業&#xff0c;都需要不斷提升自己的競爭力&#xff0c;才能在激烈的市場競爭中獲得成功。因此&#xff0c;我們需要不斷學習和成長&#xff0c;學會適應變化和面對挑戰。…

汽車智能座艙/智能駕駛SOC -2

第二篇&#xff08;筆記&#xff09;。 未來智能汽車電子電氣將會是集中式架構&#xff08;車載數據中心&#xff09;虛擬化技術&#xff08;提供車載數據中心靈活性和安全性&#xff09;這個幾乎是毋庸置疑的了。國際大廠也否紛紛布局超算芯片和車載數據中心平臺。但是演進需…

日期格式轉化成星期幾部署到linux顯示英文

異常收集 原因&#xff1a;解決辦法仰天大笑出門去&#xff0c;我輩豈是蓬蒿人 傳入一個時間獲取這個時間對應的是星期幾&#xff0c;在開發環境&#xff08;window系統&#xff09;中顯示為星期幾&#xff0c;部署到服務器&#xff08;linux系統&#xff09;中會顯示英文的時間…

serverless開發實戰

.yml格式 YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一種人類可讀的數據序列化格式&#xff0c;它使用簡潔的結構和縮進來表示數據。它被廣泛用于配置文件和數據交換的場景&#xff0c;具有易讀性和易寫性的特點。 serverless.yml配置 在項目的根目錄下增加…

Youtube新手運營——你需要的技巧與工具

對于有跨境意向的內容創作者或者品牌企業來說&#xff0c;YouTube是因其巨大的潛在受眾群和商業價值成為最值得投入變現與營銷計劃的平臺。 據統計&#xff0c;98% 的美國人每月訪問 YouTube&#xff0c;近三分之二的人每天訪問。但是&#xff0c;YouTube還遠未達到過度飽和的…

Leetcode—53.最大子數組和【中等】

2023每日刷題&#xff08;三十四&#xff09; Leetcode—53.最大子數組和 前綴和算法思想 參考靈茶山艾府 實現代碼 #define MAX(a, b) ((a > b) ? (a) : (b)) #define MIN(a, b) ((a < b) ? (a) : (b)) int maxSubArray(int* nums, int numsSize) {int ans INT_…

VMware 16 Pro 安裝以及下載

1、下載地址&#xff1a; https://www.aliyundrive.com/s/nj3PSD4TN9G 2、安裝文件 右擊打開 下一步 密鑰&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF 到此&#xff0c;安裝完畢