From cecb3d6ebca84719db7bc9c51b729ae8b695667a Mon Sep 17 00:00:00 2001 From: Tess Stoddard Date: Thu, 4 Dec 2025 10:05:46 -0700 Subject: [PATCH] feat: add p2p transfer recipient endpoints --- .../p2p_transfer/P2PTransferBaseAccessor.java | 22 ++++ .../p2p_transfer/RecipientBaseAccessor.java | 80 ++++++++++++ .../mx/path/model/mdx/model/Resources.java | 4 + .../mdx/model/p2p_transfer/Recipient.java | 16 +++ .../P2PTransferRecipientsController.java | 47 +++++++ ...P2PTransferRecipientsControllerTest.groovy | 115 ++++++++++++++++++ 6 files changed, 284 insertions(+) create mode 100644 mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/RecipientBaseAccessor.java create mode 100644 mdx-models/src/main/java/com/mx/path/model/mdx/model/p2p_transfer/Recipient.java create mode 100644 mdx-web/src/main/java/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsController.java create mode 100644 mdx-web/src/test/groovy/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsControllerTest.groovy diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/P2PTransferBaseAccessor.java b/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/P2PTransferBaseAccessor.java index a406288c..a388e7c8 100644 --- a/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/P2PTransferBaseAccessor.java +++ b/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/P2PTransferBaseAccessor.java @@ -27,6 +27,10 @@ public class P2PTransferBaseAccessor extends Accessor { @Getter(AccessLevel.PROTECTED) private FrequencyBaseAccessor frequencies; + @GatewayAPI + @Getter(AccessLevel.PROTECTED) + private RecipientBaseAccessor recipients; + /** * Accessor for account operations * @@ -80,4 +84,22 @@ public FrequencyBaseAccessor frequencies() { public void setFrequencies(FrequencyBaseAccessor frequencies) { this.frequencies = frequencies; } + + /** + * Accessor for recipient operations + * + * @return accessor + */ + @API + public RecipientBaseAccessor recipients() { + return recipients; + } + + /** + * Sets recipient accessor + * @param recipients + */ + public void setRecipients(RecipientBaseAccessor recipients) { + this.recipients = recipients; + } } diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/RecipientBaseAccessor.java b/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/RecipientBaseAccessor.java new file mode 100644 index 00000000..e6e1bba8 --- /dev/null +++ b/mdx-models/src/main/java/com/mx/path/model/mdx/accessor/p2p_transfer/RecipientBaseAccessor.java @@ -0,0 +1,80 @@ +package com.mx.path.model.mdx.accessor.p2p_transfer; + +import com.mx.path.core.common.accessor.API; +import com.mx.path.core.common.accessor.AccessorMethodNotImplementedException; +import com.mx.path.core.common.gateway.GatewayAPI; +import com.mx.path.core.common.gateway.GatewayClass; +import com.mx.path.gateway.accessor.Accessor; +import com.mx.path.gateway.accessor.AccessorResponse; +import com.mx.path.model.mdx.model.MdxList; +import com.mx.path.model.mdx.model.p2p_transfer.Recipient; + +/** + * Accessor base for p2p transfer recipient operations + */ +@GatewayClass +@API(specificationUrl = "https://developer.mx.com/drafts/mdx/p2p_transfer/index.html#recipients") +public class RecipientBaseAccessor extends Accessor { + public RecipientBaseAccessor() { + } + + /** + * Create a recipient + * + * @param recipient + * @return + */ + @GatewayAPI + @API(description = "Create a recipient") + public AccessorResponse create(Recipient recipient) { + throw new AccessorMethodNotImplementedException(); + } + + /** + * Delete a recipient + * + * @param id + * @return + */ + @GatewayAPI + @API(description = "Delete a recipient") + public AccessorResponse delete(String id) { + throw new AccessorMethodNotImplementedException(); + } + + /** + * Get a recipient + * + * @param id + * @return + */ + @GatewayAPI + @API(description = "Get a recipient") + public AccessorResponse get(String id) { + throw new AccessorMethodNotImplementedException(); + } + + /** + * List all recipients + * + * @return + */ + @GatewayAPI + @API(description = "List all recipients") + public AccessorResponse> list() { + throw new AccessorMethodNotImplementedException(); + } + + /** + * Update a recipient + * + * @param id + * @param recipient + * @return + */ + @GatewayAPI + @API(description = "Update a recipient") + public AccessorResponse update(String id, Recipient recipient) { + throw new AccessorMethodNotImplementedException(); + } +} diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/model/Resources.java b/mdx-models/src/main/java/com/mx/path/model/mdx/model/Resources.java index 63bb51bf..48fe529f 100644 --- a/mdx-models/src/main/java/com/mx/path/model/mdx/model/Resources.java +++ b/mdx-models/src/main/java/com/mx/path/model/mdx/model/Resources.java @@ -388,6 +388,10 @@ private static void registerP2PTransferModels(GsonBuilder builder) { builder.registerTypeAdapter(Frequency.class, new ModelWrappableSerializer("duration")); builder.registerTypeAdapter(new TypeToken>() { }.getType(), new ModelWrappableSerializer("durations")); + // Recipients + builder.registerTypeAdapter(com.mx.path.model.mdx.model.p2p_transfer.Recipient.class, new ModelWrappableSerializer("recipient")); + builder.registerTypeAdapter(new TypeToken>() { + }.getType(), new ModelWrappableSerializer("recipients")); } private static void registerPaymentsModels(GsonBuilder builder) { diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/model/p2p_transfer/Recipient.java b/mdx-models/src/main/java/com/mx/path/model/mdx/model/p2p_transfer/Recipient.java new file mode 100644 index 00000000..9cc557ea --- /dev/null +++ b/mdx-models/src/main/java/com/mx/path/model/mdx/model/p2p_transfer/Recipient.java @@ -0,0 +1,16 @@ +package com.mx.path.model.mdx.model.p2p_transfer; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import com.mx.path.model.mdx.model.MdxBase; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Recipient extends MdxBase { + private String id; + private String emailAddress; + private String firstName; + private String lastName; + private String phoneNumber; +} diff --git a/mdx-web/src/main/java/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsController.java b/mdx-web/src/main/java/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsController.java new file mode 100644 index 00000000..c734ebca --- /dev/null +++ b/mdx-web/src/main/java/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsController.java @@ -0,0 +1,47 @@ +package com.mx.path.model.mdx.web.controller; + +import com.mx.path.gateway.accessor.AccessorResponse; +import com.mx.path.model.mdx.model.MdxList; +import com.mx.path.model.mdx.model.p2p_transfer.Recipient; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "{clientId}", produces = BaseController.MDX_MEDIA) +public class P2PTransferRecipientsController extends BaseController { + @RequestMapping(value = "/users/{userId}/p2p_transfers/recipients", method = RequestMethod.POST, consumes = BaseController.MDX_MEDIA) + public final ResponseEntity create(@RequestBody Recipient recipientRequest) { + AccessorResponse response = gateway().p2pTransfers().recipients().create(recipientRequest); + return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), HttpStatus.OK); + } + + @RequestMapping(value = "/users/{userId}/p2p_transfers/recipients/{id}", method = RequestMethod.DELETE) + public final ResponseEntity delete(@PathVariable("id") String recipientId) { + AccessorResponse response = gateway().p2pTransfers().recipients().delete(recipientId); + return new ResponseEntity<>(createMultiMapForResponse(response.getHeaders()), HttpStatus.NO_CONTENT); + } + + @RequestMapping(value = "/users/{userId}/p2p_transfers/recipients/{id}", method = RequestMethod.GET) + public final ResponseEntity get(@PathVariable("id") String recipientId) { + AccessorResponse response = gateway().p2pTransfers().recipients().get(recipientId); + return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), HttpStatus.OK); + } + + @RequestMapping(value = "/users/{userId}/p2p_transfers/recipients", method = RequestMethod.GET) + public final ResponseEntity> list() { + AccessorResponse> response = gateway().p2pTransfers().recipients().list(); + return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), HttpStatus.OK); + } + + @RequestMapping(value = "/users/{userId}/p2p_transfers/recipients/{id}", method = RequestMethod.PUT, consumes = BaseController.MDX_MEDIA) + public final ResponseEntity update(@PathVariable("id") String recipientId, @RequestBody Recipient recipientRequest) { + AccessorResponse response = gateway().p2pTransfers().recipients().update(recipientId, recipientRequest); + return new ResponseEntity<>(response.getResult().wrapped(), createMultiMapForResponse(response.getHeaders()), HttpStatus.OK); + } +} diff --git a/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsControllerTest.groovy b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsControllerTest.groovy new file mode 100644 index 00000000..b7294dc3 --- /dev/null +++ b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/controller/P2PTransferRecipientsControllerTest.groovy @@ -0,0 +1,115 @@ +package com.mx.path.model.mdx.web.controller + +import static org.mockito.Mockito.doReturn +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.spy +import static org.mockito.Mockito.verify + +import com.mx.path.gateway.accessor.AccessorResponse +import com.mx.path.gateway.api.Gateway +import com.mx.path.gateway.api.p2p_transfer.P2PTransferGateway +import com.mx.path.gateway.api.p2p_transfer.RecipientGateway +import com.mx.path.model.mdx.model.MdxList +import com.mx.path.model.mdx.model.p2p_transfer.Recipient + +import org.springframework.http.HttpStatus + +import spock.lang.Specification + +class P2PTransferRecipientsControllerTest extends Specification { + P2PTransferRecipientsController subject + Gateway gateway + P2PTransferGateway p2pTransferGateway + RecipientGateway recipientGateway + + def setup() { + subject = new P2PTransferRecipientsController() + p2pTransferGateway = mock(P2PTransferGateway) + recipientGateway = mock(RecipientGateway) + + doReturn(recipientGateway).when(p2pTransferGateway).recipients() + gateway = spy(Gateway.builder().clientId("client-1234").p2pTransfers(p2pTransferGateway).build()) + } + + def cleanup() { + BaseController.clearGateway() + } + + def "create interacts with gateway"() { + given: + BaseController.setGateway(gateway) + def recipient = new Recipient() + doReturn(new AccessorResponse().withResult(recipient)).when(recipientGateway).create(recipient) + + when: + def result = subject.create(recipient) + + then: + HttpStatus.OK == result.statusCode + result.body == recipient + verify(recipientGateway).create(recipient) || true + } + + def "delete interacts with gateway"() { + given: + BaseController.setGateway(gateway) + def id = "id-1234" + doReturn(new AccessorResponse()).when(recipientGateway).delete(id) + + when: + def result = subject.delete(id) + + then: + HttpStatus.NO_CONTENT == result.statusCode + verify(recipientGateway).delete(id) || true + } + + def "get interacts with gateway"() { + given: + BaseController.setGateway(gateway) + def id = "id-1234" + def recipient = new Recipient() + doReturn(new AccessorResponse().withResult(recipient)).when(recipientGateway).get(id) + + when: + def result = subject.get(id) + + then: + HttpStatus.OK == result.statusCode + result.body == recipient + verify(recipientGateway).get(id) || true + } + + def "list interacts with gateway"() { + given: + BaseController.setGateway(gateway) + def recipients = new MdxList().tap { + add(new Recipient()) + } + doReturn(new AccessorResponse>().withResult(recipients)).when(recipientGateway).list() + + when: + def result = subject.list() + + then: + HttpStatus.OK == result.statusCode + result.body == recipients + verify(recipientGateway).list() || true + } + + def "update interacts with gateway"() { + given: + BaseController.setGateway(gateway) + def id = "id-1234" + def recipient = new Recipient() + doReturn(new AccessorResponse().withResult(recipient)).when(recipientGateway).update(id, recipient) + + when: + def result = subject.update(id, recipient) + + then: + HttpStatus.OK == result.statusCode + result.body == recipient + verify(recipientGateway).update(id, recipient) || true + } +}