diff --git a/api/pom.xml b/api/pom.xml index e55f3388..b64ee8b0 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -4,10 +4,7 @@ com.messagebird messagebird-api - 3.0.19 + 6.4.0 jar ${project.groupId}:${project.artifactId} @@ -30,6 +27,12 @@ MessageBird https://www.messagebird.com + + Venkateswaran S + venkat.sankaran@bird.com + MessageBird + https://www.messagebird.com + Sam Wierema sam@messagebird.com @@ -59,19 +62,51 @@ + test false + + + UTF-8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + **/ContactTest.java + **/MessageBirdClientTest.java + + + + + + + + + integration + + false + UTF-8 + disable-doclint - [8,11,) + [1.8,11) none - true UTF-8 @@ -79,16 +114,37 @@ + + + + + org.codehaus.plexus + plexus-utils + 3.6.1 + + + + com.fasterxml.jackson.core jackson-annotations - 2.11.0 + 2.13.2 com.fasterxml.jackson.core jackson-databind - 2.11.0 + 2.14.0-rc1 + + + com.auth0 + java-jwt + 4.4.0 + + + org.apache.maven + maven-artifact + 3.9.6 junit @@ -108,15 +164,14 @@ 2.21.0 test + + org.jetbrains + annotations + 13.0 + provided + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - @@ -211,14 +266,22 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 + + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - false + central + false + validated diff --git a/api/src/main/java/com/messagebird/Base64.java b/api/src/main/java/com/messagebird/Base64.java deleted file mode 100644 index 876beeb0..00000000 --- a/api/src/main/java/com/messagebird/Base64.java +++ /dev/null @@ -1,496 +0,0 @@ -package com.messagebird; - -/** - * Cutted version of iharder's base64 implementation - * @todo replace with actual library on next major bump - * - *

Encodes and decodes to and from Base64 notation.

- *

Homepage: http://iharder.net/base64.

- * - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - */ -class Base64 { - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ - public final static int NO_OPTIONS = 0; - - /** Specify that gzipped data should not be automatically gunzipped. */ - public final static int DONT_GUNZIP = 4; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** Preferred encoding. */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] _STANDARD_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' - 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' - 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet( int options ) { - if( (options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } else { - return _STANDARD_DECODABET; - } - } // end getAlphabet - - - - /** Defeats instantiation. */ - private Base64(){} - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

This is the lowest level of the decoding methods with - * all possible parameters.

- * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. - * @since 1.3 - */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options ) { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Source array was null." ); - } // end if - if( destination == null ){ - throw new NullPointerException( "Destination array was null." ); - } // end if - if( srcOffset < 0 || srcOffset + 3 >= source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); - } // end if - if( destOffset < 0 || destOffset +2 >= destination.length ){ - throw new IllegalArgumentException( String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); - } // end if - - - byte[] DECODABET = getDecodabet( options ); - - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - } - } // end decodeToBytes - - - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data - * @since 1.3 - */ - public static byte[] decode( byte[] source, int off, int len, int options ) - throws java.io.IOException { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Cannot decode null source array." ); - } // end if - if( off < 0 || off + len > source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); - } // end if - - if( len == 0 ){ - return new byte[0]; - }else if( len < 4 ){ - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len ); - } // end if - - byte[] DECODABET = getDecodabet( options ); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for( i = off; i < off+len; i++ ) { // Loop through source - - sbiDecode = DECODABET[ source[i]&0xFF ]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if( sbiDecode >= WHITE_SPACE_ENC ) { - if( sbiDecode >= EQUALS_SIGN_ENC ) { - b4[ b4Posn++ ] = source[i]; // Save non-whitespace - if( b4Posn > 3 ) { // Time to decode? - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( source[i] == EQUALS_SIGN ) { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException( String.format( - "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode - - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode( String s ) throws java.io.IOException { - return decode( s, NO_OPTIONS ); - } - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 - */ - public static byte[] decode( String s, int options ) throws java.io.IOException { - - if( s == null ){ - throw new NullPointerException( "Input string was null." ); - } // end if - - byte[] bytes; - try { - bytes = s.getBytes( PREFERRED_ENCODING ); - } // end try - catch( java.io.UnsupportedEncodingException uee ) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode( bytes, 0, bytes.length, options ); - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - boolean dontGunzip = (options & DONT_GUNZIP) != 0; - if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); - - while( ( length = gzis.read( buffer ) ) >= 0 ) { - baos.write(buffer,0,length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch( java.io.IOException e ) { - e.printStackTrace(); - // Just return originally-decoded bytes - } // end catch - finally { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - -} // end class Base64 diff --git a/api/src/main/java/com/messagebird/MessageBirdClient.java b/api/src/main/java/com/messagebird/MessageBirdClient.java index 101d3e4d..583dcb40 100644 --- a/api/src/main/java/com/messagebird/MessageBirdClient.java +++ b/api/src/main/java/com/messagebird/MessageBirdClient.java @@ -3,33 +3,7 @@ import com.messagebird.exceptions.GeneralException; import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; -import com.messagebird.objects.Balance; -import com.messagebird.objects.Contact; -import com.messagebird.objects.ContactList; -import com.messagebird.objects.ContactRequest; -import com.messagebird.objects.ErrorReport; -import com.messagebird.objects.Group; -import com.messagebird.objects.GroupList; -import com.messagebird.objects.GroupRequest; -import com.messagebird.objects.Hlr; -import com.messagebird.objects.Lookup; -import com.messagebird.objects.LookupHlr; -import com.messagebird.objects.Message; -import com.messagebird.objects.MessageList; -import com.messagebird.objects.MessageResponse; -import com.messagebird.objects.MsgType; -import com.messagebird.objects.PagedPaging; -import com.messagebird.objects.PhoneNumbersLookup; -import com.messagebird.objects.PhoneNumbersResponse; -import com.messagebird.objects.PurchasedNumber; -import com.messagebird.objects.PurchasedNumberCreatedResponse; -import com.messagebird.objects.PurchasedNumbersResponse; -import com.messagebird.objects.PurchasedNumbersFilter; -import com.messagebird.objects.Verify; -import com.messagebird.objects.VerifyRequest; -import com.messagebird.objects.VoiceMessage; -import com.messagebird.objects.VoiceMessageList; -import com.messagebird.objects.VoiceMessageResponse; +import com.messagebird.objects.*; import com.messagebird.objects.conversations.Conversation; import com.messagebird.objects.conversations.ConversationList; import com.messagebird.objects.conversations.ConversationMessage; @@ -39,10 +13,14 @@ import com.messagebird.objects.conversations.ConversationSendResponse; import com.messagebird.objects.conversations.ConversationStartRequest; import com.messagebird.objects.conversations.ConversationStatus; +import com.messagebird.objects.conversations.ConversationUpdateRequest; import com.messagebird.objects.conversations.ConversationWebhook; import com.messagebird.objects.conversations.ConversationWebhookCreateRequest; import com.messagebird.objects.conversations.ConversationWebhookList; import com.messagebird.objects.conversations.ConversationWebhookUpdateRequest; +import com.messagebird.objects.integrations.Template; +import com.messagebird.objects.integrations.TemplateList; +import com.messagebird.objects.integrations.TemplateResponse; import com.messagebird.objects.voicecalls.RecordingResponse; import com.messagebird.objects.voicecalls.TranscriptionResponse; import com.messagebird.objects.voicecalls.VoiceCall; @@ -56,20 +34,19 @@ import com.messagebird.objects.voicecalls.Webhook; import com.messagebird.objects.voicecalls.WebhookList; import com.messagebird.objects.voicecalls.WebhookResponseData; - import java.io.UnsupportedEncodingException; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.HashSet; /** * Message bird general client @@ -95,10 +72,14 @@ public class MessageBirdClient { * can, however, override this behaviour by providing absolute URLs * ourselves. */ - private static final String CONVERSATIONS_BASE_URL = "https://conversations.messagebird.com/v1"; + static final String CONVERSATIONS_BASE_URL = "https://conversations.messagebird.com/v1"; static final String VOICE_CALLS_BASE_URL = "https://voice.messagebird.com"; static final String NUMBERS_CALLS_BASE_URL = "https://numbers.messagebird.com/v1"; + static final String MESSAGING_BASE_URL = "https://messaging.messagebird.com/v1"; + static final String INTEGRATIONS_BASE_URL_V2 = "https://integrations.messagebird.com/v2"; + static final String INTEGRATIONS_BASE_URL_V3 = "https://integrations.messagebird.com/v3"; private static String[] supportedLanguages = {"de-DE", "en-AU", "en-UK", "en-US", "es-ES", "es-LA", "fr-FR", "it-IT", "nl-NL", "pt-BR"}; + static final String PARTNER_ACCOUNTS_BASE_URL = "https://partner-accounts.messagebird.com"; private static final String BALANCEPATH = "/balance"; private static final String CONTACTPATH = "/contacts"; @@ -108,11 +89,13 @@ public class MessageBirdClient { private static final String LOOKUPPATH = "/lookup"; private static final String MESSAGESPATH = "/messages"; private static final String VERIFYPATH = "/verify"; + private static final String VERIFYEMAILPATH = "/verify/messages/email"; private static final String VOICEMESSAGESPATH = "/voicemessages"; - private static final String CONVERSATION_PATH = "/conversations"; - private static final String CONVERSATION_SEND_PATH = "/send"; - private static final String CONVERSATION_MESSAGE_PATH = "/messages"; - private static final String CONVERSATION_WEBHOOK_PATH = "/webhooks"; + static final String CONVERSATION_PATH = "/conversations"; + static final String CONVERSATION_SEND_PATH = "/send"; + static final String CONVERSATION_MESSAGE_PATH = "/messages"; + static final String CONVERSATION_WEBHOOK_PATH = "/webhooks"; + static final String INTEGRATIONS_WHATSAPP_PATH = "/platforms/whatsapp"; static final String VOICECALLSPATH = "/calls"; static final String LEGSPATH = "/legs"; static final String RECORDINGPATH = "/recordings"; @@ -120,6 +103,12 @@ public class MessageBirdClient { static final String WEBHOOKS = "/webhooks"; static final String VOICECALLFLOWPATH = "/call-flows"; private static final String VOICELEGS_SUFFIX_PATH = "/legs"; + static final String FILES_PATH = "/files"; + static final String TEMPLATES_PATH = "/templates"; + static final String UNPAUSE_TEMAPLATE_PATH = "/unpause"; + static final String OUTBOUND_SMS_PRICING_PATH = "/pricing/sms/outbound"; + static final String OUTBOUND_SMS_PRICING_SMPP_PATH = "/pricing/sms/outbound/smpp/%s"; + static final String RECORDING_DOWNLOAD_FORMAT = ".wav"; static final String TRANSCRIPTION_DOWNLOAD_FORMAT = ".txt"; @@ -131,6 +120,9 @@ public class MessageBirdClient { private static final String[] MESSAGE_LIST_FILTERS_VALS = {"originator", "recipient", "direction", "searchterm", "type", "contact_id", "status", "from", "until"}; private static final Set MESSAGE_LIST_FILTERS = new HashSet<>(Arrays.asList(MESSAGE_LIST_FILTERS_VALS)); + private static final String[] CONVERSATION_MESSAGE_LIST_FILTERS_VALS = {"ids", "from"}; + private static final Set CONVERSATION_MESSAGE_LIST_FILTERS = new HashSet<>(Arrays.asList(CONVERSATION_MESSAGE_LIST_FILTERS_VALS)); + private final String DOWNLOADS = "Downloads"; private MessageBirdService messageBirdService; @@ -481,13 +473,27 @@ public Verify verifyToken(String id, String token) throws NotFoundException, Gen * @throws UnauthorizedException if client is unauthorized * @throws GeneralException general exception */ - Verify getVerifyObject(String id) throws NotFoundException, GeneralException, UnauthorizedException { + public Verify getVerifyObject(String id) throws NotFoundException, GeneralException, UnauthorizedException { if (id == null || id.isEmpty()) { throw new IllegalArgumentException("ID cannot be empty for verify"); } return messageBirdService.requestByID(VERIFYPATH, id, Verify.class); } + /** + * @param messageId is for the email message part of a verify object + * @return Verify object + * @throws NotFoundException if id is not found + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public VerifyMessage getVerifyEmailMessage(String messageId) throws UnauthorizedException, GeneralException, NotFoundException { + if (messageId == null || messageId.isEmpty()) { + throw new IllegalArgumentException("ID cannot be empty for verify email message"); + } + return messageBirdService.requestByID(VERIFYEMAILPATH, messageId, VerifyMessage.class); + } + /** * @param id id for deleting verify object * @throws NotFoundException if id is not found @@ -677,8 +683,8 @@ public VoiceCallFlowResponse sendVoiceCallFlow(final VoiceCallFlowRequest voiceC * @param id String * @param voiceCallFlowRequest VoiceCallFlowRequest * @return VoiceCallFlowResponse - * @throws UnauthorizedException - * @throws GeneralException + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception */ public VoiceCallFlowResponse updateVoiceCallFlow(String id, VoiceCallFlowRequest voiceCallFlowRequest) throws UnauthorizedException, GeneralException { @@ -899,8 +905,12 @@ public Conversation updateConversation(final String id, final ConversationStatus if (id == null) { throw new IllegalArgumentException("Id must be specified."); } + if (status == null) { + throw new IllegalArgumentException("An updated conversation status must be specified"); + } + ConversationUpdateRequest payload = new ConversationUpdateRequest(status); String url = String.format("%s%s/%s", CONVERSATIONS_BASE_URL, CONVERSATION_PATH, id); - return messageBirdService.sendPayLoad("PATCH", url, status, Conversation.class); + return messageBirdService.sendPayLoad("PATCH", url, payload, Conversation.class); } /** @@ -1002,6 +1012,23 @@ public ConversationMessage viewConversationMessage(final String messageId) return messageBirdService.requestByID(url, messageId, ConversationMessage.class); } + /** + * Gets conversation messages based on query param. + * + * @param queryParams only `ids` and `from` is available as an option + * @return The retrieved messages. + */ + public ConversationMessageList listConversationMessagesWithQueryParam(Map queryParams) + throws NotFoundException, GeneralException, UnauthorizedException { + for (String queryParam : queryParams.keySet()) { + if (!CONVERSATION_MESSAGE_LIST_FILTERS.contains(queryParam)) { + throw new IllegalArgumentException("Invalid filter name: " + queryParam); + } + } + String url = CONVERSATIONS_BASE_URL + CONVERSATION_MESSAGE_PATH; + return messageBirdService.requestByID(url, null, queryParams, ConversationMessageList.class); + } + /** * Sends a message to an existing Conversation. * @@ -1767,4 +1794,494 @@ public void cancelNumber(String number) throws UnauthorizedException, GeneralExc final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL); messageBirdService.deleteByID(url, number); } + + /** + * Uploads a file and returns the assigned ID. + * + * @param binary the bytes of the file to upload. + * @param contentType the content type of the file (e.g. "image/png"). + * @param filename optional filename to set in the upload request headers. + * @return FileUploadResponse + * @throws GeneralException general exception + * @throws UnauthorizedException if client is unauthorized + * @see #downloadFile + */ + public FileUploadResponse uploadFile(byte[] binary, String contentType, String filename) throws GeneralException, UnauthorizedException { + if (binary == null) { + throw new IllegalArgumentException("File binary must be specified."); + } + if (contentType == null) { + throw new IllegalArgumentException("Content type must be specified."); + } + + final String url = MESSAGING_BASE_URL + FILES_PATH; + final Map headers = new HashMap<>(); + headers.put("Content-Type", contentType); + if (filename != null) { + headers.put("filename", filename); + } + return messageBirdService.sendPayLoad("POST", url, headers, binary, FileUploadResponse.class); + } + + /** + * Downloads a file and stores it with the provided filename in the basePath directory. The + * basePath may be null. If basePath is null, the default download directory will be the + * /Download folder in the user home directory. The filename may be null. If filename is null, + * the provided id will be used as the filename instead. + * + * @param id the ID of the file, provided when the file was uploaded + * @param basePath store location. It should be a directory. Property is nullable if $HOME is accessible + * @param filename the name of the file to download to. + * @return the path where the downloaded file is stored + * @throws NotFoundException if the file does not exist + * @throws GeneralException general exception + * @throws UnauthorizedException if client is unauthorized + * @see #uploadFile + */ + public String downloadFile(String id, String filename, String basePath) throws GeneralException, UnauthorizedException, NotFoundException { + if (id == null) { + throw new IllegalArgumentException("File ID must be specified."); + } + + if (filename == null) { + filename = id; + } + + final String url = String.format("%s%s/%s", MESSAGING_BASE_URL, FILES_PATH, id); + return messageBirdService.getBinaryData(url, basePath, filename); + } + + /****************************************************************************************************/ + /** WhatsApp Templates **/ + /****************************************************************************************************/ + + /** + * Create a WhatsApp message template through messagebird. + * + * @param template {@link Template} object to be created + * @return {@link TemplateResponse} response object + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws IllegalArgumentException invalid template format + */ + public TemplateResponse createWhatsAppTemplate(final Template template) + throws UnauthorizedException, GeneralException, IllegalArgumentException { + template.validate(); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + return messageBirdService.sendPayLoad(url, template, TemplateResponse.class); + } + + /** + * Update a WhatsApp message template through MessageBird. + * + * @param template {@link Template} object to be created + * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable + * @param language A language code as returned by getWhatsAppTemplateBy in the language variable + * @return {@link TemplateResponse} response object + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws IllegalArgumentException invalid template format + */ + public TemplateResponse updateWhatsAppTemplate(final Template template, final String templateName, final String language) + throws UnauthorizedException, GeneralException, IllegalArgumentException { + template.validate(); + + String url = String.format( + "%s%s%s/%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language); + + return messageBirdService.sendPayLoad("PUT",url, template, TemplateResponse.class); + } + /** + * Gets a WhatsAppTemplate listing with specified pagination options. + * + * @param offset Number of objects to skip. + * @param limit Number of objects to take. + * @return List of templates. + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public TemplateList listWhatsAppTemplates(final int offset, final int limit) + throws UnauthorizedException, GeneralException { + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + return messageBirdService.requestList(url, offset, limit, TemplateList.class); + } + + /** + * Gets a WhatsAppTemplate listing with specified pagination options and a wabaID or channelID filter. + * + * @param offset Number of objects to skip. + * @param limit Number of objects to take. + * @param wabaID The WABA ID to filter templates by. + * @param channelID A channel ID filter to return only templates that can be sent via that channel. + * @return List of templates. + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws IllegalArgumentException if the provided arguments are not valid + */ + public TemplateList listWhatsAppTemplates(final int offset, final int limit, final String wabaID, final String channelID) + throws UnauthorizedException, GeneralException, IllegalArgumentException { + validateWABAIDAndChannelIDArguments(wabaID, channelID); + + Map map = new LinkedHashMap<>(); + if (wabaID != null) map.put("wabaId", wabaID); + if (channelID != null) map.put("channelId", channelID); + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + return messageBirdService.requestList(url, map, offset, limit, TemplateList.class); + } + + /** + * Gets a template listing with default pagination options. + * + * @return List of whatsapp templates. + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public TemplateList listWhatsAppTemplates() throws UnauthorizedException, GeneralException { + final int offset = 0; + final int limit = 10; + + return listWhatsAppTemplates(offset, limit); + } + + /** + * Retrieves the template of an existing template name. + * + * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable + * @return {@code List} template list + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if template name is not found + * @throws IllegalArgumentException if the provided arguments are not valid + */ + public List getWhatsAppTemplatesBy(final String templateName) + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { + if (templateName == null) { + throw new IllegalArgumentException("Template name must be specified."); + } + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + return messageBirdService.requestByIdAsList(url, templateName, TemplateResponse.class); + } + + /** + * Retrieves the template of an existing template name. + * + * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable + * @param wabaID An optional WABA ID to look for the template ID under. + * @param channelID An optional channel ID to specify. If the template can be sent via the channel, it will return the template. + * + * @return {@code List} template list + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if template name is not found under the given WABA or cannot be sent under the supplied channel ID + * @throws IllegalArgumentException if the provided arguments are not valid + */ + public List getWhatsAppTemplatesBy(final String templateName, final String wabaID, final String channelID) + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { + if (templateName == null) { + throw new IllegalArgumentException("Template name must be specified."); + } + + String id = String.format("%s%s", templateName, getWabaIDOrChannelIDQuery(wabaID, channelID)); + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + return messageBirdService.requestByIdAsList(url, id, TemplateResponse.class); + } + + /** + * Retrieves the template of an existing template name and language under the first waba connected to the requesting user. + * + * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable + * @param language A language code as returned by getWhatsAppTemplateBy in the language variable + * + * @return {@code TemplateResponse} template + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if template name and language are not found under the first waba connected to the requesting user. + * @throws IllegalArgumentException if the provided arguments are not valid + */ + public TemplateResponse fetchWhatsAppTemplateBy(final String templateName, final String language) + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { + if (templateName == null || language == null) { + throw new IllegalArgumentException("Template name and language must be specified."); + } + + String url = String.format( + "%s%s%s/%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language + ); + return messageBirdService.request(url, TemplateResponse.class); + } + + /** + * Retrieves the template of an existing template name and language under a WABA or for a channel. + * + * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable + * @param language A language code as returned by getWhatsAppTemplateBy in the language variable + * @param wabaID An optional WABA ID to look for the template ID under. + * @param channelID An optional channel ID to specify. If the template can be sent via the channel, it will return the template. + * + * @return {@code TemplateResponse} template + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if template name and language are not found under the given WABA or cannot be sent under the supplied channel ID. + * @throws IllegalArgumentException if the provided arguments are not valid + */ + public TemplateResponse fetchWhatsAppTemplateBy(final String templateName, final String language, final String wabaID, final String channelID) + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { + if (templateName == null || language == null) { + throw new IllegalArgumentException("Template name and language must be specified."); + } + + String url = String.format( + "%s%s%s/%s/%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language, + getWabaIDOrChannelIDQuery(wabaID, channelID) + ); + return messageBirdService.request(url, TemplateResponse.class); + } + + /** + * Validates the WABA ID and Channel ID argument pair. + * + * @param wabaID A WABA ID. + * @param channelID A channel ID. + * @throws IllegalArgumentException if the argument pair is invalid. + */ + private void validateWABAIDAndChannelIDArguments(String wabaID, String channelID) + throws IllegalArgumentException { + if (wabaID == null && channelID == null) { + throw new IllegalArgumentException("wabaID or channelID must be specified"); + } + + if (wabaID != null && channelID != null) { + throw new IllegalArgumentException("only supply wabaID or channelID - not both"); + } + } + + /** + * Validates the WABA ID and Channel ID argument pair and returns a valid query parameter string. + * + * @param wabaID A WABA ID. + * @param channelID A channel ID. + * @throws IllegalArgumentException if the argument pair is invalid. + */ + private String getWabaIDOrChannelIDQuery(String wabaID, String channelID) + throws IllegalArgumentException { + validateWABAIDAndChannelIDArguments(wabaID, channelID); + + String query = ""; + + if (wabaID != null) { + query = String.format("?wabaId=%s", wabaID); + } + if (channelID != null) { + query = String.format("?channelId=%s", channelID); + } + + return query; + } + + /** + * Delete templates of an existing template name. + * + * @param templateName A template name which is created on the MessageBird platform + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if template name is not found + */ + public void deleteTemplatesBy(final String templateName) + throws UnauthorizedException, GeneralException, NotFoundException { + if (templateName == null) { + throw new IllegalArgumentException("Template name must be specified."); + } + + String url = String.format( + "%s%s%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName + ); + messageBirdService.delete(url, null); + } + + public void unpauseTemplatesByTemplateName(final String templateName) + throws UnauthorizedException, GeneralException { + if (templateName == null) { + throw new IllegalArgumentException("Template name must be specified."); + } + + String url = String.format( + "%s%s%s%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + UNPAUSE_TEMAPLATE_PATH, + templateName + ); + messageBirdService.sendPayLoad("POST", url, "", null); + } + + /** + * Function to create a child account + * + * @param childAccountRequest of child account to create + * @return ChildAccountResponse created + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public ChildAccountCreateResponse createChildAccount(final ChildAccountRequest childAccountRequest) throws UnauthorizedException, GeneralException { + if (childAccountRequest.getName() == null || childAccountRequest.getName().isEmpty()) { + throw new IllegalArgumentException("Name must be specified."); + } + + String url = String.format("%s%s", PARTNER_ACCOUNTS_BASE_URL, "/child-accounts"); + return messageBirdService.sendPayLoad(url, childAccountRequest, ChildAccountCreateResponse.class); + } + + /** + * Function to update a child account + * + * @param id of child account to update + * @return ChildAccountResponse created + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public ChildAccountResponse updateChildAccount(final String name, final String id) throws UnauthorizedException, GeneralException { + if (name == null) { + throw new IllegalArgumentException("Name must be specified."); + } + + if (id == null) { + throw new IllegalArgumentException("Child account id must be specified."); + } + final ChildAccountRequest childAccountRequest = new ChildAccountRequest(); + childAccountRequest.setName(name); + final String url = String.format("%s/child-accounts/%s", PARTNER_ACCOUNTS_BASE_URL, id); + return messageBirdService.sendPayLoad("PATCH", url, childAccountRequest, ChildAccountResponse.class); + } + + /** + * Function to get a child account + * + * @param id of child account to update + * @return ChildAccountResponse created + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if id is not found + */ + public ChildAccountDetailedResponse getChildAccountById(final String id) throws UnauthorizedException, GeneralException, NotFoundException { + if (id == null) { + throw new IllegalArgumentException("Child account id must be specified."); + } + return messageBirdService.requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", id, ChildAccountDetailedResponse.class); + } + + /** + * Function to get a child account + * + * @return ChildAccountResponse created + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + public List getChildAccounts(final Integer offset, final Integer limit) throws UnauthorizedException, GeneralException { + verifyOffsetAndLimit(offset, limit); + return messageBirdService.requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", offset, limit, List.class); + } + + /** + * Function to delete a child account + * + * @param id of child account to delete + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if id is not found + */ + public void deleteChildAccount(final String id) throws UnauthorizedException, GeneralException, NotFoundException { + if (id == null) { + throw new IllegalArgumentException("Child account id must be specified."); + } + + String url = String.format("%s/child-accounts", PARTNER_ACCOUNTS_BASE_URL); + System.out.println("url: " + url); + messageBirdService.deleteByID(url, id); + } + + /** + * Returns outbound pricing for the default SMS configuration for the authenticated account. + * + * @return outbound pricing for the default SMS configuration for the authenticated account + * + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if pricing information could not be found + * + * @see Pricing API + */ + public OutboundSmsPriceResponse getOutboundSmsPrices() throws GeneralException, UnauthorizedException, NotFoundException { + return messageBirdService.request(OUTBOUND_SMS_PRICING_PATH, OutboundSmsPriceResponse.class); + } + + /** + * Returns outbound SMS pricing for a specific SMPP username. + * + * @param smppUsername the SMPP SystemID provided by MessageBird + * + * @return outbound SMS pricing for the given SMPP username + * + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if pricing information could not be found for the given SMPP username + * + * @see Pricing API + */ + public OutboundSmsPriceResponse getOutboundSmsPrices(final String smppUsername) throws GeneralException, UnauthorizedException, NotFoundException { + if (smppUsername == null) { + throw new IllegalArgumentException("SMPP username must be specified."); + } + + final String url = String.format(OUTBOUND_SMS_PRICING_SMPP_PATH, smppUsername); + return messageBirdService.request(url, OutboundSmsPriceResponse.class); + } } diff --git a/api/src/main/java/com/messagebird/MessageBirdService.java b/api/src/main/java/com/messagebird/MessageBirdService.java index 68cf1f56..43201fb7 100644 --- a/api/src/main/java/com/messagebird/MessageBirdService.java +++ b/api/src/main/java/com/messagebird/MessageBirdService.java @@ -1,16 +1,30 @@ package com.messagebird; -import java.util.Map; - import com.messagebird.exceptions.GeneralException; import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; import com.messagebird.objects.PagedPaging; +import java.util.List; +import java.util.Map; /** * Created by rvt on 1/7/15. */ public interface MessageBirdService { + + /** + * Send GET request . It will retrieve a json object R back. + * + * @author ssk910 + * @param request path to the request, for example "/messages/id/language" + * @param clazz Class type to return + * @return new class with returned dataset + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if id not found + */ + R request(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException; + /** * Execute a object by ID request. It will add the id to the request parameter and retreive a json object R back. * @@ -25,6 +39,20 @@ public interface MessageBirdService { R requestByID(String request, String id, Map params, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException; + /** + * Execute a object by ID request. It will add the id to the request parameter and retrieve a list of an object E back. + * + * @author ssk910 + * @param request path to the request, for example "/messages" + * @param id id of the object to request. id can be null in case request's that don't need a id, for example /balance + * @param elementClass Class type of List to return + * @return new list of elementClass + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException + */ + List requestByIdAsList(String request, String id, Class elementClass) throws UnauthorizedException, GeneralException, NotFoundException; + /** * Delete a object by ID. * @@ -36,6 +64,19 @@ public interface MessageBirdService { */ void deleteByID(String request, String id) throws UnauthorizedException, GeneralException, NotFoundException; + /** + * Send DELETE request. It will retrieve a json object R back. + * + * @author ssk910 + * @param request path to the request, for example "/messages/id/language" + * @param clazz Class type to return + * @return class with returned dataset + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + * @throws NotFoundException if id not found + */ + R delete(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException; + /** * Request a List 'of' object. * Allow to request a listMessage or listViewMessages objects. @@ -107,6 +148,20 @@ public interface MessageBirdService { */ R sendPayLoad(String method, String request, P payload, Class clazz) throws UnauthorizedException, GeneralException; + /** + * Send a payload with the provided method and headers and receive a payload object. + * + * @param method HTTP method to use for the request + * @param request path to the request, for example "/messages" + * @param headers additional headers to set on the request + * @param payload payload to send to the server + * @param clazz object type to return + * @return base class + * @throws UnauthorizedException if client is unauthorized + * @throws GeneralException general exception + */ + R sendPayLoad(String method, String request, Map headers, P payload, Class clazz) throws UnauthorizedException, GeneralException; + /** * Gets the data from the request URL and stores it to basePath/fileName * diff --git a/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java index 7f7ddad5..ef179b45 100644 --- a/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java +++ b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java @@ -1,6 +1,20 @@ package com.messagebird; -import java.io.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.ErrorReport; +import com.messagebird.objects.PagedPaging; +import org.apache.maven.artifact.versioning.ComparableVersion; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.HttpURLConnection; @@ -10,18 +24,16 @@ import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.*; - -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.messagebird.exceptions.GeneralException; -import com.messagebird.exceptions.NotFoundException; -import com.messagebird.exceptions.UnauthorizedException; -import com.messagebird.objects.ErrorReport; -import com.messagebird.objects.PagedPaging; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; /** * Implementation of MessageBirdService @@ -50,8 +62,7 @@ public class MessageBirdServiceImpl implements MessageBirdService { private static final String[] PROTOCOL_LISTS = new String[]{"http://", "https://"}; private static final List PROTOCOLS = Arrays.asList(PROTOCOL_LISTS); - // Used when the actual version can not be parsed. - private static final double DEFAULT_JAVA_VERSION = 0.0; + private static final ComparableVersion JAVA_VERSION = getJavaVersion(); // Indicates whether we've overridden HttpURLConnection's behaviour to // allow PATCH requests yet. Also see docs on allowPatchRequestsIfNeeded(). @@ -61,7 +72,7 @@ public class MessageBirdServiceImpl implements MessageBirdService { private final String accessKey; private final String serviceUrl; - private final String clientVersion = "3.0.19"; + private final String clientVersion = "6.3.1"; private final String userAgentString; private Proxy proxy = null; @@ -78,15 +89,17 @@ public MessageBirdServiceImpl(final String accessKey, final String serviceUrl) { } - private String determineUserAgentString() { - double javaVersion = DEFAULT_JAVA_VERSION; + private static ComparableVersion getJavaVersion() { try { - javaVersion = getVersion(); - } catch (GeneralException e) { - // Do nothing: leave the version at its default. + String version = System.getProperty("java.version"); + return new ComparableVersion(version); + } catch (IllegalArgumentException e) { + return new ComparableVersion("0.0"); } + } - return String.format("MessageBird Java/%s ApiClient/%s", javaVersion, clientVersion); + private String determineUserAgentString() { + return String.format("MessageBird Java/%s ApiClient/%s", JAVA_VERSION, clientVersion); } /** @@ -98,6 +111,12 @@ public MessageBirdServiceImpl(final String accessKey) { this(accessKey, "https://rest.messagebird.com"); } + @Override + public R request(String request, Class clazz) + throws UnauthorizedException, GeneralException, NotFoundException { + return getJsonData(request, null, "GET", clazz); + } + @Override public R requestByID(String request, String id, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException { String path = ""; @@ -121,11 +140,27 @@ public R requestByID(String request, String id, Map params, return getJsonData(request + path + queryParams, null, "GET", clazz); } + @Override + public List requestByIdAsList(String request, String id, Class elementClass) + throws UnauthorizedException, GeneralException, NotFoundException { + String path = ""; + if (id != null) { + path = "/" + id; + } + + return getJsonDataAsList(request + path, null, "GET", elementClass); + } + @Override public void deleteByID(String request, String id) throws UnauthorizedException, GeneralException, NotFoundException { getJsonData(request + "/" + id, null, "DELETE", null); } + @Override + public R delete(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException { + return getJsonData(request, null, "DELETE", clazz); + } + @Override public R requestList(String request, Integer offset, Integer limit, Class clazz) throws UnauthorizedException, GeneralException { Map map = new LinkedHashMap<>(); @@ -169,12 +204,17 @@ public R sendPayLoad(String request, P payload, Class clazz) throws Un @Override public R sendPayLoad(String method, String request, P payload, Class clazz) throws UnauthorizedException, GeneralException { + return sendPayLoad(method, request, new HashMap<>(), payload, clazz); + } + + @Override + public R sendPayLoad(String method, String request, Map headers, P payload, Class clazz) throws UnauthorizedException, GeneralException { if (!REQUEST_METHODS_WITH_PAYLOAD.contains(method)) { throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, method)); } try { - return getJsonData(request, payload, method, clazz); + return getJsonData(request, payload, method, headers, clazz); } catch (NotFoundException e) { throw new GeneralException(e); } @@ -204,6 +244,14 @@ public String getBinaryData(String request, String basePath, String fileName) th public T getJsonData(final String request, final P payload, final String requestType, final Class clazz) throws UnauthorizedException, GeneralException, NotFoundException { + return getJsonData(request, payload, requestType, new HashMap<>(), clazz); + } + + public List getJsonDataAsList(final String request, final P payload, final String requestType, final Class elementClass) throws UnauthorizedException, GeneralException, NotFoundException { + return getJsonDataAsList(request, payload, requestType, new HashMap<>(), elementClass); + } + + public T getJsonData(final String request, final P payload, final String requestType, final Map headers, final Class clazz) throws UnauthorizedException, GeneralException, NotFoundException { if (request == null) { throw new IllegalArgumentException(REQUEST_VALUE_MUST_BE_SPECIFIED); } @@ -212,7 +260,7 @@ public T getJsonData(final String request, final P payload, final String if (!isURLAbsolute(url)) { url = serviceUrl + url; } - final APIResponse apiResponse = doRequest(requestType, url, payload); + final APIResponse apiResponse = doRequest(requestType, url, headers, payload); final String body = apiResponse.getBody(); final int status = apiResponse.getStatus(); @@ -226,7 +274,10 @@ public T getJsonData(final String request, final P payload, final String mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); - return mapper.readValue(body, clazz); + // Prevents mismatched exception when clazz is null + return clazz == null + ? null + : this.readValue(mapper, body, clazz); } catch (IOException ioe) { throw new GeneralException(ioe); } @@ -237,6 +288,54 @@ public T getJsonData(final String request, final P payload, final String return null; } + // todo: need to refactor for duplicated code. + public List getJsonDataAsList(final String request, + final P payload, final String requestType, final Map headers, final Class elementClass) + throws UnauthorizedException, GeneralException, NotFoundException { + if (request == null) { + throw new IllegalArgumentException(REQUEST_VALUE_MUST_BE_SPECIFIED); + } + + String url = request; + if (!isURLAbsolute(url)) { + url = serviceUrl + url; + } + final APIResponse apiResponse = doRequest(requestType, url, headers, payload); + + final String body = apiResponse.getBody(); + final int status = apiResponse.getStatus(); + + if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED || status == HttpURLConnection.HTTP_ACCEPTED) { + try { + final ObjectMapper mapper = new ObjectMapper(); + // If we as new properties, we don't want the system to fail, we rather want to ignore them + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + // Enable case insensitivity to avoid parsing errors if parameters' case in api response doesn't match sdk's + mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); + mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); + + // Prevents mismatched exception when clazz is null + return this.readValueAsList(mapper, body, elementClass); + } catch (IOException ioe) { + throw new GeneralException(ioe); + } + } else if (status == HttpURLConnection.HTTP_NO_CONTENT) { + return Collections.emptyList(); // no content doesn't mean an error + } + handleHttpFailStatuses(status, body); + return Collections.emptyList(); + } + + private T readValue(ObjectMapper mapper, String content, Class clazz) + throws JsonProcessingException { + return mapper.readValue(content, clazz); + } + + private List readValueAsList(ObjectMapper mapper, String content, final Class elementClass) + throws JsonProcessingException { + return mapper.readValue(content, mapper.getTypeFactory().constructCollectionType(List.class, elementClass)); + } + private void handleHttpFailStatuses(final int status, String body) throws UnauthorizedException, NotFoundException, GeneralException { if (status == HttpURLConnection.HTTP_UNAUTHORIZED) { final List errorReport = getErrorReportOrNull(body); @@ -256,11 +355,12 @@ private void handleHttpFailStatuses(final int status, String body) throws Unauth * * @param method HTTP method. * @param url Absolute URL. + * @param headers additional headers to set on the request. * @param payload Payload to JSON encode for the request body. May be null. * @param

