Giter Club home page Giter Club logo

spreedly-java-client's Introduction

Spreedly Java Client Build Status

You can sign up for a Spreedly account at Spreedly.

Installation

Requires Java 1.7 and later.

Maven users

Add this dependency to your project's POM:

<dependency>
  <groupId>com.caravelo</groupId>
  <artifactId>spreedly-java-client</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

Gradle users

Add this dependency to your project's build file:

  compile "com.caravelo:spreedly-java-client:0.0.1-SNAPSHOT"

Usage

Add sample usages. (TODO)

Documentation

(TODO)

Supported actions (9/38)

Gateways

  • Authorize
  • Create
  • General Credit
  • Index
  • Options Index
  • Purchase
  • Redact
  • Retain
  • Show
  • Store
  • Transactions
  • Update
  • Verify

Payment Methods

  • Create (Transparent Redirect)
  • Create (Authenticated API)
  • Create (JavaScript API)
  • Index
  • Recache
  • Redact
  • Retain
  • Show
  • Transactions
  • Update

Transactions

  • Capture
  • Credit
  • Index
  • Purchase
  • Show
  • Transcript
  • Void

Receivers

  • Create
  • Deliver
  • Index
  • Redact
  • Show
  • Update

Certificates

  • Create
  • Update

Testing

Add testing info. (TODO)

spreedly-java-client's People

Contributors

facundofarias avatar mfornos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

spreedly-java-client's Issues

Rename MVN group ID

It's currently using com.github.agsecosince at the beginning I hoped to push it to my own repo; we might think a more appropriate one.

Support openjdk6

When the build was compiled on the openjdk6, the result was:

Results :
Tests in error: 
  testShow(spreedly.client.java.request.PaymentMethodRequestsTest): javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
  testShow(spreedly.client.java.request.TransactionRequestTest): javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors

It should be fixed, so we can enable that task on the CI.

Pluggable HTTP Client

  • Define the HTTP Transport boundary
    • Request, Response
    • Handlers: support for old blocking I/O and non-blocking NIO (sync and async modes)
    • Aspects: authentication, error handling and retrying, thread-safety, performance
    • Client contract
  • Pluggability via ServiceLocator (fallback to current low-demand client)
    • Adaptor for the current dependency-free HTTP client for testing and low-demand scenarios.
  • Chose a cool NIO-based HTTP Client and provided its adaptor in a separate module

Simplify Spreedly Client usage idioms

Suggestion 1 TENTATIVE

Fluent command builder like:

Transaction txn = PurchaseOnGateway.for(client)
    .amount(1234)
    .currency("EUR")
    .retainOnSuccess(true)
    .orderId("XYZ")
    .email("[email protected]")
    .merchantName("Descriptor name")
    .execute(); // here we can override client tokens if needed, or executeAsync();

Instead of this kind of interaction:

String gatewayToken = "XKqtfVWFvZgbwmrN5ZFdMZpB1XN";
String paymentMethodToken = "U6LMHXfN6ZkOPUdXWKx6xO8DydG";

Map<String, String> options = new HashMap<String, String>();
options.put(RETAIN_ON_SUCCESS, "true");
options.put(CURRENCY_CODE, "EUR");
options.put(ORDER_ID, "XYZ");
options.put(EMAIL, "[email protected]");
options.put(MERCHANT_NAME_DESCRIPTOR, "Descriptor name");

Transaction txn = client.purchaseOnGateway(gatewayToken, paymentMethodToken, 1234, options);

Every HTTP request execution should be done through client's 'executeRequest(Request request)' helper

E.g.:

    public Transaction redactPaymentMethod(String paymentMethodToken) throws SpreedlyClientException
    {
        URL url = UrlsBuilder.redactPaymentMethod(paymentMethodToken);
        Request request = new Request(url, POST, credentials);

        Response response = httpHandler.execute(request);

        return xmlParser.parseTransaction(response.body);
    }

If the transaction works, no problem; if there is a 404 HTTP error then the behaviour is inappropriate:

