인앱 결제를 진행하면서 결제를 실패했을 때 해당하는 값들을 처리하는 로직을 만들어야 했다.
이를 찾아보니 애플/구글 서버에서 별도의 콜백을 주는 것이 아니고 클라이언트에서 구매를 요청 했을 때 이에 해당하는 응답을 클라이언트에 직접 전달하는 것으로 보인다.
그렇기에 스위프트 같이 네이티브로 개발을 할때와 플러터 같은 크로스플랫폼으로 개발을 할때 방식이 조금 다른데 현 회사에서는 클라이언트로 플러터를 사용하기 때문에 플러터 구현 방식으로 정리를 했다.
Callback으로 서버쪽에 실패에 해당하는 값을 전달하거나 JSON 응답을 주지 않는다
대신 flutter 클라이언트의 in_app_purchase 패키지에서 처리
클라이언트에서 이를 처리 후 서버쪽에 전달하는 방식으로 DB에 저장해야 된다
마찬가지로 flutter을 사용한다면 in_app_purchase에서 처리 후 서버로 전달하는 방식으로 DB에 저장
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
class PurchaseScreen extends StatefulWidget {
_PurchaseScreenState createState() => _PurchaseScreenState();
}
class _PurchaseScreenState extends State<PurchaseScreen> {
final InAppPurchase _iap = InAppPurchase.instance;
bool _available = true;
late StreamSubscription<List<PurchaseDetails>> _subscription;
void initState() {
super.initState();
_initialize();
final purchaseUpdated = _iap.purchaseStream;
_subscription = purchaseUpdated.listen(_listenToPurchaseUpdated, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here
});
}
void _initialize() async {
final bool available = await _iap.isAvailable();
setState(() {
_available = available;
});
}
void _buyProduct(ProductDetails productDetails) async {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
await _iap.buyConsumable(purchaseParam: purchaseParam);
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
_showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
_handleError(purchaseDetails.error!);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
_deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
}
}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
});
}
void _handleError(IAPError error) {
String errorJson = jsonEncode({
"result": "failed",
"error": {
"code": error.code,
"message": error.message,
"source": error.source,
"details": error.details,
},
"transaction": null
});
print(errorJson);
}
void _showPendingUI() {
// Show a loading indicator or some UI indicating the transaction is pending
}
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
// Implement your purchase verification logic here
return true;
}
void _deliverProduct(PurchaseDetails purchaseDetails) {
// Deliver the purchased product to the user
}
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
// Handle the case when the purchase is invalid
}
void dispose() {
_subscription.cancel();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Purchase Screen"),
),
body: Center(
child: _available
? Text("Store is available")
: Text("Store is unavailable"),
),
);
}
}
in_app_purchase 패키지은 PurchaseStatus가 error인지로 확인하고 PurcahseDetails.error을 통해서 에러에 대한 상세 정보를 확인할 수 있다.
in_app_purchase의 PurchaseDetails