Type of the payload. * @return APIResponse containing the response's body and status. */ -

APIResponse doRequest(final String method, final String url, final P payload) throws GeneralException { +

APIResponse doRequest(final String method, final String url, final Map headers, final P payload) throws GeneralException { HttpURLConnection connection = null; InputStream inputStream = null; @@ -275,13 +375,16 @@

APIResponse doRequest(final String method, final String url, final P payload } try { - connection = getConnection(url, payload, method); + connection = getConnection(url, payload, method, headers); int status = connection.getResponseCode(); if (APIResponse.isSuccessStatus(status)) { inputStream = connection.getInputStream(); } else { inputStream = connection.getErrorStream(); + if (inputStream == null) { + throw new IOException("Server returned HTTP error code " + status + " with no body."); + } } return new APIResponse(readToEnd(inputStream), status); @@ -376,13 +479,13 @@ private synchronized static void allowPatchRequestsIfNeeded() throws GeneralExce Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL); - + Object noInstanceBecauseStaticField = null; - + // Determine what methods should be allowed. String[] existingMethods = (String[]) methodsField.get(noInstanceBecauseStaticField); String[] allowedMethods = getAllowedMethods(existingMethods); - + // Override the actual field to allow PATCH. methodsField.set(noInstanceBecauseStaticField, allowedMethods); @@ -450,6 +553,20 @@ private boolean isURLAbsolute(String url) { * @throws IOException io exception */ public

HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType) throws IOException { + return getConnection(serviceUrl, body, requestType, new HashMap<>()); + } + + /** + * Create a HttpURLConnection connection object + * + * @param serviceUrl URL that needs to be requested + * @param body body could not be empty for POST or PUT requests + * @param requestType Request type POST requests without a payload will generate a exception + * @param headers additional headers to set on the request + * @return base class + * @throws IOException io exception + */ + public

HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType, final Map headers) throws IOException { if (requestType == null || !REQUEST_METHODS.contains(requestType)) { throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, requestType)); } @@ -485,51 +602,48 @@ public

