diff --git a/api/pom.xml b/api/pom.xml index 5e2288db..b64ee8b0 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -4,10 +4,7 @@ com.messagebird messagebird-api - 3.2.2 + 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,6 +114,17 @@ + + + + + org.codehaus.plexus + plexus-utils + 3.6.1 + + + + com.fasterxml.jackson.core @@ -88,12 +134,17 @@ com.fasterxml.jackson.core jackson-databind - 2.13.2.2 + 2.14.0-rc1 com.auth0 java-jwt - 3.17.0 + 4.4.0 + + + org.apache.maven + maven-artifact + 3.9.6 junit @@ -121,13 +172,6 @@ - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - @@ -222,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 f96ef9fa..00000000 --- a/api/src/main/java/com/messagebird/Base64.java +++ /dev/null @@ -1,512 +0,0 @@ -package com.messagebird; - -/** - * Cutted version of iharder's base64 implementation - * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - * @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. - *

- * @deprecated This class is being deprecated together with {@link RequestSigner} - */ -@Deprecated -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 4a2859a4..583dcb40 100644 --- a/api/src/main/java/com/messagebird/MessageBirdClient.java +++ b/api/src/main/java/com/messagebird/MessageBirdClient.java @@ -105,6 +105,9 @@ public class MessageBirdClient { 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"; @@ -470,7 +473,7 @@ 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"); } @@ -1874,6 +1877,31 @@ public TemplateResponse createWhatsAppTemplate(final Template template) 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. * @@ -1894,6 +1922,34 @@ public TemplateList listWhatsAppTemplates(final int offset, final int limit) 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. * @@ -1913,12 +1969,13 @@ public TemplateList listWhatsAppTemplates() throws UnauthorizedException, Genera * * @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 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 { + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { if (templateName == null) { throw new IllegalArgumentException("Template name must be specified."); } @@ -1933,19 +1990,50 @@ public List getWhatsAppTemplatesBy(final String templateName) 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. + * 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 list + * @return {@code TemplateResponse} template * @throws UnauthorizedException if client is unauthorized * @throws GeneralException general exception - * @throws NotFoundException if template name and language are not found + * @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 { + throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException { if (templateName == null || language == null) { throw new IllegalArgumentException("Template name and language must be specified."); } @@ -1961,6 +2049,79 @@ public TemplateResponse fetchWhatsAppTemplateBy(final String templateName, final 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. * @@ -1985,30 +2146,21 @@ public void deleteTemplatesBy(final String templateName) messageBirdService.delete(url, null); } - /** - * Delete template of an existing template name and language. - * - * @param templateName A template name which is created on the MessageBird platform - * @param language A language which is created on the MessageBird platform - * @throws UnauthorizedException if client is unauthorized - * @throws GeneralException general exception - * @throws NotFoundException if template name or language are not found - */ - public void deleteTemplatesBy(final String templateName, final String language) - throws UnauthorizedException, GeneralException, NotFoundException { - if (templateName == null || language == null) { - throw new IllegalArgumentException("Template name and language must be specified."); + 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, - templateName, - language + "%s%s%s%s/%s", + INTEGRATIONS_BASE_URL_V2, + INTEGRATIONS_WHATSAPP_PATH, + TEMPLATES_PATH, + UNPAUSE_TEMAPLATE_PATH, + templateName ); - messageBirdService.delete(url, null); + messageBirdService.sendPayLoad("POST", url, "", null); } /** @@ -2095,4 +2247,41 @@ public void deleteChildAccount(final String id) throws UnauthorizedException, Ge 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/MessageBirdServiceImpl.java b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java index 892db5c6..ef179b45 100644 --- a/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java +++ b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java @@ -2,15 +2,14 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; -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.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; @@ -63,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(). @@ -74,7 +72,7 @@ public class MessageBirdServiceImpl implements MessageBirdService { private final String accessKey; private final String serviceUrl; - private final String clientVersion = "3.2.2"; + private final String clientVersion = "6.3.1"; private final String userAgentString; private Proxy proxy = null; @@ -91,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); } /** @@ -382,6 +382,9 @@

APIResponse doRequest(final String method, final String url, final Map 1.6) { + 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. * @@ -805,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/RequestSigner.java b/api/src/main/java/com/messagebird/RequestSigner.java index 32c257a2..d79e464c 100644 --- a/api/src/main/java/com/messagebird/RequestSigner.java +++ b/api/src/main/java/com/messagebird/RequestSigner.java @@ -4,13 +4,13 @@ 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: @@ -27,7 +27,7 @@ public class RequestSigner { 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. @@ -55,8 +55,8 @@ public RequestSigner(byte[] key) { @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); } } diff --git a/api/src/main/java/com/messagebird/RequestValidator.java b/api/src/main/java/com/messagebird/RequestValidator.java index f2a4613b..24ae6343 100644 --- a/api/src/main/java/com/messagebird/RequestValidator.java +++ b/api/src/main/java/com/messagebird/RequestValidator.java @@ -5,13 +5,14 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.interfaces.Clock; +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. @@ -128,7 +129,8 @@ public DecodedJWT validateSignature(Clock clock, String signature, String url, b if (!skipURLValidation) builder.withClaim("url_hash", calculateSha256(url.getBytes())); - boolean payloadHashClaimExist = !jwt.getClaim("payload_hash").isNull(); + 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."); 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 91d4d6da..aa104e52 100644 --- a/api/src/main/java/com/messagebird/objects/MessageResponse.java +++ b/api/src/main/java/com/messagebird/objects/MessageResponse.java @@ -3,6 +3,7 @@ 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; @@ -382,7 +383,7 @@ static public class Price implements Serializable { private static final long serialVersionUID = -4104837036540050532L; - private float amount; + private BigDecimal amount; private String currency; public Price() { @@ -397,6 +398,10 @@ public String toString() { } public float getAmount() { + return amount.floatValue(); + } + + public BigDecimal getAmountDecimal() { return amount; } 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/VerifyRequest.java b/api/src/main/java/com/messagebird/objects/VerifyRequest.java index 70de2591..bee684e9 100644 --- a/api/src/main/java/com/messagebird/objects/VerifyRequest.java +++ b/api/src/main/java/com/messagebird/objects/VerifyRequest.java @@ -15,6 +15,7 @@ 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; @@ -124,4 +125,12 @@ public void setSubject(String 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/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/ConversationMessage.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java index 2be027be..1dea8280 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java +++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java @@ -22,6 +22,7 @@ public class ConversationMessage { private Date updatedDatetime; private Map source; private ConversationMessageTag tag; + private ConversationMessageMetadata metadata; /** * See: {@link ConversationPlatformConstants} */ @@ -115,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; } @@ -146,6 +155,7 @@ public String toString() { ", updatedDatetime=" + updatedDatetime + ", source=" + source + ", tag=" + tag + + ", metadata=" + metadata + ", platform='" + platform + '\'' + '}'; } 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/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/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/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/MessageComponent.java b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java index 9dd25b3a..d3a7c20b 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java @@ -8,6 +8,9 @@ public class MessageComponent { 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; @@ -41,13 +44,39 @@ 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 + + ", 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 index 327525fd..9f9290cf 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java @@ -3,13 +3,28 @@ 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"); - + 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; @@ -19,13 +34,8 @@ public enum MessageComponentType { @JsonCreator public static MessageComponentType forValue(String value) { - for (MessageComponentType componentType: MessageComponentType.values()) { - if (componentType.getType().equals(value)) { - return componentType; - } - } - - return null; + Objects.requireNonNull(value, "Value cannot be null"); + return TYPE_MAP.get(value.toLowerCase(Locale.ROOT)); } @JsonValue diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java index 8de37ab7..6432369f 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java +++ b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java @@ -1,5 +1,8 @@ package com.messagebird.objects.conversations; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + public class MessageParam { private TemplateMediaType type; @@ -9,6 +12,11 @@ public class MessageParam { 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; @@ -23,6 +31,9 @@ public String getText() { } public void setText(String text) { + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("Text cannot be null or empty"); + } this.text = text; } @@ -47,6 +58,9 @@ public String getDateTime() { } public void setDateTime(String dateTime) { + if (StringUtils.isBlank(dateTime)) { + throw new IllegalArgumentException("dateTime cannot be null or empty"); + } this.dateTime = dateTime; } @@ -66,16 +80,46 @@ 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() { - return "MessageParam{" + - "type=" + type + - ", text='" + text + '\'' + - ", payload='" + payload + '\'' + - ", currency=" + currency + - ", dateTime='" + dateTime + '\'' + - ", document=" + document + - ", image=" + image + - '}'; + 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 index 51afa594..580dcc97 100644 --- a/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java +++ b/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java @@ -2,15 +2,32 @@ 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"); + 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; @@ -20,13 +37,10 @@ public enum TemplateMediaType { @JsonCreator public static TemplateMediaType forValue(String value) { - for (TemplateMediaType templateMediaType: TemplateMediaType.values()) { - if (templateMediaType.getType().equals(value)) { - return templateMediaType; - } + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); } - - return null; + return TYPE_MAP.get(value.toLowerCase()); } @JsonValue diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java index 15f4d31d..3edb1db9 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java @@ -11,16 +11,9 @@ */ public enum HSMCategory { - ACCOUNT_UPDATE("ACCOUNT_UPDATE"), - PAYMENT_UPDATE("PAYMENT_UPDATE"), - PERSONAL_FINANCE_UPDATE("PERSONAL_FINANCE_UPDATE"), - SHIPPING_UPDATE("SHIPPING_UPDATE"), - RESERVATION_UPDATE("RESERVATION_UPDATE"), - ISSUE_RESOLUTION("ISSUE_RESOLUTION"), - APPOINTMENT_UPDATE("APPOINTMENT_UPDATE"), - TRANSPORTATION_UPDATE("TRANSPORTATION_UPDATE"), - TICKET_UPDATE("TICKET_UPDATE"), - ALERT_UPDATE("ALERT_UPDATE"); + AUTHENTICATION("AUTHENTICATION"), + UTILITY("UTILITY"), + MARKETING("MARKETING"); private final String category; diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java index 51b7efba..ed93847f 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java @@ -1,5 +1,8 @@ package com.messagebird.objects.integrations; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + import java.util.List; /** @@ -13,7 +16,16 @@ public class HSMComponent { private HSMComponentType type; private HSMComponentFormat format; private String text; - private List buttons; + @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() { @@ -37,6 +49,9 @@ public String getText() { } public void setText(String text) { + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("Text cannot be null or empty"); + } this.text = text; } @@ -48,6 +63,14 @@ 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; } @@ -56,15 +79,44 @@ 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() { - return "HSMComponent{" + - "type='" + type + '\'' + - ", format='" + format + '\'' + - ", text='" + text + '\'' + - ", buttons=" + buttons + - ", example=" + example + - '}'; + 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(); } /** @@ -73,8 +125,12 @@ public String toString() { * @throws IllegalArgumentException Occurs when validation is not passed. */ public void validateComponent() throws IllegalArgumentException { - this.validateButtons(); - this.validateComponentExample(); + try { + this.validateButtons(); + this.validateComponentExample(); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Component validation failed: " + e.getMessage(), e); + } } /** @@ -121,9 +177,7 @@ private void validateComponentExample() throws IllegalArgumentException { * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code TEXT}. */ private void checkHeaderText() throws IllegalArgumentException { - if (!(type.equals(HSMComponentType.HEADER) - && format.equals(HSMComponentFormat.TEXT)) - ) { + if (!(HSMComponentType.HEADER.equals(type) && HSMComponentFormat.TEXT.equals(format))) { throw new IllegalArgumentException("\"header_text\" is available for only HEADER type and TEXT format."); } } @@ -134,10 +188,9 @@ private void checkHeaderText() throws IllegalArgumentException { * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code IMAGE}. */ private void checkHeaderUrl() throws IllegalArgumentException { - if (!(type.equals(HSMComponentType.HEADER) - && format.equals(HSMComponentFormat.IMAGE)) - ) { - throw new IllegalArgumentException("\"header_url\" is available for only HEADER type and IMAGE format."); + 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 index 6438f79e..75204df0 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java @@ -1,5 +1,7 @@ package com.messagebird.objects.integrations; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.List; /** @@ -10,79 +12,121 @@ */ public class HSMComponentButton { - private HSMComponentButtonType type; - private String text; - private String url; - private String phone_number; - private List example; - - 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; - } - - @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)); - - if (isExampleEmpty) { - return; - } - - if (isNotProperType) { - throw new IllegalArgumentException("An example field in HSMComponentButton is available for only URL or QUICK_REPLY button types."); - } - } + 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 index 937f6baf..06e6eb91 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java @@ -13,7 +13,9 @@ public enum HSMComponentButtonType { PHONE_NUMBER("PHONE_NUMBER"), URL("URL"), - QUICK_REPLY("QUICK_REPLY"); + QUICK_REPLY("QUICK_REPLY"), + OTP("OTP"), + COPY_CODE("COPY_CODE"); private final String type; 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/HSMComponentType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java index 0acda414..8610146a 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java @@ -2,6 +2,11 @@ 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 @@ -12,7 +17,19 @@ public enum HSMComponentType { BODY("BODY"), HEADER("HEADER"), FOOTER("FOOTER"), - BUTTONS("BUTTONS"); + 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; @@ -22,13 +39,8 @@ public enum HSMComponentType { @JsonCreator public static HSMComponentType forValue(String value) { - for (HSMComponentType hsmComponentType : HSMComponentType.values()) { - if (hsmComponentType.getType().equals(value)) { - return hsmComponentType; - } - } - - return null; + Objects.requireNonNull(value, "Value cannot be null"); + return TYPE_MAP.get(value.toLowerCase(Locale.ROOT)); } @JsonValue 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 index fa179b0f..1b1980f1 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java +++ b/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java @@ -16,7 +16,9 @@ public enum HSMStatus { PENDING("PENDING"), REJECTED("REJECTED"), PENDING_DELETION("PENDING_DELETION"), - DELETED("DELETED"); + DELETED("DELETED"), + DISABLED("DISABLED"), + PAUSED("PAUSED"); private final String status; diff --git a/api/src/main/java/com/messagebird/objects/integrations/Template.java b/api/src/main/java/com/messagebird/objects/integrations/Template.java index 715aeb48..f6a83e93 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/Template.java +++ b/api/src/main/java/com/messagebird/objects/integrations/Template.java @@ -12,18 +12,23 @@ 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, - List components, HSMCategory category) { + + 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() { @@ -42,6 +47,14 @@ 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; } @@ -58,13 +71,23 @@ 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 + '\'' + '}'; } @@ -77,6 +100,7 @@ public void validate() throws IllegalArgumentException { this.validateComponents(); this.validateName(); this.validateLanguage(); + this.validateWABAID(); this.validateCategory(); } @@ -123,6 +147,19 @@ private void validateLanguage() { } } + /** + * 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. * diff --git a/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java index bce35f82..3c75fabe 100644 --- a/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java +++ b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java @@ -18,6 +18,13 @@ public class TemplateResponse implements Serializable { 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; @@ -73,6 +80,22 @@ 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; } @@ -89,6 +112,22 @@ 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{" + @@ -98,8 +137,13 @@ public String toString() { ", 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/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/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/MessageBirdClientTest.java b/api/src/test/java/com/messagebird/MessageBirdClientTest.java index 11113319..b11c2f54 100644 --- a/api/src/test/java/com/messagebird/MessageBirdClientTest.java +++ b/api/src/test/java/com/messagebird/MessageBirdClientTest.java @@ -16,6 +16,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; +import static org.junit.Assume.assumeNotNull; import java.math.BigInteger; import java.util.Collections; @@ -44,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 @@ -427,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"); @@ -448,7 +451,6 @@ public void shouldThrowIllegalArgumentExceptionWhenDestinationOfVoiceCallIsMissi voiceCall.setSource("ANY_SOURCE"); final VoiceCallFlow voiceCallFlow = new VoiceCallFlow(); - voiceCallFlow.setTitle("Test title"); VoiceStep voiceStep = new VoiceStep(); voiceStep.setAction("say"); @@ -1068,6 +1070,83 @@ public void testCreateWhatsAppTemplate() throws UnauthorizedException, GeneralEx 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()); @@ -1103,6 +1182,86 @@ public void testListWhatsAppTemplates() throws UnauthorizedException, GeneralExc } } + @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"; @@ -1134,6 +1293,80 @@ public void testGetWhatsAppTemplatesBy() throws GeneralException, UnauthorizedEx } } + @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"; @@ -1165,45 +1398,86 @@ public void testFetchWhatsAppTemplateBy() throws UnauthorizedException, GeneralE } @Test - public void testDeleteTemplatesByName() throws UnauthorizedException, GeneralException, NotFoundException { + 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%s%s/%s/%s?wabaId=%s", INTEGRATIONS_BASE_URL_V2, INTEGRATIONS_WHATSAPP_PATH, TEMPLATES_PATH, - templateName + templateName, + language, + wabaID ); - when(messageBirdServiceMock.delete(url, null)).thenReturn(null); - messageBirdClientInjectMock.deleteTemplatesBy(templateName); - verify(messageBirdServiceMock).delete(url, null); + 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 testDeleteTemplatesByNameAndLanguage() - throws UnauthorizedException, GeneralException, NotFoundException { + public void testFetchWhatsAppTemplateByNameAndLanguageForChannelID() throws UnauthorizedException, GeneralException, NotFoundException { final String templateName = "sample_template_name"; - final String language = "en_US"; + 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", + "%s%s%s/%s/%s?channelId=%s", INTEGRATIONS_BASE_URL_V2, INTEGRATIONS_WHATSAPP_PATH, TEMPLATES_PATH, templateName, - language + 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, language); + messageBirdClientInjectMock.deleteTemplatesBy(templateName); verify(messageBirdServiceMock).delete(url, null); } @@ -1322,5 +1596,25 @@ private ConversationSendRequest createDummyConversationRequest() { 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); + } -} \ No newline at end of file + @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/RequestValidatorTest.java b/api/src/test/java/com/messagebird/RequestValidatorTest.java index a8d38132..23c64ef0 100644 --- a/api/src/test/java/com/messagebird/RequestValidatorTest.java +++ b/api/src/test/java/com/messagebird/RequestValidatorTest.java @@ -1,6 +1,5 @@ package com.messagebird; -import com.auth0.jwt.interfaces.Clock; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.messagebird.exceptions.RequestValidationException; @@ -13,7 +12,10 @@ 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; @@ -64,10 +66,7 @@ public static Collection data() throws IOException { public void testWebhookSignature() throws Throwable { RequestValidator validator = new RequestValidator(testCase.secret != null ? testCase.secret : ""); - Clock clock = mock(Clock.class); - Date clockDate = spy(Date.from(OffsetDateTime.parse(testCase.timestamp).toInstant())); - when(clock.getToday()).thenReturn(clockDate); - + 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)); diff --git a/api/src/test/java/com/messagebird/SpyService.java b/api/src/test/java/com/messagebird/SpyService.java index 7b741e9f..2922b1b4 100644 --- a/api/src/test/java/com/messagebird/SpyService.java +++ b/api/src/test/java/com/messagebird/SpyService.java @@ -70,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; } diff --git a/api/src/test/java/com/messagebird/TestUtil.java b/api/src/test/java/com/messagebird/TestUtil.java index d5cf47a9..3e9c5a33 100644 --- a/api/src/test/java/com/messagebird/TestUtil.java +++ b/api/src/test/java/com/messagebird/TestUtil.java @@ -6,6 +6,7 @@ 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; @@ -28,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!"); @@ -211,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); @@ -302,12 +302,31 @@ private static HSMComponent createHSMComponentButton() { 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.ACCOUNT_UPDATE); + templateResponse.setCategory(HSMCategory.AUTHENTICATION); templateResponse.setStatus(HSMStatus.NEW); + templateResponse.setCtaURLLinkTrackingOptedOut(true); templateResponse.setCreatedAt(new Date()); templateResponse.setUpdatedAt(new Date()); @@ -318,6 +337,9 @@ public static TemplateResponse createWhatsAppTemplateResponse(final String templ components.add(createHSMComponentButton()); templateResponse.setComponents(components); + templateResponse.setWabaID("testWABAID"); + templateResponse.setNamespace("testNamespace"); + return templateResponse; } @@ -325,7 +347,7 @@ public static Template createWhatsAppTemplate(final String templateName, final S final Template template = new Template(); template.setName(templateName); template.setLanguage(language); - template.setCategory(HSMCategory.ACCOUNT_UPDATE); + template.setCategory(HSMCategory.AUTHENTICATION); final List components = new ArrayList<>(); components.add(createHSMComponentHeader()); @@ -334,9 +356,48 @@ public static Template createWhatsAppTemplate(final String templateName, final S 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"); 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/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/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/examples/pom.xml b/examples/pom.xml index c94c0398..cb9b7489 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,7 +6,7 @@ com.messagebird examples - 3.2.2 + 6.4.0 @@ -20,7 +20,7 @@ com.messagebird messagebird-api - 3.2.2 + 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/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/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/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 index 8e2d9887..336340a6 100644 --- a/examples/src/main/java/ExampleCreateTemplate.java +++ b/examples/src/main/java/ExampleCreateTemplate.java @@ -25,8 +25,8 @@ public class ExampleCreateTemplate { 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\""); + 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; } @@ -84,13 +84,14 @@ public static void main(String[] args) { template.setName(args[1]); template.setLanguage("en_US"); + template.setWABAID(args[2]); template.setComponents(components); - template.setCategory(HSMCategory.ACCOUNT_UPDATE); + template.setCategory(HSMCategory.AUTHENTICATION); try { TemplateResponse response = messageBirdClient.createWhatsAppTemplate(template); System.out.println(response.toString()); - } catch (GeneralException | UnauthorizedException exception) { + } 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/ExampleDeleteTemplateByNameAndLanguage.java b/examples/src/main/java/ExampleDeleteTemplateByNameAndLanguage.java deleted file mode 100644 index 1b215f10..00000000 --- a/examples/src/main/java/ExampleDeleteTemplateByNameAndLanguage.java +++ /dev/null @@ -1,40 +0,0 @@ -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; - -/** - * Delete template by name and language - * - * @see Delete template by name and language - * @author ssk910 - */ -public class ExampleDeleteTemplateByNameAndLanguage { - - 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 langugae from input - final String templateName = args[1]; - final String language = args[2]; - - try { - System.out.println("Deleting WhatsApp Template list by {name: " + templateName + ", language: " + language + "}"); - messageBirdClient.deleteTemplatesBy(templateName, language); - System.out.println("Deleted {name: " + templateName + ", language: " + language + "}"); - } catch (GeneralException | UnauthorizedException | NotFoundException exception) { - exception.printStackTrace(); - } - } -} diff --git a/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java index 02377ed0..c28d124c 100644 --- a/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java +++ b/examples/src/main/java/ExampleFetchTemplateByNameAndLanguage.java @@ -26,7 +26,7 @@ public static void main(String[] args) { // Add the service to the client final MessageBirdClient messageBirdClient = new MessageBirdClient(wsr); - // template name and langugae from input + // template name and language from input final String templateName = args[1]; final String language = args[2]; @@ -34,7 +34,7 @@ public static void main(String[] args) { 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 exception) { + } 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/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/ExampleListTemplatesByName.java b/examples/src/main/java/ExampleListTemplatesByName.java index 36159796..33241ca6 100644 --- a/examples/src/main/java/ExampleListTemplatesByName.java +++ b/examples/src/main/java/ExampleListTemplatesByName.java @@ -34,7 +34,7 @@ public static void main(String[] args) { 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 exception) { + } 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/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/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