Skip to content

Commit

Permalink
YEL-154 [develop] apple notification 개발 서버 배포
Browse files Browse the repository at this point in the history
YEL-154 [develop] apple notification 개발 서버 배포
  • Loading branch information
hyeonjeongs authored Sep 10, 2023
2 parents 67fb4c4 + 60333bc commit 5954940
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ protected void doFilterInternal(
|| requestPath.startsWith("/docs")
|| requestPath.startsWith("/actuator") || requestPath.startsWith("/prometheus")
|| requestPath.startsWith("/api/v1/admin/login")
|| requestPath.startsWith("/v2/apple/notifications")
|| (requestPath.startsWith("/api/v1/auth")
&& !requestPath.startsWith("/api/v1/auth/token/issue"))) {
filterChain.doFilter(request, response);
Expand All @@ -59,8 +60,8 @@ protected void doFilterInternal(
log.info("Authorization-access : {}", accessHeader);
log.info("Authorization-refresh : {}", refreshHeader);

if (accessHeader == null || !accessHeader.startsWith(BEARER)
|| refreshHeader == null || !refreshHeader.startsWith(BEARER)) {
if (accessHeader==null || !accessHeader.startsWith(BEARER)
|| refreshHeader==null || !refreshHeader.startsWith(BEARER)) {
throw new CustomAuthenticationException(AUTHENTICATION_ERROR);
}

Expand All @@ -71,7 +72,7 @@ protected void doFilterInternal(
val accessHeader = request.getHeader(AUTHORIZATION);
log.info("Authorization : {}", accessHeader);

if (accessHeader == null || !accessHeader.startsWith(BEARER)) {
if (accessHeader==null || !accessHeader.startsWith(BEARER)) {
throw new CustomAuthenticationException(AUTHENTICATION_ERROR);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
|| requestPath.startsWith("/docs")
|| requestPath.startsWith("/actuator") || requestPath.startsWith("/prometheus")
|| requestPath.startsWith("/api/v1/admin/login")
|| requestPath.startsWith("/api/v1/auth")) {
|| requestPath.startsWith("/api/v1/auth")
|| requestPath.startsWith("/v2/apple/notifications")) {
filterChain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.yello.server.domain.purchase.controller;

import static com.yello.server.global.common.SuccessCode.POST_APPLE_NOTIFICATION_SUCCESS;

import com.yello.server.domain.purchase.dto.request.AppleNotificationRequest;
import com.yello.server.domain.purchase.service.PurchaseService;
import com.yello.server.global.common.dto.BaseResponse;
import com.yello.server.infrastructure.slack.annotation.SlackPurchaseNotification;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class PurchaseNotificationController {

private final PurchaseService purchaseService;

@PostMapping("/v2/apple/notifications")
@SlackPurchaseNotification
public BaseResponse appleNotification(
@RequestBody AppleNotificationRequest request
) {
purchaseService.appleNotification(request);
return BaseResponse.success(POST_APPLE_NOTIFICATION_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yello.server.domain.purchase.dto.apple;

import lombok.Builder;

@Builder
public record AppleNotificationPayloadVO(
String notificationType,
String subType,
ApplePayloadDataVO data,
String notificationUUID

) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yello.server.domain.purchase.dto.apple;

public record ApplePayloadDataVO(
String appAppleId,
String bundleId,
String environment,
String signedTransactionInfo,
String signedRenewalInfo,
int status

) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.yello.server.domain.purchase.dto.apple;

public record ApplePayloadSummaryVO(
String requestIdentifier,
String environment,
String appAppleId,
String bundleId,
String productId
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yello.server.domain.purchase.dto.request;

public record AppleNotificationRequest(
String signedPayload
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.yello.server.domain.purchase.dto.response;

import lombok.Builder;

@Builder
public record AppleNotificationResponse(

) {

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.yello.server.domain.purchase.service;

import com.yello.server.domain.purchase.dto.apple.AppleNotificationPayloadVO;
import com.yello.server.domain.purchase.dto.apple.TransactionInfoResponse;
import com.yello.server.domain.purchase.entity.Gateway;
import com.yello.server.domain.purchase.entity.ProductType;
Expand All @@ -17,4 +18,11 @@ Purchase createTicket(User user, ProductType productType, Gateway gateway,
void handleAppleTransactionError(ResponseEntity<TransactionInfoResponse> response,
String transactionId);

AppleNotificationPayloadVO decodeApplePayload(String signedPayload);

String decodeAppleNotificationData(String signedTransactionInfo);

void changeSubscriptionStatus(User user, String transactionId,
AppleNotificationPayloadVO payloadVO);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import static com.yello.server.global.common.ErrorCode.APPLE_TOKEN_SERVER_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.GOOGLE_SUBSCRIPTIONS_SUBSCRIPTION_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.NOT_FOUND_TRANSACTION_EXCEPTION;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yello.server.domain.purchase.dto.apple.AppleNotificationPayloadVO;
import com.yello.server.domain.purchase.dto.apple.TransactionInfoResponse;
import com.yello.server.domain.purchase.entity.Gateway;
import com.yello.server.domain.purchase.entity.ProductType;
Expand All @@ -12,7 +16,11 @@
import com.yello.server.domain.purchase.repository.PurchaseRepository;
import com.yello.server.domain.user.entity.Subscribe;
import com.yello.server.domain.user.entity.User;
import com.yello.server.domain.user.repository.UserRepository;
import com.yello.server.global.common.factory.DecodeTokenFactory;
import com.yello.server.global.common.factory.TokenFactory;
import com.yello.server.global.common.util.ConstantUtil;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
Expand All @@ -23,6 +31,7 @@ public class PurchaseManagerImpl implements PurchaseManager {

private final PurchaseRepository purchaseRepository;
private final TokenFactory tokenFactory;
private final UserRepository userRepository;

@Override
public Purchase createSubscribe(User user, Gateway gateway, String transactionId) {
Expand Down Expand Up @@ -56,5 +65,41 @@ public void handleAppleTransactionError(ResponseEntity<TransactionInfoResponse>
});
}

@Override
public AppleNotificationPayloadVO decodeApplePayload(String signedPayload) {
String jsonPayload = DecodeTokenFactory.decodePayload(signedPayload);

ObjectMapper objectMapper = new ObjectMapper();
try {
AppleNotificationPayloadVO payloadVO =
objectMapper.readValue(jsonPayload, AppleNotificationPayloadVO.class);
System.out.println(payloadVO + " 입니다아아아아아");
return payloadVO;
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

@Override
public String decodeAppleNotificationData(String signedTransactionInfo) {

Map<String, Object> decodeToken = DecodeTokenFactory.decodeToken(signedTransactionInfo);
String decodeTransactionId = decodeToken.get("transactionId").toString();

Purchase purchase = purchaseRepository.findByTransactionId(decodeTransactionId)
.orElseThrow(() -> new PurchaseConflictException(NOT_FOUND_TRANSACTION_EXCEPTION));

return purchase.getTransactionId();
}

@Override
public void changeSubscriptionStatus(User user, String transactionId,
AppleNotificationPayloadVO payloadVO) {
if (payloadVO.subType().equals(ConstantUtil.APPLE_SUBTYPE_AUTO_RENEW_DISABLED)
&& !user.getSubscribe().equals(Subscribe.NORMAL)) {
user.setSubscribe(Subscribe.NORMAL);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import static com.yello.server.global.common.ErrorCode.GOOGLE_TOKEN_FIELD_NOT_FOUND_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.GOOGLE_TOKEN_FORBIDDEN_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.GOOGLE_TOKEN_SERVER_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.NOT_FOUND_NOTIFICATION_TYPE_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.NOT_FOUND_TRANSACTION_EXCEPTION;
import static com.yello.server.global.common.ErrorCode.SUBSCRIBE_ACTIVE_EXCEPTION;
import static com.yello.server.global.common.util.ConstantUtil.APPLE_NOTIFICATION_CONSUMPTION_REQUEST;
import static com.yello.server.global.common.util.ConstantUtil.APPLE_NOTIFICATION_REFUND;
import static com.yello.server.global.common.util.ConstantUtil.APPLE_NOTIFICATION_SUBSCRIPTION_STATUS_CHANGE;
import static com.yello.server.global.common.util.ConstantUtil.FIVE_TICKET_ID;
import static com.yello.server.global.common.util.ConstantUtil.GOOGLE_FIVE_TICKET_ID;
import static com.yello.server.global.common.util.ConstantUtil.GOOGLE_TWO_TICKET_ID;
Expand All @@ -20,9 +24,11 @@

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.yello.server.domain.purchase.dto.apple.AppleNotificationPayloadVO;
import com.yello.server.domain.purchase.dto.apple.AppleTransaction;
import com.yello.server.domain.purchase.dto.apple.TransactionInfoResponse;
import com.yello.server.domain.purchase.dto.request.AppleInAppRefundRequest;
import com.yello.server.domain.purchase.dto.request.AppleNotificationRequest;
import com.yello.server.domain.purchase.dto.request.GoogleSubscriptionGetRequest;
import com.yello.server.domain.purchase.dto.request.GoogleTicketGetRequest;
import com.yello.server.domain.purchase.dto.response.GoogleSubscriptionGetResponse;
Expand Down Expand Up @@ -77,7 +83,7 @@ public UserSubscribeNeededResponse getUserSubscribe(User user, LocalDateTime tim
final Optional<Purchase> mostRecentPurchase =
purchaseRepository.findTopByUserAndProductTypeOrderByCreatedAtDesc(
user, ProductType.YELLO_PLUS);
final Boolean isSubscribeNeeded = user.getSubscribe() == Subscribe.CANCELED
final Boolean isSubscribeNeeded = user.getSubscribe()==Subscribe.CANCELED
&& mostRecentPurchase.isPresent()
&& Duration.between(mostRecentPurchase.get().getCreatedAt(), time).getSeconds()
< 1 * 24 * 60 * 60;
Expand All @@ -94,7 +100,7 @@ public void verifyAppleSubscriptionTransaction(Long userId,

purchaseManager.handleAppleTransactionError(verifyReceiptResponse, request.transactionId());

if (user.getSubscribe() == Subscribe.ACTIVE) {
if (user.getSubscribe()==Subscribe.ACTIVE) {
throw new SubscriptionConflictException(SUBSCRIBE_ACTIVE_EXCEPTION);
}

Expand All @@ -110,6 +116,7 @@ public void verifyAppleSubscriptionTransaction(Long userId,
public void verifyAppleTicketTransaction(Long userId, AppleTransaction request) {
final ResponseEntity<TransactionInfoResponse> verifyReceiptResponse =
apiWebClient.appleGetTransaction(request);

final User user = userRepository.getById(userId);

purchaseManager.handleAppleTransactionError(verifyReceiptResponse, request.transactionId());
Expand Down Expand Up @@ -141,7 +148,7 @@ public GoogleSubscriptionGetResponse verifyGoogleSubscriptionTransaction(Long us
User user = userRepository.getById(userId);

// exception
if (user.getSubscribe() != Subscribe.NORMAL) {
if (user.getSubscribe()!=Subscribe.NORMAL) {
throw new PurchaseConflictException(GOOGLE_SUBSCRIPTIONS_FORBIDDEN_EXCEPTION);
}

Expand Down Expand Up @@ -187,7 +194,7 @@ public GoogleSubscriptionGetResponse verifyGoogleSubscriptionTransaction(Long us
GOOGLE_SUBSCRIPTION_TRANSACTION_EXPIRED_EXCEPTION);
}
case ConstantUtil.GOOGLE_PURCHASE_SUBSCRIPTION_CANCELED -> {
if (user.getSubscribe() == Subscribe.CANCELED) {
if (user.getSubscribe()==Subscribe.CANCELED) {
throw new GoogleBadRequestException(
GOOGLE_SUBSCRIPTION_DUPLICATED_CANCEL_EXCEPTION);
} else {
Expand Down Expand Up @@ -241,7 +248,7 @@ public GoogleTicketGetResponse verifyGoogleTicketTransaction(Long userId,
throw new GoogleTokenServerErrorException(GOOGLE_TOKEN_SERVER_EXCEPTION);
}

if (inAppResponse.getBody().purchaseState() == 0) {
if (inAppResponse.getBody().purchaseState()==0) {
purchaseRepository.findByTransactionId(inAppResponse.getBody().orderId())
.ifPresent(action -> {
throw new PurchaseConflictException(
Expand Down Expand Up @@ -283,6 +290,31 @@ public void refundInAppApple(Long userId, AppleInAppRefundRequest request) {
user.setSubscribe(Subscribe.NORMAL);
}

@Transactional
public void appleNotification(AppleNotificationRequest request) {

AppleNotificationPayloadVO payloadVO =
purchaseManager.decodeApplePayload(request.signedPayload());
String transactionId =
purchaseManager.decodeAppleNotificationData(payloadVO.data().signedTransactionInfo());
Purchase purchase = purchaseRepository.findByTransactionId(transactionId)
.orElseThrow(() -> new PurchaseNotFoundException(NOT_FOUND_TRANSACTION_EXCEPTION));

switch (payloadVO.notificationType()) {
case APPLE_NOTIFICATION_CONSUMPTION_REQUEST:
break;
case APPLE_NOTIFICATION_SUBSCRIPTION_STATUS_CHANGE:
purchaseManager.changeSubscriptionStatus(purchase.getUser(), transactionId,
payloadVO);
break;
case APPLE_NOTIFICATION_REFUND:
System.out.println("dd");
break;
default:
throw new PurchaseNotFoundException(NOT_FOUND_NOTIFICATION_TYPE_EXCEPTION);
}
}

public ProductType getProductType(String googleInAppId) {
if (googleInAppId.equals(ConstantUtil.GOOGLE_ONE_TICKET_ID)) {
return ProductType.ONE_TICKET;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public enum ErrorCode {
"Google OAuth 2.0 특정 토큰이 DB에 없습니다. DBA에게 문의해주세요."),
USER_ADMIN_NOT_FOUND_EXCEPTION(NOT_FOUND, "해당 Admin이 존재하지 않습니다."),
NOT_EQUAL_TRANSACTION_EXCEPTION(NOT_FOUND, "동일하지 않은 거래입니다."),
NOT_FOUND_NOTIFICATION_TYPE_EXCEPTION(NOT_FOUND, "존재하지 않는 알림 타입 입니다"),

/**
* 409 CONFLICT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public enum SuccessCode {
READ_COOLDOWN_ADMIN_SUCCESS(OK, "어드민 페이지 쿨다운 조회에 성공하였습니다."),
DELETE_USER_ADMIN_SUCCESS(OK, "어드민 권환으로 유저 삭제에 성공하였습니다."),
DELETE_COOLDOWN_ADMIN_SUCCESS(OK, "어드민 권환으로 쿨다운 삭제에 성공하였습니다."),
POST_APPLE_NOTIFICATION_SUCCESS(OK, "apple 알림 처리에 성공하였습니다."),

/**
* 201 CREATED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ public static Map<String, Object> decodeToken(String jwtToken) {
final String payload = new String(java.util.Base64.getUrlDecoder().decode(payloadJWT));
BasicJsonParser jsonParser = new BasicJsonParser();
Map<String, Object> jsonArray = jsonParser.parseMap(payload);

return jsonArray;
}

public static String decodePayload(String payload) {
final String decodePayload = new String(java.util.Base64.getUrlDecoder().decode(payload));

return decodePayload;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public class TokenFactoryImpl implements TokenFactory {
@Override
public String generateAppleToken() {
setKey();

return Jwts.builder()
.setHeaderParam("kid", kid)
.setIssuer(iss)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public class ConstantUtil {
public static final String GOOGLE_PURCHASE_INAPP_CANCELED = "CANCELED";
public static final String YELLO_FEMALE = "yello_female";
public static final String YELLO_MALE = "yello_male";
public static final String APPLE_NOTIFICATION_CONSUMPTION_REQUEST = "CONSUMPTION_REQUEST";
public static final String APPLE_NOTIFICATION_SUBSCRIPTION_STATUS_CHANGE =
"DID_CHANGE_RENEWAL_STATUS";
public static final String APPLE_NOTIFICATION_REFUND = "REFUND";
public static final String APPLE_NOTIFICATION_EXPIRED = "EXPIRED";
public static final String APPLE_NOTIFICATION_TEST = "TEST";
public static final String APPLE_SUBTYPE_AUTO_RENEW_DISABLED = "AUTO_RENEW_DISABLED";
public static final String APPLE_SUBTYPE_VOLUNTARY = "VOLUNTARY";


private ConstantUtil() {
Expand Down
Loading

0 comments on commit 5954940

Please sign in to comment.