HttpURLConnection getConnection(final String serviceUrl, final P body DateFormat df = getDateFormat(); mapper.setDateFormat(df); - final String json = mapper.writeValueAsString(body); - connection.getOutputStream().write(json.getBytes(String.valueOf(StandardCharsets.UTF_8))); + setAdditionalHeaders(connection, headers); + + byte[] bodyBytes; + if (body instanceof byte[]) { + bodyBytes = (byte[]) body; + } else { + final String json = mapper.writeValueAsString(body); + bodyBytes = json.getBytes(StandardCharsets.UTF_8); + } + connection.getOutputStream().write(bodyBytes); } else if ("DELETE".equals(requestType)) { // could have just used rquestType as it is connection.setDoOutput(false); connection.setRequestMethod("DELETE"); connection.setRequestProperty("Content-Type", "text/plain"); + + setAdditionalHeaders(connection, headers); } else { connection.setDoOutput(false); connection.setRequestMethod("GET"); connection.setRequestProperty("Content-Type", "text/plain"); + + setAdditionalHeaders(connection, headers); } return connection; } - private DateFormat getDateFormat() { - double javaVersion = DEFAULT_JAVA_VERSION; - try { - javaVersion = getVersion(); - } catch (GeneralException e) { - // Do nothing: leave the version at its default. + private void setAdditionalHeaders(HttpURLConnection connection, Map headers) { + for (Map.Entry header : headers.entrySet()) { + connection.setRequestProperty(header.getKey(), header.getValue()); } + } - if (javaVersion > 1.6) { + private DateFormat getDateFormat() { + ComparableVersion java6 = new ComparableVersion("1.6"); + if (JAVA_VERSION.compareTo(java6) > 0) { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); } return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ"); } - private double getVersion() throws GeneralException { - String version = System.getProperty("java.version"); - - try { - int pos = version.indexOf('.'); - pos = version.indexOf('.', pos + 1); - - return Double.parseDouble(version.substring(0, pos)); - } catch (RuntimeException e) { - // Thrown if the index is out of bounds, or when we can't parse a - // double for some reason. - throw new GeneralException(e); - } - } - /** * Get the MessageBird error report data. * @@ -673,7 +787,7 @@ private String getPathVariables(final Map map) { // the value is returned from the next() call bpath.append(encodeKeyValuePair(param.getKey(), iterator.next())); count++; - } + } } else { // If the value is not a collection, create the querystring value directly. bpath.append(encodeKeyValuePair(param.getKey(), param.getValue())); diff --git a/api/src/main/java/com/messagebird/Request.java b/api/src/main/java/com/messagebird/Request.java index 41d9283b..46048b0e 100644 --- a/api/src/main/java/com/messagebird/Request.java +++ b/api/src/main/java/com/messagebird/Request.java @@ -5,7 +5,10 @@ /** * Holds request data needed to calculate a signature hash for incoming * webhooks. + * + * @deprecated This class is being deprecated together with {@link RequestSigner} */ +@Deprecated public class Request { private final String timestamp; @@ -17,11 +20,13 @@ public class Request { /** * Constructs a new request instance. * - * @param timestamp Timestamp provided in the MessageBird-Request-Timestamp - * header. + * @param timestamp Timestamp provided in the MessageBird-Request-Timestamp + * header. * @param queryParameters Query parameters in abc=foo&def=ghi format. - * @param data Raw body of this request. + * @param data Raw body of this request. + * @deprecated */ + @Deprecated public Request(String timestamp, String queryParameters, byte[] data) { if (timestamp == null || timestamp.isEmpty()) { throw new IllegalArgumentException("Timestamp can not be null or empty"); diff --git a/api/src/main/java/com/messagebird/RequestSigner.java b/api/src/main/java/com/messagebird/RequestSigner.java index e2f91d93..d79e464c 100644 --- a/api/src/main/java/com/messagebird/RequestSigner.java +++ b/api/src/main/java/com/messagebird/RequestSigner.java @@ -4,26 +4,30 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Base64; /** * RequestSigner is used to verify HTTP requests and is an implementation of: * https://developers.messagebird.com/docs/verify-http-requests. Retrieve your * signing key at https://dashboard.messagebird.com/developers/settings. + * + * @deprecated This class is being deprecated. + *

Use {@link RequestValidator} instead.

*/ +@Deprecated public class RequestSigner { private static final String ALGORITHM_SHA256 = "SHA-256"; private static final String ALGORITHM_HMAC_SHA256 = "HmacSHA256"; private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; - private SecretKeySpec secret; + private final SecretKeySpec secret; /** * Constructs a new RequestSigner instance. @@ -31,7 +35,9 @@ public class RequestSigner { * @param key Signing key. Can be retrieved through * https://dashboard.messagebird.com/developers/settings. This * is NOT your API key. + * @deprecated Use {@link RequestValidator#RequestValidator(String)} )} instead. */ + @Deprecated public RequestSigner(byte[] key) { this.secret = new SecretKeySpec(key, ALGORITHM_HMAC_SHA256); } @@ -42,13 +48,15 @@ public RequestSigner(byte[] key) { * * @param expectedSignature Signature from the MessageBird-Signature * header in its original base64 encoded state. - * @param request Request containing the values from the incoming webhook. + * @param request Request containing the values from the incoming webhook. * @return True if the computed signature matches the expected signature. + * @deprecated Use {@link RequestValidator#validateSignature(String, String, byte[])} instead. */ + @Deprecated public boolean isMatch(String expectedSignature, Request request) { try { - return isMatch(Base64.decode(expectedSignature), request); - } catch (IOException e) { + return isMatch(Base64.getDecoder().decode(expectedSignature), request); + } catch (IllegalArgumentException e) { throw new RequestSigningException(e); } } @@ -59,9 +67,11 @@ public boolean isMatch(String expectedSignature, Request request) { * * @param expectedSignature Decoded (with base64) signature * from the MessageBird-Signature header - * @param request Request containing the values from the incoming webhook. + * @param request Request containing the values from the incoming webhook. * @return True if the computed signature matches the expected signature. + * @deprecated Use {@link RequestValidator#validateSignature(String, String, byte[])} instead. */ + @Deprecated public boolean isMatch(byte[] expectedSignature, Request request) { return Arrays.equals(computeSignature(request), expectedSignature); } @@ -93,7 +103,7 @@ private byte[] getSha256Hash(byte[] bytes) { /** * Stitches the two arrays together and returns a new one. * - * @param first Start of the new array. + * @param first Start of the new array. * @param second End of the new array. * @return New array based on first and second. */ diff --git a/api/src/main/java/com/messagebird/RequestValidator.java b/api/src/main/java/com/messagebird/RequestValidator.java new file mode 100644 index 00000000..24ae6343 --- /dev/null +++ b/api/src/main/java/com/messagebird/RequestValidator.java @@ -0,0 +1,204 @@ +package com.messagebird; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier.BaseVerification; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.messagebird.exceptions.RequestValidationException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Clock; + +/** + * RequestValidator validates request signature signed by MessageBird services. + * + * @see Verify HTTP Requests + */ +public class RequestValidator { + + /** + * Signature of signed request is set with header name 'MessageBird-Signature-JWT' + */ + public static final String SIGNATURE_HEADER = "MessageBird-Signature-JWT"; + private static final String ALGORITHM_SHA256 = "SHA-256"; + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f'}; + + private final Algorithm HMAC256, HMAC384, HMAC512; + + /** + * This field instructs Validator to not validate url_hash claim. + * It is recommended to not skip URL validation to ensure high security. + * but the ability to skip URL validation is necessary in some cases, e.g. + * your service is behind proxy or when you want to validate it yourself. + * Note that when true, no query parameters should be trusted. + * Defaults to false. + */ + private final boolean skipURLValidation; + + /** + * RequestValidator validates request signature with a customer signature key. + * + * @param signatureKey customer signature key. Can be retrieved through + * Developer Settings. + * This is NOT your API key. + * @see Verify HTTP Requests + */ + public RequestValidator(String signatureKey) { + this(signatureKey, false); + } + + /** + * RequestValidator validates webhook signature with a customer signature key. + * + * @param signatureKey customer signature key. Can be retrieved through + * Developer Settings. + * This is NOT your API key. + * @param skipURLValidation whether url_hash claim validation should be skipped. + * Note that when true, no query parameters should be trusted. + * @see Verify HTTP Requests + */ + public RequestValidator(String signatureKey, boolean skipURLValidation) { + this.HMAC256 = Algorithm.HMAC256(signatureKey); + this.HMAC384 = Algorithm.HMAC384(signatureKey); + this.HMAC512 = Algorithm.HMAC512(signatureKey); + this.skipURLValidation = skipURLValidation; + } + + /** + * Returns raw signature payload after validating a signature successfully, + * otherwise throws {@code RequestValidationException}. + *

+ * This JWT is signed with a MessageBird account unique secret key, ensuring the request is from MessageBird and + * a specific account. + * The JWT contains the following claims: + *

+ *
    + *
  • "url_hash" - the raw URL hashed with SHA256 ensuring the URL wasn't altered.
  • + *
  • "payload_hash" - the raw payload hashed with SHA256 ensuring the payload wasn't altered.
  • + *
  • "jti" - a unique token ID to implement an optional non-replay check (NOT validated by default).
  • + *
  • "nbf" - the not before timestamp.
  • + *
  • "exp" - the expiration timestamp is ensuring that a request isn't captured and used at a later time.
  • + *
  • "iss" - the issuer name, always MessageBird.
  • + *
+ * + * @param clock custom {@link Clock} instance to validate timestamp claims. + * @param signature the actual signature. + * @param url the raw url including the protocol, hostname and query string, + * {@code https://example.com/?example=42}. + * @param requestBody the raw request body. + * @return raw signature payload as {@link DecodedJWT} object. + * @throws RequestValidationException when the signature is invalid. + * @see Verify HTTP Requests + */ + public DecodedJWT validateSignature(Clock clock, String signature, String url, byte[] requestBody) + throws RequestValidationException { + if (signature == null || signature.length() == 0) + throw new RequestValidationException("The signature can not be empty."); + + if (!skipURLValidation && (url == null || url.length() == 0)) + throw new RequestValidationException("The url can not be empty."); + + DecodedJWT jwt = JWT.decode(signature); + + Algorithm algorithm; + switch (jwt.getAlgorithm()) { + case "HS256": + algorithm = HMAC256; + break; + case "HS384": + algorithm = HMAC384; + break; + case "HS512": + algorithm = HMAC512; + break; + default: + throw new RequestValidationException(String.format("The signing method '%s' is invalid.", jwt.getAlgorithm())); + } + + BaseVerification builder = (BaseVerification) JWT.require(algorithm) + .withIssuer("MessageBird") + .ignoreIssuedAt() + .acceptLeeway(1); + + if (!skipURLValidation) + builder.withClaim("url_hash", calculateSha256(url.getBytes())); + + Claim payloadHashClaim = jwt.getClaim("payload_hash"); + boolean payloadHashClaimExist = !(payloadHashClaim.isNull() || payloadHashClaim.isMissing()); + if (requestBody != null && requestBody.length > 0) { + if (!payloadHashClaimExist) { + throw new RequestValidationException("The Claim 'payload_hash' is not set but payload is present."); + } + builder.withClaim("payload_hash", calculateSha256(requestBody)); + } else if (payloadHashClaimExist) { + throw new RequestValidationException("The Claim 'payload_hash' is set but actual payload is missing."); + } + + JWTVerifier verifier = clock == null ? builder.build() : builder.build(clock); + + try { + return verifier.verify(jwt); + } catch (SignatureVerificationException e) { + throw new RequestValidationException("Signature is invalid.", e); + } catch (JWTVerificationException e) { + throw new RequestValidationException(e.getMessage(), e.getCause()); + } + } + + /** + * Returns raw signature payload after validating a signature successfully, + * otherwise throws {@code RequestValidationException}. + * + * @param signature the actual signature. + * @param url the raw url including the protocol, hostname and query string, + * {@code https://example.com/?example=42}. + * @param requestBody the raw request body. + * @return raw signature payload as {@link DecodedJWT} object. + * @throws RequestValidationException when the signature is invalid. + * @see RequestValidator#validateSignature(Clock, String, String, byte[]) + */ + public DecodedJWT validateSignature(String signature, String url, byte[] requestBody) + throws RequestValidationException { + return validateSignature(null, signature, url, requestBody); + } + + /** + * Validates request signature with URL validation disabled. + * Note that no query parameters should be trusted and this only works if {@code RequestValidator} is constructed + * with {@code skipURLValidation} set to true. + * + * @param signature the actual signature. + * @param requestBody the raw request body. + * @return raw signature payload as {@link DecodedJWT} object. + * @throws RequestValidationException when the signature is invalid. + * @see RequestValidator#validateSignature(String, String, byte[]) + */ + public DecodedJWT validateSignature(String signature, byte[] requestBody) + throws RequestValidationException { + return validateSignature(null, signature, null, requestBody); + } + + private static String calculateSha256(byte[] bytes) { + try { + return encodeHex(MessageDigest.getInstance(ALGORITHM_SHA256).digest(bytes)); + } catch (NoSuchAlgorithmException e) { + throw new RequestValidationException(e); + } + } + + private static String encodeHex(final byte[] data) { + final int l = data.length; + final char[] out = new char[l << 1]; + for (int i = 0, j = 0; i < l; i++) { + out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4]; + out[j++] = HEX_DIGITS[0x0F & data[i]]; + } + return new String(out); + } +} diff --git a/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java b/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java index 9f993375..d69a1922 100644 --- a/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java +++ b/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java @@ -2,7 +2,10 @@ /** * Thrown if an error occurs during request signing. + * + * @deprecated This class is being deprecated together with {@link com.messagebird.RequestSigner} */ +@Deprecated public class RequestSigningException extends RuntimeException { public RequestSigningException() { diff --git a/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java b/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java new file mode 100644 index 00000000..f759b211 --- /dev/null +++ b/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java @@ -0,0 +1,22 @@ +package com.messagebird.exceptions; + +/** + * Thrown if an error occurs during request signing. + */ +public class RequestValidationException extends RuntimeException { + + public RequestValidationException() { + } + + public RequestValidationException(String message) { + super(message); + } + + public RequestValidationException(Throwable cause) { + super(cause); + } + + public RequestValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/com/messagebird/objects/AccessKey.java b/api/src/main/java/com/messagebird/objects/AccessKey.java new file mode 100644 index 00000000..4d529711 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/AccessKey.java @@ -0,0 +1,98 @@ +package com.messagebird.objects; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class AccessKey { + private String id; + @JsonProperty("access_key") + private String accessKey; + private String mode; + private String description; + @JsonProperty("core_user_id") + private int coreUserId; + @JsonProperty("user_id") + private int userId; + @JsonProperty("external_id") + private int externalId; + private List roles; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getCoreUserId() { + return coreUserId; + } + + public void setCoreUserId(int coreUserId) { + this.coreUserId = coreUserId; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public int getExternalId() { + return externalId; + } + + public void setExternalId(int externalId) { + this.externalId = externalId; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + @Override + public String toString() { + return "AccessKey{" + + "id='" + id + '\'' + + ", access_key='" + accessKey + '\'' + + ", mod='" + mode + '\'' + + ", description='" + description + '\'' + + ", core_user_id=" + coreUserId + + ", user_id=" + userId + + ", external_id=" + externalId + + ", roles=" + roles + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java new file mode 100644 index 00000000..40ec38a4 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java @@ -0,0 +1,53 @@ +package com.messagebird.objects; + +import java.util.List; + +public class ChildAccountCreateResponse extends ChildAccountResponse{ + private List accessKeys; + private String signingKey; + private String invoiceAggregation; + private String paymentMoment; + + public List getAccessKeys() { + return accessKeys; + } + + public void setAccessKeys(List accessKeys) { + this.accessKeys = accessKeys; + } + + public String getSigningKey() { + return signingKey; + } + + public void setSigningKey(String signingKey) { + this.signingKey = signingKey; + } + + public String getInvoiceAggregation() { + return invoiceAggregation; + } + + public void setInvoiceAggregation(String invoiceAggregation) { + this.invoiceAggregation = invoiceAggregation; + } + + public String getPaymentMoment() { + return paymentMoment; + } + + public void setPaymentMoment(String paymentMoment) { + this.paymentMoment = paymentMoment; + } + + @Override + public String toString() { + return "ChildAccountCreateResponse{" + + "id='" + getId() + '\'' + + ", name='" + getName() + '\'' + + ", accessKeys=" + accessKeys + '\'' + + ", invoiceAggregation='" + invoiceAggregation + '\'' + + ", paymentMoment='" + paymentMoment + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java new file mode 100644 index 00000000..e39d5969 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java @@ -0,0 +1,22 @@ +package com.messagebird.objects; + +public class ChildAccountDetailedResponse extends ChildAccountResponse{ + private String invoiceAggregation; + + public String getInvoiceAggregation() { + return invoiceAggregation; + } + + public void setInvoiceAggregation(String invoiceAggregation) { + this.invoiceAggregation = invoiceAggregation; + } + + @Override + public String toString() { + return "ChildAccountDetailedResponse{" + + "id='" + getId() + '\'' + + ", name='" + getName() + '\'' + + ", invoiceAggregation='" + invoiceAggregation + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java b/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java new file mode 100644 index 00000000..aa0e9e5f --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java @@ -0,0 +1,13 @@ +package com.messagebird.objects; + +public class ChildAccountRequest { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java new file mode 100644 index 00000000..de232f05 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java @@ -0,0 +1,34 @@ +package com.messagebird.objects; + +import java.io.Serializable; + +public class ChildAccountResponse implements Serializable { + private static final long serialVersionUID = -8605510461438669942L; + + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "ChildAccountResponse{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/ErrorReport.java b/api/src/main/java/com/messagebird/objects/ErrorReport.java index d0d63d5a..afd7dcf9 100644 --- a/api/src/main/java/com/messagebird/objects/ErrorReport.java +++ b/api/src/main/java/com/messagebird/objects/ErrorReport.java @@ -1,6 +1,9 @@ package com.messagebird.objects; import com.fasterxml.jackson.annotation.JsonInclude; + +import java.io.Serializable; + /** * When MessageBird returns a 4xx, you will find a list of any error codes in your return dataset. * you will receive a list of errors from the API in such case. @@ -8,7 +11,10 @@ * Created by rvt on 1/5/15. */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ErrorReport { +public class ErrorReport implements Serializable { + + private static final long serialVersionUID = -8611665867089703268L; + private Integer code; private String description; private String parameter; diff --git a/api/src/main/java/com/messagebird/objects/FileUploadResponse.java b/api/src/main/java/com/messagebird/objects/FileUploadResponse.java new file mode 100644 index 00000000..29155a63 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/FileUploadResponse.java @@ -0,0 +1,21 @@ +package com.messagebird.objects; + +public class FileUploadResponse { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "FileUploadResponse{" + + "id='" + id + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/Language.java b/api/src/main/java/com/messagebird/objects/Language.java index 6ce76027..ccb63351 100644 --- a/api/src/main/java/com/messagebird/objects/Language.java +++ b/api/src/main/java/com/messagebird/objects/Language.java @@ -1,5 +1,7 @@ package com.messagebird.objects; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Created by faizan on 09/12/15. */ @@ -11,7 +13,7 @@ public enum Language { EN_US("en-us"), ES_ES("es-es"), FR_FR("fr-fr"), - RU_RU("ru_ru"), + RU_RU("ru-ru"), ZH_CN("zh-cn"), EN_AU("en-au"), ES_MX("es-mx"), @@ -25,13 +27,21 @@ public enum Language { PT_BR("pt-br"), RO_RO("ro-ro"); - private String code; + final String code; Language(String code) { this.code = code; } + @JsonValue + public String getCode() { + return code; + } + + @Override public String toString() { - return this.code; + return "Language{" + + "code='" + code + '\'' + + '}'; } } diff --git a/api/src/main/java/com/messagebird/objects/MessageResponse.java b/api/src/main/java/com/messagebird/objects/MessageResponse.java index be132d4f..aa104e52 100644 --- a/api/src/main/java/com/messagebird/objects/MessageResponse.java +++ b/api/src/main/java/com/messagebird/objects/MessageResponse.java @@ -1,6 +1,9 @@ package com.messagebird.objects; +import org.jetbrains.annotations.Nullable; + import java.io.Serializable; +import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.List; @@ -194,7 +197,7 @@ public Map getTypeDetails() { static public class Recipients implements Serializable { private static final long serialVersionUID = 547164972757802213L; - + private Integer totalCount; private Integer totalSentCount; private Integer totalDeliveredCount; private Integer totalDeliveryFailedCount; @@ -206,13 +209,18 @@ public Recipients() { @Override public String toString() { return "Recipients{" + - "totalSentCount=" + totalSentCount + + "totalCount=" + totalCount + + ", totalSentCount=" + totalSentCount + ", totalDeliveredCount=" + totalDeliveredCount + ", totalDeliveryFailedCount=" + totalDeliveryFailedCount + ", items=" + items + '}'; } + public Integer getTotalCount() { + return totalCount; + } + /** * The count of recipients that have the message pending (status sent, and buffered). * @@ -258,8 +266,20 @@ static public class Items implements Serializable { private static final long serialVersionUID = -4104837036540050532L; private BigInteger recipient; + private BigInteger originator; private String status; private Date statusDatetime; + private String recipientCountry; + private Integer recipientCountryPrefix; + private String recipientOperator; + private Integer messageLength; + private String statusReason; + @Nullable + private Price price; + private String mccmnc; + private String mcc; + private String mnc; + private int messagePartCount; public Items() { } @@ -268,9 +288,20 @@ public Items() { public String toString() { return "Items{" + "recipient=" + recipient + + ", originator=" + originator + ", status='" + status + '\'' + ", statusDatetime=" + statusDatetime + - "}"; + ", recipientCountry='" + recipientCountry + '\'' + + ", recipientCountryPrefix=" + recipientCountryPrefix + + ", recipientOperator='" + recipientOperator + '\'' + + ", messageLength=" + messageLength + + ", statusReason='" + statusReason + '\'' + + ", price=" + price + + ", mccmnc='" + mccmnc + '\'' + + ", mcc='" + mcc + '\'' + + ", mnc='" + mnc + '\'' + + ", messagePartCount=" + messagePartCount + + '}'; } /** @@ -300,6 +331,84 @@ public Date getStatusDatetime() { return statusDatetime; } + public Price getPrice() { + return price; + } + + public BigInteger getOriginator() { + return originator; + } + + public String getRecipientCountry() { + return recipientCountry; + } + + public Integer getRecipientCountryPrefix() { + return recipientCountryPrefix; + } + + public String getRecipientOperator() { + return recipientOperator; + } + + public Integer getMessageLength() { + return messageLength; + } + + public String getStatusReason() { + return statusReason; + } + + public String getMccmnc() { + return mccmnc; + } + + public String getMcc() { + return mcc; + } + + public String getMnc() { + return mnc; + } + + public int getMessagePartCount() { + return messagePartCount; + } + } + + /** + * Response price of items + */ + static public class Price implements Serializable { + + private static final long serialVersionUID = -4104837036540050532L; + + private BigDecimal amount; + private String currency; + + public Price() { + } + + @Override + public String toString() { + return "Price{" + + "amount=" + amount + + ", currency=" + currency + + "}"; + } + + public float getAmount() { + return amount.floatValue(); + } + + public BigDecimal getAmountDecimal() { + return amount; + } + + public String getCurrency() { + return currency; + } + } } diff --git a/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java b/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java new file mode 100644 index 00000000..3733cf69 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java @@ -0,0 +1,60 @@ +package com.messagebird.objects; + +import java.math.BigDecimal; + +public class OutboundSmsPrice { + private BigDecimal price; + private String currencyCode; + private String mccmnc; + private String mcc; + private String mnc; + private String countryName; + private String countryIsoCode; + private String operatorName; + + public BigDecimal getPrice() { + return price; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public String getMccmnc() { + return mccmnc; + } + + public String getMcc() { + return mcc; + } + + public String getMnc() { + return mnc; + } + + public String getCountryName() { + return countryName; + } + + public String getCountryIsoCode() { + return countryIsoCode; + } + + public String getOperatorName() { + return operatorName; + } + + @Override + public String toString() { + return "OutboundSmsPrice{" + + "price=" + price + + ", currencyCode='" + currencyCode + '\'' + + ", mccmnc='" + mccmnc + '\'' + + ", mcc='" + mcc + '\'' + + ", mnc='" + mnc + '\'' + + ", countryName='" + countryName + '\'' + + ", countryIsoCode='" + countryIsoCode + '\'' + + ", operatorName='" + operatorName + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java b/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java new file mode 100644 index 00000000..6d036882 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java @@ -0,0 +1,36 @@ +package com.messagebird.objects; + +import java.util.List; + +public class OutboundSmsPriceResponse { + private int gateway; + private String currencyCode; + private int totalCount; + private List prices; + + public int getGateway() { + return gateway; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public int getTotalCount() { + return totalCount; + } + + public List getPrices() { + return prices; + } + + @Override + public String toString() { + return "OutboundSmsPriceResponse{" + + "gateway=" + gateway + + ", currencyCode='" + currencyCode + '\'' + + ", totalCount=" + totalCount + + ", prices=" + prices + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/VerifyMessage.java b/api/src/main/java/com/messagebird/objects/VerifyMessage.java new file mode 100644 index 00000000..b2929f11 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/VerifyMessage.java @@ -0,0 +1,35 @@ +package com.messagebird.objects; + +import java.io.Serializable; + +/** + * Created by leandro.pinto on 22/06/15. + */ +public class VerifyMessage implements Serializable { + + private String id; + private String status; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String toString() { + return "VerifyMessage {" + " " + + "id=" + this.id + " " + + "status=" + this.status + " " + + "}"; + } +} diff --git a/api/src/main/java/com/messagebird/objects/VerifyRequest.java b/api/src/main/java/com/messagebird/objects/VerifyRequest.java index 45a6b194..bee684e9 100644 --- a/api/src/main/java/com/messagebird/objects/VerifyRequest.java +++ b/api/src/main/java/com/messagebird/objects/VerifyRequest.java @@ -15,8 +15,10 @@ public class VerifyRequest implements Serializable { private String template; private Integer timeout; private Integer tokenLength; + private Integer maxAttempts; private Gender voice; private Language language; + private String subject; public VerifyRequest(String recipient) { this.recipient = recipient; @@ -54,6 +56,10 @@ public void setType(VerifyType type) { this.type = type; } + public void setType(String type) { + this.type = VerifyType.valueOf(type.toUpperCase()); + } + /** * The datacoding used by the template. * @@ -112,4 +118,19 @@ public void setLanguage(Language language) { this.language = language; } + public void setSubject(String subject) { + this.subject = subject; + } + + public String getSubject() { + return subject; + } + + public Integer getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(Integer maxAttempts) { + this.maxAttempts = maxAttempts; + } } diff --git a/api/src/main/java/com/messagebird/objects/VerifyType.java b/api/src/main/java/com/messagebird/objects/VerifyType.java index 8965e7a2..630e8e53 100644 --- a/api/src/main/java/com/messagebird/objects/VerifyType.java +++ b/api/src/main/java/com/messagebird/objects/VerifyType.java @@ -9,7 +9,8 @@ public enum VerifyType { FLASH("flash"), SMS("sms"), - TTS("tts"); + TTS("tts"), + EMAIL("email"); final String value; diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java index 9ba82bd8..8b4fe164 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java @@ -1,9 +1,16 @@ package com.messagebird.objects.conversations; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * ConversationContent wraps actual content. The field that should be set here * is indicated by ConversationContentType. + * + *

Unknown keys are ignored on deserialization so that consumers parsing + * webhook payloads in their own handlers are not broken by content fields added + * after their SDK version. Serialization is unaffected. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class ConversationContent { private ConversationContentMedia audio; diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java index 3da74c2c..824f7fc8 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java @@ -18,17 +18,20 @@ public class ConversationContentHsm { private String templateName; private ConversationHsmLanguage language; private List params; + private List components; public ConversationContentHsm( final String namespace, final String templateName, final ConversationHsmLanguage language, - final List params + final List params, + final List components ) { this.namespace = namespace; this.templateName = templateName; this.language = language; this.params = params; + this.components = components; } public ConversationContentHsm( @@ -85,6 +88,14 @@ public void setParams(final List params) { this.params = params; } + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + @Override public String toString() { return "ConversationContentHsm{" + @@ -92,6 +103,7 @@ public String toString() { ", templateName='" + templateName + '\'' + ", language=" + language + ", params=" + params + + ", components=" + components + '}'; } } diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java index b17ecbce..708938ee 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java @@ -11,10 +11,10 @@ public class ConversationHsmLocalizableParameterCurrency { /** * Instantiates a localizable parameter for currencies. * - * @param code ISO 4217 compliant currency code. + * @param currencyCode ISO 4217 compliant currency code. * @param amount Amount multiplied by 1000. E.g. 12.34 becomes 12340. */ - public ConversationHsmLocalizableParameterCurrency(final String code, final int amount) { + public ConversationHsmLocalizableParameterCurrency(final String currencyCode, final int amount) { this.currencyCode = currencyCode; this.amount = amount; } diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java index 82c935dd..1dea8280 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java @@ -13,6 +13,7 @@ public class ConversationMessage { private String id; private String conversationId; private String channelId; + private String trackId; private ConversationMessageDirection direction; private ConversationMessageStatus status; private ConversationContentType type; @@ -21,6 +22,7 @@ public class ConversationMessage { private Date updatedDatetime; private Map source; private ConversationMessageTag tag; + private ConversationMessageMetadata metadata; /** * See: {@link ConversationPlatformConstants} */ @@ -114,6 +116,14 @@ public void setTag(ConversationMessageTag tag) { this.tag = tag; } + public ConversationMessageMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ConversationMessageMetadata metadata) { + this.metadata = metadata; + } + public String getPlatform() { return platform; } @@ -122,6 +132,14 @@ public void setPlatform(String platform) { this.platform = platform; } + public String getTrackId() { + return trackId; + } + + public void setTrackId(String trackId) { + this.trackId = trackId; + } + @Override public String toString() { return "ConversationMessage{" + @@ -131,12 +149,14 @@ public String toString() { ", direction=" + direction + ", status=" + status + ", type=" + type + + ", trackID=" + trackId + ", content=" + content + ", createdDatetime=" + createdDatetime + ", updatedDatetime=" + updatedDatetime + ", source=" + source + ", tag=" + tag + + ", metadata=" + metadata + ", platform='" + platform + '\'' + '}'; } -} +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java new file mode 100644 index 00000000..c41e814b --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java @@ -0,0 +1,42 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.Date; + +/** + * Inner metadata attached to a conversation message. Present on both incoming + * messages and status webhook payloads. {@code sender.userId} always contains + * the BSUID when Meta provides one. When both identifiers exist, the phone + * number appears in the parent {@code from} field, not in this object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationMessageMetadata { + + private ConversationSenderMetadata sender; + private Date receivedAt; + + public ConversationSenderMetadata getSender() { + return sender; + } + + public void setSender(ConversationSenderMetadata sender) { + this.sender = sender; + } + + public Date getReceivedAt() { + return receivedAt; + } + + public void setReceivedAt(Date receivedAt) { + this.receivedAt = receivedAt; + } + + @Override + public String toString() { + return "ConversationMessageMetadata{" + + "sender=" + sender + + ", receivedAt=" + receivedAt + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java index 626e6075..420213e3 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java @@ -11,6 +11,8 @@ public class ConversationMessageRequest { private ConversationContent content; private String channelId; private String reportUrl; + private String trackId; + private String ttl; private Map source; public ConversationContentType getType() { @@ -53,6 +55,21 @@ public void setSource(Map source) { this.source = source; } + public String getTrackId() { + return trackId; + } + + public void setTrackId(String trackId) { + this.trackId = trackId; + } + public String getTtl() { + return ttl; + } + + public void setTtl(String ttl) { + this.ttl = ttl; + } + @Override public String toString() { return "ConversationMessageRequest{" + @@ -61,6 +78,8 @@ public String toString() { ", channelId='" + channelId + '\'' + ", reportUrl='" + reportUrl + '\'' + ", source=" + source + + ", trackID=" + trackId + + ", ttl=" + ttl + '}'; } -} +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java index 475c7b74..aee5c5f5 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java @@ -15,7 +15,25 @@ public enum ConversationMessageStatus { READ("read"), RECEIVED("received"), SENT("sent"), - UNSUPPORTED("unsupported"); + UNSUPPORTED("unsupported"), + ACCEPTED("accepted"), + REJECTED("rejected"), + UNKNOWN("unknown"), + //WA specific statuses + TRANSMITTED("transmitted"), + //SMS specific statuses + DELIVERY_FAILED("delivery_failed"), + BUFFERED("buffered"), + EXPIRED("expired"), + //Email specific statuses + CLICKED("clicked"), + OPENED("opened"), + BOUNCE("bounce"), + SPAM_COMPLAINT("spam_complaint"), + OUT_OF_BOUNDED("out_of_bounded"), + DELAYED("delayed"), + LIST_UNSUBSCRIBE("list_unsubscribe"), + DISPATCHED("dispatched"); private final String status; diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java new file mode 100644 index 00000000..7537da85 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java @@ -0,0 +1,50 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Identifies the recipient of an outbound WhatsApp message, as reported back on + * status webhook payloads under {@code status.metadata.recipient}. Mirrors + * {@link ConversationSenderMetadata} on the inbound side. + * + *

{@code userId} is the recipient's BSUID (e.g. "US.13491208655302741918"); + * {@code parentUserId} is the parent business-scoped user ID of the enterprise + * that owns the business portfolio it was scoped against (e.g. + * "US.ENT.11815799212886844830"). + * + *

Either field may be {@code null}: Meta only supplies them for accounts + * enrolled in the BSUID rollout, and the enclosing {@code recipient} object is + * omitted entirely when neither is present. This is the only place a status + * payload carries the recipient's own identity — {@code messageMetadata.to} is + * an echo of the address the message was addressed to. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationRecipientMetadata { + + private String userId; + private String parentUserId; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getParentUserId() { + return parentUserId; + } + + public void setParentUserId(String parentUserId) { + this.parentUserId = parentUserId; + } + + @Override + public String toString() { + return "ConversationRecipientMetadata{" + + "userId='" + userId + '\'' + + ", parentUserId='" + parentUserId + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java index f0f9fd5c..40ace8d7 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java @@ -8,6 +8,8 @@ public class ConversationSendRequest { private ConversationContent content; private String from; private String reportUrl; + private String trackId; + private String ttl; private ConversationFallbackOption fallback; private Map source; private ConversationMessageTag tag; @@ -90,6 +92,21 @@ public void setTag(ConversationMessageTag tag) { this.tag = tag; } + public void setTrackId(String trackId) { + this.trackId = trackId; + } + + public String getTrackId() { + return trackId; + } + + public String getTtl() { + return ttl; + } + + public void setTtl(String ttl) { + this.ttl = ttl; + } @Override public String toString() { return "ConversationSendRequest{" + @@ -98,11 +115,11 @@ public String toString() { ", content=" + content + ", from='" + from + '\'' + ", reportUrl='" + reportUrl + '\'' + + ", trackId='" + trackId + '\'' + + ", ttl='" + ttl + '\'' + ", fallback=" + fallback + '\'' + ", tags=" + tag + ", source='" + source + '\'' + '}'; } -} - - +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java new file mode 100644 index 00000000..b7fdcbb0 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java @@ -0,0 +1,66 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Metadata about the sender of a WhatsApp message. {@code userId} always + * contains the BSUID (e.g. "US.13491208655302741918") when Meta supplies one. + * When both a phone number and a BSUID are available, the phone number appears + * in the parent message's {@code from} field — not here. + * + *

{@code parentUserId} carries the sender's parent business-scoped user ID + * (e.g. "US.ENT.11815799212886844830"), which identifies the enterprise that + * owns the business portfolio the {@code userId} was scoped against. It is only + * present for accounts enrolled in Meta's parent-BSUID rollout; for everyone + * else it stays {@code null}. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationSenderMetadata { + + private String userId; + private String parentUserId; + private String username; + private String displayName; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getParentUserId() { + return parentUserId; + } + + public void setParentUserId(String parentUserId) { + this.parentUserId = parentUserId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return "ConversationSenderMetadata{" + + "userId='" + userId + '\'' + + ", parentUserId='" + parentUserId + '\'' + + ", username='" + username + '\'' + + ", displayName='" + displayName + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java index 36ebc68a..6f7c84fe 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java @@ -14,6 +14,8 @@ public class ConversationStartRequest { private ConversationMessageTag tag; private String channelId; private String reportUrl; + private String trackId; + private String ttl; public ConversationStartRequest( final String to, @@ -91,6 +93,14 @@ public void setTag(ConversationMessageTag tag) { this.tag = tag; } + public String getTrackId() { + return trackId; + } + + public void setTrackId(String trackId) { + this.trackId = trackId; + } + @Override public String toString() { return "ConversationStartRequest{" + @@ -101,6 +111,8 @@ public String toString() { ", tag=" + tag + ", channelId='" + channelId + '\'' + ", reportUrl='" + reportUrl + '\'' + + ", trackId='" + trackId + '\'' + + ", ttl='" + ttl + '\'' + '}'; } -} +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java new file mode 100644 index 00000000..47a4a8d1 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java @@ -0,0 +1,92 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * The {@code messageMetadata} block delivered inside status webhook payloads + * (e.g. {@code statusSent}, {@code statusDelivered}). Reflects the original + * message that triggered the status update. + * + *

This class is not produced by any SDK request — it is a standalone POJO + * intended for consumers who deserialize incoming webhook payloads in their + * own HTTP handlers. Use it via {@code ObjectMapper.readValue(body, ...)}. + * + *

Both {@code from} and {@code to} accept either a phone number or a + * WhatsApp Business-Scoped User ID (BSUID, e.g. "US.13491208655302741918"). + * The BSUID is also available via {@code metadata.sender.userId}. + * + *

{@code to} echoes back the address the message was originally addressed + * to, so it is not a reliable source of the recipient's BSUID. That identity + * lives alongside this block, under {@code status.metadata.recipient} — see + * {@link ConversationStatusMetadata}. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationStatusMessageMetadata { + + private String id; + private String from; + private String to; + private String type; + private ConversationContent content; + private ConversationMessageMetadata metadata; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public ConversationContent getContent() { + return content; + } + + public void setContent(ConversationContent content) { + this.content = content; + } + + public ConversationMessageMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ConversationMessageMetadata metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return "ConversationStatusMessageMetadata{" + + "id='" + id + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", type='" + type + '\'' + + ", content=" + content + + ", metadata=" + metadata + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java new file mode 100644 index 00000000..0b4d5f7f --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java @@ -0,0 +1,85 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The {@code status.metadata} block delivered inside status webhook payloads + * (e.g. {@code statusSent}, {@code statusDelivered}). + * + *

This class is not produced by any SDK request — it is a standalone POJO + * intended for consumers who deserialize incoming webhook payloads in their own + * HTTP handlers. Use it via {@code ObjectMapper.readValue(body, ...)}. + * + *

Note the mixed casing of this object. {@code pricing} and + * {@code conversation} are near-verbatim passthroughs of Meta's own objects and + * so keep their snake_case keys ({@code pricing_model}, {@code category}, …); + * they are exposed here as raw maps rather than modelled types, because their + * contents track Meta's schema rather than ours. {@code recipient} is ours and + * follows the camelCase convention used everywhere else in the API. Any other + * key present on the payload — for example {@code biz_opaque_callback_data} — + * is collected into {@link #getAdditionalProperties()} rather than dropped. + * + *

{@code recipient} is absent from payloads for accounts that never receive + * BSUIDs, in which case {@link #getRecipient()} returns {@code null}. + */ +public class ConversationStatusMetadata { + + private Map pricing; + private Map conversation; + private ConversationRecipientMetadata recipient; + private final Map additionalProperties = new LinkedHashMap<>(); + + public Map getPricing() { + return pricing; + } + + public void setPricing(Map pricing) { + this.pricing = pricing; + } + + public Map getConversation() { + return conversation; + } + + public void setConversation(Map conversation) { + this.conversation = conversation; + } + + public ConversationRecipientMetadata getRecipient() { + return recipient; + } + + public void setRecipient(ConversationRecipientMetadata recipient) { + this.recipient = recipient; + } + + /** + * Every key on the payload that has no dedicated accessor above, in the + * order it was encountered. Empty when the payload holds nothing else. + * + * @return the unmodelled remainder of the metadata object + */ + @JsonAnyGetter + public Map getAdditionalProperties() { + return additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + additionalProperties.put(name, value); + } + + @Override + public String toString() { + return "ConversationStatusMetadata{" + + "pricing=" + pricing + + ", conversation=" + conversation + + ", recipient=" + recipient + + ", additionalProperties=" + additionalProperties + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java new file mode 100644 index 00000000..9a67a516 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java @@ -0,0 +1,29 @@ +package com.messagebird.objects.conversations; + +import java.util.Objects; + +public class ConversationUpdateRequest { + + private final ConversationStatus status; + + public ConversationUpdateRequest(ConversationStatus status) { + this.status = status; + } + + public ConversationStatus getStatus() { + return status; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationUpdateRequest that = (ConversationUpdateRequest) o; + return status == that.status; + } + + @Override + public int hashCode() { + return Objects.hash(status); + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java b/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java new file mode 100644 index 00000000..24ea4be6 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java @@ -0,0 +1,31 @@ +package com.messagebird.objects.conversations; + +public class HSMCurrency { + + private String currencyCode; + private int amount; + + public String getCurrencyCode() { + return currencyCode; + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + @Override + public String toString() { + return "HSMCurrency{" + + "currencyCode='" + currencyCode + '\'' + + ", amount=" + amount + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/Media.java b/api/src/main/java/com/messagebird/objects/conversations/Media.java new file mode 100644 index 00000000..4a9bf4ea --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/Media.java @@ -0,0 +1,31 @@ +package com.messagebird.objects.conversations; + +public class Media { + + private String url; + private String caption; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getCaption() { + return caption; + } + + public void setCaption(String caption) { + this.caption = caption; + } + + @Override + public String toString() { + return "Media{" + + "url='" + url + '\'' + + ", caption='" + caption + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java new file mode 100644 index 00000000..d3a7c20b --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java @@ -0,0 +1,82 @@ +package com.messagebird.objects.conversations; + +import java.util.List; + +public class MessageComponent { + + private MessageComponentType type; + private String sub_type; + private int index; + private List parameters; + private int card_index; + private List cards; + private List components; + + public void setType(MessageComponentType type) { + this.type = type; + } + + public MessageComponentType getType() { + return type; + } + + public String getSub_type() { + return sub_type; + } + + public void setSub_type(String sub_type) { + this.sub_type = sub_type; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public void setCards(List cards) { + this.cards = cards; + } + + public List getCards() { + return cards; + } + + public int getCard_index() { + return card_index; + } + + public void setCard_index(int card_index) { + this.card_index = card_index; + } + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + + @Override + public String toString() { + return "MessageComponent{" + + "type='" + type + '\'' + + ", sub_type='" + sub_type + '\'' + + ", index=" + index + '\'' + + ", parameters=" + parameters + '\'' + + ", components=" + components + '\'' + + ", cards=" + cards + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java b/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java new file mode 100644 index 00000000..9f9290cf --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java @@ -0,0 +1,54 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.*; + +public enum MessageComponentType { + + HEADER("header"), + BODY("body"), + FOOTER("footer"), + BUTTON("button"), + CARD("card"), + CAROUSEL("carousel"), + LIMITED_TIME_OFFER("limited_time_offer"), + COPY_CODE("copy_code"); + + private static final Map TYPE_MAP; + + static { + Map map = new HashMap<>(); + for (MessageComponentType componentType : MessageComponentType.values()) { + map.put(componentType.getType().toLowerCase(), componentType); + } + TYPE_MAP = Collections.unmodifiableMap(map); + } + + private final String type; + + MessageComponentType(final String type) { + this.type = type; + } + + @JsonCreator + public static MessageComponentType forValue(String value) { + Objects.requireNonNull(value, "Value cannot be null"); + return TYPE_MAP.get(value.toLowerCase(Locale.ROOT)); + } + + @JsonValue + public String toJson() { + return getType(); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java new file mode 100644 index 00000000..6432369f --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java @@ -0,0 +1,125 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +public class MessageParam { + + private TemplateMediaType type; + private String text; + private String payload; + private HSMCurrency currency; + private String dateTime; + private Media document; + private Media image; + private Media video; + @JsonProperty("expiration_time") + private String expirationTime; + @JsonProperty("coupon_code") + private String couponCode; + + public TemplateMediaType getType() { + return type; + } + + public void setType(TemplateMediaType type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("Text cannot be null or empty"); + } + this.text = text; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + public HSMCurrency getCurrency() { + return currency; + } + + public void setCurrency(HSMCurrency currency) { + this.currency = currency; + } + + public String getDateTime() { + return dateTime; + } + + public void setDateTime(String dateTime) { + if (StringUtils.isBlank(dateTime)) { + throw new IllegalArgumentException("dateTime cannot be null or empty"); + } + this.dateTime = dateTime; + } + + public Media getDocument() { + return document; + } + + public void setDocument(Media document) { + this.document = document; + } + + public Media getImage() { + return image; + } + + public void setImage(Media image) { + this.image = image; + } + + public Media getVideo() { return video; } + + public void setVideo(Media video) { this.video = video; } + + public String getExpirationTime() { + return expirationTime; + } + + public void setExpirationTime(String expirationTime) { + if (StringUtils.isBlank(expirationTime)) { + throw new IllegalArgumentException("expirationTime cannot be null or empty"); + } + this.expirationTime = expirationTime; + } + + public String getCouponCode() { + return couponCode; + } + + public void setCouponCode(String couponCode) { + if (StringUtils.isBlank(couponCode)) { + throw new IllegalArgumentException("couponCode cannot be null or empty"); + } + this.couponCode = couponCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("MessageParam{"); + sb.append("type=").append(type) + .append(", text='").append(text).append('\'') + .append(", payload='").append(payload).append('\'') + .append(", currency=").append(currency) + .append(", dateTime='").append(dateTime).append('\'') + .append(", document=").append(document) + .append(", image=").append(image) + .append(", video=").append(video) + .append(", expirationTime='").append(expirationTime).append('\'') + .append(", couponCode='").append(couponCode).append('\'') + .append('}'); + return sb.toString(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java b/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java new file mode 100644 index 00000000..580dcc97 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java @@ -0,0 +1,60 @@ +package com.messagebird.objects.conversations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Map; +import java.util.HashMap; +import java.util.Collections; + +public enum TemplateMediaType { + + IMAGE("image"), + DOCUMENT("document"), + VIDEO("video"), + TEXT("text"), + CURRENCY("currency"), + DATETIME("date_time"), + PAYLOAD("payload"), + EXPIRATION_TIME("expiration_time"), + COUPON_CODE("coupon_code"); + + private static final Map TYPE_MAP; + + static { + Map map = new HashMap<>(); + for (TemplateMediaType templateMediaType : TemplateMediaType.values()) { + map.put(templateMediaType.getType().toLowerCase(), templateMediaType); + } + TYPE_MAP = Collections.unmodifiableMap(map); + } + + + private final String type; + + TemplateMediaType(final String type) { + this.type = type; + } + + @JsonCreator + public static TemplateMediaType forValue(String value) { + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } + return TYPE_MAP.get(value.toLowerCase()); + } + + @JsonValue + public String toJson() { + return getType(); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } + +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java new file mode 100644 index 00000000..3edb1db9 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java @@ -0,0 +1,48 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * An enum for HSMComponentFormat + * + * @see HSMComponentFormat + * @author ssk910 + */ +public enum HSMCategory { + + AUTHENTICATION("AUTHENTICATION"), + UTILITY("UTILITY"), + MARKETING("MARKETING"); + + private final String category; + + HSMCategory(String category) { + this.category = category; + } + + @JsonCreator + public static HSMCategory forValue(String value) { + for (HSMCategory hsmCategory : HSMCategory.values()) { + if (hsmCategory.getCategory().equals(value)) { + return hsmCategory; + } + } + + return null; + } + + @JsonValue + public String toJson() { + return getCategory(); + } + + public String getCategory() { + return category; + } + + @Override + public String toString() { + return getCategory(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java new file mode 100644 index 00000000..ed93847f --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java @@ -0,0 +1,196 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +/** + * A class for HSMComponent object + * + * @see HSMComponent + * @author ssk910 + */ +public class HSMComponent { + + private HSMComponentType type; + private HSMComponentFormat format; + private String text; + @JsonProperty("add_security_recommendation") + private Boolean addSecurityRecommendation; + @JsonProperty("code_expiration_minutes") + private Integer codeExpirationMinutes; + private List buttons; + @JsonProperty("has_expiration") + private Boolean hasExpiration; + + private List cards; + + private HSMExample example; + + public HSMComponentType getType() { + return type; + } + + public void setType(HSMComponentType type) { + this.type = type; + } + + public HSMComponentFormat getFormat() { + return format; + } + + public void setFormat(HSMComponentFormat format) { + this.format = format; + } + + public String getText() { + return text; + } + + public void setText(String text) { + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("Text cannot be null or empty"); + } + this.text = text; + } + + public List getButtons() { + return buttons; + } + + public void setButtons(List buttons) { + this.buttons = buttons; + } + + public List getCards() { + return cards; + } + + public void setCards(List cards) { + this.cards = cards; + } + + public HSMExample getExample() { + return example; + } + + public void setExample(HSMExample example) { + this.example = example; + } + + public Boolean getAddSecurityRecommendation() { + return addSecurityRecommendation; + } + + public void setAddSecurityRecommendation(Boolean addSecurityRecommendation) { + this.addSecurityRecommendation = addSecurityRecommendation; + } + + public Integer getCodeExpirationMinutes() { + return codeExpirationMinutes; + } + + public void setCodeExpirationMinutes(Integer codeExpirationMinutes) { + this.codeExpirationMinutes = codeExpirationMinutes; + } + + public Boolean getHasExpiration() { + return hasExpiration; + } + + public void setHasExpiration(Boolean hasExpiration) { + this.hasExpiration = hasExpiration; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("HSMComponent{"); + sb.append("type=").append(type) + .append(", format=").append(format) + .append(", text='").append(text).append('\'') + .append(", addSecurityRecommendation=").append(addSecurityRecommendation) + .append(", codeExpirationMinutes=").append(codeExpirationMinutes) + .append(", buttons=").append(buttons) + .append(", hasExpiration=").append(hasExpiration) + .append(", cards=").append(cards) + .append(", example=").append(example) + .append('}'); + return sb.toString(); + } + + /** + * Check if this component is valid. + * + * @throws IllegalArgumentException Occurs when validation is not passed. + */ + public void validateComponent() throws IllegalArgumentException { + try { + this.validateButtons(); + this.validateComponentExample(); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Component validation failed: " + e.getMessage(), e); + } + } + + /** + * Check if button list is valid. + * + * @throws IllegalArgumentException Occurs when validation is not passed. + */ + private void validateButtons() throws IllegalArgumentException { + if (this.buttons == null) { + return; + } + + for (final HSMComponentButton button : this.buttons) { + button.validateButtonExample(); + } + } + + /** + * Check for header_text and header_url. + * + * @throws IllegalArgumentException Occurs when {@code header_text} or {@code header_url} is not able to use. + */ + private void validateComponentExample() throws IllegalArgumentException { + final boolean isExampleNotNull = this.example != null; + final boolean isHeaderTextNotEmpty = + isExampleNotNull && !(this.example.getHeader_text() == null || this.example.getHeader_text() + .isEmpty()); + final boolean isHeaderUrlNotEmpty = + isExampleNotNull && !(this.example.getHeader_url() == null || this.example.getHeader_url() + .isEmpty()); + + if (isHeaderTextNotEmpty) { + this.checkHeaderText(); + } + + if (isHeaderUrlNotEmpty) { + this.checkHeaderUrl(); + } + } + + /** + * Check if header_text is able to use. + * + * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code TEXT}. + */ + private void checkHeaderText() throws IllegalArgumentException { + if (!(HSMComponentType.HEADER.equals(type) && HSMComponentFormat.TEXT.equals(format))) { + throw new IllegalArgumentException("\"header_text\" is available for only HEADER type and TEXT format."); + } + } + + /** + * Check if header_url is able to use. + * + * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code IMAGE}. + */ + private void checkHeaderUrl() throws IllegalArgumentException { + if (!(HSMComponentType.HEADER.equals(type) && + (HSMComponentFormat.IMAGE.equals(format) || HSMComponentFormat.VIDEO.equals(format) || HSMComponentFormat.DOCUMENT.equals(format)))) { + throw new IllegalArgumentException("\"header_url\" is available for only HEADER type and IMAGE, VIDEO, or DOCUMENT formats."); + } + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java new file mode 100644 index 00000000..75204df0 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java @@ -0,0 +1,132 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * HSMComponentButton + * + * @see HSMComponentButton + * @author ssk910 + */ +public class HSMComponentButton { + + private HSMComponentButtonType type; + private String text; + private String url; + private String phone_number; + private List example; + + //fields used by the authentification template + @JsonProperty("otp_type") + private HSMOTPButtonType otpType; + @JsonProperty("autofill_text") + private String autofillText; + @JsonProperty("package_name") + private String packageName; + @JsonProperty("signature_hash") + private String signatureHash; + + public HSMComponentButtonType getType() { + return type; + } + + public void setType(HSMComponentButtonType type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPhone_number() { + return phone_number; + } + + public void setPhone_number(String phone_number) { + this.phone_number = phone_number; + } + + public List getExample() { + return example; + } + + public void setExample(List example) { + this.example = example; + } + public HSMOTPButtonType getOtpType() { + return otpType; + } + + public void setOtpType(HSMOTPButtonType otpType) { + this.otpType = otpType; + } + + public String getAutofillText() { + return autofillText; + } + + public void setAutofillText(String autofillText) { + this.autofillText = autofillText; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getSignatureHash() { + return signatureHash; + } + + public void setSignatureHash(String signatureHash) { + this.signatureHash = signatureHash; + } + @Override + public String toString() { + return "HSMComponentButton{" + + "type=" + type + + ", text='" + text + '\'' + + ", url='" + url + '\'' + + ", phone_number='" + phone_number + '\'' + + ", example=" + example + + '}'; + } + + /** + * Check if example field is able to use. + * + * @throws IllegalArgumentException Occurs if button type is not {@code URL} or {@code QUICK_REPLY}. + */ + public void validateButtonExample() throws IllegalArgumentException { + final boolean isExampleEmpty = this.example == null || this.example.isEmpty(); + final boolean isNotProperType = !(this.type.equals(HSMComponentButtonType.URL) + || this.type.equals(HSMComponentButtonType.QUICK_REPLY) + || this.type.equals(HSMComponentButtonType.COPY_CODE) + ); + + if (isExampleEmpty) { + return; + } + + if (isNotProperType) { + throw new IllegalArgumentException("An example field in HSMComponentButton is available for only URL or QUICK_REPLY button types."); + } + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java new file mode 100644 index 00000000..06e6eb91 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java @@ -0,0 +1,50 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * HSMComponentButtonType + * + * @see HSMComponentButtonType + * @author ssk910 + */ +public enum HSMComponentButtonType { + + PHONE_NUMBER("PHONE_NUMBER"), + URL("URL"), + QUICK_REPLY("QUICK_REPLY"), + OTP("OTP"), + COPY_CODE("COPY_CODE"); + + private final String type; + + HSMComponentButtonType(String type) { + this.type = type; + } + + @JsonCreator + public static HSMComponentButtonType forValue(String value) { + for (HSMComponentButtonType hsmComponentButtonType : HSMComponentButtonType.values()) { + if (hsmComponentButtonType.getType().equals(value)) { + return hsmComponentButtonType; + } + } + + return null; + } + + @JsonValue + public String toJson() { + return getType(); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java new file mode 100644 index 00000000..23411941 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java @@ -0,0 +1,21 @@ +package com.messagebird.objects.integrations; + +import java.util.List; + +/** + * HSMComponentCard + * + * @author AlexL-mb + * @see HSMComponentCard + */ +public class HSMComponentCard { + private List components; + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java new file mode 100644 index 00000000..ac7d199d --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java @@ -0,0 +1,49 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * An enum for HSMComponentFormat + * + * @see HSMComponentFormat + * @author ssk910 + */ +public enum HSMComponentFormat { + + TEXT("TEXT"), + IMAGE("IMAGE"), + DOCUMENT("DOCUMENT"), + VIDEO("VIDEO"); + + private final String format; + + HSMComponentFormat(String format) { + this.format = format; + } + + @JsonCreator + public static HSMComponentFormat forValue(String value) { + for (HSMComponentFormat hsmComponentFormat : HSMComponentFormat.values()) { + if (hsmComponentFormat.getFormat().equals(value)) { + return hsmComponentFormat; + } + } + + return null; + } + + @JsonValue + public String toJson() { + return getFormat(); + } + + public String getFormat() { + return format; + } + + @Override + public String toString() { + return getFormat(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java new file mode 100644 index 00000000..8610146a --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java @@ -0,0 +1,59 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Map; +import java.util.HashMap; +import java.util.Collections; +import java.util.Locale; +import java.util.Objects; + +/** + * An enum for HSMComponentType + * + * @see HSMComponentType + */ +public enum HSMComponentType { + BODY("BODY"), + HEADER("HEADER"), + FOOTER("FOOTER"), + BUTTONS("BUTTONS"), + CAROUSEL("CAROUSEL"), + LIMITED_TIME_OFFER("LIMITED_TIME_OFFER"); + + private static final Map TYPE_MAP; + + static { + Map map = new HashMap<>(); + for (HSMComponentType hsmComponentType : HSMComponentType.values()) { + map.put(hsmComponentType.getType().toLowerCase(), hsmComponentType); + } + TYPE_MAP = Collections.unmodifiableMap(map); + } + + private final String type; + + HSMComponentType(String type) { + this.type = type; + } + + @JsonCreator + public static HSMComponentType forValue(String value) { + Objects.requireNonNull(value, "Value cannot be null"); + return TYPE_MAP.get(value.toLowerCase(Locale.ROOT)); + } + + @JsonValue + public String toJson() { + return getType(); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java b/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java new file mode 100644 index 00000000..a28fcc38 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java @@ -0,0 +1,54 @@ +package com.messagebird.objects.integrations; + +import java.util.List; + +/** + * HSMExample object + * + * @see HSMExample object + * @author ssk910 + */ +public class HSMExample { + + /* Example values for HEADER type components, TEXT format */ + private List header_text; + + /* Example set of values for the body text variables */ + private List> body_text; + + /* Example values for HEADER type components, IMAGE format */ + private List header_url; + + public List getHeader_text() { + return header_text; + } + + public void setHeader_text(List header_text) { + this.header_text = header_text; + } + + public List> getBody_text() { + return body_text; + } + + public void setBody_text(List> body_text) { + this.body_text = body_text; + } + + public List getHeader_url() { + return header_url; + } + + public void setHeader_url(List header_url) { + this.header_url = header_url; + } + + @Override + public String toString() { + return "HSMExample{" + + "header_text=" + header_text + + ", body_text=" + body_text + + ", header_url=" + header_url + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java new file mode 100644 index 00000000..499ef1f3 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java @@ -0,0 +1,38 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum HSMOTPButtonType { + ONE_TAP("ONE_TAP"), + COPY_CODE("COPY_CODE"); + + private final String type; + + HSMOTPButtonType(String type) { + this.type = type; + } + @JsonCreator + public static HSMOTPButtonType forValue(String value) { + for (HSMOTPButtonType OTPButtonType : HSMOTPButtonType.values()) { + if (OTPButtonType.getType().equals(value)) { + return OTPButtonType; + } + } + return null; + } + + @JsonValue + public String toJson() { + return getType(); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java b/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java new file mode 100644 index 00000000..112492fd --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java @@ -0,0 +1,42 @@ +package com.messagebird.objects.integrations; + +import java.util.List; + +public class HSMQualityScore { + private String score; + private long date; + private List reasons; + + public String getScore() { + return score; + } + + public void setScore(String score) { + this.score = score; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public List getReasons() { + return reasons; + } + + public void setReasons(List reasons) { + this.reasons = reasons; + } + + @Override + public String toString() { + return "HSMQualityScore{" + + "score='" + score + '\'' + + ", date=" + date + + ", reasons=" + reasons + + '}'; + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java b/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java new file mode 100644 index 00000000..1b1980f1 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java @@ -0,0 +1,53 @@ +package com.messagebird.objects.integrations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * An enum for HSMStatus object + * + * @see HSMStatus object + * @author ssk910 + */ +public enum HSMStatus { + + NEW("NEW"), + APPROVED("APPROVED"), + PENDING("PENDING"), + REJECTED("REJECTED"), + PENDING_DELETION("PENDING_DELETION"), + DELETED("DELETED"), + DISABLED("DISABLED"), + PAUSED("PAUSED"); + + private final String status; + + HSMStatus(String status) { + this.status = status; + } + + @JsonCreator + public static HSMStatus forValue(String value) { + for (HSMStatus hsmStatus : HSMStatus.values()) { + if (hsmStatus.getStatus().equals(value)) { + return hsmStatus; + } + } + + return null; + } + + @JsonValue + public String toJson() { + return getStatus(); + } + + public String getStatus() { + return status; + } + + @Override + public String toString() { + return getStatus(); + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/Template.java b/api/src/main/java/com/messagebird/objects/integrations/Template.java new file mode 100644 index 00000000..f6a83e93 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/Template.java @@ -0,0 +1,173 @@ +package com.messagebird.objects.integrations; + +import java.util.List; + +/** + * Template Object as integrations API request. + * + * @see Integrations API + * @author ssk910 + */ +public class Template { + + private String name; + private String language; + private String wabaID; + private List components; + private HSMCategory category; + private boolean ctaURLLinkTrackingOptedOut; + + public Template() { + } + + + public Template(String name, String language, String wabaID, + List components, HSMCategory category, boolean ctaURLLinkTrackingOptedOut) { + this.name = name; + this.language = language; + this.wabaID = wabaID; + this.components = components; + this.category = category; + this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getWABAID() { + return wabaID; + } + + public void setWABAID(String wabaID) { + this.wabaID = wabaID; + } + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + + public HSMCategory getCategory() { + return category; + } + + public void setCategory(HSMCategory category) { + this.category = category; + } + + public void setCtaURLLinkTrackingOptedOut (boolean ctaURLLinkTrackingOptedOut) { + this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut; + } + + public boolean getCtaURLLinkTrackingOptedOut () { + return ctaURLLinkTrackingOptedOut; + } + + @Override + public String toString() { + return "WhatsAppTemplate{" + + "name='" + name + '\'' + + ", language='" + language + '\'' + + ", wabaID='" + wabaID + '\'' + + ", components=" + components + + ", category='" + category + '\'' + + ", ctaURLLinkTrackingOptedOut='" + ctaURLLinkTrackingOptedOut + '\'' + + '}'; + } + + /** + * Validate required fields: components, name, language, category + * + * @throws IllegalArgumentException if required fields are invalid. + */ + public void validate() throws IllegalArgumentException { + this.validateComponents(); + this.validateName(); + this.validateLanguage(); + this.validateWABAID(); + this.validateCategory(); + } + + /** + * Check if components field is valid. + * + * @throws IllegalArgumentException If components field is null or empty list. + */ + private void validateComponents() throws IllegalArgumentException { + final boolean componentsNotEmpty = !(this.components == null || this.components.isEmpty()); + + if (componentsNotEmpty) { + for (final HSMComponent component : this.components) { + component.validateComponent(); + } + } else { + throw new IllegalArgumentException("A \"components\" field is required and should not be empty list."); + } + } + + /** + * Check if name field is valid. + * + * @throws IllegalArgumentException If name field is null or empty string. + */ + private void validateName() { + if (this.name == null) { + throw new IllegalArgumentException("A \"name\" field is required."); + } else if (this.name.length() == 0) { + throw new IllegalArgumentException("A \"name\" field can not be an empty string."); + } + } + + /** + * Check if language field is valid. + * + * @throws IllegalArgumentException If language field is null or empty string. + */ + private void validateLanguage() { + if (this.language == null) { + throw new IllegalArgumentException("A \"language\" field is required."); + } else if (this.language.length() == 0) { + throw new IllegalArgumentException("A \"language\" field can not be an empty string."); + } + } + + /** + * Check if wabaID field is valid. + * + * @throws IllegalArgumentException If wabaID field is null or empty string. + */ + private void validateWABAID() { + if (this.wabaID == null) { + throw new IllegalArgumentException("A \"wabaID\" field is required."); + } else if (this.wabaID.length() == 0) { + throw new IllegalArgumentException("A \"wabaID\" field can not be an empty string."); + } + } + + /** + * Check if category field is valid. + * + * @throws IllegalArgumentException If category field is null. + */ + private void validateCategory() { + if (this.category == null) { + throw new IllegalArgumentException("A \"category\" field is required."); + } + } +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java b/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java new file mode 100644 index 00000000..5cea4023 --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java @@ -0,0 +1,12 @@ +package com.messagebird.objects.integrations; + +import com.messagebird.objects.ListBase; + +/** + * Response object representing the Template list type. + * + * @author ssk910 + */ +public class TemplateList extends ListBase { + +} diff --git a/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java new file mode 100644 index 00000000..3c75fabe --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java @@ -0,0 +1,149 @@ +package com.messagebird.objects.integrations; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * Template response using integrations API. + * + * @author ssk910 + */ +public class TemplateResponse implements Serializable { + + private static final long serialVersionUID = 7154209824478715861L; + private String name; + private String language; + private HSMCategory category; + private List components; + private HSMStatus status; + private String rejectedReason; + private String wabaID; + private String namespace; + + private boolean ctaURLLinkTrackingOptedOut; + + private HSMQualityScore qualityScore; + + private Date createdAt; + private Date updatedAt; + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public HSMCategory getCategory() { + return category; + } + + public void setCategory(HSMCategory category) { + this.category = category; + } + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + + public HSMStatus getStatus() { + return status; + } + + public void setStatus(HSMStatus status) { + this.status = status; + } + + public String getRejectedReason() { + return rejectedReason; + } + + public void setRejectedReason(String rejectedReason) { + this.rejectedReason = rejectedReason; + } + + public String getWabaID() { + return wabaID; + } + + public void setWabaID(String wabaID) { + this.wabaID = wabaID; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public boolean isCtaURLLinkTrackingOptedOut() { + return ctaURLLinkTrackingOptedOut; + } + + public void setCtaURLLinkTrackingOptedOut(boolean ctaURLLinkTrackingOptedOut) { + this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut; + } + + public HSMQualityScore getQualityScore() { + return qualityScore; + } + + public void setQualityScore(HSMQualityScore qualityScore) { + this.qualityScore = qualityScore; + } + + @Override + public String toString() { + return "WhatsAppTemplateResponse{" + + "name='" + name + '\'' + + ", language='" + language + '\'' + + ", category='" + category + '\'' + + ", components=" + components + + ", status='" + status + '\'' + + ", rejectedReason='" + rejectedReason + '\'' + + ", wabaID='" + wabaID + '\'' + + ", namespace='" + namespace + '\'' + + ", ctaURLLinkTrackingOptedOut='" + ctaURLLinkTrackingOptedOut + '\'' + + ", qualityScore='" + qualityScore + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + +} diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java b/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java new file mode 100644 index 00000000..7061c8fe --- /dev/null +++ b/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java @@ -0,0 +1,73 @@ +package com.messagebird.objects.voicecalls; + +import com.fasterxml.jackson.annotation.JsonCreator; + + +/** + * More details, including additional descriptions and common caused can be found here: https://developers.messagebird.com/api/voice-calling/#sip-status-codes + * @author leandropinto + * + */ +public enum SipResponseCode { + //Successful + OK, + //The server understood the request, but is refusing to fulfill it. + FORBIDDEN, + //The server has definitive information that the user does not exist at the domain specified in the Request-URI. + NOT_FOUND, + //Couldn't find the user in time. + REQUEST_TIMEOUT, + //The user existed once, but is not available here any more. + GONE, + //Callee currently unavailable. + TEMPORARILY_UNAVAILABLE, + //Request-URI incomplete. + ADDRESS_INCOMPLETE, + //Callee is busy. + BUSY_HERE, + //Some aspect of the session description or the Request-URI is not acceptable. + NOT_ACCEPTABLE_HERE, + //The server could not fulfill the request due to some unexpected condition. + INTERNAL_SERVER_ERROR, + //The server does not have the ability to fulfill the request, such as because it does not recognize the request method. + NOT_IMPLEMENTED, + //The server is acting as a gateway or proxy, and received an invalid response from a downstream server while attempting to fulfill the request. + BAD_GATEWAY, + //The server is undergoing maintenance or is temporarily overloaded and so cannot process the request. + SERVICE_UNAVAILABLE; + + @JsonCreator + public static SipResponseCode forValue(Integer value) { + switch (value) { + case 200: + return OK; + case 403: + return FORBIDDEN; + case 404: + return NOT_FOUND; + case 408: + return REQUEST_TIMEOUT; + case 410: + return GONE; + case 480: + return TEMPORARILY_UNAVAILABLE; + case 484: + return ADDRESS_INCOMPLETE; + case 486: + return BUSY_HERE; + case 488: + return NOT_ACCEPTABLE_HERE; + case 500: + return INTERNAL_SERVER_ERROR; + case 501: + return NOT_IMPLEMENTED; + case 502: + return BAD_GATEWAY; + case 503: + return SERVICE_UNAVAILABLE; + + default: + throw new IllegalArgumentException("Unknown sip response code: " + value); + } + } +} \ No newline at end of file diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java index 8ece36ec..573b42be 100644 --- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java +++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java @@ -63,10 +63,12 @@ public Webhook getWebhook() { return webhook; } + @JsonIgnore public void setWebhook(String url) { this.setWebhook(url, null); } + @JsonIgnore public void setWebhook(String url, String token) { this.webhook.setUrl(url); this.webhook.setToken(token); diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java index d64f0d2c..53130c77 100644 --- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java +++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java @@ -23,6 +23,9 @@ public class VoiceCallFlow implements Serializable { @JsonProperty("default") private boolean defaultCall; + @JsonProperty("maxDuration") + private Integer maxDuration; + private Date createdAt; private Date updatedAt; @@ -37,10 +40,12 @@ public void setId(String id) { this.id = id; } + @Deprecated public String getTitle() { return title; } + @Deprecated public void setTitle(String title) { this.title = title; } @@ -69,6 +74,10 @@ public void setDefaultCall(boolean defaultCall) { this.defaultCall = defaultCall; } + public Integer getMaxDuration() { return maxDuration; } + + public void setMaxDuration(Integer maxDuration) { this.maxDuration = maxDuration; } + public Date getCreatedAt() { return createdAt; } @@ -101,6 +110,7 @@ public String toString() { ", record=" + record + ", steps=" + steps + ", default=" + defaultCall + + ", maxDuration=" + maxDuration + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java index 3990cfb2..e447d9d9 100644 --- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java +++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java @@ -36,10 +36,12 @@ public void setId(String id) { this.id = id; } + @Deprecated public String getTitle() { return title; } + @Deprecated public void setTitle(String title) { this.title = title; } diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java index 93dc0fa2..d09b7bdf 100644 --- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java +++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java @@ -28,6 +28,7 @@ public class VoiceCallLeg { public final Date updatedAt; public final Date answeredAt; public final Date endedAt; + public final SipResponseCode sipResponseCode; @JsonCreator @@ -44,7 +45,8 @@ public VoiceCallLeg( @JsonProperty("createdAt") Date createdAt, @JsonProperty("updatedAt") Date updatedAt, @JsonProperty("answeredAt") Date answeredAt, - @JsonProperty("endedAt") Date endedAt + @JsonProperty("endedAt") Date endedAt, + @JsonProperty("sipResponseCode") SipResponseCode sipResponseCode ) { this.id = id; this.callID = callID; @@ -59,6 +61,7 @@ public VoiceCallLeg( this.updatedAt = updatedAt; this.answeredAt = answeredAt; this.endedAt = endedAt; + this.sipResponseCode = sipResponseCode; } @Override @@ -77,6 +80,7 @@ public String toString() { ", updatedAt='" + updatedAt + '\'' + ", answeredAt='" + answeredAt + '\'' + ", endedAt='" + endedAt + '\'' + + ", sipResponseCode='" + sipResponseCode + '\'' + '}'; } @@ -97,11 +101,12 @@ public boolean equals(Object o) { Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt) && Objects.equals(answeredAt, that.answeredAt) && - Objects.equals(endedAt, that.endedAt); + Objects.equals(endedAt, that.endedAt) && + Objects.equals(sipResponseCode, that.sipResponseCode); } @Override public int hashCode() { - return Objects.hash(id, callID, source, destination, status, direction, cost, currency, duration, createdAt, updatedAt, answeredAt, endedAt); + return Objects.hash(id, callID, source, destination, status, direction, cost, currency, duration, createdAt, updatedAt, answeredAt, endedAt, sipResponseCode); } } diff --git a/api/src/test/java/com/messagebird/ContactTest.java b/api/src/test/java/com/messagebird/ContactTest.java index 2377733d..5dfdea38 100644 --- a/api/src/test/java/com/messagebird/ContactTest.java +++ b/api/src/test/java/com/messagebird/ContactTest.java @@ -8,6 +8,7 @@ import org.mockito.Mockito; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.junit.Assume.assumeNotNull; import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals; /** @@ -30,6 +31,7 @@ public class ContactTest { @BeforeClass public static void setUpClass() throws UnauthorizedException, GeneralException { String accessKey = System.getProperty("messageBirdAccessKey"); + assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey to run", accessKey); msisdn = generateMsisdn(); diff --git a/api/src/test/java/com/messagebird/ConversationMessagesTest.java b/api/src/test/java/com/messagebird/ConversationMessagesTest.java index 81357f88..beac543a 100644 --- a/api/src/test/java/com/messagebird/ConversationMessagesTest.java +++ b/api/src/test/java/com/messagebird/ConversationMessagesTest.java @@ -1,5 +1,6 @@ package com.messagebird; +import com.fasterxml.jackson.databind.ObjectMapper; import com.messagebird.exceptions.GeneralException; import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; @@ -23,6 +24,19 @@ public class ConversationMessagesTest { private static final String JSON_CONVERSATION_MESSAGE_TEXT = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}"; private static final String JSON_CONVERSATION_MESSAGE_VIDEO = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"video\",\"direction\": \"received\",\"content\": {\"video\": { \"url\": \"https://example.com/video.mp4\" } },\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}"; private static final String JSON_CONVERSATION_SEND_MESSAGE_RESPONSE = "{\"id\":\"mesid\",\"status\":\"accepted\",\"fallback\":{\"id\":\"mesid\"}}"; + private static final String JSON_CONVERSATION_MESSAGE_BSUID = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"metadata\": {\"sender\": {\"displayName\": \"Alice\",\"username\": \"alice_shop\",\"userId\": \"US.13491208655302741918\",\"parentUserId\": \"US.ENT.11815799212886844830\"},\"receivedAt\": \"2025-04-15T16:00:00Z\"},\"createdDatetime\": \"2025-04-15T16:00:00Z\",\"updatedDatetime\": \"2025-04-15T16:00:00Z\"}"; + private static final String JSON_STATUS_MESSAGE_METADATA = "{\"id\": \"e5f6a7b8-c9d0-1234-ef01-23456789abcd\",\"from\": \"15551234567\",\"to\": \"US.13491208655302741918\",\"type\": \"text\",\"content\": {\"text\": \"Hello! Your order has been shipped.\"},\"metadata\": {\"sender\": {\"userId\": \"US.13491208655302741918\"},\"receivedAt\": \"0001-01-01T00:00:00Z\"}}"; + + private static final String JSON_STATUS_METADATA_WITH_RECIPIENT = "{\"pricing\": {\"billable\": true,\"pricing_model\": \"CBP\",\"category\": \"utility\"},\"conversation\": {\"id\": \"a1b2c3d4\",\"origin\": {\"type\": \"utility\"}},\"biz_opaque_callback_data\": \"order-1234\",\"recipient\": {\"userId\": \"US.13491208655302741918\",\"parentUserId\": \"US.ENT.11815799212886844830\"}}"; + private static final String JSON_STATUS_METADATA_WITHOUT_RECIPIENT = "{\"pricing\": {\"billable\": true,\"pricing_model\": \"CBP\",\"category\": \"utility\"},\"conversation\": {\"id\": \"a1b2c3d4\",\"origin\": {\"type\": \"utility\"}}}"; + private static final String JSON_STATUS_METADATA_PARENT_ONLY = "{\"recipient\": {\"parentUserId\": \"US.ENT.11815799212886844830\"}}"; + + /** + * The same payload as JSON_STATUS_MESSAGE_METADATA with an unrecognised key + * added at every nesting level, standing in for fields the platform adds + * after this SDK version ships. + */ + private static final String JSON_STATUS_MESSAGE_METADATA_UNKNOWN_FIELDS = "{\"id\": \"e5f6a7b8-c9d0-1234-ef01-23456789abcd\",\"from\": \"15551234567\",\"to\": \"US.13491208655302741918\",\"type\": \"text\",\"futureTopLevelField\": \"ignored\",\"content\": {\"text\": \"Hello! Your order has been shipped.\",\"futureContentField\": \"ignored\"},\"metadata\": {\"sender\": {\"userId\": \"US.13491208655302741918\",\"futureSenderField\": \"ignored\"},\"receivedAt\": \"0001-01-01T00:00:00Z\",\"futureMetadataField\": \"ignored\"}}"; /** * Epsilon to use when checking two latitudes or longitudes for equality. @@ -183,6 +197,96 @@ public void testViewConversationMessageLocation() throws GeneralException, NotFo assertEquals(4.911627, location.getLongitude(), EPSILON_LOCATION_EQUALITY); } + @Test + public void testViewConversationMessageWithBsuidMetadata() throws GeneralException, NotFoundException, UnauthorizedException { + MessageBirdService messageBirdService = SpyService + .expects("GET", "messages/mesid") + .withConversationsAPIBaseURL() + .andReturns(new APIResponse(JSON_CONVERSATION_MESSAGE_BSUID)); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); + + ConversationMessage message = messageBirdClient.viewConversationMessage("mesid"); + + ConversationMessageMetadata metadata = message.getMetadata(); + assertNotNull(metadata); + assertNotNull(metadata.getReceivedAt()); + ConversationSenderMetadata sender = metadata.getSender(); + assertEquals("Alice", sender.getDisplayName()); + assertEquals("alice_shop", sender.getUsername()); + assertEquals("US.13491208655302741918", sender.getUserId()); + assertEquals("US.ENT.11815799212886844830", sender.getParentUserId()); + } + + @Test + public void testStatusMessageMetadataToleratesUnknownFields() throws Exception { + // A plain mapper, and unknown keys at every level: webhook payload POJOs + // must not force consumers to disable FAIL_ON_UNKNOWN_PROPERTIES, and + // must survive fields the platform adds after this version ships. + ConversationStatusMessageMetadata md = new ObjectMapper().readValue( + JSON_STATUS_MESSAGE_METADATA_UNKNOWN_FIELDS, ConversationStatusMessageMetadata.class); + + assertEquals("e5f6a7b8-c9d0-1234-ef01-23456789abcd", md.getId()); + assertEquals("Hello! Your order has been shipped.", md.getContent().getText()); + assertEquals("US.13491208655302741918", md.getMetadata().getSender().getUserId()); + assertNotNull(md.getMetadata().getReceivedAt()); + } + + @Test + public void testStatusMetadataDeserializesRecipient() throws Exception { + // A plain mapper: these payload POJOs must not require the caller to + // disable FAIL_ON_UNKNOWN_PROPERTIES. + ConversationStatusMetadata metadata = new ObjectMapper().readValue( + JSON_STATUS_METADATA_WITH_RECIPIENT, ConversationStatusMetadata.class); + + ConversationRecipientMetadata recipient = metadata.getRecipient(); + assertNotNull(recipient); + assertEquals("US.13491208655302741918", recipient.getUserId()); + assertEquals("US.ENT.11815799212886844830", recipient.getParentUserId()); + + // Meta's own objects pass through untouched, snake_case keys and all. + assertEquals("CBP", metadata.getPricing().get("pricing_model")); + assertEquals("a1b2c3d4", metadata.getConversation().get("id")); + + // Anything else on the payload is kept rather than dropped. + assertEquals("order-1234", metadata.getAdditionalProperties().get("biz_opaque_callback_data")); + } + + @Test + public void testStatusMetadataWithoutRecipientIsNull() throws Exception { + ConversationStatusMetadata metadata = new ObjectMapper().readValue( + JSON_STATUS_METADATA_WITHOUT_RECIPIENT, ConversationStatusMetadata.class); + + // Accounts that never receive BSUIDs get payloads with no recipient key + // at all — the rest of the metadata must still parse. + assertNull(metadata.getRecipient()); + assertEquals("CBP", metadata.getPricing().get("pricing_model")); + assertTrue(metadata.getAdditionalProperties().isEmpty()); + } + + @Test + public void testStatusMetadataRecipientWithParentOnly() throws Exception { + ConversationStatusMetadata metadata = new ObjectMapper().readValue( + JSON_STATUS_METADATA_PARENT_ONLY, ConversationStatusMetadata.class); + + ConversationRecipientMetadata recipient = metadata.getRecipient(); + assertNotNull(recipient); + assertNull(recipient.getUserId()); + assertEquals("US.ENT.11815799212886844830", recipient.getParentUserId()); + } + + @Test + public void testStatusMessageMetadataDeserializes() throws Exception { + ConversationStatusMessageMetadata md = new ObjectMapper().readValue( + JSON_STATUS_MESSAGE_METADATA, ConversationStatusMessageMetadata.class); + + assertEquals("e5f6a7b8-c9d0-1234-ef01-23456789abcd", md.getId()); + assertEquals("15551234567", md.getFrom()); + assertEquals("US.13491208655302741918", md.getTo()); + assertEquals("text", md.getType()); + assertEquals("Hello! Your order has been shipped.", md.getContent().getText()); + assertEquals("US.13491208655302741918", md.getMetadata().getSender().getUserId()); + } + @Test public void testViewConversationMessageText() throws GeneralException, NotFoundException, UnauthorizedException { MessageBirdService messageBirdService = SpyService diff --git a/api/src/test/java/com/messagebird/ConversationsTest.java b/api/src/test/java/com/messagebird/ConversationsTest.java index 81095dc3..4e7624e8 100644 --- a/api/src/test/java/com/messagebird/ConversationsTest.java +++ b/api/src/test/java/com/messagebird/ConversationsTest.java @@ -76,7 +76,7 @@ public void testStartConversation() throws GeneralException, UnauthorizedExcepti @Test public void testUpdateConversation() throws GeneralException, UnauthorizedException { MessageBirdService messageBirdService = SpyService - .expects("PATCH", "conversations/convid", ConversationStatus.ARCHIVED) + .expects("PATCH", "conversations/convid", new ConversationUpdateRequest(ConversationStatus.ARCHIVED)) .withConversationsAPIBaseURL() .andReturns(new APIResponse(JSON_CONVERSATION)); MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); diff --git a/api/src/test/java/com/messagebird/MessageBirdClientTest.java b/api/src/test/java/com/messagebird/MessageBirdClientTest.java index 031aa16e..b11c2f54 100644 --- a/api/src/test/java/com/messagebird/MessageBirdClientTest.java +++ b/api/src/test/java/com/messagebird/MessageBirdClientTest.java @@ -4,15 +4,21 @@ import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; import com.messagebird.objects.*; +import com.messagebird.objects.conversations.*; +import com.messagebird.objects.integrations.Template; +import com.messagebird.objects.integrations.TemplateList; +import com.messagebird.objects.integrations.TemplateResponse; import com.messagebird.objects.voicecalls.*; +import java.util.ArrayList; + +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; +import static org.junit.Assume.assumeNotNull; -import java.io.UnsupportedEncodingException; import java.math.BigInteger; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -20,6 +26,8 @@ import java.util.Map; import static com.messagebird.MessageBirdClient.*; +import static com.messagebird.TestUtil.*; +import static com.messagebird.TestUtil.createChildAccountDetailedResponse; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals; @@ -37,7 +45,10 @@ public class MessageBirdClientTest { @BeforeClass public static void setUpClass() { messageBirdAccessKey = System.getProperty("messageBirdAccessKey"); - messageBirdMSISDN = new BigInteger(System.getProperty("messageBirdMSISDN")); + String msisdn = System.getProperty("messageBirdMSISDN"); + assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey and -DmessageBirdMSISDN to run", + messageBirdAccessKey, msisdn); + messageBirdMSISDN = new BigInteger(msisdn); } @Before @@ -119,7 +130,7 @@ public void testListScheduledMessages() throws Exception { filters.put("status", "scheduled"); when(messageBirdServiceMock.requestList("/messages", filters, null, null, MessageList.class)) - .thenReturn(mockedResponse); + .thenReturn(mockedResponse); final MessageList response = messageBirdClientMock.listMessagesFiltered(null, null, filters); assertNotNull(response); @@ -130,7 +141,7 @@ public void testListScheduledMessages() throws Exception { public void testListScheduledMessagesWrongFilter() throws Exception { Map filters = new LinkedHashMap<>(); filters.put("does not exist", null); - + messageBirdClient.listMessagesFiltered(null, null, filters); } @@ -148,7 +159,7 @@ public void testSendDeleteMessage() throws Exception { assertNotNull(mr.getId()); assertEquals(mr.getReference(), reference); assertEquals(mr.getBody(), body); - assertEquals(mr.getDatacoding(), DataCodingType.plain); + assertEquals(DataCodingType.plain,mr.getDatacoding()); // Deleting of a message is not yet supported in test mode // Thread.sleep(1000); @@ -218,8 +229,8 @@ public void testSendDeleteFlashMessage() throws Exception { final String body = "Body test message Über € " + messageBirdMSISDN; final MessageResponse mr = messageBirdClient.sendFlashMessage("originator", body, Collections.singletonList(messageBirdMSISDN)); assertNotNull(mr.getId()); - assertSame(mr.getType(), MsgType.flash); - assertSame(mr.getMclass(), MClassType.flash); + assertSame(MsgType.flash,mr.getType() ); + assertSame(MClassType.flash, mr.getMclass()); // Deleting of a message is not yet supported in test mode // Thread.sleep(1000); @@ -290,8 +301,8 @@ public void testSendVoiceMessage() throws Exception { final VoiceMessageResponse mr = messageBirdClient.sendVoiceMessage(vm); assertNotNull(mr.getId()); assertEquals(mr.getBody(), body); - assertSame(mr.getIfMachine(), IfMachineType.hangup); - assertSame(mr.getVoice(), VoiceType.male); + assertSame(IfMachineType.hangup, mr.getIfMachine() ); + assertSame(VoiceType.male, mr.getVoice()); // Deleting of a message is not yet supported in test mode // Thread.sleep(1000); @@ -318,17 +329,6 @@ public void testSendVoiceMessage2() throws Exception { assertNotNull(mr.getId()); assertEquals(mr.getBody(), body); assertEquals(mr.getReference(), reference); - - Thread.sleep(500); - // Viewing of a message is not yet supported in test mode - // final VoiceMessageResponse mr2 = messageBirdClient.viewVoiceMessage(mr.getId()); - // assertTrue(mr2.getId() != null); - // assertTrue(mr2.getBody().equals(body)); - // assertTrue(mr2.getReference().equals(reference)); - - // Deleting of a message is not yet supported in test mode - // Thread.sleep(1000); - // Gives 404 messageBirdClient.deleteVoiceMessage(mr.getId()); } /** @@ -363,7 +363,7 @@ public void testSendVerifyToken1() throws UnauthorizedException, GeneralExceptio } @Test - public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException, GeneralException, NotFoundException { + public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException, GeneralException { Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString()); assertFalse("href is empty", verify.getHref().isEmpty()); assertFalse("id is empty", verify.getId().isEmpty()); @@ -377,7 +377,7 @@ public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException } @Test - public void testVerifyToken() throws UnauthorizedException, GeneralException, UnsupportedEncodingException { + public void testVerifyToken() throws UnauthorizedException, GeneralException { Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString()); assertFalse("href is empty", verify.getHref().isEmpty()); @@ -393,7 +393,7 @@ public void testVerifyToken() throws UnauthorizedException, GeneralException, Un } @Test - public void testDeleteVerifyToken() throws UnauthorizedException, GeneralException, NotFoundException, UnsupportedEncodingException { + public void testDeleteVerifyToken() throws UnauthorizedException, GeneralException { Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString()); assertFalse("href is empty", verify.getHref().isEmpty()); try { @@ -431,7 +431,6 @@ public void shouldThrowIllegalArgumentExceptionWhenSourceOfVoiceCallIsMissing() voiceCall.setDestination("ANY_DESTINATION"); final VoiceCallFlow voiceCallFlow = new VoiceCallFlow(); - voiceCallFlow.setTitle("Test title"); VoiceStep voiceStep = new VoiceStep(); voiceStep.setAction("say"); @@ -447,13 +446,11 @@ public void shouldThrowIllegalArgumentExceptionWhenSourceOfVoiceCallIsMissing() } @Test(expected = IllegalArgumentException.class) - public void shouldThrowIllegalArgumentExceptionWhenDestinationOfVoiceCallIsMissing() throws UnauthorizedException, - GeneralException { + public void shouldThrowIllegalArgumentExceptionWhenDestinationOfVoiceCallIsMissing() throws UnauthorizedException, GeneralException { final VoiceCall voiceCall = new VoiceCall(); voiceCall.setSource("ANY_SOURCE"); final VoiceCallFlow voiceCallFlow = new VoiceCallFlow(); - voiceCallFlow.setTitle("Test title"); VoiceStep voiceStep = new VoiceStep(); voiceStep.setAction("say"); @@ -500,8 +497,7 @@ public void testViewVoiceCall() throws UnauthorizedException, GeneralException, } @Test - public void testDeleteVoiceCall() throws UnauthorizedException, - GeneralException, NotFoundException { + public void testDeleteVoiceCall() throws UnauthorizedException, GeneralException, NotFoundException { MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); @@ -808,11 +804,11 @@ public void testListNumbersForPurchase() throws IllegalArgumentException, Genera MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); when(messageBirdServiceMock.requestByID(url, "NL", PhoneNumbersResponse.class)) - .thenReturn(mockedResponse); + .thenReturn(mockedResponse); final PhoneNumbersResponse response = messageBirdClientMock.listNumbersForPurchase("NL"); verify(messageBirdServiceMock, times(1)).requestByID(url, "NL", PhoneNumbersResponse.class); - + assertNotNull(response); assertEquals(response, mockedResponse); } @@ -831,9 +827,9 @@ public void testListNumbersForPurchaseWithParams() throws IllegalArgumentExcepti options.setLimit(1); options.setNumber(562); options.setSearchPattern(PhoneNumberSearchPattern.START); - + when(messageBirdServiceMock.requestByID(url, "US", options.toHashMap(), PhoneNumbersResponse.class)) - .thenReturn(mockedResponse); + .thenReturn(mockedResponse); final PhoneNumbersResponse response = messageBirdClientMock.listNumbersForPurchase("US", options); verify(messageBirdServiceMock, times(1)).requestByID(url, "US", options.toHashMap(), PhoneNumbersResponse.class); @@ -849,26 +845,26 @@ public void testPurchaseNumber() throws UnauthorizedException, GeneralException MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); - + final Map payload = new LinkedHashMap(); payload.put("number", "15625267429"); payload.put("countryCode", "US"); payload.put("billingIntervalMonths", 1); - + when(messageBirdServiceMock.sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class)) - .thenReturn(purchasedNumberMockData); + .thenReturn(purchasedNumberMockData); final PurchasedNumberCreatedResponse response = messageBirdClientMock.purchaseNumber("15625267429", "US", 1); verify(messageBirdServiceMock, times(1)).sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class); assertNotNull(response); assertEquals(response, purchasedNumberMockData); } - + @Test public void testListPurchasedNumbers() throws UnauthorizedException, GeneralException, NotFoundException { final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL); - + PurchasedNumbersResponse purchasedNumbersMockData = new PurchasedNumbersResponse(); - + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); @@ -879,7 +875,7 @@ public void testListPurchasedNumbers() throws UnauthorizedException, GeneralExce filter.addTag("tag"); when(messageBirdServiceMock.requestByID(url, null, filter.toHashMap(), PurchasedNumbersResponse.class)) - .thenReturn(purchasedNumbersMockData); + .thenReturn(purchasedNumbersMockData); final PurchasedNumbersResponse response = messageBirdClientMock.listPurchasedNumbers(filter); @@ -891,13 +887,13 @@ public void testListPurchasedNumbers() throws UnauthorizedException, GeneralExce @Test public void testViewPurchasedNumber() throws UnauthorizedException, GeneralException, NotFoundException { final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL); - + PurchasedNumber purchasedNumberMockData = new PurchasedNumber(); - + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); when(messageBirdServiceMock.requestByID(url, "15625267429", PurchasedNumber.class)) - .thenReturn(purchasedNumberMockData); + .thenReturn(purchasedNumberMockData); final PurchasedNumber response = messageBirdClientMock.viewPurchasedNumber("15625267429"); verify(messageBirdServiceMock, times(1)).requestByID(url, "15625267429", PurchasedNumber.class); @@ -909,17 +905,17 @@ public void testViewPurchasedNumber() throws UnauthorizedException, GeneralExce public void updatePurchasedNumber() throws UnauthorizedException, GeneralException { final String phoneNumber = "15625267429"; final String url = String.format("%s/phone-numbers/%s", NUMBERS_CALLS_BASE_URL, phoneNumber); - + PurchasedNumber updatedNumberMock = new PurchasedNumber(); - + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); - + final Map> payload = new HashMap>(); payload.put("tags", Collections.singletonList("tag")); - + when(messageBirdServiceMock.sendPayLoad("PATCH", url, payload, PurchasedNumber.class)) - .thenReturn(updatedNumberMock); + .thenReturn(updatedNumberMock); final PurchasedNumber response = messageBirdClientMock.updateNumber(phoneNumber, "tag"); verify(messageBirdServiceMock, times(1)).sendPayLoad("PATCH", url, payload, PurchasedNumber.class); assertNotNull(response); @@ -930,10 +926,10 @@ public void updatePurchasedNumber() throws UnauthorizedException, GeneralExcept public void deletePurchasedNumber() throws UnauthorizedException, GeneralException, NotFoundException { final String phoneNumber = "15625267429"; final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL); - + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock); - + messageBirdClientMock.cancelNumber(phoneNumber); verify(messageBirdServiceMock, times(1)).deleteByID(url, phoneNumber); } @@ -954,4 +950,671 @@ public void testDeleteRecording() throws NotFoundException, GeneralException, Un messageBirdClientMock.deleteRecording("ANY_CALL_ID", "ANY_LEG_ID","recordingID"); verify(messageBirdServiceMock, times(1)).deleteByID(url , "recordingID"); } + + @Test + public void testMockUploadFile() throws GeneralException, UnauthorizedException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + byte[] binary = {1, 2, 3, 4, 5, 6}; + String contentType = "image/png"; + String filename = "filename.png"; + messageBirdClient.uploadFile(binary, contentType, filename); + String url = MESSAGING_BASE_URL + FILES_PATH; + final Map headers = new HashMap<>(); + headers.put("Content-Type", contentType); + headers.put("filename", filename); + verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class); + } + + @Test + public void testUploadFileWithNullBinary() { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + String contentType = "image/png"; + String filename = "filename.png"; + assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(null, contentType, filename)); + } + + @Test + public void testUploadFileWithNullContentType() { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + byte[] binary = {1, 2, 3, 4, 5, 6}; + String filename = "filename.png"; + assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(binary, null, filename)); + } + + @Test + public void testUploadFileWithNullFilename() throws GeneralException, UnauthorizedException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + byte[] binary = {1, 2, 3, 4, 5, 6}; + String contentType = "image/png"; + messageBirdClient.uploadFile(binary, contentType, null); + String url = MESSAGING_BASE_URL + FILES_PATH; + final Map headers = new HashMap<>(); + headers.put("Content-Type", contentType); + verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class); + } + + @Test + public void testMockDownloadFile() throws GeneralException, UnauthorizedException, NotFoundException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + String id = "8144d3bf-6228-4b0e-903f-022d8917f297"; + String filename = "file.png"; + String basePath = "/base/path"; + messageBirdClient.downloadFile(id, filename, basePath); + String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id; + verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, filename); + } + + @Test + public void testDownloadFileWithNullId() { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + String filename = "file.png"; + String basePath = "/base/path"; + assertThrows(IllegalArgumentException.class, () -> messageBirdClient.downloadFile(null, filename, basePath)); + } + + @Test + public void testDownloadFileWithNullFilename() throws GeneralException, UnauthorizedException, NotFoundException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + String id = "8144d3bf-6228-4b0e-903f-022d8917f297"; + String basePath = "/base/path"; + messageBirdClient.downloadFile(id, null, basePath); + String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id; + verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, id); + } + + @Test + public void testDownloadFileWithNullBasePath() throws GeneralException, UnauthorizedException, NotFoundException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock); + String id = "8144d3bf-6228-4b0e-903f-022d8917f297"; + String filename = "file.png"; + messageBirdClient.downloadFile(id, filename, null); + String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id; + verify(messageBirdServiceMock, times(1)).getBinaryData(url, null, filename); + } + + /****************************************************************************************************/ + /** Testing WhatsApp Templates **/ + /****************************************************************************************************/ + + @Test + public void testCreateWhatsAppTemplate() throws UnauthorizedException, GeneralException { + final TemplateResponse templateResponse = TestUtil.createWhatsAppTemplateResponse("sample_template_name", "ko"); + final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko"); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + when(messageBirdServiceMock.sendPayLoad(url, template, TemplateResponse.class)) + .thenReturn(templateResponse); + + final TemplateResponse response = messageBirdClientInjectMock.createWhatsAppTemplate(template); + + verify(messageBirdServiceMock, times(1)).sendPayLoad(url, template, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), templateResponse.getName()); + assertEquals(response.getLanguage(), templateResponse.getLanguage()); + assertEquals(response.getCategory(), templateResponse.getCategory()); + assertEquals(response.getStatus(), templateResponse.getStatus()); + assertEquals(response.getWabaID(), templateResponse.getWabaID()); + assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt()); + assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt()); + + /* verify components */ + for (int i = 0; i < response.getComponents().size(); i++) { + assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType()); + assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat()); + assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText()); + } + } + @Test + public void testCreateWhatsAppCarouselTemplate() throws UnauthorizedException, GeneralException { + final TemplateResponse templateResponse = TestUtil.createWhatsAppCarouselTemplateResponse("sample_template_name", "ko"); + final Template template = TestUtil.createWhatsAppCarouselTemplate("sample_template_name", "ko"); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + when(messageBirdServiceMock.sendPayLoad(url, template, TemplateResponse.class)) + .thenReturn(templateResponse); + + final TemplateResponse response = messageBirdClientInjectMock.createWhatsAppTemplate(template); + + verify(messageBirdServiceMock, times(1)).sendPayLoad(url, template, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), templateResponse.getName()); + assertEquals(response.getLanguage(), templateResponse.getLanguage()); + assertEquals(response.getCategory(), templateResponse.getCategory()); + assertEquals(response.getStatus(), templateResponse.getStatus()); + assertEquals(response.getWabaID(), templateResponse.getWabaID()); + assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt()); + assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt()); + + /* verify components */ + for (int i = 0; i < response.getComponents().size(); i++) { + assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType()); + assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat()); + assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText()); + } + } + + @Test + public void testUpdateWhatsAppTemplate() throws UnauthorizedException, GeneralException { + final TemplateResponse templateResponse = TestUtil.createWhatsAppTemplateResponse("sample_template_name", "ko"); + final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko"); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s/%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + "sample_template_name", + "ko" + ); + + when(messageBirdServiceMock.sendPayLoad("PUT",url, template, TemplateResponse.class)) + .thenReturn(templateResponse); + + final TemplateResponse response = messageBirdClientInjectMock.updateWhatsAppTemplate(template,"sample_template_name","ko"); + verify(messageBirdServiceMock, times(1)).sendPayLoad("PUT",url, template, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), templateResponse.getName()); + assertEquals(response.getLanguage(), templateResponse.getLanguage()); + assertEquals(response.getCategory(), templateResponse.getCategory()); + assertEquals(response.getStatus(), templateResponse.getStatus()); + assertEquals(response.getWabaID(), templateResponse.getWabaID()); + assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt()); + assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt()); + + /* verify components */ + for (int i = 0; i < response.getComponents().size(); i++) { + assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType()); + assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat()); + assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText()); + } + } + + @Test + public void testListWhatsAppTemplates() throws UnauthorizedException, GeneralException { + final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name"); + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + when(messageBirdServiceMock.requestList(url, 0, 0, TemplateList.class)) + .thenReturn(templateList); + + final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0); + verify(messageBirdServiceMock, times(1)).requestList(url, 0, 0, TemplateList.class); + assertNotNull(response); + for(int i = 0; i < response.getItems().size() ; i++) { + assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i)); + } + } + + @Test + public void testListWhatsAppTemplatesDefault() throws UnauthorizedException, GeneralException { + final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name"); + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + when(messageBirdServiceMock.requestList(url, 0, 10, TemplateList.class)) + .thenReturn(templateList); + + final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(); + verify(messageBirdServiceMock, times(1)).requestList(url, 0, 10, TemplateList.class); + assertNotNull(response); + for(int i = 0; i < response.getItems().size() ; i++) { + assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i)); + } + } + + @Test + public void testListWhatsAppTemplatesByWABAID() throws UnauthorizedException, GeneralException { + final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name"); + final String wabaID = "testWABAID"; + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + Map map = new LinkedHashMap<>(); + map.put("wabaId", wabaID); + + when(messageBirdServiceMock.requestList(url, map, 0, 0, TemplateList.class)) + .thenReturn(templateList); + + final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0, wabaID, null); + verify(messageBirdServiceMock, times(1)).requestList(url, map, 0, 0, TemplateList.class); + assertNotNull(response); + for(int i = 0; i < response.getItems().size() ; i++) { + assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i)); + } + } + + @Test + public void testListWhatsAppTemplatesByChannelID() throws UnauthorizedException, GeneralException { + final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name"); + final String channelID = "channel-id"; + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V3, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + Map map = new LinkedHashMap<>(); + map.put("channelId", channelID); + + when(messageBirdServiceMock.requestList(url, map, 0, 0, TemplateList.class)) + .thenReturn(templateList); + + final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0, null, channelID); + verify(messageBirdServiceMock, times(1)).requestList(url, map, 0, 0, TemplateList.class); + assertNotNull(response); + for(int i = 0; i < response.getItems().size() ; i++) { + assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i)); + } + } + + @Test + public void testGetWhatsAppTemplatesBy() throws GeneralException, UnauthorizedException, NotFoundException { + final String templateName = "sample_template_name"; + final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US"); + final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US"); + final List templateList = new ArrayList<>(); + templateList.add(templateResponse1); + templateList.add(templateResponse2); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + + when(messageBirdServiceMock.requestByIdAsList(url, templateName, TemplateResponse.class)) + .thenReturn(templateList); + + final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName); + verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, templateName, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.size(), templateList.size()); + for(int i = 0; i < response.size() ; i++) { + assertReflectionEquals(response.get(i), templateList.get(i)); + } + } + + @Test + public void testGetWhatsAppTemplatesByNameAndWABAID() throws GeneralException, UnauthorizedException, NotFoundException { + final String templateName = "sample_template_name"; + final String wabaID = "testWABAID"; + final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US"); + final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US"); + final List templateList = new ArrayList<>(); + templateList.add(templateResponse1); + templateList.add(templateResponse2); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + String id = String.format( + "%s?wabaId=%s", + templateName, + wabaID + ); + + when(messageBirdServiceMock.requestByIdAsList(url, id, TemplateResponse.class)) + .thenReturn(templateList); + + final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName, wabaID, null); + verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, id, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.size(), templateList.size()); + for(int i = 0; i < response.size() ; i++) { + assertReflectionEquals(response.get(i), templateList.get(i)); + } + } + + @Test + public void testGetWhatsAppTemplatesByNameForChannelID() throws GeneralException, UnauthorizedException, NotFoundException { + final String templateName = "sample_template_name"; + final String channelID = "channel-id"; + final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US"); + final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US"); + final List templateList = new ArrayList<>(); + templateList.add(templateResponse1); + templateList.add(templateResponse2); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH + ); + String id = String.format( + "%s?channelId=%s", + templateName, + channelID + ); + + when(messageBirdServiceMock.requestByIdAsList(url, id, TemplateResponse.class)) + .thenReturn(templateList); + + final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName, null, channelID); + verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, id, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.size(), templateList.size()); + for(int i = 0; i < response.size() ; i++) { + assertReflectionEquals(response.get(i), templateList.get(i)); + } + } + + @Test + public void testFetchWhatsAppTemplateBy() throws UnauthorizedException, GeneralException, NotFoundException { + final String templateName = "sample_template_name"; + final String language = "ko"; + final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s/%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language + ); + + when(messageBirdServiceMock.request(url, TemplateResponse.class)) + .thenReturn(template); + + final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language); + verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), template.getName()); + assertEquals(response.getLanguage(), template.getLanguage()); + assertEquals(response.getStatus(), template.getStatus()); + assertReflectionEquals(response.getComponents(), template.getComponents()); + } + + @Test + public void testFetchWhatsAppTemplateByNameAndLanguageAndWABAID() throws UnauthorizedException, GeneralException, NotFoundException { + final String templateName = "sample_template_name"; + final String language = "ko"; + final String wabaID = "testWABAID"; + final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s/%s/%s?wabaId=%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language, + wabaID + ); + + when(messageBirdServiceMock.request(url, TemplateResponse.class)) + .thenReturn(template); + + final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language, wabaID, null); + verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), template.getName()); + assertEquals(response.getLanguage(), template.getLanguage()); + assertEquals(response.getStatus(), template.getStatus()); + assertReflectionEquals(response.getComponents(), template.getComponents()); + } + + @Test + public void testFetchWhatsAppTemplateByNameAndLanguageForChannelID() throws UnauthorizedException, GeneralException, NotFoundException { + final String templateName = "sample_template_name"; + final String language = "ko"; + final String channelID = "channel-id"; + final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s/%s/%s?channelId=%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName, + language, + channelID + ); + + when(messageBirdServiceMock.request(url, TemplateResponse.class)) + .thenReturn(template); + + final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language, null, channelID); + verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class); + assertNotNull(response); + assertEquals(response.getName(), template.getName()); + assertEquals(response.getLanguage(), template.getLanguage()); + assertEquals(response.getStatus(), template.getStatus()); + assertReflectionEquals(response.getComponents(), template.getComponents()); + } + + @Test + public void testDeleteTemplatesByName() throws UnauthorizedException, GeneralException, NotFoundException { + final String templateName = "sample_template_name"; + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + String url = String.format( + "%s%s%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + templateName + ); + + when(messageBirdServiceMock.delete(url, null)).thenReturn(null); + messageBirdClientInjectMock.deleteTemplatesBy(templateName); + verify(messageBirdServiceMock).delete(url, null); + } + + @Test + public void testCreateChildAccounts() throws GeneralException, UnauthorizedException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + ChildAccountCreateResponse childAccountCreateResponse = createChildAccountCreateResponse(); + ChildAccountRequest childAccountRequest = new ChildAccountRequest(); + childAccountRequest.setName("name"); + when(messageBirdServiceMock.sendPayLoad(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts" , childAccountRequest, ChildAccountCreateResponse.class)) + .thenReturn(childAccountCreateResponse); + final ChildAccountCreateResponse response = messageBirdClientInjectMock.createChildAccount(childAccountRequest); + + verify(messageBirdServiceMock, times(1)) + .sendPayLoad(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts" , childAccountRequest, ChildAccountCreateResponse.class); + assertNotNull(response); + assertEquals(response.getId(), childAccountCreateResponse.getId()); + assertEquals(response.getName(), childAccountCreateResponse.getName()); + assertEquals(response.getInvoiceAggregation(), childAccountCreateResponse.getInvoiceAggregation()); + assertEquals(response.getSigningKey(), childAccountCreateResponse.getSigningKey()); + assertEquals(response.getAccessKeys().get(0).getId(), childAccountCreateResponse.getAccessKeys().get(0).getId()); + assertEquals(response.getAccessKeys().get(0).getAccessKey(), childAccountCreateResponse.getAccessKeys().get(0).getAccessKey()); + assertEquals(response.getAccessKeys().get(0).getMode(), childAccountCreateResponse.getAccessKeys().get(0).getMode()); + } + + @Test + public void testGetChildAccount() throws GeneralException, UnauthorizedException, NotFoundException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + ChildAccountDetailedResponse childAccountDetailedResponse = createChildAccountDetailedResponse(); + when(messageBirdServiceMock.requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", "ANY_ID", ChildAccountDetailedResponse.class)) + .thenReturn(childAccountDetailedResponse); + ChildAccountDetailedResponse response = messageBirdClientInjectMock.getChildAccountById("ANY_ID"); + + verify(messageBirdServiceMock, times(1)) + .requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", "ANY_ID", ChildAccountDetailedResponse.class); + assertNotNull(response); + assertEquals(response.getId(), childAccountDetailedResponse.getId()); + assertEquals(response.getName(), childAccountDetailedResponse.getName()); + assertEquals(response.getInvoiceAggregation(), childAccountDetailedResponse.getInvoiceAggregation()); + } + + @Test + public void testGetChildAccounts() throws GeneralException, UnauthorizedException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + List childAccountResponses = Collections.singletonList(createChildAccountResponse()); + when(messageBirdServiceMock.requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", 1, 10, List.class)) + .thenReturn(childAccountResponses); + + List response = messageBirdClientInjectMock.getChildAccounts(1, 10); + + verify(messageBirdServiceMock, times(1)) + .requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", 1, 10, List.class); + assertNotNull(response); + assertEquals(response.get(0).getId(), childAccountResponses.get(0).getId()); + assertEquals(response.get(0).getName(), childAccountResponses.get(0).getName()); + } + + @Test + public void testUpdateChildAccount() throws GeneralException, UnauthorizedException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + ChildAccountResponse childAccountResponse = createChildAccountResponse(); + when(messageBirdServiceMock.sendPayLoad(any(), any(), any(), any())) + .thenReturn(childAccountResponse); + ChildAccountResponse response = messageBirdClientInjectMock.updateChildAccount("ANY_NAME", "ANY_ID"); + + verify(messageBirdServiceMock, times(1)) + .sendPayLoad(any(), any(), any(), any()); + assertNotNull(response); + assertEquals(response.getId(), childAccountResponse.getId()); + assertEquals(response.getName(), childAccountResponse.getName()); + } + + @Test + public void testDeleteChildAccount() throws GeneralException, UnauthorizedException, NotFoundException { + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + String url = String.format("%s/child-accounts/%s", PARTNER_ACCOUNTS_BASE_URL, "ANY_ID"); + doNothing().when(messageBirdServiceMock).deleteByID(url, "ANY_ID"); + messageBirdClientInjectMock.deleteChildAccount("id"); + } + + @Test + public void testConversationMessage() throws Exception { + ConversationSendRequest request = createDummyConversationRequest(); + ConversationSendResponse conversationSendResponse = new ConversationSendResponse(); + conversationSendResponse.setStatus("ACCEPTED"); + conversationSendResponse.setId("1234"); + + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + + when(messageBirdServiceMock.sendPayLoad( CONVERSATIONS_BASE_URL + CONVERSATION_SEND_PATH, request, ConversationSendResponse.class)) + .thenReturn(conversationSendResponse); + ConversationSendResponse response = messageBirdClientInjectMock.sendMessage(request); + + verify(messageBirdServiceMock, times(1)) + .sendPayLoad(CONVERSATIONS_BASE_URL + CONVERSATION_SEND_PATH, request, ConversationSendResponse.class); + assertNotNull(response.getId()); + assertNotNull(response.getStatus()); + } + + private ConversationSendRequest createDummyConversationRequest() { + ConversationContent conversationContent = new ConversationContent(); + conversationContent.setText("test"); + ConversationSendRequest request = new ConversationSendRequest(); + request.setFrom("channelid"); + request.setTo("+34123123123"); + request.setTtl("15s"); + request.setType(ConversationContentType.TEXT); + request.setContent(conversationContent); + request.setTrackId("mycampaign"); + return request; + } + + @Test + public void testUnpauseTemplatesByTemplateName_Success() throws UnauthorizedException, GeneralException { + final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko"); + MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class); + MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock); + String url = String.format( + "%s%s%s%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + UNPAUSE_TEMAPLATE_PATH, + "sample_template_name" + ); + messageBirdClientInjectMock.unpauseTemplatesByTemplateName("sample_template_name"); + verify(messageBirdServiceMock).sendPayLoad("POST", url, "", null); + } + + @Test(expected = GeneralException.class) + public void testUnpauseTemplatesByTemplateName_NotFound() throws UnauthorizedException, GeneralException { + messageBirdClient.unpauseTemplatesByTemplateName("foo"); + } } diff --git a/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java b/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java new file mode 100644 index 00000000..665dba71 --- /dev/null +++ b/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java @@ -0,0 +1,83 @@ +package com.messagebird; + +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.OutboundSmsPriceResponse; +import com.messagebird.util.Resources; +import org.junit.Test; + +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + +public class OutboundSmsPricesTest { + + @Test + public void testGetOutboundSmsPrices() throws GeneralException, UnauthorizedException, NotFoundException { + String responseFixture = Resources.readResourceText("/fixtures/outbound_sms_prices.json"); + + MessageBirdService messageBirdService = SpyService + .expects("GET", "pricing/sms/outbound") + .withRestAPIBaseURL() + .andReturns(new APIResponse(responseFixture, 200)); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); + + assertReceivedExpectedResponse(messageBirdClient.getOutboundSmsPrices()); + } + + @Test + public void testGetOutboundSmsPricesSmppUsername() throws GeneralException, UnauthorizedException, NotFoundException { + String responseFixture = Resources.readResourceText("/fixtures/outbound_sms_prices.json"); + + MessageBirdService messageBirdService = SpyService + .expects("GET", "pricing/sms/outbound/smpp/test-smpp-user") + .withRestAPIBaseURL() + .andReturns(new APIResponse(responseFixture, 200)); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); + + assertReceivedExpectedResponse(messageBirdClient.getOutboundSmsPrices("test-smpp-user")); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetOutboundSmsPricesSmppUsernameNull() throws GeneralException, UnauthorizedException, NotFoundException { + new MessageBirdClient(mock(MessageBirdService.class)).getOutboundSmsPrices(null); + } + + private static void assertReceivedExpectedResponse(OutboundSmsPriceResponse outboundSmsPriceResponse) { + assertEquals(10, outboundSmsPriceResponse.getGateway()); + assertEquals("EUR", outboundSmsPriceResponse.getCurrencyCode()); + assertEquals(3, outboundSmsPriceResponse.getTotalCount()); + + assertEquals(3, outboundSmsPriceResponse.getPrices().size()); + + assertEquals(new BigDecimal("0.060000"), outboundSmsPriceResponse.getPrices().get(0).getPrice()); + assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(0).getCurrencyCode()); + assertEquals("0", outboundSmsPriceResponse.getPrices().get(0).getMccmnc()); + assertEquals("0", outboundSmsPriceResponse.getPrices().get(0).getMcc()); + assertNull(outboundSmsPriceResponse.getPrices().get(0).getMnc()); + assertEquals("Default Rate", outboundSmsPriceResponse.getPrices().get(0).getCountryName()); + assertEquals("XX", outboundSmsPriceResponse.getPrices().get(0).getCountryIsoCode()); + assertEquals("Default Rate", outboundSmsPriceResponse.getPrices().get(0).getOperatorName()); + + assertEquals(new BigDecimal("0.047000"), outboundSmsPriceResponse.getPrices().get(1).getPrice()); + assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(1).getCurrencyCode()); + assertEquals("202", outboundSmsPriceResponse.getPrices().get(1).getMccmnc()); + assertEquals("202", outboundSmsPriceResponse.getPrices().get(1).getMcc()); + assertNull(outboundSmsPriceResponse.getPrices().get(1).getMnc()); + assertEquals("Greece", outboundSmsPriceResponse.getPrices().get(1).getCountryName()); + assertEquals("GR", outboundSmsPriceResponse.getPrices().get(1).getCountryIsoCode()); + assertNull(outboundSmsPriceResponse.getPrices().get(1).getOperatorName()); + + assertEquals(new BigDecimal("0.045000"), outboundSmsPriceResponse.getPrices().get(2).getPrice()); + assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(2).getCurrencyCode()); + assertEquals("20205", outboundSmsPriceResponse.getPrices().get(2).getMccmnc()); + assertEquals("202", outboundSmsPriceResponse.getPrices().get(2).getMcc()); + assertEquals("05", outboundSmsPriceResponse.getPrices().get(2).getMnc()); + assertEquals("Greece", outboundSmsPriceResponse.getPrices().get(2).getCountryName()); + assertEquals("GR", outboundSmsPriceResponse.getPrices().get(2).getCountryIsoCode()); + assertEquals("Vodafone", outboundSmsPriceResponse.getPrices().get(2).getOperatorName()); + } +} diff --git a/api/src/test/java/com/messagebird/RequestSignerTest.java b/api/src/test/java/com/messagebird/RequestSignerTest.java index 435ef1ce..17adc0dc 100644 --- a/api/src/test/java/com/messagebird/RequestSignerTest.java +++ b/api/src/test/java/com/messagebird/RequestSignerTest.java @@ -6,6 +6,10 @@ import static org.junit.Assert.*; +/** + * @deprecated This class is being deprecated together with {@link RequestSigner} + */ +@Deprecated public class RequestSignerTest { /** @@ -87,24 +91,24 @@ public void testWithRealSignature() { byte[] spoiledBody = getBytes("get shit spoiled"); assertTrue( - "Definitely valid signature is threaten as invalid", - requestSigner.isMatch(requestSignature, new Request(requestTimestamp, requestParams, requestBody)) + "Definitely valid signature is threaten as invalid", + requestSigner.isMatch(requestSignature, new Request(requestTimestamp, requestParams, requestBody)) ); assertFalse( - "Invalid signature is threaten as invalid", - requestSigner.isMatch(spoiledSignature, new Request(requestTimestamp, requestParams, requestBody)) + "Invalid signature is threaten as invalid", + requestSigner.isMatch(spoiledSignature, new Request(requestTimestamp, requestParams, requestBody)) ); assertFalse( - "Signature is still valid with replaced timestamp", - requestSigner.isMatch(requestSignature, new Request(spoiledTimestamp, requestParams, requestBody)) + "Signature is still valid with replaced timestamp", + requestSigner.isMatch(requestSignature, new Request(spoiledTimestamp, requestParams, requestBody)) ); assertFalse( - "Signature is still valid with replaced params", - requestSigner.isMatch(requestSignature, new Request(requestTimestamp, spoiledParams, requestBody)) + "Signature is still valid with replaced params", + requestSigner.isMatch(requestSignature, new Request(requestTimestamp, spoiledParams, requestBody)) ); assertFalse( - "Signature is still valid with replaced body", - requestSigner.isMatch(requestSignature, new Request(requestTimestamp, requestParams, spoiledBody)) + "Signature is still valid with replaced body", + requestSigner.isMatch(requestSignature, new Request(requestTimestamp, requestParams, spoiledBody)) ); } } diff --git a/api/src/test/java/com/messagebird/RequestValidatorTest.java b/api/src/test/java/com/messagebird/RequestValidatorTest.java new file mode 100644 index 00000000..23c64ef0 --- /dev/null +++ b/api/src/test/java/com/messagebird/RequestValidatorTest.java @@ -0,0 +1,102 @@ +package com.messagebird; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.messagebird.exceptions.RequestValidationException; +import com.messagebird.util.Resources; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +@RunWith(Parameterized.class) +public class RequestValidatorTest { + /** + * Error Map that maps test data expected outcome to actual error message. + */ + private static final Map ERROR_MAP = new HashMap() { + { + put("invalid jwt: claim nbf is in the future", "The Token can't be used before"); + put("invalid jwt: claim exp is in the past", "The Token has expired on"); + put("invalid jwt: claim url_hash is invalid", "The Claim 'url_hash' value doesn't match the required one."); + put("invalid jwt: claim payload_hash is invalid", + "The Claim 'payload_hash' value doesn't match the required one."); + put("invalid jwt: signature is invalid", "Signature is invalid."); + put("invalid jwt: claim payload_hash is set but actual payload is missing", + "The Claim 'payload_hash' is set but actual payload is missing."); + put("invalid jwt: claim payload_hash is not set but payload is present", + "The Claim 'payload_hash' is not set but payload is present."); + put("invalid jwt: signing method none is invalid", "The signing method 'none' is invalid."); + + } + }; + private final WebhookSignatureTestCase testCase; + + public RequestValidatorTest(String testName, WebhookSignatureTestCase testCase) { + this.testCase = testCase; + } + + @Parameters(name = "{0}") + public static Collection data() throws IOException { + List testCases = new ObjectMapper().readValue( + Resources.readResourceText("/webhook_test_data.json"), + new TypeReference>() { + }); + + return testCases.stream() + .map(tc -> new Object[]{tc.name, tc}) + .collect(Collectors.toList()); + } + + @Test + public void testWebhookSignature() throws Throwable { + RequestValidator validator = new RequestValidator(testCase.secret != null ? testCase.secret : ""); + + Clock clock = Clock.fixed(OffsetDateTime.parse(testCase.timestamp).toInstant(), ZoneId.systemDefault()); + ThrowingRunnable runnable = () -> validator.validateSignature(clock, testCase.token, testCase.url, + (testCase.payload == null) ? null : testCase.payload.getBytes(StandardCharsets.UTF_8)); + + if (testCase.valid) { + runnable.run(); + return; + } + + assertTrue(String.format("Expected error message mapping for '%s' but it was not found.", testCase.reason), + ERROR_MAP.containsKey(testCase.reason)); + + String expectedError = ERROR_MAP.get(testCase.reason); + + RequestValidationException err = assertThrows(RequestValidationException.class, runnable); + assertTrue(String.format("Expected error message containing: %s (originally %s) but was: %s", expectedError, + testCase.reason, err.getMessage()), err.getMessage().contains(expectedError)); + } + + /** + * WebhookSignatureTestCase + */ + public static class WebhookSignatureTestCase { + public String name; + public String method; + public String secret; + public String url; + public String payload; + public String timestamp; + public String token; + public Boolean valid; + public String reason; + } +} diff --git a/api/src/test/java/com/messagebird/SpyService.java b/api/src/test/java/com/messagebird/SpyService.java index ec7a03fe..2922b1b4 100644 --- a/api/src/test/java/com/messagebird/SpyService.java +++ b/api/src/test/java/com/messagebird/SpyService.java @@ -2,6 +2,8 @@ import com.messagebird.exceptions.GeneralException; +import java.util.HashMap; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -68,7 +70,7 @@ static

SpyService expects(final String method, final String url, final P pay service.method = method; service.url = url; service.payload = payload; - + return service; } @@ -147,7 +149,7 @@ MessageBirdService andReturns(final APIResponse apiResponse) throws GeneralExcep } MessageBirdServiceImpl messageBirdService = spy(new MessageBirdServiceImpl(getAccessKey())); - doReturn(apiResponse).when(messageBirdService).doRequest(method, url, payload); + doReturn(apiResponse).when(messageBirdService).doRequest(method, url, new HashMap<>(), payload); return messageBirdService; } diff --git a/api/src/test/java/com/messagebird/TestUtil.java b/api/src/test/java/com/messagebird/TestUtil.java index 5a854b45..3e9c5a33 100644 --- a/api/src/test/java/com/messagebird/TestUtil.java +++ b/api/src/test/java/com/messagebird/TestUtil.java @@ -2,6 +2,18 @@ import com.messagebird.objects.*; import com.messagebird.objects.conversations.*; +import com.messagebird.objects.integrations.HSMCategory; +import com.messagebird.objects.integrations.HSMComponent; +import com.messagebird.objects.integrations.HSMComponentButton; +import com.messagebird.objects.integrations.HSMComponentButtonType; +import com.messagebird.objects.integrations.HSMComponentCard; +import com.messagebird.objects.integrations.HSMComponentFormat; +import com.messagebird.objects.integrations.HSMComponentType; +import com.messagebird.objects.integrations.HSMExample; +import com.messagebird.objects.integrations.HSMStatus; +import com.messagebird.objects.integrations.Template; +import com.messagebird.objects.integrations.TemplateList; +import com.messagebird.objects.integrations.TemplateResponse; import com.messagebird.objects.voicecalls.*; import java.util.*; @@ -17,9 +29,9 @@ static VoiceCall createVoiceCall(String destination) { voiceCall.setDestination(destination); final VoiceCallFlow voiceCallFlow = new VoiceCallFlow(); - voiceCallFlow.setTitle("Test title"); VoiceStep voiceStep = new VoiceStep(); voiceStep.setAction("say"); + voiceCallFlow.setMaxDuration(28800); final VoiceStepOption voiceStepOption = new VoiceStepOption(); voiceStepOption.setPayload("This is a journey into sound. Good bye!"); @@ -200,7 +212,6 @@ public static VoiceStep createVoiceStep() { public static VoiceCallFlowRequest createVoiceCallFlowRequest() { final VoiceCallFlowRequest voiceCallFlow = new VoiceCallFlowRequest(); - voiceCallFlow.setTitle("ANY_TITLE"); voiceCallFlow.setRecord(true); voiceCallFlow.setSteps(Collections.singletonList(createVoiceStep())); voiceCallFlow.setDefaultCall(true); @@ -240,4 +251,194 @@ static ConversationWebhookCreateRequest createConversationWebhookRequest() { ) ); } + + private static HSMComponent createHSMComponentHeader() { + final HSMComponent headerComponent = new HSMComponent(); + final HSMExample headerExample = new HSMExample(); + headerExample.setHeader_url(Arrays.asList("https://www.mysample.com/sample.img")); + + headerComponent.setType(HSMComponentType.HEADER); + headerComponent.setFormat(HSMComponentFormat.IMAGE); + headerComponent.setExample(headerExample); + + return headerComponent; + } + + private static HSMComponent createHSMComponentBody() { + final HSMComponent bodyComponent = new HSMComponent(); + final HSMExample bodyExample = new HSMExample(); + final List> bodyText = new ArrayList<>(); + bodyText.add(Arrays.asList("John")); + bodyText.add(Arrays.asList("Anna")); + bodyExample.setBody_text(bodyText); + + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setText("Hey {{1}}! This is a sample template from Java."); + bodyComponent.setExample(bodyExample); + + return bodyComponent; + } + + private static HSMComponent createHSMComponentFooter() { + final HSMComponent footerComponent = new HSMComponent(); + footerComponent.setType(HSMComponentType.FOOTER); + footerComponent.setText("This is a sample footer"); + + return footerComponent; + } + + private static HSMComponent createHSMComponentButton() { + final HSMComponent buttonComponent = new HSMComponent(); + final List buttons = new ArrayList<>(); + final HSMComponentButton button = new HSMComponentButton(); + button.setType(HSMComponentButtonType.URL); + button.setText("Touch it"); + button.setUrl("https://www.messagebird.com"); + button.setExample(Arrays.asList("https://developers.messagebird.com")); + buttons.add(button); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + + return buttonComponent; + } + + private static HSMComponent createHSMComponentCarousel() { + final HSMComponent carouselComponent = new HSMComponent(); + carouselComponent.setType(HSMComponentType.CAROUSEL); + + final List cards = new ArrayList<>(); + final HSMComponentCard card = new HSMComponentCard(); + final List cardComponents = new ArrayList<>(); + cardComponents.add(createHSMComponentHeader()); + cardComponents.add(createHSMComponentBody()); + cardComponents.add(createHSMComponentButton()); + card.setComponents(cardComponents); + cards.add(card); + + carouselComponent.setCards(cards); + + return carouselComponent; + } + + public static TemplateResponse createWhatsAppTemplateResponse(final String templateName, final String language) { + final TemplateResponse templateResponse = new TemplateResponse(); + templateResponse.setName(templateName); + templateResponse.setLanguage(language); + templateResponse.setCategory(HSMCategory.AUTHENTICATION); + templateResponse.setStatus(HSMStatus.NEW); + templateResponse.setCtaURLLinkTrackingOptedOut(true); + templateResponse.setCreatedAt(new Date()); + templateResponse.setUpdatedAt(new Date()); + + final List components = new ArrayList<>(); + components.add(createHSMComponentHeader()); + components.add(createHSMComponentBody()); + components.add(createHSMComponentFooter()); + components.add(createHSMComponentButton()); + templateResponse.setComponents(components); + + templateResponse.setWabaID("testWABAID"); + templateResponse.setNamespace("testNamespace"); + + return templateResponse; + } + + public static Template createWhatsAppTemplate(final String templateName, final String language) { + final Template template = new Template(); + template.setName(templateName); + template.setLanguage(language); + template.setCategory(HSMCategory.AUTHENTICATION); + + final List components = new ArrayList<>(); + components.add(createHSMComponentHeader()); + components.add(createHSMComponentBody()); + components.add(createHSMComponentFooter()); + components.add(createHSMComponentButton()); + template.setComponents(components); + + template.setWABAID("testWABAID"); + + return template; + } + + public static TemplateResponse createWhatsAppCarouselTemplateResponse(final String templateName, final String language) { + final TemplateResponse templateResponse = new TemplateResponse(); + templateResponse.setName(templateName); + templateResponse.setLanguage(language); + templateResponse.setCategory(HSMCategory.MARKETING); + templateResponse.setStatus(HSMStatus.NEW); + templateResponse.setCreatedAt(new Date()); + templateResponse.setUpdatedAt(new Date()); + + final List components = new ArrayList<>(); + components.add(createHSMComponentBody()); + components.add(createHSMComponentCarousel()); + templateResponse.setComponents(components); + + templateResponse.setWabaID("testWABAID"); + templateResponse.setNamespace("testNamespace"); + + return templateResponse; + } + + public static Template createWhatsAppCarouselTemplate(final String templateName, final String language) { + final Template template = new Template(); + template.setName(templateName); + template.setLanguage(language); + template.setCategory(HSMCategory.MARKETING); + + final List components = new ArrayList<>(); + components.add(createHSMComponentBody()); + components.add(createHSMComponentCarousel()); + template.setComponents(components); + + template.setWABAID("testWABAID"); + + return template; + } + + + public static TemplateList createWhatsAppTemplateList(final String templateName) { + final TemplateResponse template1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US"); + final TemplateResponse template2 = TestUtil.createWhatsAppTemplateResponse(templateName, "ko"); + final TemplateList templateList = new TemplateList(); + + List templateResponseList = new ArrayList<>(); + templateResponseList.add(template1); + templateResponseList.add(template2); + + templateList.setItems(templateResponseList); + return templateList; + } + + public static ChildAccountCreateResponse createChildAccountCreateResponse() { + final AccessKey accessKey = new AccessKey(); + accessKey.setId("ANY_ID"); + accessKey.setAccessKey("ANY_KEY"); + accessKey.setMode("ANY_MOD"); + + final ChildAccountCreateResponse childAccountCreateResponse = new ChildAccountCreateResponse(); + childAccountCreateResponse.setId("ANY_ID"); + childAccountCreateResponse.setName("ANY_NAME"); + childAccountCreateResponse.setAccessKeys(Collections.singletonList(accessKey)); + childAccountCreateResponse.setInvoiceAggregation("ANY_INVOICE_AGGREGATION"); + childAccountCreateResponse.setSigningKey("ANY_SIGNING_KEY"); + + return childAccountCreateResponse; + } + + public static ChildAccountDetailedResponse createChildAccountDetailedResponse(){ + final ChildAccountDetailedResponse childAccountDetailedResponse = new ChildAccountDetailedResponse(); + childAccountDetailedResponse.setId("ANY_ID"); + childAccountDetailedResponse.setName("ANY_NAME"); + childAccountDetailedResponse.setInvoiceAggregation("ANY_INVOICE_AGGREGATION"); + return childAccountDetailedResponse; + } + + public static ChildAccountResponse createChildAccountResponse(){ + final ChildAccountResponse childAccountResponse = new ChildAccountResponse(); + childAccountResponse.setId("ANY_ID"); + childAccountResponse.setName("ANY_NAME"); + return childAccountResponse; + } } diff --git a/api/src/test/java/com/messagebird/VerifyTest.java b/api/src/test/java/com/messagebird/VerifyTest.java index 6a3d1a80..7ec8565e 100644 --- a/api/src/test/java/com/messagebird/VerifyTest.java +++ b/api/src/test/java/com/messagebird/VerifyTest.java @@ -12,8 +12,9 @@ public class VerifyTest { - private static final String VERIFY_SMS_RESPONSE = "{\"id\": \"verify-id-sms\",\"href\": \"https://rest.messagebird.com/verify/verify-id-sms\",\"recipient\": 31612345678,\"reference\": null,\"messages\": {\"href\": \"https://rest.messagebird.com/messages/5958c0d5e2df41de8154e5e88bfeb5bc\"},\"status\": \"sent\",\"createdDatetime\": \"2018-09-25T14:38:12+00:00\",\"validUntilDatetime\": \"2018-09-25T14:38:42+00:00\"}"; - private static final String VERIFY_TTS_RESPONSE = "{\"id\": \"verify-id-tts\",\"href\": \"https://rest.messagebird.com/verify/verify-id-tts\",\"recipient\": 31612345678,\"reference\": null,\"messages\": {\"href\": \"https://rest.messagebird.com/voicemessages/c043ab473f8e4f2590ab9a16d25f2899\"},\"status\": \"sent\",\"createdDatetime\": \"2018-09-25T14:35:10+00:00\",\"validUntilDatetime\": \"2018-09-25T14:35:40+00:00\"}"; + private static final String VERIFY_SMS_RESPONSE = "{\"id\": \"verify-id-sms\",\"href\": \"https://rest.messagebird.com/verify/verify-id-sms\",\"recipient\": 31612345678,\"reference\": null,\"messages\": {\"href\": \"https://rest.messagebird.com/messages/5958c0d5e2df41de8154e5e88bfeb5bc\"},\"status\": \"sent\",\"createdDatetime\": \"2018-09-25T14:38:12+00:00\",\"validUntilDatetime\": \"2018-09-25T14:38:42+00:00\"}"; + private static final String VERIFY_TTS_RESPONSE = "{\"id\": \"verify-id-tts\",\"href\": \"https://rest.messagebird.com/verify/verify-id-tts\",\"recipient\": 31612345678,\"reference\": null,\"messages\": {\"href\": \"https://rest.messagebird.com/voicemessages/c043ab473f8e4f2590ab9a16d25f2899\"},\"status\": \"sent\",\"createdDatetime\": \"2018-09-25T14:35:10+00:00\",\"validUntilDatetime\": \"2018-09-25T14:35:40+00:00\"}"; + private static final String VERIFY_EMAIL_RESPONSE = "{\"id\": \"verify-id-email\",\"href\": \"https://rest.messagebird.com/verify/verify-id-email\",\"recipient\": \"test@mb.com\",\"reference\": null,\"messages\": {\"href\": \"https://rest.messagebird.com/verify/messages/email/c043ab473f8e4f2590ab9a16d25f2899\"},\"status\": \"sent\",\"createdDatetime\": \"2018-09-25T14:35:10+00:00\",\"validUntilDatetime\": \"2018-09-25T14:35:40+00:00\"}"; @Test public void testSendVerifyTokenSms() throws GeneralException, UnauthorizedException { @@ -47,11 +48,28 @@ public void testSendVerifyTokenTts() throws GeneralException, UnauthorizedExcept assertEquals("verify-id-tts", verify.getId()); } + @Test + public void testSendVerifyTokenEmail() throws GeneralException, UnauthorizedException { + VerifyRequest verifyRequest = new VerifyRequest("rec@mb.com"); + verifyRequest.setType(VerifyType.EMAIL); + + MessageBirdService messageBirdService = SpyService + .expects("POST", "verify", verifyRequest) + .withRestAPIBaseURL() + .andReturns(new APIResponse(VERIFY_EMAIL_RESPONSE, 200)); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); + + Verify verify = messageBirdClient.sendVerifyToken(verifyRequest); + + assertEquals("verify-id-email", verify.getId()); + } + @Test public void testVerifyTypeValue() { // Important for generating proper JSON payloads... assertEquals("flash", VerifyType.FLASH.getValue()); assertEquals("sms", VerifyType.SMS.getValue()); assertEquals("tts", VerifyType.TTS.getValue()); + assertEquals("email", VerifyType.EMAIL.getValue()); } } diff --git a/api/src/test/java/com/messagebird/VoiceCallFlowTest.java b/api/src/test/java/com/messagebird/VoiceCallFlowTest.java index 9767e223..c38c0c7f 100644 --- a/api/src/test/java/com/messagebird/VoiceCallFlowTest.java +++ b/api/src/test/java/com/messagebird/VoiceCallFlowTest.java @@ -64,6 +64,21 @@ public void testView() throws GeneralException, UnauthorizedException, NotFoundE this.testVoiceCallFlowAgainstFixture(voiceCallFlow); } + @Test + public void testViewTitlePresent() throws GeneralException, UnauthorizedException, NotFoundException { + String responseFixture = Resources.readResourceText("/fixtures/call_flow_view_title.json"); + MessageBirdService messageBirdService = SpyService + .expects("GET", "call-flows/e781a76f-14ad-45b0-8490-409300244e20") + .withVoiceCallAPIBaseURL() + .andReturns(new APIResponse(responseFixture)); + MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService); + VoiceCallFlow voiceCallFlow = messageBirdClient + .viewVoiceCallFlow("e781a76f-14ad-45b0-8490-409300244e20") + .getData() + .get(0); + this.testVoiceCallFlowAgainstFixture(voiceCallFlow, true); + } + @Test (expected = IllegalArgumentException.class) public void testViewShouldThrowInvalidArgumentException() throws NotFoundException, GeneralException, UnauthorizedException { String responseFixture = Resources.readResourceText("/fixtures/call_flow_view.json"); @@ -81,7 +96,6 @@ public void testUpdate() throws GeneralException, UnauthorizedException, NotFoun String responseFixture = Resources.readResourceText("/fixtures/call_flow_update_response.json"); VoiceCallFlowRequest voiceCallFlowRequest = new VoiceCallFlowRequest("e781a76f-14ad-45b0-8490-409300244e20"); - voiceCallFlowRequest.setTitle("Forward call to 316123456782"); voiceCallFlowRequest.setDefaultCall(true); voiceCallFlowRequest.setRecord(true); voiceCallFlowRequest.setSteps( @@ -220,9 +234,15 @@ public void testDeleteIllegalArgumentException() throws NotFoundException, Gener * In order to reuse this method for further tests you need to make sure that the fixtures * match the date in this test. See call_flows_post.json */ + private void testVoiceCallFlowAgainstFixture(VoiceCallFlow voiceCallFlow) { + testVoiceCallFlowAgainstFixture(voiceCallFlow, false); + } + + private void testVoiceCallFlowAgainstFixture(VoiceCallFlow voiceCallFlow, boolean hasTitle) { assertEquals(voiceCallFlow.getId(), "e781a76f-14ad-45b0-8490-409300244e20"); - assertEquals(voiceCallFlow.getTitle(), "Forward call to 31612345678"); + if (!hasTitle) assertEquals(voiceCallFlow.getTitle(), null); + if (hasTitle) assertNotEquals(voiceCallFlow.getTitle(), null); assertEquals(voiceCallFlow.isRecord(), true); assertEquals(voiceCallFlow.isDefaultCall(), false); assertEquals(voiceCallFlow.getCreatedAt().toString(), "Tue Aug 06 16:13:06 CEST 2019"); diff --git a/api/src/test/java/com/messagebird/VoiceCallingTest.java b/api/src/test/java/com/messagebird/VoiceCallingTest.java index 778f9b1c..cc8ae90e 100644 --- a/api/src/test/java/com/messagebird/VoiceCallingTest.java +++ b/api/src/test/java/com/messagebird/VoiceCallingTest.java @@ -3,6 +3,7 @@ import com.messagebird.exceptions.GeneralException; import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.voicecalls.SipResponseCode; import com.messagebird.objects.voicecalls.VoiceCall; import com.messagebird.objects.voicecalls.VoiceLegDirection; import com.messagebird.objects.voicecalls.VoiceLegStatus; @@ -100,7 +101,7 @@ public void testGetLeg() throws IOException, GeneralException, UnauthorizedExcep VoiceLegStatus.Hangup, VoiceLegDirection.Outgoing, new BigDecimal("0.001519"), "EUR", 7, parseDate("2019-01-10T16:12:54Z"), parseDate("2019-01-10T16:13:54Z"), - parseDate("2019-01-10T16:13:24Z"), parseDate("2019-01-10T16:13:30Z") + parseDate("2019-01-10T16:13:24Z"), parseDate("2019-01-10T16:13:30Z"), SipResponseCode.OK ); private static Date parseDate(String input) { diff --git a/api/src/test/java/com/messagebird/objects/conversations/MessageComponentTypeTest.java b/api/src/test/java/com/messagebird/objects/conversations/MessageComponentTypeTest.java new file mode 100644 index 00000000..385b1652 --- /dev/null +++ b/api/src/test/java/com/messagebird/objects/conversations/MessageComponentTypeTest.java @@ -0,0 +1,28 @@ +package com.messagebird.objects.conversations; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class MessageComponentTypeTest { + @Test + public void testMessageComponentTypeForValueValid() { + assertEquals(MessageComponentType.HEADER, MessageComponentType.forValue("header")); + assertEquals(MessageComponentType.BUTTON, MessageComponentType.forValue("button")); + } + + @Test(expected = NullPointerException.class) + public void testMessageComponentTypeForValueNull() { + MessageComponentType.forValue(null); + } + + @Test + public void testMessageComponentTypeForValueInvalid() { + assertNull(MessageComponentType.forValue("invalid_type")); + } + + @Test + public void testMessageComponentTypeToString() { + assertEquals("header", MessageComponentType.HEADER.toString()); + } +} diff --git a/api/src/test/java/com/messagebird/objects/conversations/MessageParamTest.java b/api/src/test/java/com/messagebird/objects/conversations/MessageParamTest.java new file mode 100644 index 00000000..f1147d2f --- /dev/null +++ b/api/src/test/java/com/messagebird/objects/conversations/MessageParamTest.java @@ -0,0 +1,30 @@ +package com.messagebird.objects.conversations; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class MessageParamTest { + @Test + public void testMessageParamToString() { + MessageParam param = new MessageParam(); + param.setType(TemplateMediaType.IMAGE); + param.setText("Sample text"); + param.setPayload("Sample payload"); + + String expected = "MessageParam{type=image, text='Sample text', payload='Sample payload', currency=null, dateTime='null', document=null, image=null, video=null, expirationTime='null', couponCode='null'}"; + assertEquals(expected, param.toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testMessageParamSetTextInvalid() { + MessageParam param = new MessageParam(); + param.setText(""); + } + + @Test + public void testMessageParamSetTextValid() { + MessageParam param = new MessageParam(); + param.setText("Valid text"); + assertEquals("Valid text", param.getText()); + } +} diff --git a/api/src/test/java/com/messagebird/objects/conversations/TemplateMediaTypeTest.java b/api/src/test/java/com/messagebird/objects/conversations/TemplateMediaTypeTest.java new file mode 100644 index 00000000..d30bde28 --- /dev/null +++ b/api/src/test/java/com/messagebird/objects/conversations/TemplateMediaTypeTest.java @@ -0,0 +1,27 @@ +package com.messagebird.objects.conversations; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class TemplateMediaTypeTest { + @Test + public void testTemplateMediaTypeForValueValid() { + assertEquals(TemplateMediaType.VIDEO, TemplateMediaType.forValue("video")); + } + + @Test(expected = IllegalArgumentException.class) + public void testTemplateMediaTypeForValueNull() { + TemplateMediaType.forValue(null); + } + + @Test + public void testTemplateMediaTypeForValueInvalid() { + assertNull(TemplateMediaType.forValue("non_existing_value")); + } + + @Test + public void testTemplateMediaTypeToString() { + assertEquals("video", TemplateMediaType.VIDEO.toString()); + } +} diff --git a/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTest.java b/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTest.java new file mode 100644 index 00000000..9af4bb64 --- /dev/null +++ b/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTest.java @@ -0,0 +1,26 @@ +package com.messagebird.objects.integrations; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class HSMComponentTest { + @Test + public void testToString() { + HSMComponent component = new HSMComponent(); + component.setType(HSMComponentType.BODY); + component.setFormat(HSMComponentFormat.TEXT); + component.setText("Test text"); + component.setAddSecurityRecommendation(true); + component.setCodeExpirationMinutes(10); + component.setHasExpiration(true); + + String expected = "HSMComponent{type=BODY, format=TEXT, text='Test text', addSecurityRecommendation=true, codeExpirationMinutes=10, buttons=null, hasExpiration=true, cards=null, example=null}"; + assertEquals(expected, component.toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetTextInvalid() { + HSMComponent component = new HSMComponent(); + component.setText(""); + } +} diff --git a/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTypeTest.java b/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTypeTest.java new file mode 100644 index 00000000..47053509 --- /dev/null +++ b/api/src/test/java/com/messagebird/objects/integrations/HSMComponentTypeTest.java @@ -0,0 +1,23 @@ +package com.messagebird.objects.integrations; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class HSMComponentTypeTest { + @Test + public void testForValueValid() { + assertEquals(HSMComponentType.BODY, HSMComponentType.forValue("BODY")); + assertEquals(HSMComponentType.BODY, HSMComponentType.forValue("body")); + } + + @Test(expected = NullPointerException.class) + public void testForValueNull() { + HSMComponentType.forValue(null); + } + + @Test + public void testForValueInvalid() { + assertNull(HSMComponentType.forValue("INVALID")); + } +} diff --git a/api/src/test/resources/fixtures/call_flow_update_response.json b/api/src/test/resources/fixtures/call_flow_update_response.json index 13663211..1a7c1f21 100644 --- a/api/src/test/resources/fixtures/call_flow_update_response.json +++ b/api/src/test/resources/fixtures/call_flow_update_response.json @@ -2,7 +2,6 @@ "data": [ { "id": "e781a76f-14ad-45b0-8490-409300244e20", - "title": "Forward call to 31612345678", "steps": [ { "id": "a8e44a38-b935-482f-b17f-ed3472c6292c", diff --git a/api/src/test/resources/fixtures/call_flow_view.json b/api/src/test/resources/fixtures/call_flow_view.json index 247d7cb5..7546da2b 100644 --- a/api/src/test/resources/fixtures/call_flow_view.json +++ b/api/src/test/resources/fixtures/call_flow_view.json @@ -2,7 +2,6 @@ "data": [ { "id": "e781a76f-14ad-45b0-8490-409300244e20", - "title": "Forward call to 31612345678", "steps": [ { "id": "a8e44a38-b935-482f-b17f-ed3472c6292c", diff --git a/api/src/test/resources/fixtures/call_flow_view_title.json b/api/src/test/resources/fixtures/call_flow_view_title.json new file mode 100644 index 00000000..1640484d --- /dev/null +++ b/api/src/test/resources/fixtures/call_flow_view_title.json @@ -0,0 +1,41 @@ +{ + "data": [ + { + "title": "Call title", + "id": "e781a76f-14ad-45b0-8490-409300244e20", + "steps": [ + { + "id": "a8e44a38-b935-482f-b17f-ed3472c6292c", + "action": "transfer", + "options": { + "destination" : "31612345678", + "payload" : "Test payload", + "language" : "en-GB", + "voice" : "male", + "repeat" : 1, + "media" : "test.mp3", + "length" : 1, + "maxLength" : 2, + "timeout" : 3, + "finishOnKey" : "1", + "transcribe" : "false", + "transcribeLanguage" : "en-GB", + "record" : "both", + "url" : "http://", + "ifMachine" : "ifMachine", + "machineTimeout" : 200, + "onFinish" : "http://", + "mask" : false + } + } + ], + "record": true, + "default": false, + "createdAt": "2019-08-06T14:13:06Z", + "updatedAt": "2019-08-06T14:13:06Z" + } + ], + "_links": { + "self": "/call-flows/70fbdda2-4f1f-44ce-8792-75af32cf598c" + } +} \ No newline at end of file diff --git a/api/src/test/resources/fixtures/call_flows_list.json b/api/src/test/resources/fixtures/call_flows_list.json index 81d57a2d..0820526d 100644 --- a/api/src/test/resources/fixtures/call_flows_list.json +++ b/api/src/test/resources/fixtures/call_flows_list.json @@ -2,7 +2,6 @@ "data": [ { "id": "e781a76f-14ad-45b0-8490-409300244e20", - "title": "Forward call to 31612345678", "steps": [ { "id": "a8e44a38-b935-482f-b17f-ed3472c6292c", diff --git a/api/src/test/resources/fixtures/call_flows_post.json b/api/src/test/resources/fixtures/call_flows_post.json index 13663211..1a7c1f21 100644 --- a/api/src/test/resources/fixtures/call_flows_post.json +++ b/api/src/test/resources/fixtures/call_flows_post.json @@ -2,7 +2,6 @@ "data": [ { "id": "e781a76f-14ad-45b0-8490-409300244e20", - "title": "Forward call to 31612345678", "steps": [ { "id": "a8e44a38-b935-482f-b17f-ed3472c6292c", diff --git a/api/src/test/resources/fixtures/call_legs_get.json b/api/src/test/resources/fixtures/call_legs_get.json index 64d5f65b..23670a0a 100644 --- a/api/src/test/resources/fixtures/call_legs_get.json +++ b/api/src/test/resources/fixtures/call_legs_get.json @@ -13,7 +13,8 @@ "createdAt": "2019-01-10T16:12:54Z", "updatedAt": "2019-01-10T16:13:54Z", "answeredAt": "2019-01-10T16:13:24Z", - "endedAt": "2019-01-10T16:13:30Z" + "endedAt": "2019-01-10T16:13:30Z", + "sipResponseCode": 200 } ], "_links": { diff --git a/api/src/test/resources/fixtures/call_legs_list.json b/api/src/test/resources/fixtures/call_legs_list.json index 53a20f71..0ea233f6 100644 --- a/api/src/test/resources/fixtures/call_legs_list.json +++ b/api/src/test/resources/fixtures/call_legs_list.json @@ -14,6 +14,7 @@ "updatedAt":"2019-01-10T16:13:54Z", "answeredAt":"2019-01-10T16:13:24Z", "endedAt":"2019-01-10T16:13:30Z", + "sipResponseCode": 200, "_links":{ "self":"/calls/unforgiven-call/legs/first-leg-of-unforgiven-call" } diff --git a/api/src/test/resources/fixtures/outbound_sms_prices.json b/api/src/test/resources/fixtures/outbound_sms_prices.json new file mode 100644 index 00000000..261401d3 --- /dev/null +++ b/api/src/test/resources/fixtures/outbound_sms_prices.json @@ -0,0 +1,37 @@ +{ + "gateway": 10, + "currencyCode": "EUR", + "totalCount": 3, + "prices": [ + { + "price": "0.060000", + "currencyCode": "EUR", + "mccmnc": "0", + "mcc": "0", + "mnc": null, + "countryName": "Default Rate", + "countryIsoCode": "XX", + "operatorName": "Default Rate" + }, + { + "price": "0.047000", + "currencyCode": "EUR", + "mccmnc": "202", + "mcc": "202", + "mnc": null, + "countryName": "Greece", + "countryIsoCode": "GR", + "operatorName": null + }, + { + "price": "0.045000", + "currencyCode": "EUR", + "mccmnc": "20205", + "mcc": "202", + "mnc": "05", + "countryName": "Greece", + "countryIsoCode": "GR", + "operatorName": "Vodafone" + } + ] +} diff --git a/api/src/test/resources/webhook_test_data.json b/api/src/test/resources/webhook_test_data.json new file mode 100644 index 00000000..da8cb6c2 --- /dev/null +++ b/api/src/test/resources/webhook_test_data.json @@ -0,0 +1,433 @@ +[ + { + "name": "Valid JWT with no URL parameters or payload - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": true + }, + { + "name": "Valid JWT with no URL parameters or payload - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": true + }, + { + "name": "Valid JWT with no URL parameters or payload - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": true + }, + { + "name": "Valid JWT with no URL parameters or payload - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": true + }, + { + "name": "Valid JWT with no URL parameters or payload - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and no payload - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJjOTQ2YWY3Ny1lMTgyLTRlYWEtYjJmZi0xYTU0NWI1ZTk5MWEiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAifQ.huo2ou6JDoDc7sV25d75UMWeYhBeavQlLsqIibSZuac", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and no payload - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJjOTQ2YWY3Ny1lMTgyLTRlYWEtYjJmZi0xYTU0NWI1ZTk5MWEiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAifQ.huo2ou6JDoDc7sV25d75UMWeYhBeavQlLsqIibSZuac", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and no payload - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJjOTQ2YWY3Ny1lMTgyLTRlYWEtYjJmZi0xYTU0NWI1ZTk5MWEiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAifQ.huo2ou6JDoDc7sV25d75UMWeYhBeavQlLsqIibSZuac", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and no payload - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJjOTQ2YWY3Ny1lMTgyLTRlYWEtYjJmZi0xYTU0NWI1ZTk5MWEiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAifQ.huo2ou6JDoDc7sV25d75UMWeYhBeavQlLsqIibSZuac", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and no payload - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJjOTQ2YWY3Ny1lMTgyLTRlYWEtYjJmZi0xYTU0NWI1ZTk5MWEiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAifQ.huo2ou6JDoDc7sV25d75UMWeYhBeavQlLsqIibSZuac", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and payload - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI5M2U1NTAwNi1hMGU4LTQ1MjYtYTE5MC1mYTVmZjAwZWExMTYiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAiLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0._H--TOuYFLpeEH39-rg5E3IHVkjHozBcaKVWPRC5m9I", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and payload - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI5M2U1NTAwNi1hMGU4LTQ1MjYtYTE5MC1mYTVmZjAwZWExMTYiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAiLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0._H--TOuYFLpeEH39-rg5E3IHVkjHozBcaKVWPRC5m9I", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and payload - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI5M2U1NTAwNi1hMGU4LTQ1MjYtYTE5MC1mYTVmZjAwZWExMTYiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAiLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0._H--TOuYFLpeEH39-rg5E3IHVkjHozBcaKVWPRC5m9I", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and payload - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI5M2U1NTAwNi1hMGU4LTQ1MjYtYTE5MC1mYTVmZjAwZWExMTYiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAiLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0._H--TOuYFLpeEH39-rg5E3IHVkjHozBcaKVWPRC5m9I", + "valid": true + }, + { + "name": "Valid JWT with URL parameters and payload - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?bar=1\u0026foo=2", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI5M2U1NTAwNi1hMGU4LTQ1MjYtYTE5MC1mYTVmZjAwZWExMTYiLCJ1cmxfaGFzaCI6IjQxZjA1ZjBkZGQwYTIyYWIyMDlhYzQ2ZjQ3YzQ1NzJkOWNlZmEyNTdlZDc0YjI0MDA0YmFlNzUzZWNlNmMyNjAiLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0._H--TOuYFLpeEH39-rg5E3IHVkjHozBcaKVWPRC5m9I", + "valid": true + }, + { + "name": "Token received before it was issued - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ4MjgwMCwiZXhwIjoxNjI1NDgyODYwLCJqdGkiOiJmOWY4YzM4Mi0yNDQ5LTQzMTEtYjcyYi0xZGY3MTY4NzkzMWUiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.ZELgDFNGhjZH9CffQKcq3sytBe2I0KciLxpBhcfstHQ", + "valid": false, + "reason": "invalid jwt: claim nbf is in the future" + }, + { + "name": "Token received after it was expired - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3NTYwMCwiZXhwIjoxNjI1NDc1NjYwLCJqdGkiOiI1ZjAyZjUyMi02MDMwLTQ2YzgtYjVhMy0wMTI0NjQ3OGQ4YmMiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.45MSST3B_2PsjNUeiuW54_vUQgVw4rBXrdWrOUEz3lM", + "valid": false, + "reason": "invalid jwt: claim exp is in the past" + }, + { + "name": "Token received on different URL (parameters were sorted out of order) - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/path?foo=1\u0026bar=2", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiJhNzVjOTA5Ni1lODIzLTQ0MmItODVmMi03ZDNjOWQ5YjcyNmIiLCJ1cmxfaGFzaCI6IjZhMDIyZDgzNGMxODMzMGJhYWQ4YTJiZDYxMzUxNzNlZDIxNjEwYTQ1NDUxNmJlMGRmM2YxY2MwMTUxNjEwZWEifQ.z6Sw1XQIM0wuEQGBhXBdawDIIrtMg2XnmA_bpDq53pE", + "valid": false, + "reason": "invalid jwt: claim url_hash is invalid" + }, + { + "name": "Payload does not match - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJmYjYyZjAyYWNkYTdkNzQxNzdhNzAxYTFjZTAwNmU2YmFjZDkwYzdkNGQ3YWI0ODE2OTJjMWRhNDdjODEwNzZiIn0.m79RCwO6dGa9pvzsQypkVuXQ6eM0CkRl6_U7MfWg6TM", + "valid": false, + "reason": "invalid jwt: claim payload_hash is invalid" + }, + { + "name": "Payload does not match - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJmYjYyZjAyYWNkYTdkNzQxNzdhNzAxYTFjZTAwNmU2YmFjZDkwYzdkNGQ3YWI0ODE2OTJjMWRhNDdjODEwNzZiIn0.m79RCwO6dGa9pvzsQypkVuXQ6eM0CkRl6_U7MfWg6TM", + "valid": false, + "reason": "invalid jwt: claim payload_hash is invalid" + }, + { + "name": "Payload does not match - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJmYjYyZjAyYWNkYTdkNzQxNzdhNzAxYTFjZTAwNmU2YmFjZDkwYzdkNGQ3YWI0ODE2OTJjMWRhNDdjODEwNzZiIn0.m79RCwO6dGa9pvzsQypkVuXQ6eM0CkRl6_U7MfWg6TM", + "valid": false, + "reason": "invalid jwt: claim payload_hash is invalid" + }, + { + "name": "Payload does not match - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJmYjYyZjAyYWNkYTdkNzQxNzdhNzAxYTFjZTAwNmU2YmFjZDkwYzdkNGQ3YWI0ODE2OTJjMWRhNDdjODEwNzZiIn0.m79RCwO6dGa9pvzsQypkVuXQ6eM0CkRl6_U7MfWg6TM", + "valid": false, + "reason": "invalid jwt: claim payload_hash is invalid" + }, + { + "name": "Payload does not match - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJmYjYyZjAyYWNkYTdkNzQxNzdhNzAxYTFjZTAwNmU2YmFjZDkwYzdkNGQ3YWI0ODE2OTJjMWRhNDdjODEwNzZiIn0.m79RCwO6dGa9pvzsQypkVuXQ6eM0CkRl6_U7MfWg6TM", + "valid": false, + "reason": "invalid jwt: claim payload_hash is invalid" + }, + { + "name": "Different secret - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIyNDNjMjdhZS0yZjAyLTQ2YTAtODg1Mi1jNjZmMzdlYTlmNDYiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.JgSeYyOtlEKAXk8bz30iJI4tXgf4lxknoiezawuVhb4", + "valid": false, + "reason": "invalid jwt: signature is invalid" + }, + { + "name": "payload was removed in transit - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0.bHfsjj3tljfzTufoG3EU3p_yFrQh9CufyzRSaRMd4ss", + "valid": false, + "reason": "invalid jwt: claim payload_hash is set but actual payload is missing" + }, + { + "name": "payload was removed in transit - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0.bHfsjj3tljfzTufoG3EU3p_yFrQh9CufyzRSaRMd4ss", + "valid": false, + "reason": "invalid jwt: claim payload_hash is set but actual payload is missing" + }, + { + "name": "payload was removed in transit - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0.bHfsjj3tljfzTufoG3EU3p_yFrQh9CufyzRSaRMd4ss", + "valid": false, + "reason": "invalid jwt: claim payload_hash is set but actual payload is missing" + }, + { + "name": "payload was removed in transit - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0.bHfsjj3tljfzTufoG3EU3p_yFrQh9CufyzRSaRMd4ss", + "valid": false, + "reason": "invalid jwt: claim payload_hash is set but actual payload is missing" + }, + { + "name": "payload was removed in transit - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiIxNDUwMTUzMi05NmYyLTQ2ODQtOTgzMi02OGYwOTUxYWUzNDIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiJkZmZkNjAyMWJiMmJkNWIwYWY2NzYyOTA4MDllYzNhNTMxOTFkZDgxYzdmNzBhNGIyODY4OGEzNjIxODI5ODZmIn0.bHfsjj3tljfzTufoG3EU3p_yFrQh9CufyzRSaRMd4ss", + "valid": false, + "reason": "invalid jwt: claim payload_hash is set but actual payload is missing" + }, + { + "name": "payload was added in transit - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": false, + "reason": "invalid jwt: claim payload_hash is not set but payload is present" + }, + { + "name": "payload was added in transit - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": false, + "reason": "invalid jwt: claim payload_hash is not set but payload is present" + }, + { + "name": "payload was added in transit - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": false, + "reason": "invalid jwt: claim payload_hash is not set but payload is present" + }, + { + "name": "payload was added in transit - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": false, + "reason": "invalid jwt: claim payload_hash is not set but payload is present" + }, + { + "name": "payload was added in transit - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Hello, World!", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.6Fp0rVOsRr1fj9S2GidXAfkmPVofKr8_RTffC6G6r2E", + "valid": false, + "reason": "invalid jwt: claim payload_hash is not set but payload is present" + }, + { + "name": "Special characters in URL - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/?obj%5Bfield1%5D=val1\u0026obj%5Bfield2%5D=val2\u0026param=value+with+spaces", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjhhZmYzNzQ4ZDc4OWM0MzMzNTczZTFjMzFmZTViMDA0OTQwY2I1ZmM0NzQ2MGVkM2QwZTY1NGY3ZmM0ZTVmYWUifQ.mJBCMfEjKmN3IMuzqPXPHktWETrcu0iNdF3agE8PDyI", + "valid": true + }, + { + "name": "Special characters in the payload - DELETE", + "method": "DELETE", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Some text containg \u0026 \u003c \u003e, \u0026 and \\u0026", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiIyNDMxYmQ5Y2NiNDhlZDAxNmZhZDU2N2IwNzcxMjAzOGI1Y2RkOTQ5YWM5ZDQxZmU0NjkzZDVhMzg0NWNmN2U4In0.IQiKzeEPaO3A48lhKmBDSmxZyCESPCGxdSKFdi0dEJs", + "valid": true + }, + { + "name": "Special characters in the payload - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Some text containg \u0026 \u003c \u003e, \u0026 and \\u0026", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiIyNDMxYmQ5Y2NiNDhlZDAxNmZhZDU2N2IwNzcxMjAzOGI1Y2RkOTQ5YWM5ZDQxZmU0NjkzZDVhMzg0NWNmN2U4In0.IQiKzeEPaO3A48lhKmBDSmxZyCESPCGxdSKFdi0dEJs", + "valid": true + }, + { + "name": "Special characters in the payload - PATCH", + "method": "PATCH", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Some text containg \u0026 \u003c \u003e, \u0026 and \\u0026", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiIyNDMxYmQ5Y2NiNDhlZDAxNmZhZDU2N2IwNzcxMjAzOGI1Y2RkOTQ5YWM5ZDQxZmU0NjkzZDVhMzg0NWNmN2U4In0.IQiKzeEPaO3A48lhKmBDSmxZyCESPCGxdSKFdi0dEJs", + "valid": true + }, + { + "name": "Special characters in the payload - POST", + "method": "POST", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Some text containg \u0026 \u003c \u003e, \u0026 and \\u0026", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiIyNDMxYmQ5Y2NiNDhlZDAxNmZhZDU2N2IwNzcxMjAzOGI1Y2RkOTQ5YWM5ZDQxZmU0NjkzZDVhMzg0NWNmN2U4In0.IQiKzeEPaO3A48lhKmBDSmxZyCESPCGxdSKFdi0dEJs", + "valid": true + }, + { + "name": "Special characters in the payload - PUT", + "method": "PUT", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "payload": "Some text containg \u0026 \u003c \u003e, \u0026 and \\u0026", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDciLCJwYXlsb2FkX2hhc2giOiIyNDMxYmQ5Y2NiNDhlZDAxNmZhZDU2N2IwNzcxMjAzOGI1Y2RkOTQ5YWM5ZDQxZmU0NjkzZDVhMzg0NWNmN2U4In0.IQiKzeEPaO3A48lhKmBDSmxZyCESPCGxdSKFdi0dEJs", + "valid": true + }, + { + "name": "HS384 alg - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.KNMW-29X77lZeuPmThmHWc_RUAvTkaDkpxIZK6mqE08v8mWKiU9Edh4QXwAJO2nv", + "valid": true + }, + { + "name": "HS512 alg - GET", + "method": "GET", + "secret": "36efdd1aace2e26cd490f0d951138253bef2f7c6d34d18981da781555cc4cebb", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.rd6r0iMyNGnVPCwurETphE3Y8rpAyvvnUK0S8WvGUkt2E1QSRAZ7NZJZBHw1Y_Wb5W-sK9HJr_PRL2vz4jRT3Q", + "valid": true + }, + { + "name": "none alg - GET", + "method": "GET", + "url": "https://example.com/", + "timestamp": "2021-07-05T12:00:00+02:00", + "token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJNZXNzYWdlQmlyZCIsImlhdCI6MTYyNTQ3OTIwMCwiZXhwIjoxNjI1NDc5MjYwLCJqdGkiOiI1OWEyNDRkYy1lOWFkLTRlMjMtOTc3OC0zNzFmYWEyMzhmNzIiLCJ1cmxfaGFzaCI6IjBmMTE1ZGIwNjJiN2MwZGQwMzBiMTY4NzhjOTlkZWE1YzM1NGI0OWRjMzdiMzhlYjg4NDYxNzljNzc4M2U5ZDcifQ.", + "valid": false, + "reason": "invalid jwt: signing method none is invalid" + } +] diff --git a/examples/pom.xml b/examples/pom.xml index 96c5e146..cb9b7489 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,7 +6,7 @@ com.messagebird examples - 3.0.19 + 6.4.0 @@ -20,7 +20,7 @@ com.messagebird messagebird-api - 3.0.19 + 6.4.0 compile diff --git a/examples/src/main/java/ExampleConversationSendCarouselTemplate.java b/examples/src/main/java/ExampleConversationSendCarouselTemplate.java new file mode 100644 index 00000000..f386917d --- /dev/null +++ b/examples/src/main/java/ExampleConversationSendCarouselTemplate.java @@ -0,0 +1,116 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ExampleConversationSendCarouselTemplate { + + public static void main(String[] args) { + + if (args.length < 3) { + System.out.println("Please at least specify your access key, the channel id and destination address.\n" + + "Usage : java -jar test_accesskey(Required) channel_id(Required) to(Required)"); + return; + } + + //First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + //Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + ConversationContent conversationContent = new ConversationContent(); + ConversationContentHsm conversationContentHsm = new ConversationContentHsm(); + conversationContentHsm.setNamespace("c663a566_a4de_492f_86b2_028cbf612345"); + conversationContentHsm.setTemplateName("carousel_template_test_hello"); + ConversationHsmLanguage language = new ConversationHsmLanguage(); + language.setCode("en_US"); + conversationContentHsm.setLanguage(language); + + + +// card0 components + List card0Components = new ArrayList<>(); +// card0 header + MessageComponent card0HeaderComponent = new MessageComponent(); + card0HeaderComponent.setType(MessageComponentType.HEADER); + MessageParam imageParam = new MessageParam(); + imageParam.setType(TemplateMediaType.IMAGE); + Media media = new Media(); + media.setUrl("https://upload.wikimedia.org/wikipedia/commons/f/f9/Phoenicopterus_ruber_in_S%C3%A3o_Paulo_Zoo.jpg"); + imageParam.setImage(media); + card0HeaderComponent.setParameters(Collections.singletonList(imageParam)); + card0Components.add(card0HeaderComponent); +// card0 body + MessageComponent card0BodyComponent = new MessageComponent(); + card0BodyComponent.setType(MessageComponentType.BODY); + MessageParam textParam = new MessageParam(); + textParam.setType(TemplateMediaType.TEXT); + textParam.setText("dummy text"); + card0BodyComponent.setParameters(Collections.singletonList(textParam)); + card0Components.add(card0BodyComponent); +// card0 button + MessageComponent card0ButtonComponent = new MessageComponent(); + card0ButtonComponent.setType(MessageComponentType.BUTTON); + card0ButtonComponent.setSub_type("quick_reply"); + card0ButtonComponent.setIndex(0); + MessageParam buttonParam = new MessageParam(); + buttonParam.setType(TemplateMediaType.PAYLOAD); + buttonParam.setPayload("dummy button"); + card0ButtonComponent.setParameters(Collections.singletonList(buttonParam)); + card0Components.add(card0ButtonComponent); + +// card0 + MessageComponent card0 = new MessageComponent(); + card0.setType(MessageComponentType.CARD); + card0.setCard_index(0); + card0.setComponents(card0Components); + + +// cards list + List cards = new ArrayList<>(); + cards.add(card0); + +// carousel component + MessageComponent carousel = new MessageComponent(); + carousel.setType(MessageComponentType.CAROUSEL); + carousel.setCards(cards); +// body component + MessageComponent body = new MessageComponent(); + body.setType(MessageComponentType.BODY); + MessageParam bodyParam = new MessageParam(); + bodyParam.setType(TemplateMediaType.TEXT); + bodyParam.setText("Jackson"); + body.setParameters(Collections.singletonList(bodyParam)); + +// set components to message + List messageComponents = new ArrayList<>(); + messageComponents.add(body); + messageComponents.add(carousel); + conversationContentHsm.setComponents(messageComponents); + + conversationContent.setHsm(conversationContentHsm); + ConversationSendRequest request = new ConversationSendRequest( + args[2], + ConversationContentType.HSM, + conversationContent, + args[1], + "", + null, + null, + null); + + try { + ConversationSendResponse sendResponse = messageBirdClient.sendMessage(request); + System.out.println(sendResponse.toString()); + + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleConversationSendEmailMessage.java b/examples/src/main/java/ExampleConversationSendEmailMessage.java index 9a8f4021..63734bb8 100644 --- a/examples/src/main/java/ExampleConversationSendEmailMessage.java +++ b/examples/src/main/java/ExampleConversationSendEmailMessage.java @@ -21,7 +21,7 @@ public static void main(String[] args) { if (args.length < 4) { System.out.println("Please at least specify your access key, the channel id and destination address.\n" + - "Usage : java -jar test_accesskey(Required) channel_id(Required) to(Required) from(Required)"); + "Usage : java -jar test_accesskey(Required) channel_id(Required) from(Required) to(Required)"); return; } diff --git a/examples/src/main/java/ExampleConversationSendHSMCopyCodeTemplate.java b/examples/src/main/java/ExampleConversationSendHSMCopyCodeTemplate.java new file mode 100644 index 00000000..4d5dcd3f --- /dev/null +++ b/examples/src/main/java/ExampleConversationSendHSMCopyCodeTemplate.java @@ -0,0 +1,92 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.*; + +import java.util.ArrayList; +import java.util.List; + +public class ExampleConversationSendHSMCopyCodeTemplate { + + public static void main(String[] args) { + if (args.length < 6) { + System.out.println("Please at least specify your access key, the channel id and destination address.\n" + + "Usage : java -jar test_accesskey(Required) channel_id(Required) from(Required) destination(Required) templateName(Required) namespace(Required) couponCodeInput(Required)"); + return; + } + + final String accessKey = args[0]; + final String from = args[1]; + final String destination = args[2]; + final String templateName = args[3]; + final String namespace = args[4]; + final String couponCodeInput = args[5]; + + //First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(accessKey); + //Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + ConversationContent conversationContent = new ConversationContent(); + ConversationContentHsm conversationContentHsm = new ConversationContentHsm(); + conversationContentHsm.setNamespace(namespace); + conversationContentHsm.setTemplateName(templateName); + ConversationHsmLanguage language = new ConversationHsmLanguage(); + language.setCode("en"); + conversationContentHsm.setLanguage(language); + List messageComponents = new ArrayList<>(); + + // Add LTO component + MessageComponent messageCopyCodeComponent = new MessageComponent(); + messageCopyCodeComponent.setType(MessageComponentType.BUTTON); + messageCopyCodeComponent.setSub_type(MessageComponentType.COPY_CODE.toString()); + List messageCCParams = new ArrayList<>(); + + MessageParam couponCodeParam = new MessageParam(); + couponCodeParam.setType(TemplateMediaType.COUPON_CODE); + couponCodeParam.setCouponCode(couponCodeInput); + messageCCParams.add(couponCodeParam); + + messageCopyCodeComponent.setParameters(messageCCParams); + + // Add body component + MessageComponent messageBodyComponent = new MessageComponent(); + messageBodyComponent.setType(MessageComponentType.BODY); + List messageBodyParams = new ArrayList<>(); + + MessageParam text = new MessageParam(); + text.setType(TemplateMediaType.TEXT); + text.setText("Bob"); + messageBodyParams.add(text); + + messageBodyComponent.setParameters(messageBodyParams); + + messageComponents.add(messageCopyCodeComponent); + messageComponents.add(messageBodyComponent); + conversationContentHsm.setComponents(messageComponents); + conversationContent.setHsm(conversationContentHsm); + ConversationSendRequest request = new ConversationSendRequest( + destination, + ConversationContentType.HSM, + conversationContent, + from, + "", + null, + null, + null); + + try { + System.out.println(request.toString()); + ConversationSendResponse sendResponse = messageBirdClient.sendMessage(request); + System.out.println(sendResponse.toString()); + + } catch (UnauthorizedException e) { + System.err.println("Authorization failed. Please check your access key: " + e.getMessage()); + } catch (GeneralException e) { + System.err.println("An error occurred while sending the message: " + e.getMessage()); + } + } + +} diff --git a/examples/src/main/java/ExampleConversationSendHSMLimitedTimeOfferTemplate.java b/examples/src/main/java/ExampleConversationSendHSMLimitedTimeOfferTemplate.java new file mode 100644 index 00000000..65b959a8 --- /dev/null +++ b/examples/src/main/java/ExampleConversationSendHSMLimitedTimeOfferTemplate.java @@ -0,0 +1,90 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.*; + +import java.util.ArrayList; +import java.util.List; + +public class ExampleConversationSendHSMLimitedTimeOfferTemplate { + + public static void main(String[] args) { + if (args.length < 6) { + System.out.println("Please at least specify your access key, the channel id and destination address.\n" + + "Usage : java -jar test_accesskey(Required) channel_id(Required) from(Required) destination(Required) templateName(Required) namespace(Required) expirationTimeInput(Required)"); + return; + } + + final String accessKey = args[0]; + final String from = args[1]; + final String destination = args[2]; + final String templateName = args[3]; + final String namespace = args[4]; + final String expirationTimeInput = args[5]; + + //First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(accessKey); + //Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + ConversationContent conversationContent = new ConversationContent(); + ConversationContentHsm conversationContentHsm = new ConversationContentHsm(); + conversationContentHsm.setNamespace(namespace); + conversationContentHsm.setTemplateName(templateName); + ConversationHsmLanguage language = new ConversationHsmLanguage(); + language.setCode("en"); + conversationContentHsm.setLanguage(language); + List messageComponents = new ArrayList<>(); + + // Add LTO component + MessageComponent messageLTOComponent = new MessageComponent(); + messageLTOComponent.setType(MessageComponentType.LIMITED_TIME_OFFER); + List messageLTOParams = new ArrayList<>(); + + MessageParam expirationTime = new MessageParam(); + expirationTime.setType(TemplateMediaType.EXPIRATION_TIME); + expirationTime.setExpirationTime(expirationTimeInput); + messageLTOParams.add(expirationTime); + + messageLTOComponent.setParameters(messageLTOParams); + + // Add body component + MessageComponent messageBodyComponent = new MessageComponent(); + messageBodyComponent.setType(MessageComponentType.BODY); + List messageBodyParams = new ArrayList<>(); + + MessageParam text = new MessageParam(); + text.setType(TemplateMediaType.TEXT); + text.setText("Bob"); + messageBodyParams.add(text); + + messageBodyComponent.setParameters(messageBodyParams); + + messageComponents.add(messageLTOComponent); + messageComponents.add(messageBodyComponent); + conversationContentHsm.setComponents(messageComponents); + conversationContent.setHsm(conversationContentHsm); + ConversationSendRequest request = new ConversationSendRequest( + destination, + ConversationContentType.HSM, + conversationContent, + from, + "", + null, + null, + null); + + try { + ConversationSendResponse sendResponse = messageBirdClient.sendMessage(request); + System.out.println(sendResponse.toString()); + + } catch (UnauthorizedException e) { + System.err.println("Authorization failed. Please check your access key: " + e.getMessage()); + } catch (GeneralException e) { + System.err.println("An error occurred while sending the message: " + e.getMessage()); + } + } + +} diff --git a/examples/src/main/java/ExampleConversationSendHSMMediaTemplate.java b/examples/src/main/java/ExampleConversationSendHSMMediaTemplate.java new file mode 100644 index 00000000..3f495aee --- /dev/null +++ b/examples/src/main/java/ExampleConversationSendHSMMediaTemplate.java @@ -0,0 +1,103 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.ConversationContent; +import com.messagebird.objects.conversations.ConversationContentHsm; +import com.messagebird.objects.conversations.ConversationContentType; +import com.messagebird.objects.conversations.ConversationHsmLanguage; +import com.messagebird.objects.conversations.ConversationSendRequest; +import com.messagebird.objects.conversations.ConversationSendResponse; +import com.messagebird.objects.conversations.Media; +import com.messagebird.objects.conversations.MessageComponent; +import com.messagebird.objects.conversations.MessageComponentType; +import com.messagebird.objects.conversations.MessageParam; +import com.messagebird.objects.conversations.TemplateMediaType; +import java.util.ArrayList; +import java.util.List; + +public class ExampleConversationSendHSMMediaTemplate { + + // Reference Example: https://developers.messagebird.com/quickstarts/whatsapp/send-media-template-message/ + public static void main(String[] args) { + + if (args.length < 4) { + System.out.println("Please at least specify your access key, the channel id and destination address.\n" + + "Usage : java -jar test_accesskey(Required) channel_id(Required) from(Required) to(Required)"); + return; + } + + //First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + //Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + + ConversationContent conversationContent = new ConversationContent(); + ConversationContentHsm conversationContentHsm = new ConversationContentHsm(); + conversationContentHsm.setNamespace("20332cd4_f095_b080_d255_35677159aaff"); + conversationContentHsm.setTemplateName("33172012024_ship_img_but_1"); + ConversationHsmLanguage language = new ConversationHsmLanguage(); + language.setCode("en"); + conversationContentHsm.setLanguage(language); + List messageComponents = new ArrayList<>(); + //Define header component with image + MessageComponent messageHeaderComponent = new MessageComponent(); + messageHeaderComponent.setType(MessageComponentType.HEADER); + MessageParam imageParam = new MessageParam(); + Media media = new Media(); + media.setUrl("https://i.ytimg.com/vi/3fDoOw4lIeU/maxresdefault.jpg"); + imageParam.setImage(media); + imageParam.setType(TemplateMediaType.IMAGE); + List messageHeaderParams = new ArrayList<>(); + messageHeaderParams.add(imageParam); + messageHeaderComponent.setParameters(messageHeaderParams); + //Define body component with texts + MessageComponent messageBodyComponent = new MessageComponent(); + messageBodyComponent.setType(MessageComponentType.BODY); + List messageBodyParams = new ArrayList<>(); + messageBodyComponent.setParameters(messageBodyParams); + MessageParam firstText = new MessageParam(); + firstText.setType(TemplateMediaType.TEXT); + firstText.setText("John"); + messageBodyParams.add(firstText); + + MessageParam secondText = new MessageParam(); + secondText.setType(TemplateMediaType.TEXT); + secondText.setText("MB93824"); + messageBodyParams.add(secondText); + + MessageParam thirdText = new MessageParam(); + thirdText.setType(TemplateMediaType.TEXT); + thirdText.setText("2 days"); + messageBodyParams.add(thirdText); + + MessageParam fourthText = new MessageParam(); + fourthText.setType(TemplateMediaType.TEXT); + fourthText.setText("MessageBird"); + messageBodyParams.add(fourthText); + + messageComponents.add(messageHeaderComponent); + messageComponents.add(messageBodyComponent); + conversationContentHsm.setComponents(messageComponents); + conversationContent.setHsm(conversationContentHsm); + ConversationSendRequest request = new ConversationSendRequest( + args[2], + ConversationContentType.HSM, + conversationContent, + args[1], + "", + null, + null, + null); + + try { + ConversationSendResponse sendResponse = messageBirdClient.sendMessage(request); + System.out.println(sendResponse.toString()); + + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleConversationSendHSMTemplateWithButtons.java b/examples/src/main/java/ExampleConversationSendHSMTemplateWithButtons.java new file mode 100644 index 00000000..40202a26 --- /dev/null +++ b/examples/src/main/java/ExampleConversationSendHSMTemplateWithButtons.java @@ -0,0 +1,63 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.*; + +import java.util.Collections; + +public class ExampleConversationSendHSMTemplateWithButtons { + + // Reference Example: https://developers.messagebird.com/quickstarts/whatsapp/send-message-with-buttons/ + public static void main(String[] args) { + + if (args.length < 4) { + System.out.println("Please at least specify your access key, the channel id and destination address.\n" + + "Usage : java -jar test_accesskey(Required) channel_id(Required) from(Required) to(Required)"); + return; + } + + //First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + //Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + ConversationContent conversationContent = new ConversationContent(); + ConversationContentHsm conversationContentHsm = new ConversationContentHsm(); + conversationContentHsm.setNamespace("20332cd4_f095_b080_d255_35677159aaff"); + conversationContentHsm.setTemplateName("33172012024_ship_img_but_1"); + ConversationHsmLanguage language = new ConversationHsmLanguage(); + language.setCode("en"); + conversationContentHsm.setLanguage(language); + + //Define component with button + MessageComponent messageButtonComponent = new MessageComponent(); + messageButtonComponent.setType(MessageComponentType.BUTTON); + messageButtonComponent.setSub_type("url"); + MessageParam textParam = new MessageParam(); + textParam.setType(TemplateMediaType.TEXT); + textParam.setText("23493282245"); + + messageButtonComponent.setParameters(Collections.singletonList(textParam)); + conversationContentHsm.setComponents(Collections.singletonList(messageButtonComponent)); + conversationContent.setHsm(conversationContentHsm); + ConversationSendRequest request = new ConversationSendRequest( + args[2], + ConversationContentType.HSM, + conversationContent, + args[1], + "", + null, + null, + null); + + try { + ConversationSendResponse sendResponse = messageBirdClient.sendMessage(request); + System.out.println(sendResponse.toString()); + + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleCreateAuthTemplate.java b/examples/src/main/java/ExampleCreateAuthTemplate.java new file mode 100644 index 00000000..31b5f415 --- /dev/null +++ b/examples/src/main/java/ExampleCreateAuthTemplate.java @@ -0,0 +1,69 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.*; + +import java.util.ArrayList; +import java.util.List; + +public class ExampleCreateAuthTemplate { + public static void main(String[] args) { + + if (args.length < 3) { + System.out.println("Please specify your access key and a template name and WABA ID example : java -jar test_accesskey \"My template name\" \"WABA ID\""); + return; + } + + // First create your service object + MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + /* body */ + HSMComponent bodyComponent = new HSMComponent(); + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setAddSecurityRecommendation(true); + + /* footer */ + HSMComponent footerComponent = new HSMComponent(); + footerComponent.setType(HSMComponentType.FOOTER); + footerComponent.setCodeExpirationMinutes(8); + + /* button */ + HSMComponent buttonComponent = new HSMComponent(); + List buttons = new ArrayList<>(); + HSMComponentButton otpButton = new HSMComponentButton(); + otpButton.setOtpType(HSMOTPButtonType.ONE_TAP); + otpButton.setText("Copy code"); + otpButton.setAutofillText("Autofill"); + otpButton.setPackageName("com.example.luckyshrub"); + otpButton.setSignatureHash("K8a%2FAINcGX7"); + + buttons.add(otpButton); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + + /* set components */ + Template template = new Template(); + List components = new ArrayList<>(); + components.add(bodyComponent); + components.add(footerComponent); + components.add(buttonComponent); + + template.setName(args[1]); + template.setLanguage("en_US"); + template.setWABAID(args[2]); + template.setComponents(components); + template.setCategory(HSMCategory.AUTHENTICATION); + + try { + TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); + System.out.println(response.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleCreateCarouselTemplate.java b/examples/src/main/java/ExampleCreateCarouselTemplate.java new file mode 100644 index 00000000..54bea0dd --- /dev/null +++ b/examples/src/main/java/ExampleCreateCarouselTemplate.java @@ -0,0 +1,113 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Create template. + * + * @see Doc - Create template + * @author AlexL-mb + */ +public class ExampleCreateCarouselTemplate { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key and a template name and WABA ID example : java -jar test_accesskey \"My template name\" \"WABA ID\""); + return; + } + + // First create your service object + MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + /* body */ + HSMComponent bodyComponent = new HSMComponent(); + final HSMExample bodyExample = new HSMExample(); + final List> bodyText = new ArrayList<>(); + bodyText.add(Arrays.asList("John")); + bodyExample.setBody_text(bodyText); + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setText("Hey {{1}}! This is a sample template from Java."); + bodyComponent.setExample(bodyExample); + + /* carousel */ + final HSMComponent carouselComponent = new HSMComponent(); + carouselComponent.setType(HSMComponentType.CAROUSEL); + + /* cards */ + final List cards = new ArrayList<>(); + /* card 0 */ + final HSMComponentCard card = new HSMComponentCard(); + final List cardComponents = new ArrayList<>(); + + /* card header */ + final HSMComponent headerComponent = new HSMComponent(); + final HSMExample headerExample = new HSMExample(); + headerExample.setHeader_url(Arrays.asList("https://images.freeimages.com/images/small-previews/c5a/colourful-paper-rip-1-1195879.jpg")); + + headerComponent.setType(HSMComponentType.HEADER); + headerComponent.setFormat(HSMComponentFormat.IMAGE); + headerComponent.setExample(headerExample); + cardComponents.add(headerComponent); + + /* card body */ + final HSMComponent cardBodyComponent = new HSMComponent(); + final HSMExample cardBodyExample = new HSMExample(); + final List> cardBodyText = new ArrayList<>(); + cardBodyText.add(Arrays.asList("John")); + cardBodyExample.setBody_text(cardBodyText); + + cardBodyComponent.setType(HSMComponentType.BODY); + cardBodyComponent.setText("Hey {{1}}! This is a sample template from Java."); + cardBodyComponent.setExample(cardBodyExample); + cardComponents.add(cardBodyComponent); + + /* card buttons */ + final HSMComponent buttonComponent = new HSMComponent(); + final List buttons = new ArrayList<>(); + final HSMComponentButton button = new HSMComponentButton(); + button.setType(HSMComponentButtonType.URL); + button.setText("Touch it"); + button.setUrl("https://www.messagebird.com"); + button.setExample(Arrays.asList("https://developers.messagebird.com")); + buttons.add(button); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + cardComponents.add(buttonComponent); + + card.setComponents(cardComponents); + + cards.add(card); + + carouselComponent.setCards(cards); + + /* set components */ + Template template = new Template(); + List components = new ArrayList<>(); + components.add(bodyComponent); + components.add(carouselComponent); + + template.setName(args[1]); + template.setLanguage("en_US"); + template.setWABAID(args[2]); + template.setComponents(components); + template.setCategory(HSMCategory.MARKETING); + + try { + TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); + System.out.println(response.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + + } +} diff --git a/examples/src/main/java/ExampleCreateChildAccount.java b/examples/src/main/java/ExampleCreateChildAccount.java new file mode 100644 index 00000000..2c71ae51 --- /dev/null +++ b/examples/src/main/java/ExampleCreateChildAccount.java @@ -0,0 +1,30 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.ChildAccountRequest; +import com.messagebird.objects.ChildAccountCreateResponse; + +public class ExampleCreateChildAccount { + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and name of child account arguments"); + return; + } + + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Creating a child account of partner accounts"); + final ChildAccountRequest childAccountRequest = new ChildAccountRequest(); + childAccountRequest.setName(args[1]); + ChildAccountCreateResponse childAccount = messageBirdClient.createChildAccount(childAccountRequest); + System.out.println("Child account is created: " + childAccount.toString()); + + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleCreateCouponTemplate.java b/examples/src/main/java/ExampleCreateCouponTemplate.java new file mode 100644 index 00000000..f5480434 --- /dev/null +++ b/examples/src/main/java/ExampleCreateCouponTemplate.java @@ -0,0 +1,81 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Create template. + * + * @see Doc - Create template + * @author AlexL-mb + */ +public class ExampleCreateCouponTemplate { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key and a template name and WABA ID example : java -jar test_accesskey \"My template name\" \"WABA ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + /* header */ + final HSMComponent headerComponent = new HSMComponent(); + headerComponent.setType(HSMComponentType.HEADER); + headerComponent.setFormat(HSMComponentFormat.TEXT); + headerComponent.setText("Our Fall Sale is on!"); + + /* body */ + final HSMComponent bodyComponent = new HSMComponent(); + final HSMExample bodyExample = new HSMExample(); + final List> bodyText = new ArrayList<>(); + bodyText.add(Arrays.asList("25OFF", "25%")); + bodyExample.setBody_text(bodyText); + + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setText("Shop now through November and use code {{1}} to get {{2}} off of all merchandise!"); + bodyComponent.setExample(bodyExample); + + + /* button */ + final HSMComponent buttonComponent = new HSMComponent(); + final List buttons = new ArrayList<>(); + final HSMComponentButton button = new HSMComponentButton(); + button.setType(HSMComponentButtonType.COPY_CODE); + button.setExample(Arrays.asList("CODE25")); + buttons.add(button); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + + /* set components */ + final Template template = new Template(); + final List components = new ArrayList<>(); + components.add(headerComponent); + components.add(bodyComponent); + components.add(buttonComponent); + + template.setName(args[1]); + template.setLanguage("en_US"); + template.setWABAID(args[2]); + template.setComponents(components); + template.setCategory(HSMCategory.MARKETING); + template.setCtaURLLinkTrackingOptedOut(true); + + try { + TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); + System.out.println(response.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleCreateLimitedTimeOfferTemplate.java b/examples/src/main/java/ExampleCreateLimitedTimeOfferTemplate.java new file mode 100644 index 00000000..dc721f9f --- /dev/null +++ b/examples/src/main/java/ExampleCreateLimitedTimeOfferTemplate.java @@ -0,0 +1,95 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ExampleCreateLimitedTimeOfferTemplate { + + public static void main(String[] args) { + if (args.length < 4) { + System.out.println("Please specify your access key and a template name and WABA ID example : java -jar test_accesskey(Required) templateName(Required) wabaID(required)"); + return; + } + + final String accessKey = args[0]; + final String templateName = args[1]; + final String wabaID = args[2]; + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(accessKey); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // header + final HSMComponent headerComponent = new HSMComponent(); + final HSMExample headerExample = new HSMExample(); + headerExample.setHeader_url(Arrays.asList("https://images.freeimages.com/images/small-previews/c5a/colourful-paper-rip-1-1195879.jpg")); + + headerComponent.setType(HSMComponentType.HEADER); + headerComponent.setFormat(HSMComponentFormat.IMAGE); + headerComponent.setExample(headerExample); + + // limited time offer + final HSMComponent ltoComponent = new HSMComponent(); + ltoComponent.setType(HSMComponentType.LIMITED_TIME_OFFER); + ltoComponent.setText("Expiring offer!"); + ltoComponent.setHasExpiration(true); + + // body + final HSMComponent bodyComponent = new HSMComponent(); + final HSMExample bodyExample = new HSMExample(); + final List> bodyText = new ArrayList<>(); + bodyText.add(Arrays.asList("John", "CARIBE25")); + bodyExample.setBody_text(bodyText); + + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setText("Good news, {{1}}! Use code {{2}} to get 0% off all packages!"); + bodyComponent.setExample(bodyExample); + + // buttons + final HSMComponent buttonComponent = new HSMComponent(); + final List buttons = new ArrayList<>(); + + final HSMComponentButton buttonCopyCode = new HSMComponentButton(); + buttonCopyCode.setType(HSMComponentButtonType.COPY_CODE); + buttonCopyCode.setExample(Arrays.asList("CARIBE25")); + + final HSMComponentButton buttonBookNow = new HSMComponentButton(); + buttonBookNow.setType(HSMComponentButtonType.URL); + buttonBookNow.setText("Book now!"); + buttonBookNow.setUrl("https://www.bird.com?code={{1}}"); + buttonBookNow.setExample(Arrays.asList("https://www.bird.com?code=CARIBE25")); + + buttons.addAll(Arrays.asList(buttonCopyCode, buttonBookNow)); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + + // set components + final Template template = new Template(); + final List components = new ArrayList<>(); + components.addAll(Arrays.asList(headerComponent, ltoComponent, bodyComponent, buttonComponent)); + + template.setName(templateName); + template.setLanguage("en_US"); + template.setWABAID(wabaID); + template.setComponents(components); + template.setCategory(HSMCategory.MARKETING); + + try { + TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); + System.out.println(response.toString()); + } catch (UnauthorizedException e) { + System.err.println("Authorization failed. Please check your access key: " + e.getMessage()); + } catch (GeneralException e) { + System.err.println("An error occurred while sending the message: " + e.getMessage()); + } + } + +} diff --git a/examples/src/main/java/ExampleCreateTemplate.java b/examples/src/main/java/ExampleCreateTemplate.java new file mode 100644 index 00000000..336340a6 --- /dev/null +++ b/examples/src/main/java/ExampleCreateTemplate.java @@ -0,0 +1,98 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.HSMCategory; +import com.messagebird.objects.integrations.HSMComponent; +import com.messagebird.objects.integrations.HSMComponentButton; +import com.messagebird.objects.integrations.HSMComponentButtonType; +import com.messagebird.objects.integrations.HSMComponentFormat; +import com.messagebird.objects.integrations.HSMComponentType; +import com.messagebird.objects.integrations.HSMExample; +import com.messagebird.objects.integrations.Template; +import com.messagebird.objects.integrations.TemplateResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Create template. + * + * @see Doc - Create template + * @author ssk910 + */ +public class ExampleCreateTemplate { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key and a template name and WABA ID example : java -jar test_accesskey \"My template name\" \"WABA ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + /* header */ + final HSMComponent headerComponent = new HSMComponent(); + final HSMExample headerExample = new HSMExample(); + headerExample.setHeader_url(Arrays.asList("https://images.freeimages.com/images/small-previews/c5a/colourful-paper-rip-1-1195879.jpg")); + + headerComponent.setType(HSMComponentType.HEADER); + headerComponent.setFormat(HSMComponentFormat.IMAGE); + headerComponent.setExample(headerExample); + + /* body */ + final HSMComponent bodyComponent = new HSMComponent(); + final HSMExample bodyExample = new HSMExample(); + final List> bodyText = new ArrayList<>(); + bodyText.add(Arrays.asList("John")); + bodyText.add(Arrays.asList("Anna")); + bodyExample.setBody_text(bodyText); + + bodyComponent.setType(HSMComponentType.BODY); + bodyComponent.setText("Hey {{1}}! This is a sample template from Java."); + bodyComponent.setExample(bodyExample); + + /* footer */ + final HSMComponent footerComponent = new HSMComponent(); + footerComponent.setType(HSMComponentType.FOOTER); + footerComponent.setText("This is a sample footer"); + + /* button */ + final HSMComponent buttonComponent = new HSMComponent(); + final List buttons = new ArrayList<>(); + final HSMComponentButton button = new HSMComponentButton(); + button.setType(HSMComponentButtonType.URL); + button.setText("Touch it"); + button.setUrl("https://www.messagebird.com"); + button.setExample(Arrays.asList("https://developers.messagebird.com")); + buttons.add(button); + buttonComponent.setType(HSMComponentType.BUTTONS); + buttonComponent.setButtons(buttons); + + /* set components */ + final Template template = new Template(); + final List components = new ArrayList<>(); + components.add(headerComponent); + components.add(bodyComponent); + components.add(footerComponent); + components.add(buttonComponent); + + template.setName(args[1]); + template.setLanguage("en_US"); + template.setWABAID(args[2]); + template.setComponents(components); + template.setCategory(HSMCategory.AUTHENTICATION); + + try { + TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); + System.out.println(response.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleCreateVoiceCallFlow.java b/examples/src/main/java/ExampleCreateVoiceCallFlow.java index 8a39c177..83f265b1 100644 --- a/examples/src/main/java/ExampleCreateVoiceCallFlow.java +++ b/examples/src/main/java/ExampleCreateVoiceCallFlow.java @@ -24,7 +24,6 @@ public static void main(String[] args) { final VoiceCallFlowRequest voiceCallFlowRequest = new VoiceCallFlowRequest(); - voiceCallFlowRequest.setTitle(args[1]); voiceCallFlowRequest.setRecord(true); // Can be false as well, see docs VoiceStep voiceStep = new VoiceStep(); voiceCallFlowRequest.setSteps(Collections.singletonList(voiceStep)); // VoiceStep Object diff --git a/examples/src/main/java/ExampleDeleteChildAccount.java b/examples/src/main/java/ExampleDeleteChildAccount.java new file mode 100644 index 00000000..2724a59a --- /dev/null +++ b/examples/src/main/java/ExampleDeleteChildAccount.java @@ -0,0 +1,27 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; + +public class ExampleDeleteChildAccount { + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and id of child account arguments"); + return; + } + + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Deleting a child account of partner accounts"); + messageBirdClient.deleteChildAccount(args[1]); + System.out.println("Child account is deleted"); + + } catch (GeneralException | UnauthorizedException | NotFoundException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleDeleteTemplatesByName.java b/examples/src/main/java/ExampleDeleteTemplatesByName.java new file mode 100644 index 00000000..c8d529f3 --- /dev/null +++ b/examples/src/main/java/ExampleDeleteTemplatesByName.java @@ -0,0 +1,39 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; + +/** + * List templates by name + * + * @see List templates by name + * @author ssk910 + */ +public class ExampleDeleteTemplatesByName { + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and a template name example : java -jar test_accesskey \"My template name\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name from input + final String templateName = args[1]; + + try { + System.out.println("Deleting WhatsApp Templates by name : " + templateName); + messageBirdClient.deleteTemplatesBy(templateName); + System.out.println("Template [" + templateName + "] deleted."); + } catch (GeneralException | UnauthorizedException | NotFoundException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java new file mode 100644 index 00000000..c28d124c --- /dev/null +++ b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java @@ -0,0 +1,41 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; + +/** + * Fetch template by name and language + * + * @see Fetch template by name and language + * @author ssk910 + */ +public class ExampleFetchTemplateByNameAndLanguage { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key and a template name example : java -jar test_accesskey \"My template name\" \"Template language\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name and language from input + final String templateName = args[1]; + final String language = args[2]; + + try { + System.out.println("Fetching WhatsApp Template list by {name: " + templateName + ", language: " + language + "}"); + final TemplateResponse template = messageBirdClient.fetchWhatsAppTemplateBy(templateName, language); + System.out.println(template.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageAndWABAID.java b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageAndWABAID.java new file mode 100644 index 00000000..deb12bf8 --- /dev/null +++ b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageAndWABAID.java @@ -0,0 +1,42 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; + +/** + * Fetch template by name and language by WABA ID + * + * @see Fetch template by name and language by WABA ID + * @author ssk910 + */ +public class ExampleFetchTemplateByNameAndLanguageAndWABAID { + + public static void main(String[] args) { + if (args.length < 4) { + System.out.println("Please specify your access key, template name, language and WABA ID example : java -jar test_accesskey \"My template name\" \"Template language\" \"WABA ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name, language and waba ID from input + final String templateName = args[1]; + final String language = args[2]; + final String wabaID = args[3]; + + try { + System.out.println("Fetching WhatsApp Template list by {name: " + templateName + ", language: " + language + ", wabaID: " + wabaID + "}"); + final TemplateResponse template = messageBirdClient.fetchWhatsAppTemplateBy(templateName, language, wabaID, null); + System.out.println(template.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageForChannelID.java b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageForChannelID.java new file mode 100644 index 00000000..639370e2 --- /dev/null +++ b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguageForChannelID.java @@ -0,0 +1,43 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; + +/** + * Fetch template by name and language for Channel ID + * + * @see Fetch template by name and language for Channel ID + * @author ssk910 + */ +public class ExampleFetchTemplateByNameAndLanguageForChannelID { + + public static void main(String[] args) { + if (args.length < 4) { + System.out.println("Please specify your access key, template name, language and Channel ID example : java -jar test_accesskey \"My template name\" \"Template language\" \"Channel ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name, language and the channel ID from input + final String templateName = args[1]; + final String language = args[2]; + final String channelID = args[3]; + + // Will return a template only if the channel belongs to the same WABA that the template belongs to. + try { + System.out.println("Fetching WhatsApp Template list by {name: " + templateName + ", language: " + language + ", channelID: " + channelID + "}"); + final TemplateResponse template = messageBirdClient.fetchWhatsAppTemplateBy(templateName, language, null, channelID); + System.out.println(template.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleGetChildAccountById.java b/examples/src/main/java/ExampleGetChildAccountById.java new file mode 100644 index 00000000..2e42fd6e --- /dev/null +++ b/examples/src/main/java/ExampleGetChildAccountById.java @@ -0,0 +1,27 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.ChildAccountDetailedResponse; + +public class ExampleGetChildAccountById { + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and id of child account arguments"); + return; + } + + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Get a child account by id"); + ChildAccountDetailedResponse response = messageBirdClient.getChildAccountById(args[1]); + System.out.println("Response: " + response.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleGetChildAccounts.java b/examples/src/main/java/ExampleGetChildAccounts.java new file mode 100644 index 00000000..79dd17cb --- /dev/null +++ b/examples/src/main/java/ExampleGetChildAccounts.java @@ -0,0 +1,28 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.ChildAccountResponse; + +import java.util.List; + +public class ExampleGetChildAccounts { + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key, offset, limit arguments"); + return; + } + + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Get a child accounts of partner account"); + List response = messageBirdClient.getChildAccounts(Integer.valueOf(args[1]), Integer.valueOf(args[2])); + System.out.println("response: " + response); + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleGetOutboundSmsPrices.java b/examples/src/main/java/ExampleGetOutboundSmsPrices.java new file mode 100644 index 00000000..e90d5151 --- /dev/null +++ b/examples/src/main/java/ExampleGetOutboundSmsPrices.java @@ -0,0 +1,33 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.MessageBirdException; +import com.messagebird.objects.OutboundSmsPriceResponse; + +public class ExampleGetOutboundSmsPrices { + + public static void main(String[] args) { + if (args.length != 1 && args.length != 2) { + System.out.println("Please specify your access key and (optionally) SMPP username"); + return; + } + + final MessageBirdClient messageBirdClient = new MessageBirdClient(new MessageBirdServiceImpl(args[0])); + + try { + System.out.println("Get a list of outbound SMS prices"); + + final OutboundSmsPriceResponse outboundSmsPriceResponse; + + if (args.length == 2) { + final String smppUsername = args[1]; + outboundSmsPriceResponse = messageBirdClient.getOutboundSmsPrices(smppUsername); + } else { + outboundSmsPriceResponse = messageBirdClient.getOutboundSmsPrices(); + } + + System.out.println("response: " + outboundSmsPriceResponse); + } catch (MessageBirdException e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleListConversationMessagesWithQueryParam.java b/examples/src/main/java/ExampleListConversationMessagesWithQueryParam.java new file mode 100644 index 00000000..5ab13cf5 --- /dev/null +++ b/examples/src/main/java/ExampleListConversationMessagesWithQueryParam.java @@ -0,0 +1,43 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.conversations.ConversationMessageList; +import java.util.HashMap; + +public class ExampleListConversationMessagesWithQueryParam { + public static void main(String[] args) { + + if (args.length == 0) { + System.out.println("Please specify your access key example : java -jar test_accesskey"); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + // Get list of conversation messages with query param + System.out.println("Retrieving message list"); + final ConversationMessageList conversationMessageList = messageBirdClient.listConversationMessagesWithQueryParam( + new HashMap() { + { + put("ids", "9f0b413e79e24d76b01b895381b12a6d,d46054ee0f7245bcbc7ba586878d0ab4"); + } + }); + + // Display conversation Message list + System.out.println(conversationMessageList.toString()); + } catch (UnauthorizedException | GeneralException | NotFoundException exception) { + if (exception.getErrors() != null) { + System.out.println(exception.getErrors().toString()); + } + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleListTemplates.java b/examples/src/main/java/ExampleListTemplates.java new file mode 100644 index 00000000..b91b2ec2 --- /dev/null +++ b/examples/src/main/java/ExampleListTemplates.java @@ -0,0 +1,36 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateList; + +/** + * List templates + * + * @see List templates + * @author ssk910 + */ +public class ExampleListTemplates { + + public static void main(String[] args) { + if (args.length == 0) { + System.out.println("Please specify your access key example : java -jar test_accesskey"); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Retrieving WhatsApp Template list"); + final TemplateList templateList = messageBirdClient.listWhatsAppTemplates(); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleListTemplatesByName.java b/examples/src/main/java/ExampleListTemplatesByName.java new file mode 100644 index 00000000..33241ca6 --- /dev/null +++ b/examples/src/main/java/ExampleListTemplatesByName.java @@ -0,0 +1,41 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; +import java.util.List; + +/** + * List templates by name + * + * @see List templates by name + * @author ssk910 + */ +public class ExampleListTemplatesByName { + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and a template name example : java -jar test_accesskey \"My template name\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name from input + final String templateName = args[1]; + + try { + System.out.println("Retrieving WhatsApp Template list by name : " + templateName); + final List templateList = messageBirdClient.getWhatsAppTemplatesBy(templateName); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleListTemplatesByNameAndForChannelID.java b/examples/src/main/java/ExampleListTemplatesByNameAndForChannelID.java new file mode 100644 index 00000000..02f2377b --- /dev/null +++ b/examples/src/main/java/ExampleListTemplatesByNameAndForChannelID.java @@ -0,0 +1,45 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; +import java.util.List; + +/** + * List templates by name and for channel ID + * + * @see List templates by name and for channel ID + * @author ssk910 + */ +public class ExampleListTemplatesByNameAndForChannelID { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key, template name and channel ID example : java -jar test_accesskey \"My template name\" \"Channel ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name from input + final String templateName = args[1]; + + // Channel ID from input + final String channelID = args[2]; + + // Will return templates only if the channel belongs to the same WABA that the templates belongs to. + try { + System.out.println("Retrieving WhatsApp Template list by name '" + templateName + "' and channel ID '" + channelID + "'"); + final List templateList = messageBirdClient.getWhatsAppTemplatesBy(templateName, null, channelID); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleListTemplatesByNameAndWABAID.java b/examples/src/main/java/ExampleListTemplatesByNameAndWABAID.java new file mode 100644 index 00000000..6bb8fd3f --- /dev/null +++ b/examples/src/main/java/ExampleListTemplatesByNameAndWABAID.java @@ -0,0 +1,44 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateResponse; +import java.util.List; + +/** + * List templates by name and WABA ID + * + * @see List templates by name and WABA ID + * @author ssk910 + */ +public class ExampleListTemplatesByNameAndWABAID { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Please specify your access key, template name and WABA ID example : java -jar test_accesskey \"My template name\" \"WABA ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + // template name from input + final String templateName = args[1]; + + // WABA ID from input + final String wabaID = args[2]; + + try { + System.out.println("Retrieving WhatsApp Template list by name '" + templateName + "' and WABA ID '" + wabaID + "'"); + final List templateList = messageBirdClient.getWhatsAppTemplatesBy(templateName, wabaID, null); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException | NotFoundException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleListTemplatesByWABAID.java b/examples/src/main/java/ExampleListTemplatesByWABAID.java new file mode 100644 index 00000000..a11ed37f --- /dev/null +++ b/examples/src/main/java/ExampleListTemplatesByWABAID.java @@ -0,0 +1,36 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateList; + +/** + * List templates by WABA ID + * + * @see List templates by WABA ID + * @author ssk910 + */ +public class ExampleListTemplatesByWABAID { + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and WABA ID example : java -jar test_accesskey \"WABA ID\""); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Retrieving WhatsApp Template list by WABA"); + final TemplateList templateList = messageBirdClient.listWhatsAppTemplates(0, 25, args[1], null); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/examples/src/main/java/ExampleListTemplatesForChannelID.java b/examples/src/main/java/ExampleListTemplatesForChannelID.java new file mode 100644 index 00000000..4a2bdda9 --- /dev/null +++ b/examples/src/main/java/ExampleListTemplatesForChannelID.java @@ -0,0 +1,36 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.integrations.TemplateList; + +/** + * List templates for Channel ID + * + * @see List templates for Channel ID + * @author ssk910 + */ +public class ExampleListTemplatesForChannelID { + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key and channel ID example : java -jar test_accesskey"); + return; + } + + // First create your service object + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + + // Add the service to the client + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Retrieving WhatsApp Template list for a channel"); + final TemplateList templateList = messageBirdClient.listWhatsAppTemplates(0, 25, null, args[1]); + System.out.println(templateList.toString()); + } catch (GeneralException | UnauthorizedException | IllegalArgumentException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleRequestSignatureValidation.java b/examples/src/main/java/ExampleRequestSignatureValidation.java index 37ee8af6..e7b92566 100644 --- a/examples/src/main/java/ExampleRequestSignatureValidation.java +++ b/examples/src/main/java/ExampleRequestSignatureValidation.java @@ -1,48 +1,47 @@ import com.messagebird.MessageBirdClient; import com.messagebird.MessageBirdService; import com.messagebird.MessageBirdServiceImpl; -import com.messagebird.RequestSigner; +import com.messagebird.RequestValidator; import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.RequestValidationException; import com.messagebird.exceptions.UnauthorizedException; import com.messagebird.objects.Message; -import com.messagebird.Request; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import util.HttpHandlerHelpers; -import java.io.*; +import java.io.IOException; import java.net.InetSocketAddress; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Map; /** * Created by hasselbach * - * Complete example of MessageBird webhook signature verification - * @see com.messagebird.RequestSignerTest for simplified examples - * * For exposing your application for external calls (webhooks from MessageBird) - * you can use serveo.net: `ssh -R 80:localhost:3000 serveo.net` + * you can use localtunnel (requires NodeJS). + * + * Here is example of usage: * - * Here is example of usage + * Install localtunnel globally: + * npm install -g localtunnel * * Select a free port: 3000, for example * export MBEXAMPLEPORT=3000 * * Run: - * ssh -R 80:localhost:$MBEXAMPLEPORT serveo.net + * lt --port $MBEXAMPLEPORT * * It will show you something like this: - * Hi there - * Forwarding HTTP traffic from https://blabla.serveo.net - * Press g to start a GUI session and ctrl-c to quit. + * your url is: https://loud-yak-31.loca.lt * * * NOTE * you should not terminate this process, so next operations should be done in the other terminal session * * Remember the address from output: - * export FORWARDING_URL=https://blabla.serveo.net + * export FORWARDING_URL=https://loud-yak-31.loca.lt * * Take your access and secret key from Dashboard: * secret key from https://dashboard.messagebird.com/en/developers/settings @@ -50,7 +49,7 @@ * *NOTE* you should use `Live` key for receiving webhooks * * Run this example as: - * java -jar $MBEXAMPLEPORT test_accesskey test_secret $FORWARDING_URL/webhook + * java -jar $MBEXAMPLEPORT test_accesskey test_secret $FORWARDING_URL * * Now you able to play: * send an SMS: @@ -59,8 +58,8 @@ * and then you will see in example app output: * New request: * GET /webhook?id=ee2d02749a6fb78a572bd7ce9118dff&mccmnc=20409&ported=0&recipient=XXXXXX&reference=example-server&status=delivered&statusDatetime=2019-01-10T09%3A23%3A03%2B00%3A00 - * Request has valid signature * Message for XXXXXX is delivered + * Request has valid signature * * Description of webhook parameters can be found on * https://developers.messagebird.com/docs/sms-messaging#handle-a-status-report @@ -83,16 +82,17 @@ public static void main(String[] args) { int serverPort = Integer.parseInt(args[0]); String apiKey = args[1]; String apiSecret = args[2]; - URL reportURL = new URL(args[3]); + String forwardURL = args[3]; + URL reportURL = new URL(forwardURL + "/webhook"); - RequestSigner reqSigner = new RequestSigner(apiSecret.getBytes()); + RequestValidator reqValidator = new RequestValidator(apiSecret); // Creating MessageBird client final MessageBirdService wsr = new MessageBirdServiceImpl(apiKey); final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); HttpServer httpServer = HttpServer.create(new InetSocketAddress(serverPort), 0); - httpServer.createContext("/webhook", new MessageBirdWebhookHandler(reqSigner)); + httpServer.createContext("/webhook", new MessageBirdWebhookHandler(reqValidator, forwardURL)); httpServer.createContext("/send", new MessageBirdSender(messageBirdClient, reportURL)); @@ -110,10 +110,12 @@ public static void main(String[] args) { */ static class MessageBirdWebhookHandler extends HttpHandlerHelpers implements HttpHandler { - private final RequestSigner reqSigner; + private final RequestValidator reqValidator; + private final String baseURL; - MessageBirdWebhookHandler(RequestSigner reqSigner) { - this.reqSigner = reqSigner; + MessageBirdWebhookHandler(RequestValidator reqSigner, String baseURL) { + this.reqValidator = reqSigner; + this.baseURL = baseURL; } @Override @@ -121,33 +123,29 @@ public void handle(HttpExchange he) throws IOException { System.out.println("New request:"); try { - String requestSignature = he.getRequestHeaders().getFirst("MessageBird-Signature"); - String requestTimestamp = he.getRequestHeaders().getFirst("MessageBird-Request-Timestamp"); - String requestParams = he.getRequestURI().getRawQuery(); + String requestSignature = he.getRequestHeaders().getFirst(RequestValidator.SIGNATURE_HEADER); + String requestURL = URI.create(baseURL).resolve(he.getRequestURI()).toString(); byte[] requestBody = readAllBytes(he.getRequestBody()); - Request request = new Request(requestTimestamp, requestParams, requestBody); - printRequest( he.getRequestMethod(), he.getRequestURI().toString(), new String(requestBody, StandardCharsets.UTF_8) ); - if (reqSigner.isMatch(requestSignature, request)) { - // then only if signature is valid we can look at what is sent - // for SMS status parameters can be found on https://developers.messagebird.com/docs/sms-messaging#handle-a-status-report - reportStatus(parseQuery(he.getRequestURI().getQuery())); + reqValidator.validateSignature(requestSignature, requestURL, requestBody); - // MessageBird expects for `200 OK` status - // otherwise, MessageBird will retry this request limited times - sendResponse(he, 200, "Ok"); - System.out.println("Request has valid signature"); + // then only if signature is valid we can look at what is sent + // for SMS status parameters can be found on https://developers.messagebird.com/docs/sms-messaging#handle-a-status-report + reportStatus(parseQuery(he.getRequestURI().getQuery())); - } else { - sendResponse(he, 401, "Signature is invalid"); - System.out.println("Request has invalid signature"); - } + // MessageBird expects for `200 OK` status + // otherwise, MessageBird will retry this request limited times + sendResponse(he, 200, "Ok"); + System.out.println("Request has valid signature"); + } catch (RequestValidationException e) { + sendResponse(he, 401, "Signature is invalid"); + System.out.println("Request has invalid signature: " + e.getMessage()); } catch (Exception e) { sendResponse(he, 500, e.getMessage()); } @@ -163,6 +161,7 @@ private void reportStatus(Map queryParams) { /** * Simple endpoint for sending SMS-messages via MessageBird + * * @see ExampleSendMessage simplified example for message sending */ static class MessageBirdSender extends HttpHandlerHelpers implements HttpHandler { @@ -192,7 +191,7 @@ public void handle(HttpExchange he) throws IOException { try { messageBirdClient.sendMessage(message); - sendResponse(he,201, "Message sent"); + sendResponse(he, 201, "Message sent"); } catch (GeneralException | UnauthorizedException e) { sendResponse(he, 500, e.getMessage()); throw new IOException(e); diff --git a/examples/src/main/java/ExampleSendVoiceCall.java b/examples/src/main/java/ExampleSendVoiceCall.java index 64617a47..13381ca9 100644 --- a/examples/src/main/java/ExampleSendVoiceCall.java +++ b/examples/src/main/java/ExampleSendVoiceCall.java @@ -33,9 +33,7 @@ public static void main(String[] args) { voiceCall.setDestination(args[1]); voiceCall.setWebhook("https://example.com/","foobar"); - //Title and steps are required fields for creating callFlow final VoiceCallFlow voiceCallFlow = new VoiceCallFlow(); - voiceCallFlow.setTitle("Test title"); //action is required VoiceStep voiceStep = new VoiceStep(); voiceStep.setAction("say"); @@ -48,6 +46,7 @@ public static void main(String[] args) { voiceStep.setOptions(voiceStepOption); voiceCallFlow.setSteps(Collections.singletonList(voiceStep)); + voiceCallFlow.setMaxDuration(28800); voiceCall.setCallFlow(voiceCallFlow); //Sending request to client final VoiceCallResponse response = messageBirdClient.sendVoiceCall(voiceCall); diff --git a/examples/src/main/java/ExampleUpdateChildAccount.java b/examples/src/main/java/ExampleUpdateChildAccount.java new file mode 100644 index 00000000..1976f6ab --- /dev/null +++ b/examples/src/main/java/ExampleUpdateChildAccount.java @@ -0,0 +1,26 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.ChildAccountResponse; + +public class ExampleUpdateChildAccount { + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Please specify your access key, name of child account, id of child account parameters"); + return; + } + + final MessageBirdService wsr = new MessageBirdServiceImpl(args[0]); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + + try { + System.out.println("Updating a child account"); + ChildAccountResponse response = messageBirdClient.updateChildAccount(args[1], args[2]); + System.out.println("Child account is updated: " + response.toString()); + } catch (GeneralException | UnauthorizedException exception) { + exception.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/ExampleVerifyToken.java b/examples/src/main/java/ExampleUpdateConversation.java similarity index 54% rename from examples/src/main/java/ExampleVerifyToken.java rename to examples/src/main/java/ExampleUpdateConversation.java index 41eb28f8..0dc58be3 100644 --- a/examples/src/main/java/ExampleVerifyToken.java +++ b/examples/src/main/java/ExampleUpdateConversation.java @@ -2,18 +2,15 @@ import com.messagebird.MessageBirdService; import com.messagebird.MessageBirdServiceImpl; import com.messagebird.exceptions.GeneralException; -import com.messagebird.exceptions.NotFoundException; import com.messagebird.exceptions.UnauthorizedException; -import com.messagebird.objects.Verify; +import com.messagebird.objects.conversations.*; -/** - * Created by faizan on 10/12/15. - */ -public class ExampleVerifyToken { +public class ExampleUpdateConversation { public static void main(String[] args) { if (args.length < 3) { - System.out.println("Please specify your access key, verifyId and a token : java -jar test_accessKey verifyId token"); + System.out.println("Please specify your access key, the ID of a conversation and the status to update the conversation to." + + " Example : java -jar test_accesskey test_conversationId archived"); return; } @@ -22,19 +19,12 @@ public static void main(String[] args) { // Add the service to the client final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); - - + final ConversationStatus newStatus = ConversationStatus.forValue(args[2]); try { - // Send verify token - final String verifyId = args[1]; - final String token = args[2]; - - System.out.println("verifying token request: " + token); - //Sending token to verify - final Verify verify = messageBirdClient.verifyToken(verifyId, token); - //Display result - System.out.println(verify.toString()); - } catch (UnauthorizedException | GeneralException | NotFoundException exception) { + final Conversation response = messageBirdClient.updateConversation(args[1], newStatus); + // Display message response + System.out.println(response.toString()); + } catch (UnauthorizedException | GeneralException exception) { if (exception.getErrors() != null) { System.out.println(exception.getErrors().toString()); } diff --git a/examples/src/main/java/ExampleUpdateVoiceCallFlow.java b/examples/src/main/java/ExampleUpdateVoiceCallFlow.java index d653c37e..4a6bcf1c 100644 --- a/examples/src/main/java/ExampleUpdateVoiceCallFlow.java +++ b/examples/src/main/java/ExampleUpdateVoiceCallFlow.java @@ -24,7 +24,6 @@ public static void main(String[] args) { final VoiceCallFlowRequest voiceCallFlowRequest = new VoiceCallFlowRequest(); - voiceCallFlowRequest.setTitle(args[2]); voiceCallFlowRequest.setRecord(true); // Can be false as well, see docs VoiceStep voiceStep = new VoiceStep(); voiceCallFlowRequest.setSteps(Collections.singletonList(voiceStep)); // VoiceStep Object diff --git a/examples/src/main/java/ExampleVerifyEmail.java b/examples/src/main/java/ExampleVerifyEmail.java new file mode 100644 index 00000000..b2ab99bf --- /dev/null +++ b/examples/src/main/java/ExampleVerifyEmail.java @@ -0,0 +1,51 @@ +import com.messagebird.MessageBirdClient; +import com.messagebird.MessageBirdService; +import com.messagebird.MessageBirdServiceImpl; +import com.messagebird.exceptions.GeneralException; +import com.messagebird.exceptions.NotFoundException; +import com.messagebird.exceptions.UnauthorizedException; +import com.messagebird.objects.Verify; +import com.messagebird.objects.VerifyMessage; +import com.messagebird.objects.VerifyRequest; + +/** + * Created by leandro.pinto on 23/06/21. + */ +public class ExampleVerifyEmail { + + public static void main(String[] args) throws UnauthorizedException, GeneralException, NotFoundException { + + final String ACCESS_KEY = args[0]; + final String METHOD = args[1]; + + final MessageBirdService wsr = new MessageBirdServiceImpl(ACCESS_KEY); + final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); + Verify verify = null; + + switch (METHOD) { + case "send": + VerifyRequest request = new VerifyRequest(""); + request.setType("email"); + request.setOriginator(""); + request.setSubject(""); + request.setTimeout(300); + + verify = messageBirdClient.sendVerifyToken(request); + System.out.println(verify.toString()); + + break; + case "verify": + final String VERIFY_ID = args[2]; + final String TOKEN = args[3]; + + verify = messageBirdClient.verifyToken(VERIFY_ID, TOKEN); + System.out.println(verify.toString()); + break; + case "view": + final String MESSAGE_ID = args[2]; + VerifyMessage verifyMessage = messageBirdClient.getVerifyEmailMessage(MESSAGE_ID); + System.out.println(verifyMessage.toString()); + break; + } + } +} diff --git a/examples/src/main/java/ExampleViewVoiceCallLegs.java b/examples/src/main/java/ExampleViewVoiceCallLegs.java index 00713686..8429d57c 100644 --- a/examples/src/main/java/ExampleViewVoiceCallLegs.java +++ b/examples/src/main/java/ExampleViewVoiceCallLegs.java @@ -34,7 +34,7 @@ public static void main(String[] args) { messageBirdClient.viewCallLegsByCallId(voiceCall.getId(), null, null); //Display voice call leg response object for (VoiceCallLeg callLeg : voiceCallLegResponse.getData()) { - System.out.printf("\t\t%s -> %s, %s [%s]\n", callLeg.source, callLeg.destination, callLeg.direction, callLeg.status); + System.out.printf("\t\t%s -> %s, %s [status: %s sipResponseCode: %s] \n", callLeg.source, callLeg.destination, callLeg.direction, callLeg.status, callLeg.sipResponseCode); } }