spreedly.client.java.exception.XmlParserException: Unable to parse XML to object
    at spreedly.client.java.xml.SimpleXmlParser.read(SimpleXmlParser.java:94)
    at spreedly.client.java.xml.SimpleXmlParser.parseTransaction(SimpleXmlParser.java:54)
    at spreedly.client.java.Spreedly.redactPaymentMethod(Spreedly.java:132)
    at spreedly.client.java.SpreedlyTest.testRedactPaymentMethod(SpreedlyTest.java:249)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.InvokeMethod$evaluate.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
    at co.freeside.betamax.Recorder$_1_evaluate_closure1.doCall(Recorder.groovy:186)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at co.freeside.betamax.Recorder$_1_evaluate_closure1.doCall(Recorder.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:39)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
    at co.freeside.betamax.Recorder.withTape(Recorder.groovy:168)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1085)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:78)
    at co.freeside.betamax.Recorder.this$dist$invoke$2(Recorder.groovy)
    at co.freeside.betamax.Recorder$1.methodMissing(Recorder.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:837)
    at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1134)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1087)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:149)
    at co.freeside.betamax.Recorder$1.evaluate(Recorder.groovy:185)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.simpleframework.xml.core.ValueRequiredException: Unable to satisfy @org.simpleframework.xml.Element(name=token, data=false, required=true, type=void) on field 'token' protected final java.lang.String spreedly.client.java.model.Base.token for class spreedly.client.java.model.Transaction at line 2
    at org.simpleframework.xml.core.Composite.validate(Composite.java:644)
    at org.simpleframework.xml.core.Composite.readElements(Composite.java:449)
    at org.simpleframework.xml.core.Composite.access$400(Composite.java:59)
    at org.simpleframework.xml.core.Composite$Injector.read(Composite.java:1433)
    at org.simpleframework.xml.core.Composite.read(Composite.java:201)
    at org.simpleframework.xml.core.Composite.read(Composite.java:148)
    at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
    at org.simpleframework.xml.core.Persister.read(Persister.java:625)
    at org.simpleframework.xml.core.Persister.read(Persister.java:606)
    at org.simpleframework.xml.core.Persister.read(Persister.java:584)
    at org.simpleframework.xml.core.Persister.read(Persister.java:543)
    at spreedly.client.java.xml.SimpleXmlParser.read(SimpleXmlParser.java:88)
    ... 81 more

Instead, it should have thrown a SpreedlyClientException with the appropriate message.

Refactor @Betamax tests

Tests implemented with Betamax seem to me sort of integrated tests rather than unit.

  • SpreedlyTest.testCreditTransactionShouldWork()
  • SpreedlyTest.testPurchaseAndRetain()
  • SpreedlyTest.testShowPaymentMethodShouldWork()
  • SpreedlyTest.testShowTransactionShouldWork()
  • SpreedlyTest.testVerifyAndRetain()

These should have their unit counterparts and consequently moved to an integrated tests folder.

Usage examples

Common scenarios.
Reference for advanced customizations/integrations.

Use an InputStream or a Reader for response serialization instead of a String

IMHO, passing the stream returned by HttpURLConnection.getInputStream (or a Reader over it) is a nicer approach (and adds less overhead) than transforming the stream to a string and re-processing it. Although the benefit depends on the underlying XML parser implementation. In the current case, Simple XML tries to use any StAX implementation found in the classpath. So, it seems worth doing.


Changes Needed

See the TODO in https://github.com/CYFLabs/spreedly-java-client/blob/e5ffc343f3a3e6c6e74f6aa68270535e08d2de25/spreedly-java-client/src/main/java/spreedly/client/java/http/UrlConnectionHttpHandler.java#L152

Redefine the interface and implementation of https://github.com/CYFLabs/spreedly-java-client/blob/e5ffc343f3a3e6c6e74f6aa68270535e08d2de25/spreedly-java-client/src/main/java/spreedly/client/java/xml/XmlParser.java

Modify https://github.com/CYFLabs/spreedly-java-client/blob/e5ffc343f3a3e6c6e74f6aa68270535e08d2de25/spreedly-java-client/src/main/java/spreedly/client/java/http/Response.java to hold the reference to the stream/reader instead of body.

Handle 404 HTTP status in Spreedly client

404 only should happen during client's development, but this scenario should be handled properly anyways.

At least throw SpreedlyClientException with message "Not Found", for instance.

Decouple domain and XML parsing concerns

Currently, domain classes like PaymentMethod are tied to SimpleXML's annotations. I think this is not good and perhaps they should be decoupled from this concerns somehow...

What do you think?

Provide some beautiful logging

It would really nice to log at DEBUG HTTP input/output as well as XML serialisation/parsing.

It'd be ideal to finish #6 before.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.